re-write grep plugin built-in + move things around

memory-refactor
Patrick Cleaveliln 2025-07-09 04:25:24 +00:00
parent 97326b54f8
commit 06d9750cd2
11 changed files with 1088 additions and 279 deletions

View File

@ -10,6 +10,10 @@
"features": {
"./odin-feature": {
"version": "latest"
},
"ghcr.io/devcontainers/features/rust:1": {
"version": "1.88.0",
"profile": "default"
}
}
}

View File

@ -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
odin build src/ -out:bin/editor -debug
grep:
cargo build --manifest-path "src/pkg/grep_lib/Cargo.toml"

View File

@ -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 = &current_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;

196
src/input/input.odin Normal file
View File

@ -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");
}

View File

@ -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..<state.panels.len {
panel := &state.panels.data[i]
if panel.render_proc != nil {
panel.render_proc(state, &panel.panel_state)
for i in 0..<len(state.panels.data) {
if panel, ok := util.get(&state.panels, i).?; ok {
if panel.render_proc != nil {
panel.render_proc(state, &panel.panel_state)
}
}
}
}
@ -282,7 +84,8 @@ draw :: proc(state: ^State) {
ui.compute_layout_2(new_ui)
ui.draw(new_ui, state)
if state.mode != .Insert && state.current_input_map != &state.input_map.mode[state.mode] {
// TODO: figure out when to not show the input help menu
if state.mode != .Insert { // && state.current_input_map != &state.input_map.mode[state.mode] {
longest_description := 0;
for key, action in state.current_input_map.key_actions {
if len(action.description) > 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 = &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;
@ -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)
}
}
}
}
}

View File

@ -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..<results.len {
r := results.results[i]
query_results[i] = core.GrepQueryResult {
file_context = strings.clone_from_ptr(r.text, int(r.text_len), allocator) or_continue,
file_path = strings.clone_from_ptr(r.path, int(r.path_len), allocator) or_continue,
line = int(r.line_number),
col = int(r.column),
}
}
return query_results
}
// NOTE: odd that this is here, but I don't feel like thinking of a better dep-tree to fix it
register_default_leader_actions :: proc(input_map: ^core.InputActions) {
core.register_key_action(input_map, .Q, proc(state: ^core.State) {
core.reset_input_map(state)
}, "close this help");
core.register_key_action(input_map, .R, proc(state: ^core.State) {
open(state, make_grep_panel(state))
}, "Grep Workspace")
}
open :: proc(state: ^core.State, panel: core.Panel, make_active: bool = true) {
if panel_id, ok := util.append_static_list(&state.panels, panel).?; ok && make_active {
state.current_panel = panel_id
core.reset_input_map(state)
}
}
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)
}
util.delete(&state.panels, panel_id)
core.reset_input_map(state)
}
}
open_file_buffer_in_new_panel :: proc(state: ^core.State, file_path: string) {
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_index := len(state.buffers)
runtime.append(&state.buffers, buffer);
open(state, make_file_buffer_panel(buffer_index))
}
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;
@ -64,10 +152,24 @@ render_raw_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBu
}
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");
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 := &current_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
}

381
src/pkg/grep_lib/Cargo.lock generated Normal file
View File

@ -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"

View File

@ -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"

208
src/pkg/grep_lib/src/lib.rs Normal file
View File

@ -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<T: std::fmt::Display>(message: T) -> Self {
eprintln!("{message}");
Self::StandardError
}
}
#[derive(Debug)]
struct Match {
text: Vec<u8>,
path: String,
line_number: Option<u64>,
column: u64,
}
impl Match {
fn from_sink_match_with_path(
value: &grep::searcher::SinkMatch<'_>,
path: Option<String>,
) -> Result<Self, SimpleSinkError> {
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<String>,
matches: Vec<Match>,
}
impl Sink for SimpleSink {
type Error = SimpleSinkError;
fn matched(
&mut self,
_searcher: &grep::searcher::Searcher,
mat: &grep::searcher::SinkMatch<'_>,
) -> Result<bool, Self::Error> {
self.matches.push(Match::from_sink_match_with_path(
mat,
self.current_path.clone(),
)?);
Ok(true)
}
}
fn search(pattern: &str, paths: &[&str]) -> Result<SimpleSink, Box<dyn Error>> {
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<Match> 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<GrepResult> 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::<Vec<_>>()
.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) { }

View File

@ -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
}
}
}

View File

@ -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..<len(list.data) {
if !list.data[i].active {
list.data[i].active = true
list.data[i].data = value
return i
}
}
list.data[list.len] = value
list.len += 1
return true
return nil
}
append :: proc{append_static_list}
make_static_list :: proc($T: typeid, len: int) -> 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}
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}