diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5e05819..bbfe67b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,6 +10,10 @@ "features": { "./odin-feature": { "version": "latest" + }, + "ghcr.io/devcontainers/features/rust:1": { + "version": "1.88.0", + "profile": "default" } } } \ No newline at end of file diff --git a/Makefile b/Makefile index 9fcfd75..004a5dd 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,10 @@ +export RUSTFLAGS=-C target-feature=-avx2 + all: editor -editor: src/**/*.odin +editor: grep src/**/*.odin mkdir -p bin - odin build src/ -out:bin/editor -debug \ No newline at end of file + odin build src/ -out:bin/editor -debug + +grep: + cargo build --manifest-path "src/pkg/grep_lib/Cargo.toml" diff --git a/src/core/core.odin b/src/core/core.odin index ac9ab59..8093769 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -2,6 +2,7 @@ package core import "base:runtime" import "base:intrinsics" +import "core:mem" import "core:reflect" import "core:fmt" import "core:log" @@ -10,6 +11,8 @@ import lua "vendor:lua/5.4" import "../util" +HardcodedFontPath :: "bin/JetBrainsMono-Regular.ttf"; + Mode :: enum { Normal, Insert, @@ -43,7 +46,6 @@ State :: struct { log_buffer: FileBuffer, - input_map: InputMap, current_input_map: ^InputActions, commands: EditorCommandList, @@ -72,11 +74,16 @@ EditorCommandArgument :: union #no_nil { 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, input_map: InputMap, buffer_proc: PanelBufferProc, + on_buffer_input_proc: PanelBufferInputProc, render_proc: PanelRenderProc, + drop: PanelDropProc, } PanelState :: union { @@ -89,13 +96,15 @@ FileBufferPanel :: struct { } GrepPanel :: struct { + query_arena: mem.Arena, buffer: int, + selected_result: int, search_query: string, query_results: []GrepQueryResult, - selected_result: int, } GrepQueryResult :: struct { + file_context: string, file_path: string, line: int, col: int, @@ -121,6 +130,16 @@ current_buffer :: proc(state: ^State) -> ^FileBuffer { return &state.buffers[state.current_buffer]; } +reset_input_map_from_state_mode :: proc(state: ^State) { + reset_input_map_from_mode(state, state.mode) +} +reset_input_map_from_mode :: proc(state: ^State, mode: Mode) { + if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { + state.current_input_map = ¤t_panel.input_map.mode[mode] + } +} +reset_input_map :: proc{reset_input_map_from_mode, reset_input_map_from_state_mode} + buffer_from_index :: proc(state: ^State, buffer_index: int) -> ^FileBuffer { if buffer_index == -2 { return &state.log_buffer; diff --git a/src/input/input.odin b/src/input/input.odin new file mode 100644 index 0000000..7686c80 --- /dev/null +++ b/src/input/input.odin @@ -0,0 +1,196 @@ +package input + +import "core:log" + +import "vendor:sdl2" + +import "../core" + +State :: core.State + +register_default_go_actions :: proc(input_map: ^core.InputActions) { + core.register_key_action(input_map, .H, proc(state: ^State) { + core.move_cursor_start_of_line(core.current_buffer(state)); + core.reset_input_map(state) + }, "move to beginning of line"); + core.register_key_action(input_map, .L, proc(state: ^State) { + core.move_cursor_end_of_line(core.current_buffer(state)); + 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) { + core.move_cursor_forward_start_of_word(core.current_buffer(state)); + }, "move forward one word"); + core.register_key_action(input_map, .E, proc(state: ^State) { + core.move_cursor_forward_end_of_word(core.current_buffer(state)); + }, "move forward to end of word"); + + core.register_key_action(input_map, .B, proc(state: ^State) { + core.move_cursor_backward_start_of_word(core.current_buffer(state)); + }, "move backward one word"); + + core.register_key_action(input_map, .K, proc(state: ^State) { + core.move_cursor_up(core.current_buffer(state)); + }, "move up one line"); + core.register_key_action(input_map, .J, proc(state: ^State) { + core.move_cursor_down(core.current_buffer(state)); + }, "move down one line"); + core.register_key_action(input_map, .H, proc(state: ^State) { + core.move_cursor_left(core.current_buffer(state)); + }, "move left one char"); + core.register_key_action(input_map, .L, proc(state: ^State) { + core.move_cursor_right(core.current_buffer(state)); + }, "move right one char"); + + core.register_ctrl_key_action(input_map, .U, proc(state: ^State) { + core.scroll_file_buffer(core.current_buffer(state), .Up); + }, "scroll buffer up"); + core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { + core.scroll_file_buffer(core.current_buffer(state), .Down); + }, "scroll buffer up"); + } + + // Scale font size + { + core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) { + 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) { + 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"); + } + + 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) { + state.mode = .Visual; + core.reset_input_map(state) + + core.current_buffer(state).selection = core.new_selection(core.current_buffer(state).cursor); + }, "enter visual mode"); + +} + +register_default_visual_actions :: proc(input_map: ^core.InputActions) { + core.register_key_action(input_map, .ESCAPE, proc(state: ^State) { + state.mode = .Normal; + core.reset_input_map(state) + + core.current_buffer(state).selection = nil; + core.update_file_buffer_scroll(core.current_buffer(state)) + }, "exit visual mode"); + + // Cursor Movement + { + core.register_key_action(input_map, .W, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.move_cursor_forward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end); + }, "move forward one word"); + core.register_key_action(input_map, .E, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.move_cursor_forward_end_of_word(core.current_buffer(state), cursor = &sel_cur.end); + }, "move forward to end of word"); + + core.register_key_action(input_map, .B, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.move_cursor_backward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end); + }, "move backward one word"); + + core.register_key_action(input_map, .K, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.move_cursor_up(core.current_buffer(state), cursor = &sel_cur.end); + }, "move up one line"); + core.register_key_action(input_map, .J, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.move_cursor_down(core.current_buffer(state), cursor = &sel_cur.end); + }, "move down one line"); + core.register_key_action(input_map, .H, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.move_cursor_left(core.current_buffer(state), cursor = &sel_cur.end); + }, "move left one char"); + core.register_key_action(input_map, .L, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.move_cursor_right(core.current_buffer(state), cursor = &sel_cur.end); + }, "move right one char"); + + core.register_ctrl_key_action(input_map, .U, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.scroll_file_buffer(core.current_buffer(state), .Up, cursor = &sel_cur.end); + }, "scroll buffer up"); + core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.scroll_file_buffer(core.current_buffer(state), .Down, cursor = &sel_cur.end); + }, "scroll buffer up"); + } + + // Text Modification + { + core.register_key_action(input_map, .D, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.delete_content(core.current_buffer(state), sel_cur); + core.current_buffer(state).selection = nil; + core.update_file_buffer_scroll(core.current_buffer(state)) + + state.mode = .Normal + core.reset_input_map(state) + }, "delete selection"); + + core.register_key_action(input_map, .C, proc(state: ^State) { + sel_cur := &(core.current_buffer(state).selection.?); + + core.delete_content(core.current_buffer(state), sel_cur); + core.current_buffer(state).selection = nil; + core.update_file_buffer_scroll(core.current_buffer(state)) + + state.mode = .Insert + core.reset_input_map(state, core.Mode.Normal) + sdl2.StartTextInput(); + }, "change selection"); + } +} + +register_default_text_input_actions :: proc(input_map: ^core.InputActions) { + core.register_key_action(input_map, .I, proc(state: ^State) { + state.mode = .Insert; + sdl2.StartTextInput(); + }, "enter insert mode"); + core.register_key_action(input_map, .A, proc(state: ^State) { + core.move_cursor_right(core.current_buffer(state), false); + state.mode = .Insert; + sdl2.StartTextInput(); + }, "enter insert mode after character (append)"); + + // TODO: add shift+o to insert newline above current one + + core.register_key_action(input_map, .O, proc(state: ^State) { + core.move_cursor_end_of_line(core.current_buffer(state), false); + core.insert_content(core.current_buffer(state), []u8{'\n'}); + state.mode = .Insert; + + sdl2.StartTextInput(); + }, "insert mode on newline"); +} diff --git a/src/main.odin b/src/main.odin index 098783b..09b68cd 100644 --- a/src/main.odin +++ b/src/main.odin @@ -19,8 +19,6 @@ import "panels" import "theme" import "ui" -HardcodedFontPath :: "bin/JetBrainsMono-Regular.ttf"; - State :: core.State; FileBuffer :: core.FileBuffer; @@ -47,202 +45,6 @@ do_insert_mode :: proc(state: ^State, buffer: ^FileBuffer) { do_visual_mode :: proc(state: ^State, buffer: ^FileBuffer) { } -register_default_leader_actions :: proc(input_map: ^core.InputActions) { - core.register_key_action(input_map, .Q, proc(state: ^State) { - state.current_input_map = &state.input_map.mode[state.mode]; - }, "close this help"); -} - -register_default_go_actions :: proc(input_map: ^core.InputActions) { - core.register_key_action(input_map, .H, proc(state: ^State) { - core.move_cursor_start_of_line(core.current_buffer(state)); - state.current_input_map = &state.input_map.mode[state.mode]; - }, "move to beginning of line"); - core.register_key_action(input_map, .L, proc(state: ^State) { - core.move_cursor_end_of_line(core.current_buffer(state)); - state.current_input_map = &state.input_map.mode[state.mode]; - }, "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) { - core.move_cursor_forward_start_of_word(core.current_buffer(state)); - }, "move forward one word"); - core.register_key_action(input_map, .E, proc(state: ^State) { - core.move_cursor_forward_end_of_word(core.current_buffer(state)); - }, "move forward to end of word"); - - core.register_key_action(input_map, .B, proc(state: ^State) { - core.move_cursor_backward_start_of_word(core.current_buffer(state)); - }, "move backward one word"); - - core.register_key_action(input_map, .K, proc(state: ^State) { - core.move_cursor_up(core.current_buffer(state)); - }, "move up one line"); - core.register_key_action(input_map, .J, proc(state: ^State) { - core.move_cursor_down(core.current_buffer(state)); - }, "move down one line"); - core.register_key_action(input_map, .H, proc(state: ^State) { - core.move_cursor_left(core.current_buffer(state)); - }, "move left one char"); - core.register_key_action(input_map, .L, proc(state: ^State) { - core.move_cursor_right(core.current_buffer(state)); - }, "move right one char"); - - core.register_ctrl_key_action(input_map, .U, proc(state: ^State) { - core.scroll_file_buffer(core.current_buffer(state), .Up); - }, "scroll buffer up"); - core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { - core.scroll_file_buffer(core.current_buffer(state), .Down); - }, "scroll buffer up"); - } - - // Scale font size - { - core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) { - 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, HardcodedFontPath); - } - log.debug(state.source_font_height); - }, "increase font size"); - core.register_ctrl_key_action(input_map, .EQUAL, proc(state: ^State) { - state.source_font_height += 2; - state.source_font_width = state.source_font_height / 2; - - state.font_atlas = core.gen_font_atlas(state, HardcodedFontPath); - }, "decrease font size"); - } - - core.register_key_action(input_map, .SPACE, core.new_input_actions(), "leader commands"); - register_default_leader_actions(&(&input_map.key_actions[.SPACE]).action.(core.InputActions)); - - 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(&state.input_map.mode[.Normal], .V, proc(state: ^State) { - state.mode = .Visual; - state.current_input_map = &state.input_map.mode[.Visual]; - - core.current_buffer(state).selection = core.new_selection(core.current_buffer(state).cursor); - }, "enter visual mode"); - -} - -register_default_visual_actions :: proc(input_map: ^core.InputActions) { - core.register_key_action(input_map, .ESCAPE, proc(state: ^State) { - state.mode = .Normal; - state.current_input_map = &state.input_map.mode[.Normal]; - - core.current_buffer(state).selection = nil; - core.update_file_buffer_scroll(core.current_buffer(state)) - }, "exit visual mode"); - - // Cursor Movement - { - core.register_key_action(input_map, .W, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.move_cursor_forward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end); - }, "move forward one word"); - core.register_key_action(input_map, .E, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.move_cursor_forward_end_of_word(core.current_buffer(state), cursor = &sel_cur.end); - }, "move forward to end of word"); - - core.register_key_action(input_map, .B, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.move_cursor_backward_start_of_word(core.current_buffer(state), cursor = &sel_cur.end); - }, "move backward one word"); - - core.register_key_action(input_map, .K, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.move_cursor_up(core.current_buffer(state), cursor = &sel_cur.end); - }, "move up one line"); - core.register_key_action(input_map, .J, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.move_cursor_down(core.current_buffer(state), cursor = &sel_cur.end); - }, "move down one line"); - core.register_key_action(input_map, .H, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.move_cursor_left(core.current_buffer(state), cursor = &sel_cur.end); - }, "move left one char"); - core.register_key_action(input_map, .L, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.move_cursor_right(core.current_buffer(state), cursor = &sel_cur.end); - }, "move right one char"); - - core.register_ctrl_key_action(input_map, .U, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.scroll_file_buffer(core.current_buffer(state), .Up, cursor = &sel_cur.end); - }, "scroll buffer up"); - core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.scroll_file_buffer(core.current_buffer(state), .Down, cursor = &sel_cur.end); - }, "scroll buffer up"); - } - - // Text Modification - { - core.register_key_action(input_map, .D, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.delete_content(core.current_buffer(state), sel_cur); - core.current_buffer(state).selection = nil; - core.update_file_buffer_scroll(core.current_buffer(state)) - - state.mode = .Normal - state.current_input_map = &state.input_map.mode[.Normal]; - }, "delete selection"); - - core.register_key_action(input_map, .C, proc(state: ^State) { - sel_cur := &(core.current_buffer(state).selection.?); - - core.delete_content(core.current_buffer(state), sel_cur); - core.current_buffer(state).selection = nil; - core.update_file_buffer_scroll(core.current_buffer(state)) - - state.mode = .Insert - state.current_input_map = &state.input_map.mode[.Normal]; - sdl2.StartTextInput(); - }, "change selection"); - } -} - -register_default_text_input_actions :: proc(input_map: ^core.InputActions) { - core.register_key_action(input_map, .I, proc(state: ^State) { - state.mode = .Insert; - sdl2.StartTextInput(); - }, "enter insert mode"); - core.register_key_action(input_map, .A, proc(state: ^State) { - core.move_cursor_right(core.current_buffer(state), false); - state.mode = .Insert; - sdl2.StartTextInput(); - }, "enter insert mode after character (append)"); - - // TODO: add shift+o to insert newline above current one - - core.register_key_action(input_map, .O, proc(state: ^State) { - core.move_cursor_end_of_line(core.current_buffer(state), false); - core.insert_content(core.current_buffer(state), []u8{'\n'}); - state.mode = .Insert; - - sdl2.StartTextInput(); - }, "insert mode on newline"); -} - ui_font_width :: proc() -> i32 { return i32(state.source_font_width); } @@ -269,11 +71,11 @@ draw :: proc(state: ^State) { kind = {ui.Grow{}, ui.Grow{}}, }) { - for i in 0.. longest_description { @@ -406,7 +209,6 @@ main :: proc() { screen_height = 480, source_font_width = 8, source_font_height = 16, - input_map = core.new_input_map(), commands = make(core.EditorCommandList), command_arena = mem.arena_allocator(&_command_arena), @@ -429,11 +231,7 @@ main :: proc() { mem.scratch_allocator_init(&scratch, 1024*1024); scratch_alloc = mem.scratch_allocator(&scratch); - state.current_input_map = &state.input_map.mode[.Normal]; - register_default_input_actions(&state.input_map.mode[.Normal]); - register_default_visual_actions(&state.input_map.mode[.Visual]); - - register_default_text_input_actions(&state.input_map.mode[.Normal]); + core.reset_input_map(&state) // core.register_editor_command( // &state.commands, @@ -482,6 +280,7 @@ main :: proc() { "Opens a new scratch buffer", proc(state: ^State) { buffer := core.new_virtual_file_buffer(context.allocator); + util.append_static_list(&state.panels, panels.make_file_buffer_panel(len(state.buffers))) runtime.append(&state.buffers, buffer); } ) @@ -500,13 +299,7 @@ main :: proc() { if args, ok := core.attempt_read_command_args(Args, state.command_args[:]); ok { log.info("attempting to open file", args.file_path) - buffer, err := core.new_file_buffer(context.allocator, args.file_path, state.directory); - if err.type != .None { - log.error("Failed to create file buffer:", err); - return; - } - - runtime.append(&state.buffers, buffer); + panels.open_file_buffer_in_new_panel(state, args.file_path) } } ) @@ -522,14 +315,7 @@ main :: proc() { if len(os.args) > 1 { for arg in os.args[1:] { - buffer, err := core.new_file_buffer(context.allocator, arg, state.directory); - if err.type != .None { - log.error("Failed to create file buffer:", err); - continue; - } - - util.append_static_list(&state.panels, panels.make_file_buffer_panel(len(state.buffers))) - runtime.append(&state.buffers, buffer); + panels.open_file_buffer_in_new_panel(&state, arg) } } else { buffer := core.new_virtual_file_buffer(context.allocator); @@ -538,9 +324,6 @@ 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; @@ -578,7 +361,7 @@ main :: proc() { log.error("Failed to create renderer:", sdl2.GetError()); return; } - state.font_atlas = core.gen_font_atlas(&state, HardcodedFontPath); + state.font_atlas = core.gen_font_atlas(&state, core.HardcodedFontPath); defer { if state.font_atlas.font != nil { ttf.CloseFont(state.font_atlas.font); @@ -608,10 +391,6 @@ 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; @@ -733,6 +512,12 @@ main :: proc() { append(&buffer.input_buffer, u8(char)); } } + + 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) + } + } } } } diff --git a/src/panels/panels.odin b/src/panels/panels.odin index 540ecb4..b44e290 100644 --- a/src/panels/panels.odin +++ b/src/panels/panels.odin @@ -1,15 +1,103 @@ package panels import "base:runtime" +import "core:mem" import "core:path/filepath" import "core:fmt" +import "core:strings" +import "core:log" import "vendor:sdl2" import "../core" +import "../input" import "../util" import "../ui" +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 { + query_results := make([]core.GrepQueryResult, results.len) + + for i in 0.. 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"); + + 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 }, - // TODO: move the input registration from main.odin to here - input_map = core.new_input_map(), + input_map = input_map, + buffer_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (buffer: ^core.FileBuffer, ok: bool) { + panel_state := panel_state.(core.FileBufferPanel) or_return; + + return &state.buffers[panel_state.buffer_index], true + }, 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 @@ -81,10 +183,44 @@ make_file_buffer_panel :: proc(buffer_index: int) -> core.Panel { } make_grep_panel :: proc(state: ^core.State) -> core.Panel { + query_arena: mem.Arena + mem.arena_init(&query_arena, make([]u8, 1024*1024)) + input_map := core.new_input_map() 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) { + mem.arena_free_all(&panel_state.query_arena) + panel_state.query_results = nil + + 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, mem.arena_allocator(&panel_state.query_arena)) + free_grep_results(rs_results) + } + + core.register_key_action(&input_map.mode[.Normal], .ENTER, proc(state: ^core.State) { + if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { + this_panel := state.current_panel.? + + if panel_state, ok := ¤t_panel.panel_state.(core.GrepPanel); ok { + if panel_state.query_results != nil { + selected_result := &panel_state.query_results[panel_state.selected_result] + + open_file_buffer_in_new_panel(state, selected_result.file_path) + + mem.arena_free_all(&panel_state.query_arena) + panel_state.query_results = nil + + close(state, this_panel) + } + } + } + }, "Open File"); core.register_key_action(&input_map.mode[.Normal], .I, proc(state: ^core.State) { state.mode = .Insert; sdl2.StartTextInput(); @@ -114,29 +250,18 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel { 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"); + core.register_key_action(&input_map.mode[.Normal], .ESCAPE, proc(state: ^core.State) { + if state.current_panel != nil { + close(state, state.current_panel.?) + } + }, "close panel"); + - 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 { + query_arena = query_arena, buffer = len(state.buffers)-1, - query_results = results, + query_results = nil, }, input_map = input_map, buffer_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (buffer: ^core.FileBuffer, ok: bool) { @@ -144,28 +269,75 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel { return &state.buffers[panel_state.buffer], true }, + 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 { + delete(panel_state.query_arena.data) + } + }, 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) + // 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 + ui.open_element(s, nil, { + dir = .TopToBottom, + kind = {ui.Grow{}, ui.Grow{}} + }) + { + for result, i in panel_state.query_results { + ui.open_element(s, nil, { + dir = .LeftToRight, + kind = {ui.Fit{}, ui.Fit{}}, + }) + { + 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.open_element(s, fmt.tprintf("%v:%v: ", result.line, result.col), {}) + ui.close_element(s) + + // 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.close_element(s) - } else { - ui.open_element(s, result.file_path, {}) + + // file contents + selected_result := &panel_state.query_results[panel_state.selected_result] + ui.open_element(s, selected_result.file_context, { + kind = {ui.Grow{}, ui.Grow{}} + }) ui.close_element(s) } } + ui.close_element(s) + // text input ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Exact(state.source_font_height)} }) @@ -175,6 +347,7 @@ make_grep_panel :: proc(state: ^core.State) -> core.Panel { render_raw_buffer(state, s, &state.buffers[panel_state.buffer]) } } + ui.close_element(s) return true } diff --git a/src/pkg/grep_lib/Cargo.lock b/src/pkg/grep_lib/Cargo.lock new file mode 100644 index 0000000..1adc1e1 --- /dev/null +++ b/src/pkg/grep_lib/Cargo.lock @@ -0,0 +1,381 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "grep" +version = "0.1.0" +dependencies = [ + "grep 0.3.2", + "termcolor", + "walkdir", +] + +[[package]] +name = "grep" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308ae749734e28d749a86f33212c7b756748568ce332f970ac1d9cd8531f32e6" +dependencies = [ + "grep-cli", + "grep-matcher", + "grep-printer", + "grep-regex", + "grep-searcher", +] + +[[package]] +name = "grep-cli" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f1288f0e06f279f84926fa4c17e3fcd2a22b357927a82f2777f7be26e4cec0" +dependencies = [ + "bstr", + "globset", + "libc", + "log", + "termcolor", + "winapi-util", +] + +[[package]] +name = "grep-matcher" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02" +dependencies = [ + "memchr", +] + +[[package]] +name = "grep-printer" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c112110ae4a891aa4d83ab82ecf734b307497d066f437686175e83fbd4e013fe" +dependencies = [ + "bstr", + "grep-matcher", + "grep-searcher", + "log", + "serde", + "serde_json", + "termcolor", +] + +[[package]] +name = "grep-regex" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edd147c7e3296e7a26bd3a81345ce849557d5a8e48ed88f736074e760f91f7e" +dependencies = [ + "bstr", + "grep-matcher", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "grep-searcher" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b6c14b3fc2e0a107d6604d3231dec0509e691e62447104bc385a46a7892cda" +dependencies = [ + "bstr", + "encoding_rs", + "encoding_rs_io", + "grep-matcher", + "log", + "memchr", + "memmap2", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/src/pkg/grep_lib/Cargo.toml b/src/pkg/grep_lib/Cargo.toml new file mode 100644 index 0000000..70f36a1 --- /dev/null +++ b/src/pkg/grep_lib/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "grep" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +grep = "0.3.2" +termcolor = "1.4.1" +walkdir = "2.5.0" diff --git a/src/pkg/grep_lib/src/lib.rs b/src/pkg/grep_lib/src/lib.rs new file mode 100644 index 0000000..02f8328 --- /dev/null +++ b/src/pkg/grep_lib/src/lib.rs @@ -0,0 +1,208 @@ +use std::{ + error::Error, + ffi::{CStr, CString, OsString}, + path::Path, + str::FromStr, + sync::mpsc::{Receiver, Sender}, + thread, +}; + +use grep::{ + regex::RegexMatcherBuilder, + searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError}, +}; +use std::sync::mpsc::channel; +use walkdir::WalkDir; + +#[derive(Debug)] +pub enum SimpleSinkError { + StandardError, + NoLine, + BadString, +} + +impl SinkError for SimpleSinkError { + fn error_message(message: T) -> Self { + eprintln!("{message}"); + + Self::StandardError + } +} + +#[derive(Debug)] +struct Match { + text: Vec, + path: String, + line_number: Option, + column: u64, +} +impl Match { + fn from_sink_match_with_path( + value: &grep::searcher::SinkMatch<'_>, + path: Option, + ) -> Result { + let line = value + .lines() + .next() + .ok_or(SimpleSinkError::NoLine)? + .to_vec(); + let column = value.bytes_range_in_buffer().len() as u64; + + Ok(Self { + text: line, + path: path.unwrap_or_default(), + line_number: value.line_number(), + column, + }) + } +} + +#[derive(Default, Debug)] +struct SimpleSink { + current_path: Option, + matches: Vec, +} +impl Sink for SimpleSink { + type Error = SimpleSinkError; + + fn matched( + &mut self, + _searcher: &grep::searcher::Searcher, + mat: &grep::searcher::SinkMatch<'_>, + ) -> Result { + self.matches.push(Match::from_sink_match_with_path( + mat, + self.current_path.clone(), + )?); + + Ok(true) + } +} + +fn search(pattern: &str, paths: &[&str]) -> Result> { + let matcher = RegexMatcherBuilder::new() + .case_smart(true) + .fixed_strings(true) + .build(pattern)?; + let mut searcher = SearcherBuilder::new() + .binary_detection(BinaryDetection::quit(b'\x00')) + .line_number(true) + .build(); + + let mut sink = SimpleSink::default(); + for path in paths { + for result in WalkDir::new(path).into_iter().filter_entry(|dent| { + if dent.file_type().is_dir() + && (dent.path().ends_with("target") || dent.path().ends_with(".git")) + { + return false; + } + + true + }) { + let dent = match result { + Ok(dent) => dent, + Err(err) => { + eprintln!("{}", err); + continue; + } + }; + if !dent.file_type().is_file() { + continue; + } + sink.current_path = Some(dent.path().to_string_lossy().into()); + + let result = searcher.search_path(&matcher, dent.path(), &mut sink); + + if let Err(err) = result { + eprintln!("{}: {:?}", dent.path().display(), err); + } + } + } + + Ok(sink) +} + +#[repr(C)] +struct GrepResult { + line_number: u64, + column: u64, + + text_len: u32, + path_len: u32, + + text: *const u8, + path: *const u8, +} + +impl From for GrepResult { + fn from(value: Match) -> Self { + Self { + line_number: value.line_number.unwrap_or(1), + column: value.column, + // 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(), + } + } +} +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 path = std::slice::from_raw_parts(value.path, value.path_len as usize).to_vec(); + let path = String::from_utf8_unchecked(path); + + Self { + text, + path, + line_number: Some(value.line_number), + column: value.column, + } + } + } +} + +#[repr(C)] +struct GrepResults { + results: *const GrepResult, + len: u32, +} + +// NOTE(pcleavelin): for some reason the current odin compiler (2025-04 as of this comment) is providing an extra argument (I assume a pointer to the odin context struct) +#[unsafe(no_mangle)] +extern "C" fn grep( + // _: *const std::ffi::c_char, + pattern: *const std::ffi::c_char, + directory: *const std::ffi::c_char, +) -> GrepResults { + let (pattern, directory) = unsafe { + ( + CStr::from_ptr(pattern).to_string_lossy(), + CStr::from_ptr(directory).to_string_lossy(), + ) + }; + + println!("pattern: '{pattern}', directory: '{directory}'"); + + let boxed = search(&pattern, &[&directory]) + .into_iter() + .map(|sink| sink.matches.into_iter()) + .flatten() + .map(Into::into) + .collect::>() + .into_boxed_slice(); + + let len = boxed.len() as u32; + + GrepResults { + results: Box::leak(boxed).as_ptr(), + len, + } +} + +#[unsafe(no_mangle)] +extern "C" fn free_grep_results(_: GrepResults) { } diff --git a/src/ui/ui.odin b/src/ui/ui.odin index e560d01..40c3188 100644 --- a/src/ui/ui.odin +++ b/src/ui/ui.odin @@ -106,7 +106,7 @@ close_element :: proc(state: ^State, loc := #caller_location) -> UI_Layout { switch v in e.kind { case UI_Element_Kind_Text: { // FIXME: properly use font size - e.layout.size.x = len(v) * 9 + e.layout.size.x = len(v) * 10 } case UI_Element_Kind_Image: { // TODO @@ -230,11 +230,19 @@ grow_children :: proc(state: ^State, index: int) { case .RightToLeft: fallthrough case .LeftToRight: { children_size.x += child.layout.size.x + + if children_size.y < child.layout.size.y { + children_size.y = child.layout.size.y + } } case .BottomToTop: fallthrough case .TopToBottom: { children_size.y += child.layout.size.y + + if children_size.x < child.layout.size.x { + children_size.x = child.layout.size.x + } } } diff --git a/src/util/list.odin b/src/util/list.odin index 962a38b..d1fbd4e 100644 --- a/src/util/list.odin +++ b/src/util/list.odin @@ -3,25 +3,31 @@ package util import "base:runtime" StaticList :: struct($T: typeid) { - data: []T, - len: int, + data: []StaticListSlot(T), } -append_static_list :: proc(list: ^StaticList($T), value: T) -> bool { - if list.len >= len(list.data) { - return false +StaticListSlot :: struct($T: typeid) { + active: bool, + data: T, +} + +append_static_list :: proc(list: ^StaticList($T), value: T) -> Maybe(int) { + for i in 0.. StaticList(T) { list := StaticList(T) { - data = runtime.make_slice([]T, len) + data = runtime.make_slice([]StaticListSlot(T), len) } return list @@ -30,11 +36,23 @@ 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 { + if index < 0 || index >= len(list.data) { return nil } - return &list.data[index] + if list.data[index].active { + return &list.data[index].data + } + + return nil } -get :: proc{get_static_list_elem} \ No newline at end of file +get :: proc{get_static_list_elem} + +delete_static_list_elem :: proc(list: ^StaticList($T), index: int) { + if index >= 0 && index < len(list.data) { + list.data[index].active = false + } +} + +delete :: proc{delete_static_list_elem} \ No newline at end of file