remove input_buffer for intermediate text input, directly insert into filebuffer

main
Patrick Cleaveliln 2025-07-25 05:14:32 +00:00
parent dab095e88d
commit ad5e4f85fd
9 changed files with 181 additions and 163 deletions

View File

@ -137,8 +137,6 @@ current_buffer :: proc(state: ^State) -> ^FileBuffer {
}
yank_whole_line :: proc(state: ^State, buffer: ^FileBuffer) {
context.allocator = buffer.allocator
if state.yank_register.data != nil {
delete(state.yank_register.data)
state.yank_register.data = nil
@ -171,7 +169,12 @@ yank_selection :: proc(state: ^State, buffer: ^FileBuffer) {
length := selection_length(buffer, selection)
state.yank_register.whole_line = false
state.yank_register.data = make([]u8, length)
err: runtime.Allocator_Error
state.yank_register.data, err = make([]u8, length)
if err != nil {
log.error("failed to allocate memory for yank register")
}
it := new_file_buffer_iter_with_cursor(buffer, selection.start)

View File

@ -55,8 +55,6 @@ FileBuffer :: struct {
history: FileHistory,
glyphs: GlyphBuffer,
input_buffer: [dynamic]u8,
}
BufferFlagSet :: bit_set[BufferFlags]
@ -712,7 +710,6 @@ new_virtual_file_buffer :: proc(allocator := context.allocator) -> FileBuffer {
history = make_history(),
glyphs = make_glyph_buffer(width, height),
input_buffer = make([dynamic]u8, 0, 1024),
};
return buffer;
@ -776,7 +773,6 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
history = make_history(content),
glyphs = make_glyph_buffer(width, height),
input_buffer = make([dynamic]u8, 0, 1024),
};
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(&buffer))
@ -834,7 +830,6 @@ free_file_buffer :: proc(buffer: ^FileBuffer) {
ts.delete_state(&buffer.tree)
free_history(&buffer.history)
delete(buffer.glyphs.buffer)
delete(buffer.input_buffer)
}
color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette_index: theme.PaletteColor) {
@ -906,26 +901,6 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x, y, w, h: int, sh
draw_rect(state, start_sel_x, start_sel_y, state.source_font_width, state.source_font_height, .Green);
draw_rect(state, end_sel_x, end_sel_y, state.source_font_width, state.source_font_height, .Blue);
} else if state.mode == .Insert {
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Green);
num_line_break := 0;
line_length := 0;
for c in buffer.input_buffer {
if c == '\n' {
num_line_break += 1;
line_length = 0;
} else {
line_length += 1;
}
}
if num_line_break > 0 {
cursor_x = x + padding + line_length * state.source_font_width;
cursor_y = cursor_y + num_line_break * state.source_font_height;
} else {
cursor_x += line_length * state.source_font_width;
}
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Blue);
}
}
@ -1007,42 +982,33 @@ scroll_file_buffer :: proc(buffer: ^FileBuffer, dir: ScrollDir, cursor: Maybe(^C
}
}
insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end: bool = false) {
insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8) {
if len(to_be_inserted) == 0 {
return;
}
buffer.flags += { .UnsavedChanges }
index := buffer.history.cursor.index if !append_to_end else new_piece_table_index_from_end(buffer_piece_table(buffer))
index := buffer.history.cursor.index
insert_text(buffer_piece_table(buffer), to_be_inserted, buffer.history.cursor.index)
if !append_to_end {
update_file_buffer_index_from_cursor(buffer);
move_cursor_right(buffer, false, amt = len(to_be_inserted) - 1);
}
update_file_buffer_index_from_cursor(buffer);
move_cursor_right(buffer, false, amt = len(to_be_inserted));
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
}
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);
} else {
buffer.flags += { .UnsavedChanges }
buffer.flags += { .UnsavedChanges }
amount := amount - len(buffer.input_buffer);
runtime.clear(&buffer.input_buffer);
// Calculate proper line/col values
it := new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor);
iterate_file_buffer_reverse(&it)
// Calculate proper line/col values
it := new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor);
iterate_file_buffer_reverse(&it)
delete_text(buffer_piece_table(buffer), &buffer.history.cursor.index)
delete_text(buffer_piece_table(buffer), &buffer.history.cursor.index)
buffer.history.cursor.line = it.cursor.line
buffer.history.cursor.col = it.cursor.col
}
buffer.history.cursor.line = it.cursor.line
buffer.history.cursor.col = it.cursor.col
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
}
@ -1067,3 +1033,43 @@ delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};
get_buffer_indent :: proc(buffer: ^FileBuffer, cursor: Maybe(Cursor) = nil) -> int {
cursor := cursor;
if cursor == nil {
cursor = buffer.history.cursor;
}
ptr_cursor := &cursor.?
move_cursor_start_of_line(buffer, ptr_cursor)
move_cursor_forward_end_of_word(buffer, ptr_cursor)
move_cursor_backward_start_of_word(buffer, ptr_cursor)
return cursor.?.col
}
buffer_to_string :: proc(buffer: ^FileBuffer, allocator := context.allocator) -> string {
context.allocator = allocator
length := 0
for chunk in buffer_piece_table(buffer).chunks {
length += len(chunk)
}
buffer_contents := make([]u8, length)
offset := 0
for chunk in buffer_piece_table(buffer).chunks {
for c in chunk {
buffer_contents[offset] = c
offset += 1
}
}
return string(buffer_contents[:len(buffer_contents)-1])
}
buffer_append_new_line :: proc(buffer: ^FileBuffer) {
}

