diff --git a/src/core/core.odin b/src/core/core.odin index bffb2a1..ebb9401 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -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) diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index 4073f7a..e5adfc0 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -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) { + +} \ No newline at end of file diff --git a/src/core/glyph_buffer.odin b/src/core/glyph_buffer.odin index 0e43132..5a6eb66 100644 --- a/src/core/glyph_buffer.odin +++ b/src/core/glyph_buffer.odin @@ -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.. 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..= 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; diff --git a/src/core/logger.odin b/src/core/logger.odin index 47637c0..139a1b3 100644 --- a/src/core/logger.odin +++ b/src/core/logger.odin @@ -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); } diff --git a/src/core/piece_table.odin b/src/core/piece_table.odin index e087f84..8bce31c 100644 --- a/src/core/piece_table.odin +++ b/src/core/piece_table.odin @@ -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 diff --git a/src/main.odin b/src/main.odin index 1e4925d..f316571 100644 --- a/src/main.odin +++ b/src/main.odin @@ -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..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); } } diff --git a/src/panels/file_buffer.odin b/src/panels/file_buffer.odin index cd33a7a..761d1eb 100644 --- a/src/panels/file_buffer.odin +++ b/src/panels/file_buffer.odin @@ -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.. 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) { diff --git a/src/tests/tests.odin b/src/tests/tests.odin index 775f5d8..46e58c7 100644 --- a/src/tests/tests.odin +++ b/src/tests/tests.odin @@ -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..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); }