From 97326b54f8d1b5a0906d69460a7ae28a4ff1fc74 Mon Sep 17 00:00:00 2001 From: Patrick Cleaveliln Date: Sun, 6 Jul 2025 01:16:34 +0000 Subject: [PATCH] add initial grep panel, use input map of currently focused panel --- src/core/core.odin | 28 +++++- src/main.odin | 101 ++++++++++++--------- src/panels/panels.odin | 193 ++++++++++++++++++++++++++++++++++------- src/util/list.odin | 12 ++- todo.md | 1 + 5 files changed, 259 insertions(+), 76 deletions(-) diff --git a/src/core/core.odin b/src/core/core.odin index e622c55..ac9ab59 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -50,6 +50,7 @@ State :: struct { command_arena: runtime.Allocator, command_args: [dynamic]EditorCommandArgument, + current_panel: Maybe(int), panels: util.StaticList(Panel), } @@ -70,20 +71,45 @@ EditorCommandArgument :: union #no_nil { } PanelRenderProc :: proc(state: ^State, panel_state: ^PanelState) -> (ok: bool) +PanelBufferProc :: proc(state: ^State, panel_state: ^PanelState) -> (buffer: ^FileBuffer, ok: bool) Panel :: struct { panel_state: PanelState, + input_map: InputMap, + buffer_proc: PanelBufferProc, render_proc: PanelRenderProc, } PanelState :: union { - FileBufferPanel + FileBufferPanel, + GrepPanel, } FileBufferPanel :: struct { buffer_index: int, } +GrepPanel :: struct { + buffer: int, + search_query: string, + query_results: []GrepQueryResult, + selected_result: int, +} + +GrepQueryResult :: struct { + file_path: string, + line: int, + col: int, +} + current_buffer :: proc(state: ^State) -> ^FileBuffer { + if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { + if current_panel.buffer_proc != nil { + if panel_buffer, ok := current_panel.buffer_proc(state, ¤t_panel.panel_state); ok && panel_buffer != nil { + return panel_buffer + } + } + } + if state.current_buffer == -2 { return &state.log_buffer; } diff --git a/src/main.odin b/src/main.odin index 6fe1a62..098783b 100644 --- a/src/main.odin +++ b/src/main.odin @@ -264,11 +264,6 @@ draw :: proc(state: ^State) { new_ui.max_size.x = state.screen_width new_ui.max_size.y = state.screen_height - // TODO: use the new panels stuff - // if file_buffer := core.current_buffer(state); file_buffer != nil { - // ui_file_buffer(new_ui, file_buffer) - // } - ui.open_element(new_ui, nil, { dir = .LeftToRight, kind = {ui.Grow{}, ui.Grow{}}, @@ -543,6 +538,9 @@ main :: proc() { runtime.append(&state.buffers, buffer); } + util.append_static_list(&state.panels, panels.make_grep_panel(&state)) + state.current_panel = state.panels.len-1 + if sdl2.Init({.VIDEO}) < 0 { log.error("SDL failed to initialize:", sdl2.GetError()); return; @@ -610,6 +608,10 @@ main :: proc() { control_key_pressed: bool; for !state.should_close { + if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { + state.current_input_map = ¤t_panel.input_map.mode[state.mode] + } + { // ui_context.last_mouse_left_down = ui_context.mouse_left_down; // ui_context.last_mouse_right_down = ui_context.mouse_right_down; @@ -639,6 +641,36 @@ main :: proc() { } } + run_key_action := proc(state: ^core.State, control_key_pressed: bool, key: core.Key) -> bool { + if state.current_input_map != nil { + if control_key_pressed { + if action, exists := state.current_input_map.ctrl_key_actions[key]; exists { + switch value in action.action { + case core.EditorAction: + value(state); + return true; + case core.InputActions: + state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions) + return true; + } + } + } else { + if action, exists := state.current_input_map.key_actions[key]; exists { + switch value in action.action { + case core.EditorAction: + value(state); + return true; + case core.InputActions: + state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions) + return true; + } + } + } + } + + return false + } + switch state.mode { case .Visual: fallthrough case .Normal: { @@ -647,26 +679,8 @@ main :: proc() { if key == .LCTRL { control_key_pressed = true; - } else if state.current_input_map != nil { - if control_key_pressed { - if action, exists := state.current_input_map.ctrl_key_actions[key]; exists { - switch value in action.action { - case core.EditorAction: - value(&state); - case core.InputActions: - state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions) - } - } - } else { - if action, exists := state.current_input_map.key_actions[key]; exists { - switch value in action.action { - case core.EditorAction: - value(&state); - case core.InputActions: - state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions) - } - } - } + } else { + run_key_action(&state, control_key_pressed, key) } } if sdl_event.type == .KEYUP { @@ -682,26 +696,29 @@ main :: proc() { if sdl_event.type == .KEYDOWN { key := core.Key(sdl_event.key.keysym.sym); - #partial switch key { - case .ESCAPE: { - state.mode = .Normal; + // TODO: make this work properly + if true || !run_key_action(&state, control_key_pressed, key) { + #partial switch key { + 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); - sdl2.StopTextInput(); - } - case .TAB: { - // TODO: change this to insert a tab character - for _ in 0..<4 { - append(&buffer.input_buffer, ' '); + sdl2.StopTextInput(); + } + case .TAB: { + // TODO: change this to insert a tab character + for _ in 0..<4 { + append(&buffer.input_buffer, ' '); + } + } + case .BACKSPACE: { + core.delete_content(buffer, 1); + } + case .ENTER: { + append(&buffer.input_buffer, '\n'); } - } - case .BACKSPACE: { - core.delete_content(buffer, 1); - } - case .ENTER: { - append(&buffer.input_buffer, '\n'); } } } diff --git a/src/panels/panels.odin b/src/panels/panels.odin index 62246c5..540ecb4 100644 --- a/src/panels/panels.odin +++ b/src/panels/panels.odin @@ -1,53 +1,182 @@ package panels +import "base:runtime" import "core:path/filepath" import "core:fmt" +import "vendor:sdl2" + import "../core" +import "../util" import "../ui" +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.glyph_buffer_width = e.layout.size.x / state.source_font_width; + buffer.glyph_buffer_height = e.layout.size.y / state.source_font_height + 1; + + core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.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{}}, + }) + { + 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) + + ui.open_element(s, nil, { + kind = {ui.Grow{}, ui.Exact(state.source_font_height)} + }) + { + ui.open_element(s, fmt.tprintf("%s", state.mode), {}) + 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.glyph_buffer_width = e.layout.size.x / state.source_font_width; + buffer.glyph_buffer_height = e.layout.size.y / state.source_font_height + 1; + + core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.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) + +} + make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel { return core.Panel { panel_state = core.FileBufferPanel { buffer_index = buffer_index }, + // TODO: move the input registration from main.odin to here + input_map = core.new_input_map(), render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) { panel_state := panel_state.(core.FileBufferPanel) or_return; - - draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data; - if buffer != nil { - buffer.glyph_buffer_width = e.layout.size.x / state.source_font_width; - buffer.glyph_buffer_height = e.layout.size.y / state.source_font_height + 1; - - core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y); - } - }; - s := transmute(^ui.State)state.ui buffer := &state.buffers[panel_state.buffer_index] - 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{}}, - }) - { - 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) - - ui.open_element(s, nil, { - kind = {ui.Grow{}, ui.Exact(state.source_font_height)} - }) - { - ui.open_element(s, fmt.tprintf("%s", state.mode), {}) - ui.close_element(s) - } - ui.close_element(s) - } - ui.close_element(s) + render_file_buffer(state, s, buffer) return true } } } + +make_grep_panel :: proc(state: ^core.State) -> core.Panel { + input_map := core.new_input_map() + grep_input_buffer := core.new_virtual_file_buffer(context.allocator) + runtime.append(&state.buffers, grep_input_buffer) + + core.register_key_action(&input_map.mode[.Normal], .I, proc(state: ^core.State) { + state.mode = .Insert; + sdl2.StartTextInput(); + }, "enter insert mode"); + core.register_key_action(&input_map.mode[.Normal], .K, proc(state: ^core.State) { + // NOTE: this is really jank, should probably update the input + // action stuff to allow panels to be passed into these handlers + if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { + if panel_state, ok := ¤t_panel.panel_state.(core.GrepPanel); ok { + // TODO: bounds checking + panel_state.selected_result -= 1 + } + } + }, "move selection up"); + core.register_key_action(&input_map.mode[.Normal], .J, proc(state: ^core.State) { + // NOTE: this is really jank, should probably update the input + // action stuff to allow panels to be passed into these handlers + if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { + if panel_state, ok := ¤t_panel.panel_state.(core.GrepPanel); ok { + // TODO: bounds checking + panel_state.selected_result += 1 + } + } + }, "move selection down"); + + core.register_key_action(&input_map.mode[.Insert], .ESCAPE, proc(state: ^core.State) { + state.mode = .Normal; + sdl2.StopTextInput(); + }, "exit insert mode"); + core.register_key_action(&input_map.mode[.Insert], .ENTER, proc(state: ^core.State) { + state.mode = .Normal; + sdl2.StopTextInput(); + }, "search"); + + results := make([]core.GrepQueryResult, 4) + results[0] = core.GrepQueryResult { + file_path = "src/main.odin" + } + results[1] = core.GrepQueryResult { + file_path = "src/core/core.odin" + } + results[2] = core.GrepQueryResult { + file_path = "src/panels/panels.odin" + } + results[3] = core.GrepQueryResult { + file_path = "src/core/gfx.odin" + } + + return core.Panel { + panel_state = core.GrepPanel { + buffer = len(state.buffers)-1, + query_results = results, + }, + input_map = input_map, + buffer_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (buffer: ^core.FileBuffer, ok: bool) { + panel_state := panel_state.(core.GrepPanel) or_return; + + return &state.buffers[panel_state.buffer], true + }, + render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) { + panel_state := panel_state.(core.GrepPanel) or_return; + + s := transmute(^ui.State)state.ui + ui.open_element(s, nil, { + dir = .TopToBottom, + kind = {ui.Grow{}, ui.Grow{}} + }) + { + defer ui.close_element(s) + + for result, i in panel_state.query_results { + // TODO: when styling is implemented, make this look better + if panel_state.selected_result == i { + ui.open_element(s, fmt.tprintf("%s <--", result.file_path), {}) + ui.close_element(s) + } else { + ui.open_element(s, result.file_path, {}) + ui.close_element(s) + } + } + + 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]) + } + } + + return true + } + } +} \ No newline at end of file diff --git a/src/util/list.odin b/src/util/list.odin index 86516f4..962a38b 100644 --- a/src/util/list.odin +++ b/src/util/list.odin @@ -27,4 +27,14 @@ make_static_list :: proc($T: typeid, len: int) -> StaticList(T) { return list } -make :: proc{make_static_list} \ No newline at end of file +make :: proc{make_static_list} + +get_static_list_elem :: proc(list: ^StaticList($T), index: int) -> Maybe(^T) { + if index >= list.len { + return nil + } + + return &list.data[index] +} + +get :: proc{get_static_list_elem} \ No newline at end of file diff --git a/todo.md b/todo.md index f808ae8..0e3698b 100644 --- a/todo.md +++ b/todo.md @@ -1,6 +1,7 @@ # Bugs - Fix crash when cursor is over a new-line - Fix jumping forward a word jumping past consecutive brackets +- Odd scrolling behavior on small screen heights # Planned Features - LSP Integration