move piece table into its own structure

memory-refactor
Patrick Cleaveliln 2025-07-12 03:24:04 +00:00
parent 22a7d40d30
commit d28a707a8f
4 changed files with 322 additions and 229 deletions

View File

@ -27,15 +27,10 @@ ContentSlice :: struct {
slice: []u8, slice: []u8,
} }
FileBufferIndex :: struct {
slice_index: int,
content_index: int,
}
Cursor :: struct { Cursor :: struct {
col: int, col: int,
line: int, line: int,
index: FileBufferIndex, index: PieceTableIndex,
} }
Selection :: struct { Selection :: struct {
@ -54,10 +49,7 @@ FileBuffer :: struct {
cursor: Cursor, cursor: Cursor,
selection: Maybe(Selection), selection: Maybe(Selection),
original_content: [dynamic]u8, piece_table: PieceTable,
added_content: [dynamic]u8,
content_slices: [dynamic][]u8,
glyphs: GlyphBuffer, glyphs: GlyphBuffer,
input_buffer: [dynamic]u8, input_buffer: [dynamic]u8,
@ -66,14 +58,22 @@ FileBuffer :: struct {
FileBufferIter :: struct { FileBufferIter :: struct {
cursor: Cursor, cursor: Cursor,
buffer: ^FileBuffer, buffer: ^FileBuffer,
piter: PieceTableIter,
hit_end: bool, hit_end: bool,
} }
new_file_buffer_iter_from_beginning :: proc(file_buffer: ^FileBuffer) -> FileBufferIter { new_file_buffer_iter_from_beginning :: proc(file_buffer: ^FileBuffer) -> FileBufferIter {
return FileBufferIter { buffer = file_buffer }; return FileBufferIter {
buffer = file_buffer,
piter = new_piece_table_iter(&file_buffer.piece_table)
};
} }
new_file_buffer_iter_with_cursor :: proc(file_buffer: ^FileBuffer, cursor: Cursor) -> FileBufferIter { new_file_buffer_iter_with_cursor :: proc(file_buffer: ^FileBuffer, cursor: Cursor) -> FileBufferIter {
return FileBufferIter { buffer = file_buffer, cursor = cursor }; return FileBufferIter {
buffer = file_buffer,
cursor = cursor,
piter = new_piece_table_iter_from_index(&file_buffer.piece_table, cursor.index)
};
} }
new_file_buffer_iter :: proc{new_file_buffer_iter_from_beginning, new_file_buffer_iter_with_cursor}; new_file_buffer_iter :: proc{new_file_buffer_iter_from_beginning, new_file_buffer_iter_with_cursor};
@ -81,89 +81,53 @@ file_buffer_end :: proc(buffer: ^FileBuffer) -> Cursor {
return Cursor { return Cursor {
col = 0, col = 0,
line = 0, line = 0,
index = FileBufferIndex { index = new_piece_table_index_from_end(&buffer.piece_table)
slice_index = len(buffer.content_slices)-1,
content_index = len(buffer.content_slices[len(buffer.content_slices)-1])-1,
}
}; };
} }
iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) { iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) {
if it.cursor.index.slice_index >= len(it.buffer.content_slices) || it.cursor.index.content_index >= len(it.buffer.content_slices[it.cursor.index.slice_index]) { character, idx, cond = iterate_piece_table_iter(&it.piter)
return;
}
cond = true;
character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
if it.cursor.index.content_index < len(it.buffer.content_slices[it.cursor.index.slice_index])-1 {
it.cursor.index.content_index += 1;
} else if it.cursor.index.slice_index < len(it.buffer.content_slices)-1 {
it.cursor.index.content_index = 0;
it.cursor.index.slice_index += 1;
} else if it.hit_end {
return character, it.cursor.index, false;
} else {
it.hit_end = true;
return character, it.cursor.index, true;
}
if character == '\n' { if character == '\n' {
it.cursor.col = 0; it.cursor.col = 0
it.cursor.line += 1; it.cursor.line += 1
} else { } else {
it.cursor.col += 1; it.cursor.col += 1
} }
return character, it.cursor.index, true; it.cursor.index = it.piter.index
} it.hit_end = it.piter.hit_end
iterate_file_buffer_reverse_mangle_cursor :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) {
if len(it.buffer.content_slices[it.cursor.index.slice_index]) < 0 { return
return character, idx, false;
} }
character = it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index];
if it.cursor.index.content_index == 0 {
if it.cursor.index.slice_index > 0 {
it.cursor.index.slice_index -= 1;
it.cursor.index.content_index = len(it.buffer.content_slices[it.cursor.index.slice_index])-1;
} else if it.hit_end {
return character, it.cursor.index, false;
} else {
it.hit_end = true;
return character, it.cursor.index, true;
}
} else {
it.cursor.index.content_index -= 1;
}
return character, it.cursor.index, true;
}
// TODO: figure out how to give the first character of the buffer // TODO: figure out how to give the first character of the buffer
iterate_file_buffer_reverse :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool) { iterate_file_buffer_reverse :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) {
if character, idx, cond = iterate_file_buffer_reverse_mangle_cursor(it); cond { character, idx, cond = iterate_piece_table_iter_reverse(&it.piter)
if it.cursor.col < 1 {
if it.cursor.line > 0 {
line_length := file_buffer_line_length(it.buffer, it.cursor.index);
if line_length < 0 { line_length = 0; }
it.cursor.line -= 1; it.cursor.index = it.piter.index
it.cursor.col = line_length; it.hit_end = it.piter.hit_end
} else {
return character, it.cursor.index, false; if cond {
} if it.cursor.col > 0 {
} else { it.cursor.col -= 1
it.cursor.col -= 1; } else if it.cursor.line > 0 {
line_length := file_buffer_line_length(it.buffer, it.cursor.index)
if line_length < 0 { line_length = 0 }
it.cursor.line -= 1
it.cursor.col = line_length
} }
} }
return character, it.cursor.index, cond; return
} }
get_character_at_iter :: proc(it: FileBufferIter) -> u8 { get_character_at_iter :: proc(it: FileBufferIter) -> u8 {
return it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index]; return get_character_at_piece_table_index(&it.buffer.piece_table, it.cursor.index);
} }
IterProc :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, cond: bool); IterProc :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool);
UntilProc :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool; UntilProc :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool;
iterate_file_buffer_until :: proc(it: ^FileBufferIter, until_proc: UntilProc) { iterate_file_buffer_until :: proc(it: ^FileBufferIter, until_proc: UntilProc) {
@ -356,7 +320,7 @@ until_single_quote :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> bool {
} }
until_line_break :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> (cond: bool) { until_line_break :: proc(it: ^FileBufferIter, iter_proc: IterProc) -> (cond: bool) {
if it.buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index] == '\n' { if get_character_at_piece_table_index(&it.buffer.piece_table, it.cursor.index) == '\n' {
return false; return false;
} }
@ -392,18 +356,18 @@ update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) {
update_file_buffer_scroll(buffer); update_file_buffer_scroll(buffer);
} }
file_buffer_line_length :: proc(buffer: ^FileBuffer, index: FileBufferIndex) -> int { file_buffer_line_length :: proc(buffer: ^FileBuffer, index: PieceTableIndex) -> int {
line_length := 0; line_length := 0;
if len(buffer.content_slices) <= 0 do return line_length // if len(buffer.content_slices) <= 0 do return line_length
first_character := buffer.content_slices[index.slice_index][index.content_index]; first_character := get_character_at_piece_table_index(&buffer.piece_table, index);
left_it := new_piece_table_iter_from_index(&buffer.piece_table, index);
left_it := new_file_buffer_iter_with_cursor(buffer, Cursor { index = index });
if first_character == '\n' { if first_character == '\n' {
iterate_file_buffer_reverse_mangle_cursor(&left_it); iterate_piece_table_iter_reverse(&left_it);
} }
for character in iterate_file_buffer_reverse_mangle_cursor(&left_it) { for character in iterate_piece_table_iter_reverse(&left_it) {
if character == '\n' { if character == '\n' {
break; break;
} }
@ -411,9 +375,9 @@ file_buffer_line_length :: proc(buffer: ^FileBuffer, index: FileBufferIndex) ->
line_length += 1; line_length += 1;
} }
right_it := new_file_buffer_iter_with_cursor(buffer, Cursor { index = index }); right_it := new_piece_table_iter_from_index(&buffer.piece_table, index);
first := true; first := true;
for character in iterate_file_buffer(&right_it) { for character in iterate_piece_table_iter(&right_it) {
if character == '\n' { if character == '\n' {
break; break;
} }
@ -666,10 +630,11 @@ swap_selections :: proc(selection: Selection) -> (swapped: Selection) {
return swapped return swapped
} }
// TODO: don't access PieceTableIndex directly
is_selection_inverted :: proc(selection: Selection) -> bool { is_selection_inverted :: proc(selection: Selection) -> bool {
return selection.start.index.slice_index > selection.end.index.slice_index || return selection.start.index.chunk_index > selection.end.index.chunk_index ||
(selection.start.index.slice_index == selection.end.index.slice_index (selection.start.index.chunk_index == selection.end.index.chunk_index
&& selection.start.index.content_index > selection.end.index.content_index) && selection.start.index.char_index > selection.end.index.char_index)
} }
selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int { selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int {
@ -697,16 +662,12 @@ new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
allocator = allocator, allocator = allocator,
file_path = "virtual_buffer", file_path = "virtual_buffer",
original_content = slice.clone_to_dynamic([]u8{'\n'}), piece_table = make_piece_table(),
added_content = make([dynamic]u8, 0, 1024*1024),
content_slices = make([dynamic][]u8, 0, 1024*1024),
glyphs = make_glyph_buffer(width, height), glyphs = make_glyph_buffer(width, height),
input_buffer = make([dynamic]u8, 0, 1024), input_buffer = make([dynamic]u8, 0, 1024),
}; };
append(&buffer.content_slices, buffer.original_content[:]);
return buffer; return buffer;
} }
@ -749,21 +710,12 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
// file_path = fi.fullpath[4:], // file_path = fi.fullpath[4:],
extension = extension, extension = extension,
original_content = slice.clone_to_dynamic(original_content), piece_table = make_piece_table(original_content),
added_content = make([dynamic]u8, 0, 1024*1024),
content_slices = make([dynamic][]u8, 0, 1024*1024),
glyphs = make_glyph_buffer(width, height), glyphs = make_glyph_buffer(width, height),
input_buffer = make([dynamic]u8, 0, 1024), input_buffer = make([dynamic]u8, 0, 1024),
}; };
if len(buffer.original_content) > 0 {
append(&buffer.content_slices, buffer.original_content[:]);
} else {
append(&buffer.added_content, '\n')
append(&buffer.content_slices, buffer.added_content[:])
}
return buffer, error(); return buffer, error();
} else { } else {
return FileBuffer{}, error(ErrorType.FileIOError, fmt.aprintf("failed to read from file")); return FileBuffer{}, error(ErrorType.FileIOError, fmt.aprintf("failed to read from file"));
@ -775,10 +727,10 @@ save_buffer_to_disk :: proc(state: ^State, buffer: ^FileBuffer) -> (error: os.Er
defer os.close(fd); defer os.close(fd);
offset: i64 = 0 offset: i64 = 0
for content_slice in buffer.content_slices { for chunk in buffer.piece_table.chunks {
os.write(fd, content_slice) or_return os.write(fd, chunk) or_return
offset += i64(len(content_slice)) offset += i64(len(chunk))
} }
os.flush(fd) os.flush(fd)
@ -801,9 +753,9 @@ next_buffer :: proc(state: ^State, prev_buffer: ^int) -> int {
// TODO: replace this with arena for the file buffer // TODO: replace this with arena for the file buffer
free_file_buffer :: proc(buffer: ^FileBuffer) { free_file_buffer :: proc(buffer: ^FileBuffer) {
delete(buffer.original_content); delete(buffer.piece_table.original_content);
delete(buffer.added_content); delete(buffer.piece_table.added_content);
delete(buffer.content_slices); delete(buffer.piece_table.chunks);
delete(buffer.glyphs.buffer); delete(buffer.glyphs.buffer);
delete(buffer.input_buffer); delete(buffer.input_buffer);
} }
@ -987,28 +939,9 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end:
return; return;
} }
// TODO: is this even needed? would mean that the cursor isn't always in a valid state. index := buffer.cursor.index if !append_to_end else new_piece_table_index_from_end(&buffer.piece_table)
// update_file_buffer_index_from_cursor(buffer);
it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor) if !append_to_end else new_file_buffer_iter_with_cursor(buffer, file_buffer_end(buffer));
length := append(&buffer.added_content, ..to_be_inserted); insert_text(&buffer.piece_table, to_be_inserted, buffer.cursor.index)
inserted_slice: []u8 = buffer.added_content[len(buffer.added_content)-length:];
if it.cursor.index.content_index == 0 {
// insertion happening in beginning of content slice
inject_at(&buffer.content_slices, it.cursor.index.slice_index, inserted_slice);
}
else {
// insertion is happening in middle of content slice
// cut current slice
end_slice := buffer.content_slices[it.cursor.index.slice_index][it.cursor.index.content_index:];
buffer.content_slices[it.cursor.index.slice_index] = buffer.content_slices[it.cursor.index.slice_index][:it.cursor.index.content_index];
inject_at(&buffer.content_slices, it.cursor.index.slice_index+1, inserted_slice);
inject_at(&buffer.content_slices, it.cursor.index.slice_index+2, end_slice);
}
if !append_to_end { if !append_to_end {
update_file_buffer_index_from_cursor(buffer); update_file_buffer_index_from_cursor(buffer);
@ -1016,44 +949,6 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end:
} }
} }
// TODO: potentially add FileBufferIndex as parameter
split_content_slice_from_cursor :: proc(buffer: ^FileBuffer, cursor: ^Cursor) -> (did_split: bool) {
if cursor.index.content_index == 0 {
return;
}
end_slice := buffer.content_slices[cursor.index.slice_index][cursor.index.content_index:];
buffer.content_slices[cursor.index.slice_index] = buffer.content_slices[cursor.index.slice_index][:cursor.index.content_index];
inject_at(&buffer.content_slices, cursor.index.slice_index+1, end_slice);
// TODO: maybe move this out of this function
cursor.index.slice_index += 1;
cursor.index.content_index = 0;
return true
}
split_content_slice_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
// TODO: swap selections
log.info("start:", selection.start, "- end:", selection.end);
// move the end cursor forward one (we want the splitting to be exclusive, not inclusive)
it := new_file_buffer_iter_with_cursor(buffer, selection.end);
iterate_file_buffer(&it);
selection.end = it.cursor;
split_content_slice_from_cursor(buffer, &selection.end);
if split_content_slice_from_cursor(buffer, &selection.start) {
selection.end.index.slice_index += 1;
}
log.info("start:", selection.start, "- end:", selection.end);
}
split_content_slice :: proc{split_content_slice_from_cursor, split_content_slice_from_selection};
delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) { delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
if amount <= len(buffer.input_buffer) { if amount <= len(buffer.input_buffer) {
runtime.resize(&buffer.input_buffer, len(buffer.input_buffer)-amount); runtime.resize(&buffer.input_buffer, len(buffer.input_buffer)-amount);
@ -1061,65 +956,22 @@ delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
amount := amount - len(buffer.input_buffer); amount := amount - len(buffer.input_buffer);
runtime.clear(&buffer.input_buffer); runtime.clear(&buffer.input_buffer);
if len(buffer.content_slices) < 1 { // Calculate proper line/col values
return;
}
split_content_slice(buffer, &buffer.cursor);
it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor); it := new_file_buffer_iter_with_cursor(buffer, buffer.cursor);
iterate_file_buffer_reverse(&it)
// go back one (to be at the end of the content slice) delete_text(&buffer.piece_table, &buffer.cursor.index)
iterate_file_buffer_reverse(&it);
for i in 0..<amount { buffer.cursor.line = it.cursor.line
content_slice_ptr := &buffer.content_slices[it.cursor.index.slice_index]; buffer.cursor.col = it.cursor.col
content_slice_len := len(content_slice_ptr^);
if content_slice_len == 1 {
// move cursor to previous content_slice so we can delete the current one
iterate_file_buffer_reverse(&it);
if it.hit_end {
if len(buffer.content_slices) > 1 {
runtime.ordered_remove(&buffer.content_slices, it.cursor.index.slice_index);
}
} else {
runtime.ordered_remove(&buffer.content_slices, it.cursor.index.slice_index+1);
}
} else if !it.hit_end {
iterate_file_buffer_reverse(&it);
content_slice_ptr^ = content_slice_ptr^[:len(content_slice_ptr^)-1];
}
}
if !it.hit_end {
iterate_file_buffer(&it);
}
buffer.cursor = it.cursor;
} }
} }
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) { delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
assert(len(buffer.content_slices) >= 1);
selection^ = swap_selections(selection^) selection^ = swap_selections(selection^)
delete_text_in_span(&buffer.piece_table, &selection.start.index, &selection.end.index)
split_content_slice(buffer, selection); buffer.cursor.index = selection.start.index
it := new_file_buffer_iter_with_cursor(buffer, selection.start);
// go back one (to be at the end of the content slice)
iterate_file_buffer_reverse(&it);
for _ in selection.start.index.slice_index..<selection.end.index.slice_index {
runtime.ordered_remove(&buffer.content_slices, selection.start.index.slice_index);
}
if !it.hit_end {
iterate_file_buffer(&it);
}
buffer.cursor = it.cursor;
} }
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection}; delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};