View File

@ -61,25 +61,26 @@ update_glyph_buffer_from_file_buffer :: proc(buffer: ^FileBuffer, width, height:
// don't render past the screen
if rendered_line >= begin && screen_line >= buffer.glyphs.height { break; }
// render INSERT mode text into glyph buffer
if len(buffer.input_buffer) > 0 && rendered_line == buffer.history.cursor.line && rendered_col >= buffer.history.cursor.col && rendered_col < buffer.history.cursor.col + len(buffer.input_buffer) {
for k in 0..<len(buffer.input_buffer) {
screen_line = rendered_line - begin;
// NOTE: `input_buffer` doesn't exist anymore, but this is a nice reference for just inserting text within the glyph buffer
//
// if len(buffer.input_buffer) > 0 && rendered_line == buffer.history.cursor.line && rendered_col >= buffer.history.cursor.col && rendered_col < buffer.history.cursor.col + len(buffer.input_buffer) {
// for k in 0..<len(buffer.input_buffer) {
// screen_line = rendered_line - begin;
if buffer.input_buffer[k] == '\n' {
rendered_col = 0;
rendered_line += 1;
continue;
}
// if buffer.input_buffer[k] == '\n' {
// rendered_col = 0;
// rendered_line += 1;
// continue;
// }
if rendered_line >= begin && rendered_col < buffer.glyphs.width {
buffer.glyphs.buffer[rendered_col + screen_line * buffer.glyphs.width].color = .Foreground;
buffer.glyphs.buffer[rendered_col + screen_line * buffer.glyphs.width].codepoint = buffer.input_buffer[k];
// if rendered_line >= begin && rendered_col < buffer.glyphs.width {
// buffer.glyphs.buffer[rendered_col + screen_line * buffer.glyphs.width].color = .Foreground;
// buffer.glyphs.buffer[rendered_col + screen_line * buffer.glyphs.width].codepoint = buffer.input_buffer[k];
rendered_col += 1;
}
}
}
// rendered_col += 1;
// }
// }
// }
screen_line = rendered_line - begin;

View File

@ -32,10 +32,11 @@ new_logger :: proc(buffer: ^FileBuffer) -> runtime.Logger {
logger_proc :: proc(data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) {
buffer := cast(^FileBuffer)data;
if .Level in options {
insert_content(buffer, transmute([]u8)(Level_Header[level]), true);
}
// FIXME
// if .Level in options {
// insert_content(buffer, transmute([]u8)(Level_Header[level]), true);
// }
insert_content(buffer, transmute([]u8)(text), true);
insert_content(buffer, {'\n'}, true);
// insert_content(buffer, transmute([]u8)(text), true);
// insert_content(buffer, {'\n'}, true);
}

View File

@ -3,6 +3,8 @@ 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,
}
@ -158,7 +160,19 @@ insert_text :: proc(t: ^PieceTable, to_be_inserted: []u8, index: PieceTableIndex
if index.char_index == 0 {
// insertion happening in beginning of content slice
inject_at(&t.chunks, index.chunk_index, inserted_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

View File

@ -25,24 +25,6 @@ FileBuffer :: core.FileBuffer;
state := core.State {};
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
}
do_insert_mode :: proc(state: ^State, buffer: ^FileBuffer) {
key := 0;
for key > 0 {
if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 {
append(&buffer.input_buffer, u8(key));
}
key = 0;
}
}
do_visual_mode :: proc(state: ^State, buffer: ^FileBuffer) {
}
ui_font_width :: proc() -> i32 {
return i32(state.source_font_width);
}
@ -444,22 +426,47 @@ main :: proc() {
case .ESCAPE: {
state.mode = .Normal;
core.insert_content(buffer, buffer.input_buffer[:]);
runtime.clear(&buffer.input_buffer);
// core.insert_content(buffer, buffer.input_buffer[:]);
// runtime.clear(&buffer.input_buffer);
core.move_cursor_left(buffer)
sdl2.StopTextInput();
}
case .TAB: {
// TODO: change this to insert a tab character
for _ in 0..<4 {
append(&buffer.input_buffer, ' ');
// for _ in 0..<4 {
// append(&buffer.input_buffer, ' ');
// }
core.insert_content(buffer, transmute([]u8)string(" "))
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(&state)
}
}
}
case .BACKSPACE: {
core.delete_content(buffer, 1);
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(&state)
}
}
}
case .ENTER: {
append(&buffer.input_buffer, '\n');
indent := core.get_buffer_indent(buffer)
core.insert_content(buffer, []u8{'\n'})
for i in 0..<indent {
core.insert_content(buffer, []u8{' '})
}
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(&state)
}
}
}
}
}
@ -471,8 +478,9 @@ main :: proc() {
break;
}
if char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1 {
append(&buffer.input_buffer, u8(char));
if char >= 32 && char <= 125 {
// append(&buffer.input_buffer, u8(char));
core.insert_content(buffer, []u8{char})
}
}
@ -489,18 +497,6 @@ main :: proc() {
draw(&state);
switch state.mode {
case .Normal:
buffer := core.current_buffer(&state);
do_normal_mode(&state, buffer);
case .Insert:
buffer := core.current_buffer(&state);
do_insert_mode(&state, buffer);
case .Visual:
buffer := core.current_buffer(&state);
do_visual_mode(&state, buffer);
}
runtime.free_all(context.temp_allocator);
}
}

