diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index 35fcb56..1a8ca90 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -27,15 +27,10 @@ ContentSlice :: struct { slice: []u8, } -FileBufferIndex :: struct { - slice_index: int, - content_index: int, -} - Cursor :: struct { col: int, line: int, - index: FileBufferIndex, + index: PieceTableIndex, } Selection :: struct { @@ -54,10 +49,7 @@ FileBuffer :: struct { cursor: Cursor, selection: Maybe(Selection), - original_content: [dynamic]u8, - added_content: [dynamic]u8, - content_slices: [dynamic][]u8, - + piece_table: PieceTable, glyphs: GlyphBuffer, input_buffer: [dynamic]u8, @@ -66,14 +58,22 @@ FileBuffer :: struct { FileBufferIter :: struct { cursor: Cursor, buffer: ^FileBuffer, + piter: PieceTableIter, hit_end: bool, } 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 { - 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}; @@ -81,89 +81,53 @@ file_buffer_end :: proc(buffer: ^FileBuffer) -> Cursor { return Cursor { col = 0, line = 0, - index = FileBufferIndex { - slice_index = len(buffer.content_slices)-1, - content_index = len(buffer.content_slices[len(buffer.content_slices)-1])-1, - } + index = new_piece_table_index_from_end(&buffer.piece_table) }; } -iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBufferIndex, 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]) { - 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; - } +iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) { + character, idx, cond = iterate_piece_table_iter(&it.piter) if character == '\n' { - it.cursor.col = 0; - it.cursor.line += 1; + it.cursor.col = 0 + it.cursor.line += 1 } else { - it.cursor.col += 1; + it.cursor.col += 1 } - return character, it.cursor.index, true; -} -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 character, idx, false; - } + it.cursor.index = it.piter.index + it.hit_end = it.piter.hit_end - 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; + return } + // 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) { - if character, idx, cond = iterate_file_buffer_reverse_mangle_cursor(it); cond { - 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; } +iterate_file_buffer_reverse :: proc(it: ^FileBufferIter) -> (character: u8, idx: PieceTableIndex, cond: bool) { + character, idx, cond = iterate_piece_table_iter_reverse(&it.piter) - it.cursor.line -= 1; - it.cursor.col = line_length; - } else { - return character, it.cursor.index, false; - } - } else { - it.cursor.col -= 1; - } + it.cursor.index = it.piter.index + it.hit_end = it.piter.hit_end + + if cond { + if it.cursor.col > 0 { + 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 { - 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; 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) { - 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; } @@ -392,18 +356,18 @@ update_file_buffer_index_from_cursor :: proc(buffer: ^FileBuffer) { 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; - 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' { - 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' { break; } @@ -411,9 +375,9 @@ file_buffer_line_length :: proc(buffer: ^FileBuffer, index: FileBufferIndex) -> 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; - for character in iterate_file_buffer(&right_it) { + for character in iterate_piece_table_iter(&right_it) { if character == '\n' { break; } @@ -666,10 +630,11 @@ swap_selections :: proc(selection: Selection) -> (swapped: Selection) { return swapped } +// TODO: don't access PieceTableIndex directly is_selection_inverted :: proc(selection: Selection) -> bool { - return selection.start.index.slice_index > selection.end.index.slice_index || - (selection.start.index.slice_index == selection.end.index.slice_index - && selection.start.index.content_index > selection.end.index.content_index) + return selection.start.index.chunk_index > selection.end.index.chunk_index || + (selection.start.index.chunk_index == selection.end.index.chunk_index + && selection.start.index.char_index > selection.end.index.char_index) } selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int { @@ -697,16 +662,12 @@ new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer { allocator = allocator, file_path = "virtual_buffer", - original_content = slice.clone_to_dynamic([]u8{'\n'}), - added_content = make([dynamic]u8, 0, 1024*1024), - content_slices = make([dynamic][]u8, 0, 1024*1024), + piece_table = make_piece_table(), glyphs = make_glyph_buffer(width, height), input_buffer = make([dynamic]u8, 0, 1024), }; - append(&buffer.content_slices, buffer.original_content[:]); - return buffer; } @@ -749,21 +710,12 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s // file_path = fi.fullpath[4:], extension = extension, - original_content = slice.clone_to_dynamic(original_content), - added_content = make([dynamic]u8, 0, 1024*1024), - content_slices = make([dynamic][]u8, 0, 1024*1024), + piece_table = make_piece_table(original_content), glyphs = make_glyph_buffer(width, height), 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(); } else { 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); offset: i64 = 0 - for content_slice in buffer.content_slices { - os.write(fd, content_slice) or_return + for chunk in buffer.piece_table.chunks { + os.write(fd, chunk) or_return - offset += i64(len(content_slice)) + offset += i64(len(chunk)) } 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 free_file_buffer :: proc(buffer: ^FileBuffer) { - delete(buffer.original_content); - delete(buffer.added_content); - delete(buffer.content_slices); + delete(buffer.piece_table.original_content); + delete(buffer.piece_table.added_content); + delete(buffer.piece_table.chunks); delete(buffer.glyphs.buffer); delete(buffer.input_buffer); } @@ -987,28 +939,9 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end: return; } - // TODO: is this even needed? would mean that the cursor isn't always in a valid state. - // 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)); + index := buffer.cursor.index if !append_to_end else new_piece_table_index_from_end(&buffer.piece_table) - length := append(&buffer.added_content, ..to_be_inserted); - 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); - } + insert_text(&buffer.piece_table, to_be_inserted, buffer.cursor.index) if !append_to_end { 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) { if amount <= len(buffer.input_buffer) { 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); runtime.clear(&buffer.input_buffer); - if len(buffer.content_slices) < 1 { - return; - } - - split_content_slice(buffer, &buffer.cursor); - + // Calculate proper line/col values 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) - iterate_file_buffer_reverse(&it); + delete_text(&buffer.piece_table, &buffer.cursor.index) - for i in 0.. 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; + buffer.cursor.line = it.cursor.line + buffer.cursor.col = it.cursor.col } } delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) { - assert(len(buffer.content_slices) >= 1); - selection^ = swap_selections(selection^) + delete_text_in_span(&buffer.piece_table, &selection.start.index, &selection.end.index) - split_content_slice(buffer, selection); - - 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.. 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.. (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; + } +} \ No newline at end of file diff --git a/src/panels/panels.odin b/src/panels/panels.odin index 2b1c451..6f8cfd7 100644 --- a/src/panels/panels.odin +++ b/src/panels/panels.odin @@ -190,8 +190,8 @@ render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileB "%v:%v - Slice %v:%v - Char: %v", buffer.cursor.line + 1, buffer.cursor.col + 1, - buffer.cursor.index.slice_index, - buffer.cursor.index.content_index, + buffer.cursor.index.chunk_index, + buffer.cursor.index.char_index, core.get_character_at_iter(it) ), {} diff --git a/src/tests/tests.odin b/src/tests/tests.odin index 79f5be6..6c0a03a 100644 --- a/src/tests/tests.odin +++ b/src/tests/tests.odin @@ -32,15 +32,15 @@ buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string { } length := 0 - for content_slice in buffer.content_slices { - length += len(content_slice) + for chunk in buffer.piece_table.chunks { + length += len(chunk) } buffer_contents := make([]u8, length) offset := 0 - for content_slice in buffer.content_slices { - for c in content_slice { + for chunk in buffer.piece_table.chunks { + for c in chunk { buffer_contents[offset] = c 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) } -expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, slice_index, content_index: int) { - testing.expect_value(t, cursor.index.slice_index, slice_index) - testing.expect_value(t, cursor.index.content_index, content_index) +expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, chunk_index, char_index: int) { + testing.expect_value(t, cursor.index.chunk_index, chunk_index) + testing.expect_value(t, cursor.index.char_index, char_index) } @(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_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 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_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)