added a window api of sorts, got started on grep style tool

plugins
Patrick Cleavelin 2024-01-01 22:10:50 -06:00
parent ff020fd059
commit a7fa2f6b1d
5 changed files with 306 additions and 91 deletions

View File

@ -8,6 +8,36 @@ Mode :: enum {
Insert, Insert,
} }
WindowDrawProc :: proc(win: ^Window, state: ^State);
WindowFreeProc :: proc(win: ^Window, state: ^State);
WindowGetBufferProc :: proc(win: ^Window) -> ^FileBuffer;
Window :: struct {
input_map: InputMap,
draw: WindowDrawProc,
free: WindowFreeProc,
get_buffer: WindowGetBufferProc,
// TODO: create hook for when mode changes happen
}
request_window_close :: proc(state: ^State) {
state.should_close_window = true;
}
close_window_and_free :: proc(state: ^State) {
if state.window != nil {
if state.window.free != nil {
state.window->free(state);
}
delete_input_map(&state.window.input_map);
free(state.window);
state.window = nil;
state.current_input_map = &state.input_map;
}
}
State :: struct { State :: struct {
mode: Mode, mode: Mode,
should_close: bool, should_close: bool,
@ -22,10 +52,8 @@ State :: struct {
current_buffer: int, current_buffer: int,
buffers: [dynamic]FileBuffer, buffers: [dynamic]FileBuffer,
// TODO: replace this with generic pointer to floating window window: ^Window,
buffer_list_window_is_visible: bool, should_close_window: bool,
buffer_list_window_selected_buffer: int,
buffer_list_window_input_map: InputMap,
input_map: InputMap, input_map: InputMap,
current_input_map: ^InputMap, current_input_map: ^InputMap,
@ -50,6 +78,10 @@ new_input_map :: proc() -> InputMap {
return input_map; return input_map;
} }
delete_input_map :: proc(input_map: ^InputMap) {
delete(input_map.key_actions);
delete(input_map.ctrl_key_actions);
}
// NOTE(pcleavelin): might be a bug in the compiler where it can't coerce // NOTE(pcleavelin): might be a bug in the compiler where it can't coerce
// `EditorAction` to `InputGroup` when given as a proc parameter, that is why there // `EditorAction` to `InputGroup` when given as a proc parameter, that is why there

View File

@ -86,8 +86,11 @@ iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBuf
} else if it.cursor.index.slice_index < len(it.buffer.content_slices)-1 { } else if it.cursor.index.slice_index < len(it.buffer.content_slices)-1 {
it.cursor.index.content_index = 0; it.cursor.index.content_index = 0;
it.cursor.index.slice_index += 1; it.cursor.index.slice_index += 1;
} else { } else if it.hit_end {
return character, it.cursor.index, false; return character, it.cursor.index, false;
} else {
it.hit_end = true;
return character, it.cursor.index, true;
} }
if character == '\n' { if character == '\n' {
@ -531,6 +534,31 @@ move_cursor_backward_end_of_word :: proc(buffer: ^FileBuffer) {
update_file_buffer_scroll(buffer); update_file_buffer_scroll(buffer);
} }
new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
context.allocator = allocator;
width := 256;
height := 256;
buffer := FileBuffer {
allocator = allocator,
file_path = "virtual_buffer",
original_content = slice.clone_to_dynamic([]u8{'\n'}),
added_content = make([dynamic]u8, 0, 1024*1024),
content_slices = make([dynamic][]u8, 0, 1024*1024),
glyph_buffer_width = width,
glyph_buffer_height = height,
glyph_buffer = make([dynamic]Glyph, width*height, width*height),
input_buffer = make([dynamic]u8, 0, 1024),
};
append(&buffer.content_slices, buffer.original_content[:]);
return buffer;
}
new_file_buffer :: proc(allocator: mem.Allocator, file_path: string) -> (FileBuffer, Error) { new_file_buffer :: proc(allocator: mem.Allocator, file_path: string) -> (FileBuffer, Error) {
context.allocator = allocator; context.allocator = allocator;
@ -567,6 +595,14 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string) -> (FileBuf
} }
} }
free_file_buffer :: proc(buffer: ^FileBuffer) {
delete(buffer.original_content);
delete(buffer.added_content);
delete(buffer.content_slices);
delete(buffer.glyph_buffer);
delete(buffer.input_buffer);
}
is_keyword :: proc(start: FileBufferIter, end: FileBufferIter) -> (matches: bool) { is_keyword :: proc(start: FileBufferIter, end: FileBufferIter) -> (matches: bool) {
keywords := []string { keywords := []string {
"using", "using",

View File

@ -19,8 +19,8 @@ FileBuffer :: core.FileBuffer;
// TODO: use buffer list in state // TODO: use buffer list in state
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) { do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
if state.current_input_map != nil { if state.current_input_map != nil {
if raylib.IsKeyDown(.ESCAPE) { if raylib.IsKeyPressed(.ESCAPE) {
state.current_input_map = &state.input_map; core.request_window_close(state);
} else if raylib.IsKeyDown(.LEFT_CONTROL) { } else if raylib.IsKeyDown(.LEFT_CONTROL) {
for key, action in &state.current_input_map.ctrl_key_actions { for key, action in &state.current_input_map.ctrl_key_actions {
if raylib.IsKeyPressed(key) { if raylib.IsKeyPressed(key) {
@ -87,9 +87,14 @@ switch_to_buffer :: proc(state: ^State, item: ^ui.MenuBarItem) {
register_default_leader_actions :: proc(input_map: ^core.InputMap) { register_default_leader_actions :: proc(input_map: ^core.InputMap) {
core.register_key_action(input_map, .B, proc(state: ^State) { core.register_key_action(input_map, .B, proc(state: ^State) {
state.buffer_list_window_is_visible = true; state.window = ui.create_buffer_list_window();
state.current_input_map = &state.buffer_list_window_input_map; state.current_input_map = &state.window.input_map;
}, "show list of open buffers"); }, "show list of open buffers");
core.register_key_action(input_map, .R, proc(state: ^State) {
state.window = ui.create_grep_window();
state.current_input_map = &state.window.input_map;
state.mode = .Insert;
}, "live grep");
core.register_key_action(input_map, .Q, proc(state: ^State) { core.register_key_action(input_map, .Q, proc(state: ^State) {
state.current_input_map = &state.input_map; state.current_input_map = &state.input_map;
}, "close this help"); }, "close this help");
@ -107,6 +112,8 @@ register_default_go_actions :: proc(input_map: ^core.InputMap) {
} }
register_default_input_actions :: proc(input_map: ^core.InputMap) { register_default_input_actions :: proc(input_map: ^core.InputMap) {
// Cursor Movement
{
core.register_key_action(input_map, .W, proc(state: ^State) { core.register_key_action(input_map, .W, proc(state: ^State) {
core.move_cursor_forward_start_of_word(&state.buffers[state.current_buffer]); core.move_cursor_forward_start_of_word(&state.buffers[state.current_buffer]);
}, "move forward one word"); }, "move forward one word");
@ -137,8 +144,10 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) {
core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { core.register_ctrl_key_action(input_map, .D, proc(state: ^State) {
core.scroll_file_buffer(&state.buffers[state.current_buffer], .Down); core.scroll_file_buffer(&state.buffers[state.current_buffer], .Down);
}, "scroll buffer up"); }, "scroll buffer up");
}
// Scale font size // Scale font size
{
core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) { core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) {
if state.source_font_height > 16 { if state.source_font_height > 16 {
state.source_font_height -= 2; state.source_font_height -= 2;
@ -155,7 +164,10 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) {
state.font = raylib.LoadFontEx("/System/Library/Fonts/Supplemental/Andale Mono.ttf", i32(state.source_font_height*2), nil, 0); state.font = raylib.LoadFontEx("/System/Library/Fonts/Supplemental/Andale Mono.ttf", i32(state.source_font_height*2), nil, 0);
raylib.SetTextureFilter(state.font.texture, .BILINEAR); raylib.SetTextureFilter(state.font.texture, .BILINEAR);
}, "decrease font size"); }, "decrease font size");
}
// Inserting Text
{
core.register_key_action(input_map, .I, proc(state: ^State) { core.register_key_action(input_map, .I, proc(state: ^State) {
state.mode = .Insert; state.mode = .Insert;
}, "enter insert mode"); }, "enter insert mode");
@ -163,6 +175,7 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) {
core.move_cursor_right(&state.buffers[state.current_buffer], false); core.move_cursor_right(&state.buffers[state.current_buffer], false);
state.mode = .Insert; state.mode = .Insert;
}, "enter insert mode after character (append)"); }, "enter insert mode after character (append)");
}
core.register_key_action(input_map, .SPACE, core.new_input_map(), "leader commands"); core.register_key_action(input_map, .SPACE, core.new_input_map(), "leader commands");
register_default_leader_actions(&(&input_map.key_actions[.SPACE]).action.(core.InputMap)); register_default_leader_actions(&(&input_map.key_actions[.SPACE]).action.(core.InputMap));
@ -171,44 +184,15 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) {
register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputMap)); register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputMap));
} }
register_buffer_list_input_actions :: proc(input_map: ^core.InputMap) {
core.register_key_action(input_map, .K, proc(state: ^State) {
if state.buffer_list_window_selected_buffer > 0 {
state.buffer_list_window_selected_buffer -= 1;
} else {
state.buffer_list_window_selected_buffer = len(state.buffers)-1;
}
}, "move selection up");
core.register_key_action(input_map, .J, proc(state: ^State) {
if state.buffer_list_window_selected_buffer >= len(state.buffers)-1 {
state.buffer_list_window_selected_buffer = 0;
} else {
state.buffer_list_window_selected_buffer += 1;
}
}, "move selection down");
core.register_key_action(input_map, .ENTER, proc(state: ^State) {
state.current_buffer = state.buffer_list_window_selected_buffer;
state.buffer_list_window_is_visible = false;
state.current_input_map = &state.input_map;
}, "switch to file");
core.register_key_action(input_map, .Q, proc(state: ^State) {
state.buffer_list_window_is_visible = false;
state.current_input_map = &state.input_map;
}, "close window");
}
main :: proc() { main :: proc() {
state := State { state := State {
source_font_width = 8, source_font_width = 8,
source_font_height = 16, source_font_height = 16,
input_map = core.new_input_map(), input_map = core.new_input_map(),
buffer_list_window_input_map = core.new_input_map(), window = nil,
}; };
state.current_input_map = &state.input_map; state.current_input_map = &state.input_map;
register_default_input_actions(&state.input_map); register_default_input_actions(&state.input_map);
register_buffer_list_input_actions(&state.buffer_list_window_input_map);
for arg in os.args[1:] { for arg in os.args[1:] {
buffer, err := core.new_file_buffer(context.allocator, arg); buffer, err := core.new_file_buffer(context.allocator, arg);
@ -323,8 +307,8 @@ main :: proc() {
0, 0,
theme.get_palette_raylib_color(.Background1)); theme.get_palette_raylib_color(.Background1));
if state.buffer_list_window_is_visible { if state.window != nil && state.window.draw != nil {
ui.draw_buffer_list_window(&state); state.window->draw(&state);
} }
if state.current_input_map != &state.input_map { if state.current_input_map != &state.input_map {
@ -377,10 +361,23 @@ main :: proc() {
switch state.mode { switch state.mode {
case .Normal: case .Normal:
if state.window != nil && state.window.get_buffer != nil {
do_normal_mode(&state, state.window->get_buffer());
} else {
do_normal_mode(&state, buffer); do_normal_mode(&state, buffer);
}
case .Insert: case .Insert:
if state.window != nil && state.window.get_buffer != nil {
do_insert_mode(&state, state.window->get_buffer());
} else {
do_insert_mode(&state, buffer); do_insert_mode(&state, buffer);
} }
}
if state.should_close_window {
state.should_close_window = false;
core.close_window_and_free(&state);
}
ui.test_menu_bar(&state, &menu_bar_state, 0,0, mouse_pos, raylib.IsMouseButtonReleased(.LEFT), state.source_font_height); ui.test_menu_bar(&state, &menu_bar_state, 0,0, mouse_pos, raylib.IsMouseButtonReleased(.LEFT), state.source_font_height);
} }

View File

@ -6,7 +6,60 @@ import "vendor:raylib"
import "../core" import "../core"
import "../theme" import "../theme"
draw_buffer_list_window :: proc(state: ^core.State) { BufferListWindow :: struct {
using window: core.Window,
selected_buffer: int,
}
create_buffer_list_window :: proc() -> ^BufferListWindow {
input_map := core.new_input_map();
core.register_key_action(&input_map, .K, proc(state: ^core.State) {
win := cast(^BufferListWindow)(state.window);
if win.selected_buffer > 0 {
win.selected_buffer -= 1;
} else {
win.selected_buffer = len(state.buffers)-1;
}
}, "move selection up");
core.register_key_action(&input_map, .J, proc(state: ^core.State) {
win := cast(^BufferListWindow)(state.window);
if win.selected_buffer >= len(state.buffers)-1 {
win.selected_buffer = 0;
} else {
win.selected_buffer += 1;
}
}, "move selection down");
core.register_key_action(&input_map, .ENTER, proc(state: ^core.State) {
win := cast(^BufferListWindow)(state.window);
state.current_buffer = win.selected_buffer;
core.request_window_close(state);
}, "switch to file");
core.register_key_action(&input_map, .Q, proc(state: ^core.State) {
core.request_window_close(state);
}, "close window");
list_window := new(BufferListWindow);
list_window^ = BufferListWindow {
window = core.Window {
input_map = input_map,
draw = draw_buffer_list_window,
},
};
return list_window;
}
draw_buffer_list_window :: proc(win: ^core.Window, state: ^core.State) {
win := cast(^BufferListWindow)(win);
win_rec := raylib.Rectangle { win_rec := raylib.Rectangle {
x = f32(state.screen_width/8), x = f32(state.screen_width/8),
y = f32(state.screen_height/8), y = f32(state.screen_height/8),
@ -37,7 +90,7 @@ draw_buffer_list_window :: proc(state: ^core.State) {
text := raylib.TextFormat("%s:%d", buffer.file_path, buffer.cursor.line+1); text := raylib.TextFormat("%s:%d", buffer.file_path, buffer.cursor.line+1);
text_width := raylib.MeasureTextEx(state.font, text, f32(state.source_font_height), 0); text_width := raylib.MeasureTextEx(state.font, text, f32(state.source_font_height), 0);
if index == state.buffer_list_window_selected_buffer { if index == win.selected_buffer {
buffer.glyph_buffer_height = glyph_buffer_height; buffer.glyph_buffer_height = glyph_buffer_height;
buffer.glyph_buffer_width = glyph_buffer_width; buffer.glyph_buffer_width = glyph_buffer_width;
core.draw_file_buffer( core.draw_file_buffer(

97
src/ui/grep_window.odin Normal file
View File

@ -0,0 +1,97 @@
package ui;
import "core:math"
import "vendor:raylib"
import "../core"
import "../theme"
GrepWindow :: struct {
using window: core.Window,
input_buffer: core.FileBuffer,
}
create_grep_window :: proc() -> ^GrepWindow {
input_map := core.new_input_map();
core.register_key_action(&input_map, .ENTER, proc(state: ^core.State) {
win := cast(^GrepWindow)(state.window);
core.request_window_close(state);
}, "jump to location");
core.register_key_action(&input_map, .I, proc(state: ^core.State) {
state.mode = .Insert;
}, "enter insert mode");
grep_window := new(GrepWindow);
grep_window^ = GrepWindow {
window = core.Window {
input_map = input_map,
draw = draw_grep_window,
get_buffer = grep_window_get_buffer,
free = free_grep_window,
},
input_buffer = core.new_virtual_file_buffer(context.allocator),
};
return grep_window;
}
free_grep_window :: proc(win: ^core.Window, state: ^core.State) {
win := cast(^GrepWindow)(win);
core.free_file_buffer(&win.input_buffer);
}
grep_window_get_buffer :: proc(win: ^core.Window) -> ^core.FileBuffer {
win := cast(^GrepWindow)(win);
return &win.input_buffer;
}
@private
grep_files :: proc(win: ^core.Window, state: ^core.State) {
// TODO: use rip-grep to search through files
}
draw_grep_window :: proc(win: ^core.Window, state: ^core.State) {
win := cast(^GrepWindow)(win);
win_rec := raylib.Rectangle {
x = f32(state.screen_width/8),
y = f32(state.screen_height/8),
width = f32(state.screen_width - state.screen_width/4),
height = f32(state.screen_height - state.screen_height/4),
};
raylib.DrawRectangleRec(
win_rec,
theme.get_palette_raylib_color(.Background4));
win_margin := raylib.Vector2 { f32(state.source_font_width), f32(state.source_font_height) };
buffer_prev_width := (win_rec.width - win_margin.x*2) / 2;
buffer_prev_height := win_rec.height - win_margin.y*2;
glyph_buffer_width := int(buffer_prev_width) / state.source_font_width - 1;
glyph_buffer_height := 1;
raylib.DrawRectangle(
i32(win_rec.x + win_margin.x),
i32(win_rec.y + win_rec.height - win_margin.y * 2),
i32(buffer_prev_width),
i32(state.source_font_height),
theme.get_palette_raylib_color(.Background2));
win.input_buffer.glyph_buffer_height = glyph_buffer_height;
win.input_buffer.glyph_buffer_width = glyph_buffer_width;
core.draw_file_buffer(
state,
&win.input_buffer,
int(win_rec.x + win_margin.x),
int(win_rec.y + win_rec.height - win_margin.y * 2),
state.font,
show_line_numbers = false);
}