View File

@ -502,10 +502,23 @@ file_buffer_text_input_actions :: proc(input_map: ^core.InputActions) {
core.push_new_snapshot(&buffer.history)
if buffer := buffer; buffer != nil {
core.move_cursor_end_of_line(buffer, false);
runtime.clear(&buffer.input_buffer)
core.move_cursor_end_of_line(buffer);
char := core.get_character_at_piece_table_index(core.buffer_piece_table(buffer), buffer.history.cursor.index)
indent := core.get_buffer_indent(buffer)
if char == '{' {
// TODO: update tab to be configurable
indent += 4
}
append(&buffer.input_buffer, '\n')
if char != '\n' {
core.move_cursor_right(buffer, stop_at_end = false)
}
core.insert_content(buffer, []u8{'\n'})
for i in 0..<indent {
core.insert_content(buffer, []u8{' '})
}
state.mode = .Insert;
@ -534,9 +547,8 @@ file_buffer_text_input_actions :: proc(input_map: ^core.InputActions) {
core.push_new_snapshot(&buffer.history)
if state.yank_register.whole_line {
core.move_cursor_end_of_line(buffer, false);
core.move_cursor_end_of_line(buffer, stop_at_end = false);
core.insert_content(buffer, []u8{'\n'});
core.move_cursor_right(buffer, false);
} else {
core.move_cursor_right(buffer)
}

View File

@ -21,7 +21,7 @@ open_grep_panel :: proc(state: ^core.State) {
}
make_grep_panel :: proc() -> core.Panel {
run_query :: proc(panel_state: ^core.GrepPanel, query: string, directory: string) {
run_query :: proc(panel_state: ^core.GrepPanel, buffer: ^core.FileBuffer, directory: string) {
if panel_state.query_region.arena != nil {
mem.end_arena_temp_memory(panel_state.query_region)
}
@ -30,7 +30,7 @@ make_grep_panel :: proc() -> core.Panel {
context.allocator = mem.arena_allocator(&panel_state.query_arena)
rs_results := grep(
strings.clone_to_cstring(query),
strings.clone_to_cstring(core.buffer_to_string(buffer)),
strings.clone_to_cstring(directory)
);
@ -142,7 +142,7 @@ make_grep_panel :: proc() -> core.Panel {
},
on_buffer_input = proc(panel: ^core.Panel, state: ^core.State) {
if panel_state, ok := &panel.type.(core.GrepPanel); ok {
run_query(panel_state, string(panel_state.buffer.input_buffer[:]), state.directory)
run_query(panel_state, &panel_state.buffer, state.directory)
}
},
render = proc(panel: ^core.Panel, state: ^core.State) -> (ok: bool) {

View File

@ -270,7 +270,6 @@ insert_before_slice :: proc(t: ^testing.T) {
run_text_insertion(&e, " rich")
expect_line_col(t, buffer.history.cursor, 0, 20)
expect_cursor_index(t, buffer.history.cursor, 2, 4)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
@ -346,8 +345,7 @@ delete_in_slice :: proc(t: ^testing.T) {
run_input_multiple(&e, press_key(.BACKSPACE), 3)
run_input_multiple(&e, press_key(.ESCAPE), 1)
expect_line_col(t, buffer.history.cursor, 0, 17)
expect_cursor_index(t, buffer.history.cursor, 3, 0)
expect_line_col(t, buffer.history.cursor, 0, 16)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
@ -387,15 +385,14 @@ delete_across_slices :: proc(t: ^testing.T) {
run_input_multiple(&e, press_key(.ESCAPE), 1)
// Move right, passed the 'h' on to the space before 'world!'
run_input_multiple(&e, press_key(.L), 1)
run_input_multiple(&e, press_key(.L), 2)
// Remove the ' h', which consists of two content slices
run_input_multiple(&e, press_key(.I), 1)
run_input_multiple(&e, press_key(.BACKSPACE), 2)
run_input_multiple(&e, press_key(.ESCAPE), 1)
expect_line_col(t, buffer.history.cursor, 0, 16)
expect_cursor_index(t, buffer.history.cursor, 2, 0)
expect_line_col(t, buffer.history.cursor, 0, 15)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
@ -632,14 +629,12 @@ append_end_of_line :: proc(t: ^testing.T) {
run_input_multiple(&e, press_key(.A), 1)
run_input_multiple(&e, press_key(.ESCAPE), 1)
expect_line_col(t, buffer.history.cursor, 0, 5)
expect_cursor_index(t, buffer.history.cursor, 1, 0)
expect_line_col(t, buffer.history.cursor, 0, 4)
run_input_multiple(&e, press_key(.A), 1)
run_input_multiple(&e, press_key(.ESCAPE), 1)
expect_line_col(t, buffer.history.cursor, 0, 5)
expect_cursor_index(t, buffer.history.cursor, 1, 0)
expect_line_col(t, buffer.history.cursor, 0, 4)
}
@(test)
@ -667,15 +662,11 @@ insert_line_under_current :: proc(t: ^testing.T) {
// Insert line below and enter insert mode
run_input_multiple(&e, press_key(.O), 1)
// Technically the cursor is still on the first line, because the `input_buffer`
// has been modified but not the actual contents of the filebuffer
expect_line_col(t, buffer.history.cursor, 0, 13)
expect_cursor_index(t, buffer.history.cursor, 0, 13)
expect_line_col(t, buffer.history.cursor, 1, 0)
run_text_insertion(&e, "This is the second line")
expect_line_col(t, buffer.history.cursor, 1, 22)
expect_cursor_index(t, buffer.history.cursor, 1, 23)
contents := buffer_to_string(core.current_buffer(&e))
defer delete(contents)
@ -812,21 +803,40 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
#partial switch key.key {
case .ESCAPE: {
state.mode = .Normal;
core.insert_content(buffer, buffer.input_buffer[:]);
runtime.clear(&buffer.input_buffer);
core.move_cursor_left(buffer)
}
case .TAB: {
// TODO: change this to insert a tab character
for _ in 0..<4 {
append(&buffer.input_buffer, ' ');
core.insert_content(buffer, transmute([]u8)string(" "))
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(state)
}
}
}
case .BACKSPACE: {
core.delete_content(buffer, 1);
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(state)
}
}
}
case .ENTER: {
append(&buffer.input_buffer, '\n');
indent := core.get_buffer_indent(buffer)
core.insert_content(buffer, []u8{'\n'})
for i in 0..<indent {
core.insert_content(buffer, []u8{' '})
}
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(state)
}
}
}
}
}
@ -839,8 +849,8 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
break;
}
if char == '\n' || (char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1) {
append(&buffer.input_buffer, u8(char));
if char == '\n' || (char >= 32 && char <= 125) {
core.insert_content(buffer, []u8{u8(char)})
}
}
@ -854,30 +864,5 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
}
}
// TODO: share this with the main application
do_insert_mode :: proc(state: ^core.State, buffer: ^core.FileBuffer) {
key := 0;
for key > 0 {
if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 {
append(&buffer.input_buffer, u8(key));
}
key = 0;
}
}
switch state.mode {
case .Normal:
// buffer := core.current_buffer(state);
// do_normal_mode(state, buffer);
case .Insert:
buffer := core.current_buffer(state);
do_insert_mode(state, buffer);
case .Visual:
// buffer := core.current_buffer(state);
// do_visual_mode(state, buffer);
}
runtime.free_all(context.temp_allocator);
}