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_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, &current_panel.panel_state); ok && panel_buffer != nil {
return panel_buffer
}
}
}
if state.current_buffer == -2 {
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.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 = &current_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');
}
}
}

View File

@ -1,51 +1,180 @@
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 := &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
}

View File

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