From 9ace98118dbaf1f9f735ccfce0b23d4717c106ec Mon Sep 17 00:00:00 2001 From: Patrick Cleaveliln Date: Sat, 19 Jul 2025 21:22:14 +0000 Subject: [PATCH] refactor how buffers are stored, group panel lifetimes --- src/core/core.odin | 55 ++-- src/core/file_buffer.odin | 9 +- src/input/input.odin | 354 ------------------------- src/main.odin | 43 ++-- src/panels/file_buffer.odin | 497 ++++++++++++++++++++++++++++++++++++ src/panels/grep.odin | 309 ++++++++++++++++++++++ src/panels/panels.odin | 442 +------------------------------- src/pkg/grep_lib/src/lib.rs | 23 +- src/tree_sitter/ts.odin | 1 + src/util/list.odin | 6 +- todo.md | 1 + 11 files changed, 908 insertions(+), 832 deletions(-) delete mode 100644 src/input/input.odin create mode 100644 src/panels/file_buffer.odin create mode 100644 src/panels/grep.odin diff --git a/src/core/core.odin b/src/core/core.odin index 11b302f..9662d49 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -74,33 +74,43 @@ EditorCommandArgument :: union #no_nil { i32 } -PanelRenderProc :: proc(state: ^State, panel_state: ^PanelState) -> (ok: bool) -PanelBufferProc :: proc(state: ^State, panel_state: ^PanelState) -> (buffer: ^FileBuffer, ok: bool) -PanelBufferInputProc :: proc(state: ^State, panel_state: ^PanelState) -PanelDropProc :: proc(state: ^State, panel_state: ^PanelState) Panel :: struct { - is_floating: bool, - panel_state: PanelState, + using vtable: Panel_VTable, + arena: mem.Arena, + allocator: mem.Allocator, + + id: int, + type: PanelType, input_map: InputMap, - buffer_proc: PanelBufferProc, - on_buffer_input_proc: PanelBufferInputProc, - render_proc: PanelRenderProc, - drop: PanelDropProc, + is_floating: bool, } -PanelState :: union { +Panel_VTable :: struct { + create: proc(panel: ^Panel, state: ^State), + drop: proc(panel: ^Panel, state: ^State), + + on_buffer_input: proc(panel: ^Panel, state: ^State), + buffer: proc(panel: ^Panel, state: ^State) -> (buffer: ^FileBuffer, ok: bool), + render: proc(panel: ^Panel, state: ^State) -> (ok: bool), +} + +PanelType :: union { FileBufferPanel, GrepPanel, } FileBufferPanel :: struct { - buffer_index: int, + buffer: FileBuffer, + + // only used for initialization + file_path: string, + line, col: int, } GrepPanel :: struct { query_arena: mem.Arena, query_region: mem.Arena_Temp_Memory, - buffer: int, + buffer: FileBuffer, selected_result: int, search_query: string, query_results: []GrepQueryResult, @@ -114,6 +124,17 @@ GrepQueryResult :: struct { col: int, } +current_buffer :: proc(state: ^State) -> ^FileBuffer { + if current_panel, ok := state.current_panel.?; ok { + if panel, ok := util.get(&state.panels, current_panel).?; ok { + buffer, _ := panel->buffer(state) + return buffer + } + } + + return nil +} + yank_whole_line :: proc(state: ^State, buffer: ^FileBuffer) { if state.yank_register.data != nil { delete(state.yank_register.data) @@ -190,7 +211,9 @@ Action :: struct { description: string, } -new_input_map :: proc() -> InputMap { +new_input_map :: proc(allocator := context.allocator) -> InputMap { + context.allocator = allocator + input_map := InputMap { mode = make(map[Mode]InputActions), } @@ -205,7 +228,9 @@ new_input_map :: proc() -> InputMap { return input_map; } -new_input_actions :: proc() -> InputActions { +new_input_actions :: proc(allocator := context.allocator) -> InputActions { + context.allocator = allocator + input_actions := InputActions { key_actions = make(map[Key]Action), ctrl_key_actions = make(map[Key]Action), diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index 55b2a7e..abae2a9 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -662,7 +662,7 @@ selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int { return length } -new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer { +new_virtual_file_buffer :: proc(allocator := context.allocator) -> FileBuffer { context.allocator = allocator; width := 256; height := 256; @@ -706,6 +706,11 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s extension := filepath.ext(fi.fullpath); if original_content, success := os.read_entire_file_from_handle(fd); success { + defer delete(original_content) + + content := make([]u8, len(original_content)) + copy_slice(content, original_content) + width := 256; height := 256; @@ -721,7 +726,7 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s // TODO: derive language type from extension tree = ts.make_state(.Odin), - history = make_history(original_content), + history = make_history(content), glyphs = make_glyph_buffer(width, height), input_buffer = make([dynamic]u8, 0, 1024), diff --git a/src/input/input.odin b/src/input/input.odin deleted file mode 100644 index fef54f9..0000000 --- a/src/input/input.odin +++ /dev/null @@ -1,354 +0,0 @@ -package input - -import "base:runtime" -import "core:log" - -import "vendor:sdl2" - -import "../core" -import "../util" - -State :: core.State - -register_default_go_actions :: proc(input_map: ^core.InputActions) { - core.register_key_action(input_map, .H, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_start_of_line(buffer); - core.reset_input_map(state) - }, "move to beginning of line"); - core.register_key_action(input_map, .L, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_end_of_line(buffer); - core.reset_input_map(state) - }, "move to end of line"); -} - -register_default_input_actions :: proc(input_map: ^core.InputActions) { - // Cursor Movement - { - core.register_key_action(input_map, .W, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_forward_start_of_word(buffer); - }, "move forward one word"); - core.register_key_action(input_map, .E, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_forward_end_of_word(buffer); - }, "move forward to end of word"); - - core.register_key_action(input_map, .B, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_backward_start_of_word(buffer); - }, "move backward one word"); - - core.register_key_action(input_map, .K, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_up(buffer); - }, "move up one line"); - core.register_key_action(input_map, .J, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_down(buffer); - }, "move down one line"); - core.register_key_action(input_map, .H, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_left(buffer); - }, "move left one char"); - core.register_key_action(input_map, .L, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.move_cursor_right(buffer); - }, "move right one char"); - - core.register_ctrl_key_action(input_map, .U, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.scroll_file_buffer(buffer, .Up); - }, "scroll buffer up"); - core.register_ctrl_key_action(input_map, .D, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.scroll_file_buffer(buffer, .Down); - }, "scroll buffer up"); - } - - // Scale font size - { - core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State, user_data: rawptr) { - if state.source_font_height > 16 { - state.source_font_height -= 2; - state.source_font_width = state.source_font_height / 2; - - state.font_atlas = core.gen_font_atlas(state, core.HardcodedFontPath); - } - log.debug(state.source_font_height); - }, "increase font size"); - core.register_ctrl_key_action(input_map, .EQUAL, proc(state: ^State, user_data: rawptr) { - state.source_font_height += 2; - state.source_font_width = state.source_font_height / 2; - - state.font_atlas = core.gen_font_atlas(state, core.HardcodedFontPath); - }, "decrease font size"); - } - - // Save file - core.register_ctrl_key_action(input_map, .S, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - if err := core.save_buffer_to_disk(state, buffer); err != nil { - log.errorf("failed to save buffer to disk: %v", err) - } - }, "Save file") - - core.register_key_action(input_map, .G, core.new_input_actions(), "Go commands"); - register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputActions)); - - core.register_key_action(input_map, .V, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - state.mode = .Visual; - core.reset_input_map(state) - - buffer.selection = core.new_selection(buffer.history.cursor); - }, "enter visual mode"); - -} - -register_default_visual_actions :: proc(input_map: ^core.InputActions) { - core.register_key_action(input_map, .ESCAPE, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - state.mode = .Normal; - core.reset_input_map(state) - - buffer.selection = nil; - core.update_file_buffer_scroll(buffer) - }, "exit visual mode"); - - // Cursor Movement - { - core.register_key_action(input_map, .W, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.move_cursor_forward_start_of_word(buffer, cursor = &sel_cur.end); - }, "move forward one word"); - core.register_key_action(input_map, .E, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.move_cursor_forward_end_of_word(buffer, cursor = &sel_cur.end); - }, "move forward to end of word"); - - core.register_key_action(input_map, .B, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.move_cursor_backward_start_of_word(buffer, cursor = &sel_cur.end); - }, "move backward one word"); - - core.register_key_action(input_map, .K, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.move_cursor_up(buffer, cursor = &sel_cur.end); - }, "move up one line"); - core.register_key_action(input_map, .J, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.move_cursor_down(buffer, cursor = &sel_cur.end); - }, "move down one line"); - core.register_key_action(input_map, .H, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.move_cursor_left(buffer, cursor = &sel_cur.end); - }, "move left one char"); - core.register_key_action(input_map, .L, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.move_cursor_right(buffer, cursor = &sel_cur.end); - }, "move right one char"); - - core.register_ctrl_key_action(input_map, .U, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.scroll_file_buffer(buffer, .Up, cursor = &sel_cur.end); - }, "scroll buffer up"); - core.register_ctrl_key_action(input_map, .D, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - sel_cur := &(buffer.selection.?); - - core.scroll_file_buffer(buffer, .Down, cursor = &sel_cur.end); - }, "scroll buffer up"); - } - - // Text Modification - { - core.register_key_action(input_map, .D, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.push_new_snapshot(&buffer.history) - - sel_cur := &(buffer.selection.?); - - core.delete_content(buffer, sel_cur); - buffer.selection = nil; - core.update_file_buffer_scroll(buffer) - - state.mode = .Normal - core.reset_input_map(state) - }, "delete selection"); - - core.register_key_action(input_map, .C, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.push_new_snapshot(&buffer.history) - - sel_cur := &(buffer.selection.?); - - core.delete_content(buffer, sel_cur); - buffer.selection = nil; - core.update_file_buffer_scroll(buffer) - - state.mode = .Insert - core.reset_input_map(state, core.Mode.Normal) - sdl2.StartTextInput(); - }, "change selection"); - } - - // Copy-Paste - { - core.register_key_action(input_map, .Y, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.yank_selection(state, buffer) - - state.mode = .Normal; - core.reset_input_map(state) - - buffer.selection = nil; - core.update_file_buffer_scroll(buffer) - }, "Yank Line"); - - core.register_key_action(input_map, .P, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.push_new_snapshot(&buffer.history) - - if state.yank_register.whole_line { - core.insert_content(buffer, []u8{'\n'}); - core.paste_register(state, state.yank_register, buffer) - core.insert_content(buffer, []u8{'\n'}); - } else { - core.paste_register(state, state.yank_register, buffer) - } - - core.reset_input_map(state) - }, "Paste"); - } -} - -register_default_text_input_actions :: proc(input_map: ^core.InputActions) { - core.register_key_action(input_map, .I, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.push_new_snapshot(&buffer.history) - - state.mode = .Insert; - sdl2.StartTextInput(); - }, "enter insert mode"); - core.register_key_action(input_map, .A, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.push_new_snapshot(&buffer.history) - - core.move_cursor_right(buffer, false); - state.mode = .Insert; - sdl2.StartTextInput(); - }, "enter insert mode after character (append)"); - - core.register_key_action(input_map, .U, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.pop_snapshot(&buffer.history, true) - }, "Undo"); - - core.register_ctrl_key_action(input_map, .R, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.recover_snapshot(&buffer.history) - }, "Redo"); - - // TODO: add shift+o to insert newline above current one - - core.register_key_action(input_map, .O, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.push_new_snapshot(&buffer.history) - - if buffer := buffer; buffer != nil { - core.move_cursor_end_of_line(buffer, false); - runtime.clear(&buffer.input_buffer) - - append(&buffer.input_buffer, '\n') - - state.mode = .Insert; - - sdl2.StartTextInput(); - } - }, "insert mode on newline"); - - // Copy-Paste - { - { - yank_actions := core.new_input_actions() - defer core.register_key_action(input_map, .Y, yank_actions) - - core.register_key_action(&yank_actions, .Y, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.yank_whole_line(state, buffer) - - core.reset_input_map(state) - }, "Yank Line"); - } - - core.register_key_action(input_map, .P, proc(state: ^State, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data - - core.push_new_snapshot(&buffer.history) - - if state.yank_register.whole_line { - core.move_cursor_end_of_line(buffer, false); - core.insert_content(buffer, []u8{'\n'}); - core.move_cursor_right(buffer, false); - } else { - core.move_cursor_right(buffer) - } - core.paste_register(state, state.yank_register, buffer) - core.move_cursor_start_of_line(buffer) - - core.reset_input_map(state) - }, "Paste"); - } - -} diff --git a/src/main.odin b/src/main.odin index c62c972..91d2863 100644 --- a/src/main.odin +++ b/src/main.odin @@ -23,9 +23,6 @@ import ts "tree_sitter" State :: core.State; FileBuffer :: core.FileBuffer; -// TODO: should probably go into state -scratch: mem.Scratch; -scratch_alloc: runtime.Allocator; state := core.State {}; do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) { @@ -69,8 +66,8 @@ draw :: proc(state: ^State) { { for i in 0..render(state) } } } @@ -161,7 +158,14 @@ expose_event_watcher :: proc "c" (state: rawptr, event: ^sdl2.Event) -> i32 { } main :: proc() { - ts.set_allocator() + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + + defer { + } + + ts.set_allocator() _command_arena: mem.Arena mem.arena_init(&_command_arena, make([]u8, 1024*1024)); @@ -190,10 +194,6 @@ main :: proc() { prev_elements = make([]ui.UI_Element, 8192), } - // TODO: don't use this - mem.scratch_allocator_init(&scratch, 1024*1024); - scratch_alloc = mem.scratch_allocator(&scratch); - core.reset_input_map(&state) // core.register_editor_command( @@ -278,13 +278,10 @@ main :: proc() { if len(os.args) > 1 { for arg in os.args[1:] { - panels.open_file_buffer_in_new_panel(&state, arg, 0, 0) + panels.open(&state, panels.make_file_buffer_panel(arg)) } } else { - // buffer := core.new_virtual_file_buffer(context.allocator); - - // panels.open(&state, panels.make_file_buffer_panel(len(state.buffers))) - // runtime.append(&state.buffers, buffer); + panels.open(&state, panels.make_file_buffer_panel("")) } if sdl2.Init({.VIDEO}) < 0 { @@ -393,7 +390,7 @@ main :: proc() { if action, exists := state.current_input_map.ctrl_key_actions[key]; exists { switch value in action.action { case core.EditorAction: - value(state, &panel); + value(state, panel); return true; case core.InputActions: state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions) @@ -404,7 +401,7 @@ main :: proc() { if action, exists := state.current_input_map.key_actions[key]; exists { switch value in action.action { case core.EditorAction: - value(state, &panel); + value(state, panel); return true; case core.InputActions: state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions) @@ -481,8 +478,8 @@ main :: proc() { } if current_panel, ok := state.current_panel.?; ok { - if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input_proc != nil { - panel.on_buffer_input_proc(&state, &panel.panel_state) + if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil { + panel->on_buffer_input(&state) } } } @@ -509,4 +506,12 @@ main :: proc() { } sdl2.Quit(); + + if len(track.allocation_map) > 0 { + fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map)) + for _, entry in track.allocation_map { + fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location) + } + } + mem.tracking_allocator_destroy(&track) } diff --git a/src/panels/file_buffer.odin b/src/panels/file_buffer.odin new file mode 100644 index 0000000..40ec8f0 --- /dev/null +++ b/src/panels/file_buffer.odin @@ -0,0 +1,497 @@ +package panels + +import "base:runtime" +import "core:log" +import "core:fmt" +import "core:path/filepath" + +import "vendor:sdl2" + +import ts "../tree_sitter" +import "../core" +import "../ui" + +make_file_buffer_panel :: proc(file_path: string, line: int = 0, col: int = 0) -> core.Panel { + return core.Panel { + type = core.FileBufferPanel { + file_path = file_path, + line = line, + col = col, + }, + drop = proc(panel: ^core.Panel, state: ^core.State) { + panel_state := &panel.type.(core.FileBufferPanel) + + ts.delete_state(&panel_state.buffer.tree) + }, + create = proc(panel: ^core.Panel, state: ^core.State) { + context.allocator = panel.allocator + + panel_state := &panel.type.(core.FileBufferPanel) + panel.input_map = core.new_input_map() + + if len(panel_state.file_path) == 0 { + panel_state.buffer = core.new_virtual_file_buffer(panel.allocator) + } else { + buffer, err := core.new_file_buffer(panel.allocator, panel_state.file_path, state.directory) + if err.type != .None { + log.error("Failed to create file buffer:", err); + return; + } + panel_state.buffer = buffer + + buffer.history.cursor.line = panel_state.line + buffer.history.cursor.col = panel_state.col + buffer.top_line = buffer.history.cursor.line + core.update_file_buffer_index_from_cursor(&buffer) + } + + leader_actions := core.new_input_actions() + register_default_leader_actions(&leader_actions); + core.register_key_action(&panel.input_map.mode[.Normal], .SPACE, leader_actions, "leader commands"); + + core.register_ctrl_key_action(&panel.input_map.mode[.Normal], .W, core.new_input_actions(), "Panel Navigation") + register_default_panel_actions(&(&panel.input_map.mode[.Normal].ctrl_key_actions[.W]).action.(core.InputActions)) + + file_buffer_input_actions(&panel.input_map.mode[.Normal]); + file_buffer_visual_actions(&panel.input_map.mode[.Visual]); + file_buffer_text_input_actions(&panel.input_map.mode[.Normal]); + }, + buffer = proc(panel: ^core.Panel, state: ^core.State) -> (buffer: ^core.FileBuffer, ok: bool) { + panel_state := &panel.type.(core.FileBufferPanel) + + return &panel_state.buffer, true + }, + render = proc(panel: ^core.Panel, state: ^core.State) -> (ok: bool) { + panel_state := &panel.type.(core.FileBufferPanel) + + s := transmute(^ui.State)state.ui + render_file_buffer(state, s, &panel_state.buffer) + + return true + } + } +} + +open_file_buffer_in_new_panel :: proc(state: ^core.State, file_path: string, line, col: int) -> (panel_id: int, ok: bool) { + if panel_id, ok := open(state, make_file_buffer_panel(file_path, line, col)); ok { + return panel_id, true + } + + return -1, false +} + +render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) { + draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) { + buffer := transmute(^core.FileBuffer)user_data; + if buffer != nil { + buffer.glyphs.width = e.layout.size.x / state.source_font_width; + buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1; + + core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y); + } + }; + + relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator) + + ui.open_element(s, nil, + { + dir = .TopToBottom, + kind = {ui.Grow{}, ui.Grow{}}, + }, + style = { + border = {.Left, .Right, .Top, .Bottom}, + border_color = .Background4, + background_color = .Background1, + } + ) + { + ui.open_element(s, + ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)buffer}, + { + kind = {ui.Grow{}, ui.Grow{}} + }, + style = { + border = {.Left, .Right, .Top, .Bottom}, + border_color = .Background4, + background_color = .Background1, + } + ) + ui.close_element(s) + + ui.open_element(s, nil, { + kind = {ui.Grow{}, ui.Exact(state.source_font_height)} + }, + style = { + border = {.Left, .Right, .Top, .Bottom}, + border_color = .Background4, + } + ) + { + ui.open_element(s, fmt.tprintf("%s", state.mode), {}) + ui.close_element(s) + + ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Grow{}}}) + ui.close_element(s) + + it := core.new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor) + ui.open_element( + s, + fmt.tprintf( + "%v:%v - Slice %v:%v - Char: %v", + buffer.history.cursor.line + 1, + buffer.history.cursor.col + 1, + buffer.history.cursor.index.chunk_index, + buffer.history.cursor.index.char_index, + core.get_character_at_iter(it) + ), + {} + ) + ui.close_element(s) + } + ui.close_element(s) + } + ui.close_element(s) +} + +file_buffer_go_actions :: proc(input_map: ^core.InputActions) { + core.register_key_action(input_map, .H, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_start_of_line(buffer); + core.reset_input_map(state) + }, "move to beginning of line"); + core.register_key_action(input_map, .L, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_end_of_line(buffer); + core.reset_input_map(state) + }, "move to end of line"); +} + +file_buffer_input_actions :: proc(input_map: ^core.InputActions) { + // Cursor Movement + { + core.register_key_action(input_map, .W, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_forward_start_of_word(buffer); + }, "move forward one word"); + core.register_key_action(input_map, .E, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_forward_end_of_word(buffer); + }, "move forward to end of word"); + + core.register_key_action(input_map, .B, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_backward_start_of_word(buffer); + }, "move backward one word"); + + core.register_key_action(input_map, .K, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_up(buffer); + }, "move up one line"); + core.register_key_action(input_map, .J, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_down(buffer); + }, "move down one line"); + core.register_key_action(input_map, .H, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_left(buffer); + }, "move left one char"); + core.register_key_action(input_map, .L, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.move_cursor_right(buffer); + }, "move right one char"); + + core.register_ctrl_key_action(input_map, .U, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.scroll_file_buffer(buffer, .Up); + }, "scroll buffer up"); + core.register_ctrl_key_action(input_map, .D, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.scroll_file_buffer(buffer, .Down); + }, "scroll buffer up"); + } + + // Scale font size + { + core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^core.State, user_data: rawptr) { + if state.source_font_height > 16 { + state.source_font_height -= 2; + state.source_font_width = state.source_font_height / 2; + + state.font_atlas = core.gen_font_atlas(state, core.HardcodedFontPath); + } + log.debug(state.source_font_height); + }, "increase font size"); + core.register_ctrl_key_action(input_map, .EQUAL, proc(state: ^core.State, user_data: rawptr) { + state.source_font_height += 2; + state.source_font_width = state.source_font_height / 2; + + state.font_atlas = core.gen_font_atlas(state, core.HardcodedFontPath); + }, "decrease font size"); + } + + // Save file + core.register_ctrl_key_action(input_map, .S, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + if err := core.save_buffer_to_disk(state, buffer); err != nil { + log.errorf("failed to save buffer to disk: %v", err) + } + }, "Save file") + + core.register_key_action(input_map, .G, core.new_input_actions(), "Go commands"); + file_buffer_go_actions(&(&input_map.key_actions[.G]).action.(core.InputActions)); + + core.register_key_action(input_map, .V, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + state.mode = .Visual; + core.reset_input_map(state) + + buffer.selection = core.new_selection(buffer.history.cursor); + }, "enter visual mode"); + +} + +file_buffer_visual_actions :: proc(input_map: ^core.InputActions) { + core.register_key_action(input_map, .ESCAPE, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + state.mode = .Normal; + core.reset_input_map(state) + + buffer.selection = nil; + core.update_file_buffer_scroll(buffer) + }, "exit visual mode"); + + // Cursor Movement + { + core.register_key_action(input_map, .W, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.move_cursor_forward_start_of_word(buffer, cursor = &sel_cur.end); + }, "move forward one word"); + core.register_key_action(input_map, .E, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.move_cursor_forward_end_of_word(buffer, cursor = &sel_cur.end); + }, "move forward to end of word"); + + core.register_key_action(input_map, .B, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.move_cursor_backward_start_of_word(buffer, cursor = &sel_cur.end); + }, "move backward one word"); + + core.register_key_action(input_map, .K, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.move_cursor_up(buffer, cursor = &sel_cur.end); + }, "move up one line"); + core.register_key_action(input_map, .J, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.move_cursor_down(buffer, cursor = &sel_cur.end); + }, "move down one line"); + core.register_key_action(input_map, .H, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.move_cursor_left(buffer, cursor = &sel_cur.end); + }, "move left one char"); + core.register_key_action(input_map, .L, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.move_cursor_right(buffer, cursor = &sel_cur.end); + }, "move right one char"); + + core.register_ctrl_key_action(input_map, .U, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.scroll_file_buffer(buffer, .Up, cursor = &sel_cur.end); + }, "scroll buffer up"); + core.register_ctrl_key_action(input_map, .D, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + sel_cur := &(buffer.selection.?); + + core.scroll_file_buffer(buffer, .Down, cursor = &sel_cur.end); + }, "scroll buffer up"); + } + + // Text Modification + { + core.register_key_action(input_map, .D, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.push_new_snapshot(&buffer.history) + + sel_cur := &(buffer.selection.?); + + core.delete_content(buffer, sel_cur); + buffer.selection = nil; + core.update_file_buffer_scroll(buffer) + + state.mode = .Normal + core.reset_input_map(state) + }, "delete selection"); + + core.register_key_action(input_map, .C, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.push_new_snapshot(&buffer.history) + + sel_cur := &(buffer.selection.?); + + core.delete_content(buffer, sel_cur); + buffer.selection = nil; + core.update_file_buffer_scroll(buffer) + + state.mode = .Insert + core.reset_input_map(state, core.Mode.Normal) + sdl2.StartTextInput(); + }, "change selection"); + } + + // Copy-Paste + { + core.register_key_action(input_map, .Y, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.yank_selection(state, buffer) + + state.mode = .Normal; + core.reset_input_map(state) + + buffer.selection = nil; + core.update_file_buffer_scroll(buffer) + }, "Yank Line"); + + core.register_key_action(input_map, .P, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.push_new_snapshot(&buffer.history) + + if state.yank_register.whole_line { + core.insert_content(buffer, []u8{'\n'}); + core.paste_register(state, state.yank_register, buffer) + core.insert_content(buffer, []u8{'\n'}); + } else { + core.paste_register(state, state.yank_register, buffer) + } + + core.reset_input_map(state) + }, "Paste"); + } +} + +file_buffer_text_input_actions :: proc(input_map: ^core.InputActions) { + core.register_key_action(input_map, .I, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.push_new_snapshot(&buffer.history) + + state.mode = .Insert; + sdl2.StartTextInput(); + }, "enter insert mode"); + core.register_key_action(input_map, .A, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.push_new_snapshot(&buffer.history) + + core.move_cursor_right(buffer, false); + state.mode = .Insert; + sdl2.StartTextInput(); + }, "enter insert mode after character (append)"); + + core.register_key_action(input_map, .U, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.pop_snapshot(&buffer.history, true) + }, "Undo"); + + core.register_ctrl_key_action(input_map, .R, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.recover_snapshot(&buffer.history) + }, "Redo"); + + // TODO: add shift+o to insert newline above current one + + core.register_key_action(input_map, .O, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.push_new_snapshot(&buffer.history) + + if buffer := buffer; buffer != nil { + core.move_cursor_end_of_line(buffer, false); + runtime.clear(&buffer.input_buffer) + + append(&buffer.input_buffer, '\n') + + state.mode = .Insert; + + sdl2.StartTextInput(); + } + }, "insert mode on newline"); + + // Copy-Paste + { + { + yank_actions := core.new_input_actions() + defer core.register_key_action(input_map, .Y, yank_actions) + + core.register_key_action(&yank_actions, .Y, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.yank_whole_line(state, buffer) + + core.reset_input_map(state) + }, "Yank Line"); + } + + core.register_key_action(input_map, .P, proc(state: ^core.State, user_data: rawptr) { + buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer + + core.push_new_snapshot(&buffer.history) + + if state.yank_register.whole_line { + core.move_cursor_end_of_line(buffer, false); + core.insert_content(buffer, []u8{'\n'}); + core.move_cursor_right(buffer, false); + } else { + core.move_cursor_right(buffer) + } + core.paste_register(state, state.yank_register, buffer) + core.move_cursor_start_of_line(buffer) + + core.reset_input_map(state) + }, "Paste"); + } + +} \ No newline at end of file diff --git a/src/panels/grep.odin b/src/panels/grep.odin new file mode 100644 index 0000000..2becc35 --- /dev/null +++ b/src/panels/grep.odin @@ -0,0 +1,309 @@ +package panels + +import "base:runtime" +import "core:mem" +import "core:fmt" +import "core:strings" +import "core:log" + +import "vendor:sdl2" + +import ts "../tree_sitter" +import "../core" +import "../input" +import "../util" +import "../ui" + +open_grep_panel :: proc(state: ^core.State) { + open(state, make_grep_panel()) + + state.mode = .Insert + sdl2.StartTextInput() +} + +make_grep_panel :: proc() -> core.Panel { + run_query :: proc(panel_state: ^core.GrepPanel, query: string, directory: string) { + if panel_state.query_region.arena != nil { + mem.end_arena_temp_memory(panel_state.query_region) + } + panel_state.query_region = mem.begin_arena_temp_memory(&panel_state.query_arena) + + context.allocator = mem.arena_allocator(&panel_state.query_arena) + + rs_results := grep( + strings.clone_to_cstring(query), + strings.clone_to_cstring(directory) + ); + + panel_state.query_results = rs_grep_as_results(&rs_results) + + panel_state.selected_result = 0 + if len(panel_state.query_results) > 0 { + core.update_glyph_buffer_from_bytes( + &panel_state.glyphs, + transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context, + panel_state.query_results[panel_state.selected_result].line, + ) + } + } + + return core.Panel { + type = core.GrepPanel {}, + drop = proc(panel: ^core.Panel, state: ^core.State) { + panel_state := &panel.type.(core.GrepPanel) + + ts.delete_state(&panel_state.buffer.tree) + }, + create = proc(panel: ^core.Panel, state: ^core.State) { + context.allocator = panel.allocator + + panel_state := &panel.type.(core.GrepPanel) + + mem.arena_init(&panel_state.query_arena, make([]u8, 1024*1024*2)) + + panel.input_map = core.new_input_map() + panel_state.glyphs = core.make_glyph_buffer(256,256) + panel_state.buffer = core.new_virtual_file_buffer() + + core.register_key_action(&panel.input_map.mode[.Normal], .ENTER, proc(state: ^core.State, user_data: rawptr) { + this_panel := transmute(^core.Panel)user_data + + if panel_state, ok := &this_panel.type.(core.GrepPanel); ok { + if panel_state.query_results != nil { + selected_result := &panel_state.query_results[panel_state.selected_result] + + if panel_id, ok := open_file_buffer_in_new_panel(state, selected_result.file_path, selected_result.line, selected_result.col); ok { + close(state, this_panel.id) + + state.current_panel = panel_id + } else { + log.error("failed to open file buffer in new panel") + } + } + } + }, "Open File"); + core.register_key_action(&panel.input_map.mode[.Normal], .I, proc(state: ^core.State, user_data: rawptr) { + state.mode = .Insert; + sdl2.StartTextInput(); + }, "enter insert mode"); + core.register_key_action(&panel.input_map.mode[.Normal], .K, proc(state: ^core.State, user_data: rawptr) { + this_panel := transmute(^core.Panel)user_data + + if panel_state, ok := &this_panel.type.(core.GrepPanel); ok { + // TODO: bounds checking + panel_state.selected_result -= 1 + + core.update_glyph_buffer_from_bytes( + &panel_state.glyphs, + transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context, + panel_state.query_results[panel_state.selected_result].line, + ) + } + }, "move selection up"); + core.register_key_action(&panel.input_map.mode[.Normal], .J, proc(state: ^core.State, user_data: rawptr) { + this_panel := transmute(^core.Panel)user_data + + if panel_state, ok := &this_panel.type.(core.GrepPanel); ok { + // TODO: bounds checking + panel_state.selected_result += 1 + + core.update_glyph_buffer_from_bytes( + &panel_state.glyphs, + transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context, + panel_state.query_results[panel_state.selected_result].line, + ) + } + }, "move selection down"); + + core.register_key_action(&panel.input_map.mode[.Insert], .ESCAPE, proc(state: ^core.State, user_data: rawptr) { + state.mode = .Normal; + sdl2.StopTextInput(); + }, "exit insert mode"); + core.register_key_action(&panel.input_map.mode[.Normal], .ESCAPE, proc(state: ^core.State, user_data: rawptr) { + this_panel := transmute(^core.Panel)user_data + close(state, this_panel.id) + }, "close panel"); + }, + buffer = proc(panel: ^core.Panel, state: ^core.State) -> (buffer: ^core.FileBuffer, ok: bool) { + if panel_state, ok := &panel.type.(core.GrepPanel); ok { + return &panel_state.buffer, true + } + + return + }, + 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) + } + }, + render = proc(panel: ^core.Panel, state: ^core.State) -> (ok: bool) { + if panel_state, ok := &panel.type.(core.GrepPanel); ok { + s := transmute(^ui.State)state.ui + + ui.open_element(s, nil, { + dir = .TopToBottom, + kind = {ui.Grow{}, ui.Grow{}} + }) + { + // query results and file contents side-by-side + ui.open_element(s, nil, { + dir = .LeftToRight, + kind = {ui.Grow{}, ui.Grow{}} + }) + { + if panel_state.query_results != nil { + // query results + query_result_container := ui.open_element(s, nil, + { + dir = .TopToBottom, + kind = {ui.Grow{}, ui.Grow{}} + }, + style = { + border = {.Left, .Right, .Top, .Bottom}, + border_color = .Background4 + } + ) + { + container_height := query_result_container.layout.size.y + max_results := container_height / 16 + + for result, i in panel_state.query_results { + if i > max_results { + break + } + + ui.open_element(s, nil, { + dir = .LeftToRight, + kind = {ui.Fit{}, ui.Fit{}}, + }) + { + defer ui.close_element(s) + + ui.open_element(s, fmt.tprintf("%v:%v: ", result.line, result.col), {}) + ui.close_element(s) + + + style := ui.UI_Style{} + + if panel_state.selected_result == i { + style.background_color = .Background2 + } + + ui.open_element(s, result.file_path, {}, style) + ui.close_element(s) + } + } + } + ui.close_element(s) + + // file contents + selected_result := &panel_state.query_results[panel_state.selected_result] + + core.update_glyph_buffer_from_bytes( + &panel_state.glyphs, + transmute([]u8)selected_result.file_context, + selected_result.line, + ) + render_glyph_buffer(state, s, &panel_state.glyphs) + } + } + ui.close_element(s) + + // text input + ui.open_element(s, nil, { + kind = {ui.Grow{}, ui.Exact(state.source_font_height)} + }) + { + defer ui.close_element(s) + + // render_raw_buffer(state, s, &state.buffers[panel_state.buffer]) + } + } + ui.close_element(s) + + return true + } + + return false + } + } +} + +foreign import grep_lib "../pkg/grep_lib/target/debug/libgrep.a" +@(default_calling_convention = "c") +foreign grep_lib { + grep :: proc (pattern: cstring, directory: cstring) -> RS_GrepResults --- + free_grep_results :: proc(results: RS_GrepResults) --- +} + +RS_GrepResults :: struct { + results: [^]RS_GrepResult, + len: u32, +} +RS_GrepResult :: struct { + line_number: u64, + column: u64, + + text_len: u32, + path_len: u32, + + text: [^]u8, + path: [^]u8, +} + +@(private) +rs_grep_as_results :: proc(results: ^RS_GrepResults, allocator := context.allocator) -> []core.GrepQueryResult { + context.allocator = allocator + + query_results := make([]core.GrepQueryResult, results.len) + + for i in 0.. RS_GrepResults --- - free_grep_results :: proc(results: RS_GrepResults) --- -} - -RS_GrepResults :: struct { - results: [^]RS_GrepResult, - len: u32, -} -RS_GrepResult :: struct { - line_number: u64, - column: u64, - - text_len: u32, - path_len: u32, - - text: [^]u8, - path: [^]u8, -} - -@(private) -rs_grep_as_results :: proc(results: ^RS_GrepResults, allocator := context.allocator) -> []core.GrepQueryResult { - context.allocator = allocator - - query_results := make([]core.GrepQueryResult, results.len) - - for i in 0.. (panel_id: int, ok: bool) { - if panel_id, ok := util.append_static_list(&state.panels, panel).?; ok && make_active { + if panel_id, panel, ok := util.append_static_list(&state.panels, panel); ok && make_active { + panel.id = panel_id state.current_panel = panel_id + mem.arena_init(&panel.arena, make([]u8, 1024*1024*4)) + panel.allocator = mem.arena_allocator(&panel.arena) + + panel->create(state) + core.reset_input_map(state) return panel_id, true @@ -120,9 +83,11 @@ open :: proc(state: ^core.State, panel: core.Panel, make_active: bool = true) -> close :: proc(state: ^core.State, panel_id: int) { if panel, ok := util.get(&state.panels, panel_id).?; ok { if panel.drop != nil { - panel.drop(state, &panel.panel_state) + panel->drop(state) } + mem.free(raw_data(panel.arena.data)) + util.delete(&state.panels, panel_id) // TODO: keep track of the last active panel instead of focusing back to the first one @@ -133,390 +98,3 @@ close :: proc(state: ^core.State, panel_id: int) { core.reset_input_map(state) } } - -open_file_buffer_in_new_panel :: proc(state: ^core.State, file_path: string, line, col: int) -> (panel_id, buffer_index: int, ok: bool) { - // FIXME: move into panel - buffer, err := core.new_file_buffer(context.allocator, file_path, state.directory); - if err.type != .None { - log.error("Failed to create file buffer:", err); - return; - } - - buffer.history.cursor.line = line - buffer.history.cursor.col = col - buffer.top_line = buffer.history.cursor.line - core.update_file_buffer_index_from_cursor(&buffer) - - // buffer_index = len(state.buffers) - // runtime.append(&state.buffers, buffer); - - if panel_id, ok := open(state, make_file_buffer_panel(buffer_index)); ok { - return panel_id, buffer_index, true - } - - return -1, -1, false -} - -render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) { - draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data; - if buffer != nil { - buffer.glyphs.width = e.layout.size.x / state.source_font_width; - buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1; - - core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y); - } - }; - - relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator) - - ui.open_element(s, nil, - { - dir = .TopToBottom, - kind = {ui.Grow{}, ui.Grow{}}, - }, - style = { - border = {.Left, .Right, .Top, .Bottom}, - border_color = .Background4, - background_color = .Background1, - } - ) - { - ui.open_element(s, - ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)buffer}, - { - kind = {ui.Grow{}, ui.Grow{}} - }, - style = { - border = {.Left, .Right, .Top, .Bottom}, - border_color = .Background4, - background_color = .Background1, - } - ) - ui.close_element(s) - - ui.open_element(s, nil, { - kind = {ui.Grow{}, ui.Exact(state.source_font_height)} - }, - style = { - border = {.Left, .Right, .Top, .Bottom}, - border_color = .Background4, - } - ) - { - ui.open_element(s, fmt.tprintf("%s", state.mode), {}) - ui.close_element(s) - - ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Grow{}}}) - ui.close_element(s) - - it := core.new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor) - ui.open_element( - s, - fmt.tprintf( - "%v:%v - Slice %v:%v - Char: %v", - buffer.history.cursor.line + 1, - buffer.history.cursor.col + 1, - buffer.history.cursor.index.chunk_index, - buffer.history.cursor.index.char_index, - core.get_character_at_iter(it) - ), - {} - ) - ui.close_element(s) - } - ui.close_element(s) - } - ui.close_element(s) -} - -render_raw_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) { - draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data; - if buffer != nil { - buffer.glyphs.width = e.layout.size.x / state.source_font_width; - buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1; - - core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y, false); - } - }; - - ui.open_element(s, ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)buffer}, { - kind = {ui.Grow{}, ui.Grow{}} - }) - ui.close_element(s) - -} - -render_glyph_buffer :: proc(state: ^core.State, s: ^ui.State, glyphs: ^core.GlyphBuffer) { - draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) { - glyphs := transmute(^core.GlyphBuffer)user_data; - if glyphs != nil { - glyphs.width = e.layout.size.x / state.source_font_width; - glyphs.height = e.layout.size.y / state.source_font_height + 1; - - core.draw_glyph_buffer(state, glyphs, e.layout.pos.x, e.layout.pos.y, 0, true); - } - }; - - ui.open_element(s, ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)glyphs}, { - kind = {ui.Grow{}, ui.Grow{}} - }) - ui.close_element(s) - -} - -make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel { - input_map := core.new_input_map() - - leader_actions := core.new_input_actions() - register_default_leader_actions(&leader_actions); - core.register_key_action(&input_map.mode[.Normal], .SPACE, leader_actions, "leader commands"); - - core.register_ctrl_key_action(&input_map.mode[.Normal], .W, core.new_input_actions(), "Panel Navigation") - register_default_panel_actions(&(&input_map.mode[.Normal].ctrl_key_actions[.W]).action.(core.InputActions)) - - input.register_default_input_actions(&input_map.mode[.Normal]); - input.register_default_visual_actions(&input_map.mode[.Visual]); - input.register_default_text_input_actions(&input_map.mode[.Normal]); - - return core.Panel { - panel_state = core.FileBufferPanel { buffer_index = buffer_index }, - input_map = input_map, - drop = proc(state: ^core.State, panel_state: ^core.PanelState) { - if panel_state, ok := &panel_state.(core.FileBufferPanel); ok { - // core.free_file_buffer(&state.buffers[panel_state.buffer_index]) - } - }, - render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) { - panel_state := panel_state.(core.FileBufferPanel) or_return; - - // FIXME: use buffer from panel - // s := transmute(^ui.State)state.ui - // buffer := &state.buffers[panel_state.buffer_index] - // render_file_buffer(state, s, buffer) - - return true - } - } -} - -open_grep_panel :: proc(state: ^core.State) { - open(state, make_grep_panel(state)) - - state.mode = .Insert - sdl2.StartTextInput() -} - -make_grep_panel :: proc(state: ^core.State) -> core.Panel { - query_arena: mem.Arena - mem.arena_init(&query_arena, make([]u8, 1024*1024*2, state.ctx.allocator)) - - glyphs := core.make_glyph_buffer(256,256, allocator = mem.arena_allocator(&query_arena)) - - input_map := core.new_input_map() - // FIXME: add to panel - grep_input_buffer := core.new_virtual_file_buffer(context.allocator) - // runtime.append(&state.buffers, grep_input_buffer) - - run_query :: proc(panel_state: ^core.GrepPanel, query: string, directory: string) { - if panel_state.query_region.arena != nil { - mem.end_arena_temp_memory(panel_state.query_region) - } - panel_state.query_region = mem.begin_arena_temp_memory(&panel_state.query_arena) - - context.allocator = mem.arena_allocator(&panel_state.query_arena) - - rs_results := grep( - strings.clone_to_cstring(query, allocator = context.temp_allocator), - strings.clone_to_cstring(directory, allocator = context.temp_allocator) - ); - - panel_state.query_results = rs_grep_as_results(&rs_results) - free_grep_results(rs_results) - - panel_state.selected_result = 0 - if len(panel_state.query_results) > 0 { - core.update_glyph_buffer_from_bytes( - &panel_state.glyphs, - transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context, - panel_state.query_results[panel_state.selected_result].line, - ) - } - } - - core.register_key_action(&input_map.mode[.Normal], .ENTER, proc(state: ^core.State, user_data: rawptr) { - this_panel := transmute(^core.Panel)user_data - - if panel_state, ok := &this_panel.panel_state.(core.GrepPanel); ok { - if panel_state.query_results != nil { - selected_result := &panel_state.query_results[panel_state.selected_result] - - if panel_id, buffer, ok := open_file_buffer_in_new_panel(state, selected_result.file_path, selected_result.line, selected_result.col); ok { - // FIXME: store panel_id in core.Panel - // close(state, this_panel) - - state.current_panel = panel_id - } else { - log.error("failed to open file buffer in new panel") - } - } - } - }, "Open File"); - core.register_key_action(&input_map.mode[.Normal], .I, proc(state: ^core.State, user_data: rawptr) { - state.mode = .Insert; - sdl2.StartTextInput(); - }, "enter insert mode"); - core.register_key_action(&input_map.mode[.Normal], .K, proc(state: ^core.State, user_data: rawptr) { - this_panel := transmute(^core.Panel)user_data - - if panel_state, ok := &this_panel.panel_state.(core.GrepPanel); ok { - // TODO: bounds checking - panel_state.selected_result -= 1 - - core.update_glyph_buffer_from_bytes( - &panel_state.glyphs, - transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context, - panel_state.query_results[panel_state.selected_result].line, - ) - } - }, "move selection up"); - core.register_key_action(&input_map.mode[.Normal], .J, proc(state: ^core.State, user_data: rawptr) { - this_panel := transmute(^core.Panel)user_data - - if panel_state, ok := &this_panel.panel_state.(core.GrepPanel); ok { - // TODO: bounds checking - panel_state.selected_result += 1 - - core.update_glyph_buffer_from_bytes( - &panel_state.glyphs, - transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context, - panel_state.query_results[panel_state.selected_result].line, - ) - } - }, "move selection down"); - - core.register_key_action(&input_map.mode[.Insert], .ESCAPE, proc(state: ^core.State, user_data: rawptr) { - state.mode = .Normal; - sdl2.StopTextInput(); - }, "exit insert mode"); - core.register_key_action(&input_map.mode[.Normal], .ESCAPE, proc(state: ^core.State, user_data: rawptr) { - if state.current_panel != nil { - // FIXME; store panel_id in core.Panel - // close(state, state.current_panel.?) - } - }, "close panel"); - - - return core.Panel { - panel_state = core.GrepPanel { - query_arena = query_arena, - // buffer = len(state.buffers)-1, - query_results = nil, - glyphs = glyphs, - }, - input_map = input_map, - on_buffer_input_proc = proc(state: ^core.State, panel_state: ^core.PanelState) { - if panel_state, ok := &panel_state.(core.GrepPanel); ok { - // buffer := &state.buffers[panel_state.buffer] - // run_query(panel_state, string(buffer.input_buffer[:]), state.directory) - } - }, - drop = proc(state: ^core.State, panel_state: ^core.PanelState) { - if panel_state, ok := &panel_state.(core.GrepPanel); ok { - // core.free_file_buffer(&state.buffers[panel_state.buffer]) - delete(panel_state.query_arena.data, state.ctx.allocator) - } - }, - render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) { - if panel_state, ok := &panel_state.(core.GrepPanel); ok { - s := transmute(^ui.State)state.ui - - ui.open_element(s, nil, { - dir = .TopToBottom, - kind = {ui.Grow{}, ui.Grow{}} - }) - { - // query results and file contents side-by-side - ui.open_element(s, nil, { - dir = .LeftToRight, - kind = {ui.Grow{}, ui.Grow{}} - }) - { - if panel_state.query_results != nil { - // query results - query_result_container := ui.open_element(s, nil, - { - dir = .TopToBottom, - kind = {ui.Grow{}, ui.Grow{}} - }, - style = { - border = {.Left, .Right, .Top, .Bottom}, - border_color = .Background4 - } - ) - { - container_height := query_result_container.layout.size.y - max_results := container_height / 16 - - for result, i in panel_state.query_results { - if i > max_results { - break - } - - ui.open_element(s, nil, { - dir = .LeftToRight, - kind = {ui.Fit{}, ui.Fit{}}, - }) - { - defer ui.close_element(s) - - ui.open_element(s, fmt.tprintf("%v:%v: ", result.line, result.col), {}) - ui.close_element(s) - - - style := ui.UI_Style{} - - if panel_state.selected_result == i { - style.background_color = .Background2 - } - - ui.open_element(s, result.file_path, {}, style) - ui.close_element(s) - } - } - } - ui.close_element(s) - - // file contents - selected_result := &panel_state.query_results[panel_state.selected_result] - - core.update_glyph_buffer_from_bytes( - &panel_state.glyphs, - transmute([]u8)selected_result.file_context, - selected_result.line, - ) - render_glyph_buffer(state, s, &panel_state.glyphs) - } - } - ui.close_element(s) - - // text input - ui.open_element(s, nil, { - kind = {ui.Grow{}, ui.Exact(state.source_font_height)} - }) - { - defer ui.close_element(s) - - // render_raw_buffer(state, s, &state.buffers[panel_state.buffer]) - } - } - ui.close_element(s) - - return true - } - - return false - } - } -} \ No newline at end of file diff --git a/src/pkg/grep_lib/src/lib.rs b/src/pkg/grep_lib/src/lib.rs index be2df2c..2345cbd 100644 --- a/src/pkg/grep_lib/src/lib.rs +++ b/src/pkg/grep_lib/src/lib.rs @@ -139,17 +139,17 @@ impl From for GrepResult { // this won't totally bite me later text_len: value.text.len() as u32, path_len: value.path.len() as u32, - text: Box::leak(value.text.into_boxed_slice()).as_ptr(), - path: Box::leak(value.path.into_bytes().into_boxed_slice()).as_ptr(), + text: Box::into_raw(value.text.into_boxed_slice()) as _, + path: Box::into_raw(value.path.into_bytes().into_boxed_slice()) as _, } } } impl From for Match { fn from(value: GrepResult) -> Self { unsafe { - let text = std::slice::from_raw_parts(value.text, value.text_len as usize).to_vec(); + let text = Box::from_raw(std::slice::from_raw_parts_mut(value.text as *mut _, value.text_len as usize)).to_vec(); - let path = std::slice::from_raw_parts(value.path, value.path_len as usize).to_vec(); + let path = Box::from_raw(std::slice::from_raw_parts_mut(value.path as *mut _, value.path_len as usize)).to_vec(); let path = String::from_utf8_unchecked(path); Self { @@ -188,17 +188,26 @@ extern "C" fn grep( .into_iter() .map(|sink| sink.matches.into_iter()) .flatten() - .map(Into::into) + .map(|v| GrepResult::from(v)) .collect::>() .into_boxed_slice(); let len = boxed.len() as u32; GrepResults { - results: Box::leak(boxed).as_ptr(), + results: Box::into_raw(boxed) as _, len, } } #[unsafe(no_mangle)] -extern "C" fn free_grep_results(_: GrepResults) { } +extern "C" fn free_grep_results(results: GrepResults) { + unsafe { + let mut array = std::slice::from_raw_parts_mut(results.results as *mut GrepResult, results.len as usize); + let array = Box::from_raw(array); + + for v in array { + let _ = Match::from(v); + } + } +} diff --git a/src/tree_sitter/ts.odin b/src/tree_sitter/ts.odin index 31155ef..08c8e14 100644 --- a/src/tree_sitter/ts.odin +++ b/src/tree_sitter/ts.odin @@ -223,6 +223,7 @@ make_state :: proc(type: LanguageType, allocator := context.allocator) -> State } delete_state :: proc(state: ^State) { + delete(state.highlights) tree_cursor_delete(&state.cursor) tree_delete(state.tree) parser_delete(state.parser) diff --git a/src/util/list.odin b/src/util/list.odin index 301cfe4..81e88f1 100644 --- a/src/util/list.odin +++ b/src/util/list.odin @@ -11,17 +11,17 @@ StaticListSlot :: struct($T: typeid) { data: T, } -append_static_list :: proc(list: ^StaticList($T), value: T) -> Maybe(int) { +append_static_list :: proc(list: ^StaticList($T), value: T) -> (id: int, panel: ^T, ok: bool) { for i in 0..