277 lines
7.9 KiB
Plaintext
277 lines
7.9 KiB
Plaintext
package core
|
|
|
|
PieceTable :: struct {
|
|
original_content: []u8,
|
|
added_content: [dynamic]u8,
|
|
|
|
// TODO: don't actually reference `added_content` and `original_content` via pointers, since they can be re-allocated
|
|
chunks: [dynamic][]u8,
|
|
}
|
|
|
|
PieceTableIter :: struct {
|
|
t: ^PieceTable,
|
|
index: PieceTableIndex,
|
|
hit_end: bool,
|
|
}
|
|
|
|
PieceTableIndex :: struct {
|
|
chunk_index: int,
|
|
char_index: int,
|
|
}
|
|
|
|
make_empty_piece_table :: proc(starting_capacity: int = 1024*1024, allocator := context.allocator) -> PieceTable {
|
|
context.allocator = allocator
|
|
|
|
original_content := transmute([]u8)string("\n")
|
|
chunks := make([dynamic][]u8, 0, starting_capacity)
|
|
|
|
append(&chunks, original_content[:])
|
|
|
|
return PieceTable {
|
|
original_content = original_content,
|
|
added_content = make([dynamic]u8, 0, starting_capacity),
|
|
chunks = chunks,
|
|
}
|
|
}
|
|
|
|
make_piece_table_from_bytes :: proc(data: []u8, starting_capacity: int = 1024*1024, allocator := context.allocator) -> PieceTable {
|
|
context.allocator = allocator
|
|
|
|
added_content := make([dynamic]u8, 0, starting_capacity)
|
|
chunks := make([dynamic][]u8, 0, starting_capacity)
|
|
|
|
if len(data) > 0 {
|
|
append(&chunks, data[:])
|
|
} else {
|
|
append(&added_content, '\n')
|
|
append(&chunks, added_content[:])
|
|
}
|
|
|
|
return PieceTable {
|
|
original_content = data,
|
|
added_content = added_content,
|
|
chunks = chunks,
|
|
}
|
|
}
|
|
|
|
make_piece_table :: proc{make_empty_piece_table, make_piece_table_from_bytes}
|
|
|
|
new_piece_table_iter :: proc(t: ^PieceTable) -> PieceTableIter {
|
|
return PieceTableIter {
|
|
t = t,
|
|
index = PieceTableIndex {
|
|
chunk_index = 0,
|
|
char_index = 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
new_piece_table_iter_from_index :: proc(t: ^PieceTable, index: PieceTableIndex) -> PieceTableIter {
|
|
return PieceTableIter {
|
|
t = t,
|
|
index = index
|
|
}
|
|
}
|
|
|
|
new_piece_table_iter_from_byte_offset :: proc(t: ^PieceTable, byte_offset: int) -> (iter: PieceTableIter, ok: bool) {
|
|
bytes := 0
|
|
|
|
for chunk, chunk_index in t.chunks {
|
|
if bytes + len(chunk) > byte_offset {
|
|
char_index := byte_offset - bytes
|
|
|
|
return PieceTableIter {
|
|
t = t,
|
|
index = PieceTableIndex {
|
|
chunk_index = chunk_index,
|
|
char_index = char_index,
|
|
}
|
|
}, true
|
|
} else {
|
|
bytes += len(chunk)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
new_piece_table_index_from_end :: proc(t: ^PieceTable) -> PieceTableIndex {
|
|
chunk_index := len(t.chunks)-1
|
|
char_index := len(t.chunks[chunk_index])-1
|
|
|
|
return PieceTableIndex {
|
|
chunk_index = chunk_index,
|
|
char_index = char_index,
|
|
}
|
|
}
|
|
|
|
iterate_piece_table_iter :: proc(it: ^PieceTableIter) -> (character: u8, index: PieceTableIndex, cond: bool) {
|
|
if it.index.chunk_index >= len(it.t.chunks) || it.index.char_index >= len(it.t.chunks[it.index.chunk_index]) {
|
|
return
|
|
}
|
|
|
|
character = it.t.chunks[it.index.chunk_index][it.index.char_index]
|
|
if it.hit_end {
|
|
return character, it.index, false
|
|
}
|
|
|
|
if it.index.char_index < len(it.t.chunks[it.index.chunk_index])-1 {
|
|
it.index.char_index += 1
|
|
} else if it.index.chunk_index < len(it.t.chunks)-1 {
|
|
it.index.char_index = 0
|
|
it.index.chunk_index += 1
|
|
} else {
|
|
it.hit_end = true
|
|
}
|
|
|
|
return character, it.index, true
|
|
}
|
|
|
|
iterate_piece_table_iter_reverse :: proc(it: ^PieceTableIter) -> (character: u8, index: PieceTableIndex, cond: bool) {
|
|
if it.index.chunk_index >= len(it.t.chunks) || it.index.char_index >= len(it.t.chunks[it.index.chunk_index]) {
|
|
return
|
|
}
|
|
|
|
character = it.t.chunks[it.index.chunk_index][it.index.char_index]
|
|
if it.hit_end {
|
|
return character, it.index, false
|
|
}
|
|
|
|
if it.index.char_index > 0 {
|
|
it.index.char_index -= 1
|
|
} else if it.index.chunk_index > 0 {
|
|
it.index.chunk_index -= 1
|
|
it.index.char_index = len(it.t.chunks[it.index.chunk_index])-1
|
|
} else {
|
|
it.hit_end = true
|
|
}
|
|
|
|
return character, it.index, true
|
|
}
|
|
|
|
get_character_at_piece_table_index :: proc(t: ^PieceTable, index: PieceTableIndex) -> u8 {
|
|
return t.chunks[index.chunk_index][index.char_index]
|
|
}
|
|
|
|
insert_text :: proc(t: ^PieceTable, to_be_inserted: []u8, index: PieceTableIndex) {
|
|
length := append(&t.added_content, ..to_be_inserted);
|
|
inserted_slice: []u8 = t.added_content[len(t.added_content)-length:];
|
|
|
|
if index.char_index == 0 {
|
|
// insertion happening in beginning of content slice
|
|
|
|
if len(t.chunks) > 1 && index.chunk_index > 0 {
|
|
last_chunk_index := len(t.chunks[index.chunk_index-1])-1
|
|
|
|
if (&t.chunks[index.chunk_index-1][last_chunk_index]) == (&t.added_content[len(t.added_content)-1 - length]) {
|
|
start := len(t.added_content)-1 - last_chunk_index - length
|
|
|
|
t.chunks[index.chunk_index-1] = t.added_content[start:]
|
|
} else {
|
|
inject_at(&t.chunks, index.chunk_index, inserted_slice);
|
|
}
|
|
} else {
|
|
inject_at(&t.chunks, index.chunk_index, inserted_slice);
|
|
}
|
|
}
|
|
else {
|
|
// insertion is happening in middle of content slice
|
|
|
|
// cut current slice
|
|
end_slice := t.chunks[index.chunk_index][index.char_index:];
|
|
t.chunks[index.chunk_index] = t.chunks[index.chunk_index][:index.char_index];
|
|
|
|
inject_at(&t.chunks, index.chunk_index+1, inserted_slice);
|
|
inject_at(&t.chunks, index.chunk_index+2, end_slice);
|
|
}
|
|
}
|
|
|
|
delete_text :: proc(t: ^PieceTable, index: ^PieceTableIndex) {
|
|
if len(t.chunks) < 1 {
|
|
return;
|
|
}
|
|
|
|
split_from_index(t, index);
|
|
|
|
it := new_piece_table_iter_from_index(t, index^);
|
|
|
|
// go back one (to be at the end of the chunk)
|
|
iterate_piece_table_iter_reverse(&it);
|
|
|
|
chunk_ptr := &t.chunks[it.index.chunk_index];
|
|
chunk_len := len(chunk_ptr^);
|
|
|
|
if chunk_len == 1 {
|
|
// move cursor to previous chunk so we can delete the current one
|
|
iterate_piece_table_iter_reverse(&it);
|
|
|
|
if it.hit_end {
|
|
if len(t.chunks) > 1 {
|
|
ordered_remove(&t.chunks, it.index.chunk_index);
|
|
}
|
|
} else {
|
|
ordered_remove(&t.chunks, it.index.chunk_index+1);
|
|
}
|
|
} else if !it.hit_end {
|
|
iterate_piece_table_iter_reverse(&it);
|
|
chunk_ptr^ = chunk_ptr^[:len(chunk_ptr^)-1];
|
|
}
|
|
|
|
|
|
if !it.hit_end {
|
|
iterate_piece_table_iter(&it);
|
|
}
|
|
|
|
index^ = it.index
|
|
}
|
|
|
|
// Assumes end >= start
|
|
delete_text_in_span :: proc(t: ^PieceTable, start: ^PieceTableIndex, end: ^PieceTableIndex) {
|
|
assert(len(t.chunks) >= 1);
|
|
|
|
split_from_span(t, start, end);
|
|
|
|
it := new_piece_table_iter_from_index(t, start^);
|
|
|
|
// go back one (to be at the end of the content slice)
|
|
iterate_piece_table_iter_reverse(&it);
|
|
|
|
for _ in start.chunk_index..<end.chunk_index {
|
|
ordered_remove(&t.chunks, start.chunk_index);
|
|
}
|
|
|
|
if !it.hit_end {
|
|
iterate_piece_table_iter(&it);
|
|
}
|
|
|
|
start^ = it.index
|
|
end^ = it.index
|
|
}
|
|
|
|
split_from_index :: proc(t: ^PieceTable, index: ^PieceTableIndex) -> (did_split: bool) {
|
|
if index.char_index == 0 {
|
|
return;
|
|
}
|
|
|
|
end_slice := t.chunks[index.chunk_index][index.char_index:];
|
|
t.chunks[index.chunk_index] = t.chunks[index.chunk_index][:index.char_index];
|
|
|
|
inject_at(&t.chunks, index.chunk_index+1, end_slice);
|
|
|
|
index.chunk_index += 1;
|
|
index.char_index = 0;
|
|
|
|
return true
|
|
}
|
|
|
|
split_from_span :: proc(t: ^PieceTable, start: ^PieceTableIndex, end: ^PieceTableIndex) {
|
|
// move the end cursor forward one (we want the splitting to be exclusive, not inclusive)
|
|
it := new_piece_table_iter_from_index(t, end^);
|
|
iterate_piece_table_iter(&it);
|
|
end^ = it.index;
|
|
|
|
split_from_index(t, end);
|
|
if split_from_index(t, start) {
|
|
end.chunk_index += 1;
|
|
}
|
|
} |