add initial grep panel, use input map of currently focused panel

memory-refactor
Patrick Cleaveliln 2025-07-06 01:16:34 +00:00
parent 342f31478e
commit 97326b54f8
5 changed files with 259 additions and 76 deletions

View File

@ -50,6 +50,7 @@ State :: struct {
command_arena: runtime.Allocator, command_arena: runtime.Allocator,
command_args: [dynamic]EditorCommandArgument, command_args: [dynamic]EditorCommandArgument,
current_panel: Maybe(int),
panels: util.StaticList(Panel), panels: util.StaticList(Panel),
} }
@ -70,20 +71,45 @@ EditorCommandArgument :: union #no_nil {
} }
PanelRenderProc :: proc(state: ^State, panel_state: ^PanelState) -> (ok: bool) PanelRenderProc :: proc(state: ^State, panel_state: ^PanelState) -> (ok: bool)
PanelBufferProc :: proc(state: ^State, panel_state: ^PanelState) -> (buffer: ^FileBuffer, ok: bool)
Panel :: struct { Panel :: struct {
panel_state: PanelState, panel_state: PanelState,
input_map: InputMap,
buffer_proc: PanelBufferProc,
render_proc: PanelRenderProc, render_proc: PanelRenderProc,
} }
PanelState :: union { PanelState :: union {
FileBufferPanel FileBufferPanel,
GrepPanel,
} }
FileBufferPanel :: struct { FileBufferPanel :: struct {
buffer_index: int, 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 { 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, &current_panel.panel_state); ok && panel_buffer != nil {
return panel_buffer
}
}
}
if state.current_buffer == -2 { if state.current_buffer == -2 {
return &state.log_buffer; return &state.log_buffer;
} }

View File

@ -264,11 +264,6 @@ draw :: proc(state: ^State) {
new_ui.max_size.x = state.screen_width new_ui.max_size.x = state.screen_width
new_ui.max_size.y = state.screen_height 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, { ui.open_element(new_ui, nil, {
dir = .LeftToRight, dir = .LeftToRight,
kind = {ui.Grow{}, ui.Grow{}}, kind = {ui.Grow{}, ui.Grow{}},
@ -543,6 +538,9 @@ main :: proc() {
runtime.append(&state.buffers, buffer); 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 { if sdl2.Init({.VIDEO}) < 0 {
log.error("SDL failed to initialize:", sdl2.GetError()); log.error("SDL failed to initialize:", sdl2.GetError());
return; return;
@ -610,6 +608,10 @@ main :: proc() {
control_key_pressed: bool; control_key_pressed: bool;
for !state.should_close { for !state.should_close {
if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok {
state.current_input_map = &current_panel.input_map.mode[state.mode]
}
{ {
// ui_context.last_mouse_left_down = ui_context.mouse_left_down; // ui_context.last_mouse_left_down = ui_context.mouse_left_down;
// ui_context.last_mouse_right_down = ui_context.mouse_right_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 { switch state.mode {
case .Visual: fallthrough case .Visual: fallthrough
case .Normal: { case .Normal: {
@ -647,26 +679,8 @@ main :: proc() {
if key == .LCTRL { if key == .LCTRL {
control_key_pressed = true; 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 { } else {
if action, exists := state.current_input_map.key_actions[key]; exists { run_key_action(&state, control_key_pressed, key)
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)
}
}
}
} }
} }
if sdl_event.type == .KEYUP { if sdl_event.type == .KEYUP {
@ -682,6 +696,8 @@ main :: proc() {
if sdl_event.type == .KEYDOWN { if sdl_event.type == .KEYDOWN {
key := core.Key(sdl_event.key.keysym.sym); key := core.Key(sdl_event.key.keysym.sym);
// TODO: make this work properly
if true || !run_key_action(&state, control_key_pressed, key) {
#partial switch key { #partial switch key {
case .ESCAPE: { case .ESCAPE: {
state.mode = .Normal; state.mode = .Normal;
@ -705,6 +721,7 @@ main :: proc() {
} }
} }
} }
}
if sdl_event.type == .TEXTINPUT { if sdl_event.type == .TEXTINPUT {
for char in sdl_event.text.text { for char in sdl_event.text.text {

View File

@ -1,17 +1,16 @@
package panels package panels
import "base:runtime"
import "core:path/filepath" import "core:path/filepath"
import "core:fmt" import "core:fmt"
import "vendor:sdl2"
import "../core" import "../core"
import "../util"
import "../ui" import "../ui"
make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel { render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) {
return core.Panel {
panel_state = core.FileBufferPanel { buffer_index = buffer_index },
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) { draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data; buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil { if buffer != nil {
@ -22,8 +21,6 @@ make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel {
} }
}; };
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) relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator)
ui.open_element(s, nil, { ui.open_element(s, nil, {
@ -46,6 +43,138 @@ make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel {
ui.close_element(s) 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;
s := transmute(^ui.State)state.ui
buffer := &state.buffers[panel_state.buffer_index]
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 := &current_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 := &current_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 return true
} }

View File

@ -28,3 +28,13 @@ make_static_list :: proc($T: typeid, len: int) -> StaticList(T) {
} }
make :: proc{make_static_list} 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}

View File

@ -1,6 +1,7 @@
# Bugs # Bugs
- Fix crash when cursor is over a new-line - Fix crash when cursor is over a new-line
- Fix jumping forward a word jumping past consecutive brackets - Fix jumping forward a word jumping past consecutive brackets
- Odd scrolling behavior on small screen heights
# Planned Features # Planned Features
- LSP Integration - LSP Integration