241
src/core/piece_table.odin Normal file
View File

@ -0,0 +1,241 @@
package core
PieceTable :: struct {
original_content: []u8,
added_content: [dynamic]u8,
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_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
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;
}
}

View File

@ -190,8 +190,8 @@ render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileB
"%v:%v - Slice %v:%v - Char: %v", "%v:%v - Slice %v:%v - Char: %v",
buffer.cursor.line + 1, buffer.cursor.line + 1,
buffer.cursor.col + 1, buffer.cursor.col + 1,
buffer.cursor.index.slice_index, buffer.cursor.index.chunk_index,
buffer.cursor.index.content_index, buffer.cursor.index.char_index,
core.get_character_at_iter(it) core.get_character_at_iter(it)
), ),
{} {}

View File

@ -32,15 +32,15 @@ buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string {
} }
length := 0 length := 0
for content_slice in buffer.content_slices { for chunk in buffer.piece_table.chunks {
length += len(content_slice) length += len(chunk)
} }
buffer_contents := make([]u8, length) buffer_contents := make([]u8, length)
offset := 0 offset := 0
for content_slice in buffer.content_slices { for chunk in buffer.piece_table.chunks {
for c in content_slice { for c in chunk {
buffer_contents[offset] = c buffer_contents[offset] = c
offset += 1 offset += 1
} }
@ -126,9 +126,9 @@ expect_line_col :: proc(t: ^testing.T, cursor: core.Cursor, line, col: int) {
testing.expect_value(t, cursor.col, col) testing.expect_value(t, cursor.col, col)
} }
expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, slice_index, content_index: int) { expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, chunk_index, char_index: int) {
testing.expect_value(t, cursor.index.slice_index, slice_index) testing.expect_value(t, cursor.index.chunk_index, chunk_index)
testing.expect_value(t, cursor.index.content_index, content_index) testing.expect_value(t, cursor.index.char_index, char_index)
} }
@(test) @(test)
@ -267,7 +267,7 @@ delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
expect_line_col(t, buffer.cursor, 0, 0) expect_line_col(t, buffer.cursor, 0, 0)
expect_cursor_index(t, buffer.cursor, 0, 0) expect_cursor_index(t, buffer.cursor, 0, 0)
testing.expect(t, len(buffer.content_slices) > 0, "BACKSPACE deleted final content slice in buffer") testing.expect(t, len(buffer.piece_table.chunks) > 0, "BACKSPACE deleted final content slice in buffer")
// "commit" insert mode changes, then re-enter insert mode and try to delete again // "commit" insert mode changes, then re-enter insert mode and try to delete again
run_input_multiple(&e, press_key(.ESCAPE), 1) run_input_multiple(&e, press_key(.ESCAPE), 1)
@ -276,7 +276,7 @@ delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
expect_line_col(t, buffer.cursor, 0, 0) expect_line_col(t, buffer.cursor, 0, 0)
expect_cursor_index(t, buffer.cursor, 0, 0) expect_cursor_index(t, buffer.cursor, 0, 0)
testing.expect(t, len(buffer.content_slices) > 0, "BACKSPACE deleted final content slice in buffer") testing.expect(t, len(buffer.piece_table.chunks) > 0, "BACKSPACE deleted final content slice in buffer")
} }
@(test) @(test)