refactor how buffers are stored, group panel lifetimes

memory-refactor
Patrick Cleaveliln 2025-07-19 21:22:14 +00:00
parent e412dbe7a2
commit 9ace98118d
11 changed files with 908 additions and 832 deletions

View File

@ -74,33 +74,43 @@ EditorCommandArgument :: union #no_nil {
i32
}
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,
using vtable: Panel_VTable,
arena: mem.Arena,
allocator: mem.Allocator,
id: int,
type: PanelType,
input_map: InputMap,
buffer_proc: PanelBufferProc,
on_buffer_input_proc: PanelBufferInputProc,
render_proc: PanelRenderProc,
drop: PanelDropProc,
is_floating: bool,
}
PanelState :: union {
Panel_VTable :: struct {
create: proc(panel: ^Panel, state: ^State),
drop: proc(panel: ^Panel, state: ^State),
on_buffer_input: proc(panel: ^Panel, state: ^State),
buffer: proc(panel: ^Panel, state: ^State) -> (buffer: ^FileBuffer, ok: bool),
render: proc(panel: ^Panel, state: ^State) -> (ok: bool),
}
PanelType :: union {
FileBufferPanel,
GrepPanel,
}
FileBufferPanel :: struct {
buffer_index: int,
buffer: FileBuffer,
// only used for initialization
file_path: string,
line, col: int,
}
GrepPanel :: struct {
query_arena: mem.Arena,
query_region: mem.Arena_Temp_Memory,
buffer: int,
buffer: FileBuffer,
selected_result: int,
search_query: string,
query_results: []GrepQueryResult,
@ -114,6 +124,17 @@ GrepQueryResult :: struct {
col: int,
}
current_buffer :: proc(state: ^State) -> ^FileBuffer {
if current_panel, ok := state.current_panel.?; ok {
if panel, ok := util.get(&state.panels, current_panel).?; ok {
buffer, _ := panel->buffer(state)
return buffer
}
}
return nil
}
yank_whole_line :: proc(state: ^State, buffer: ^FileBuffer) {
if state.yank_register.data != nil {
delete(state.yank_register.data)
@ -190,7 +211,9 @@ Action :: struct {
description: string,
}
new_input_map :: proc() -> InputMap {
new_input_map :: proc(allocator := context.allocator) -> InputMap {
context.allocator = allocator
input_map := InputMap {
mode = make(map[Mode]InputActions),
}
@ -205,7 +228,9 @@ new_input_map :: proc() -> InputMap {
return input_map;
}
new_input_actions :: proc() -> InputActions {
new_input_actions :: proc(allocator := context.allocator) -> InputActions {
context.allocator = allocator
input_actions := InputActions {
key_actions = make(map[Key]Action),
ctrl_key_actions = make(map[Key]Action),

View File

@ -662,7 +662,7 @@ selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int {
return length
}
new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
new_virtual_file_buffer :: proc(allocator := context.allocator) -> FileBuffer {
context.allocator = allocator;
width := 256;
height := 256;
@ -706,6 +706,11 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
extension := filepath.ext(fi.fullpath);
if original_content, success := os.read_entire_file_from_handle(fd); success {
defer delete(original_content)
content := make([]u8, len(original_content))
copy_slice(content, original_content)
width := 256;
height := 256;
@ -721,7 +726,7 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
// TODO: derive language type from extension
tree = ts.make_state(.Odin),
history = make_history(original_content),
history = make_history(content),
glyphs = make_glyph_buffer(width, height),
input_buffer = make([dynamic]u8, 0, 1024),

View File

@ -1,354 +0,0 @@
package input
import "base:runtime"
import "core:log"
import "vendor:sdl2"
import "../core"
import "../util"
State :: core.State
register_default_go_actions :: proc(input_map: ^core.InputActions) {
core.register_key_action(input_map, .H, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_start_of_line(buffer);
core.reset_input_map(state)
}, "move to beginning of line");
core.register_key_action(input_map, .L, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_end_of_line(buffer);
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, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_forward_start_of_word(buffer);
}, "move forward one word");
core.register_key_action(input_map, .E, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_forward_end_of_word(buffer);
}, "move forward to end of word");
core.register_key_action(input_map, .B, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_backward_start_of_word(buffer);
}, "move backward one word");
core.register_key_action(input_map, .K, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_up(buffer);
}, "move up one line");
core.register_key_action(input_map, .J, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_down(buffer);
}, "move down one line");
core.register_key_action(input_map, .H, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_left(buffer);
}, "move left one char");
core.register_key_action(input_map, .L, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.move_cursor_right(buffer);
}, "move right one char");
core.register_ctrl_key_action(input_map, .U, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.scroll_file_buffer(buffer, .Up);
}, "scroll buffer up");
core.register_ctrl_key_action(input_map, .D, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.scroll_file_buffer(buffer, .Down);
}, "scroll buffer up");
}
// Scale font size
{
core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State, user_data: rawptr) {
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, user_data: rawptr) {
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");
}
// Save file
core.register_ctrl_key_action(input_map, .S, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
if err := core.save_buffer_to_disk(state, buffer); err != nil {
log.errorf("failed to save buffer to disk: %v", err)
}
}, "Save file")
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, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
state.mode = .Visual;
core.reset_input_map(state)
buffer.selection = core.new_selection(buffer.history.cursor);
}, "enter visual mode");
}
register_default_visual_actions :: proc(input_map: ^core.InputActions) {
core.register_key_action(input_map, .ESCAPE, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
state.mode = .Normal;
core.reset_input_map(state)
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
}, "exit visual mode");
// Cursor Movement
{
core.register_key_action(input_map, .W, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.move_cursor_forward_start_of_word(buffer, cursor = &sel_cur.end);
}, "move forward one word");
core.register_key_action(input_map, .E, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.move_cursor_forward_end_of_word(buffer, cursor = &sel_cur.end);
}, "move forward to end of word");
core.register_key_action(input_map, .B, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.move_cursor_backward_start_of_word(buffer, cursor = &sel_cur.end);
}, "move backward one word");
core.register_key_action(input_map, .K, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.move_cursor_up(buffer, cursor = &sel_cur.end);
}, "move up one line");
core.register_key_action(input_map, .J, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.move_cursor_down(buffer, cursor = &sel_cur.end);
}, "move down one line");
core.register_key_action(input_map, .H, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.move_cursor_left(buffer, cursor = &sel_cur.end);
}, "move left one char");
core.register_key_action(input_map, .L, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.move_cursor_right(buffer, cursor = &sel_cur.end);
}, "move right one char");
core.register_ctrl_key_action(input_map, .U, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.scroll_file_buffer(buffer, .Up, cursor = &sel_cur.end);
}, "scroll buffer up");
core.register_ctrl_key_action(input_map, .D, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
sel_cur := &(buffer.selection.?);
core.scroll_file_buffer(buffer, .Down, cursor = &sel_cur.end);
}, "scroll buffer up");
}
// Text Modification
{
core.register_key_action(input_map, .D, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.push_new_snapshot(&buffer.history)
sel_cur := &(buffer.selection.?);
core.delete_content(buffer, sel_cur);
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
state.mode = .Normal
core.reset_input_map(state)
}, "delete selection");
core.register_key_action(input_map, .C, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.push_new_snapshot(&buffer.history)
sel_cur := &(buffer.selection.?);
core.delete_content(buffer, sel_cur);
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
state.mode = .Insert
core.reset_input_map(state, core.Mode.Normal)
sdl2.StartTextInput();
}, "change selection");
}
// Copy-Paste
{
core.register_key_action(input_map, .Y, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.yank_selection(state, buffer)
state.mode = .Normal;
core.reset_input_map(state)
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
}, "Yank Line");
core.register_key_action(input_map, .P, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.push_new_snapshot(&buffer.history)
if state.yank_register.whole_line {
core.insert_content(buffer, []u8{'\n'});
core.paste_register(state, state.yank_register, buffer)
core.insert_content(buffer, []u8{'\n'});
} else {
core.paste_register(state, state.yank_register, buffer)
}
core.reset_input_map(state)
}, "Paste");
}
}
register_default_text_input_actions :: proc(input_map: ^core.InputActions) {
core.register_key_action(input_map, .I, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.push_new_snapshot(&buffer.history)
state.mode = .Insert;
sdl2.StartTextInput();
}, "enter insert mode");
core.register_key_action(input_map, .A, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.push_new_snapshot(&buffer.history)
core.move_cursor_right(buffer, false);
state.mode = .Insert;
sdl2.StartTextInput();
}, "enter insert mode after character (append)");
core.register_key_action(input_map, .U, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.pop_snapshot(&buffer.history, true)
}, "Undo");
core.register_ctrl_key_action(input_map, .R, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.recover_snapshot(&buffer.history)
}, "Redo");
// TODO: add shift+o to insert newline above current one
core.register_key_action(input_map, .O, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.push_new_snapshot(&buffer.history)
if buffer := buffer; buffer != nil {
core.move_cursor_end_of_line(buffer, false);
runtime.clear(&buffer.input_buffer)
append(&buffer.input_buffer, '\n')
state.mode = .Insert;
sdl2.StartTextInput();
}
}, "insert mode on newline");
// Copy-Paste
{
{
yank_actions := core.new_input_actions()
defer core.register_key_action(input_map, .Y, yank_actions)
core.register_key_action(&yank_actions, .Y, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.yank_whole_line(state, buffer)
core.reset_input_map(state)
}, "Yank Line");
}
core.register_key_action(input_map, .P, proc(state: ^State, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data
core.push_new_snapshot(&buffer.history)
if state.yank_register.whole_line {
core.move_cursor_end_of_line(buffer, false);
core.insert_content(buffer, []u8{'\n'});
core.move_cursor_right(buffer, false);
} else {
core.move_cursor_right(buffer)
}
core.paste_register(state, state.yank_register, buffer)
core.move_cursor_start_of_line(buffer)
core.reset_input_map(state)
}, "Paste");
}
}

View File

@ -23,9 +23,6 @@ import ts "tree_sitter"
State :: core.State;
FileBuffer :: core.FileBuffer;
// TODO: should probably go into state
scratch: mem.Scratch;
scratch_alloc: runtime.Allocator;
state := core.State {};
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
@ -69,8 +66,8 @@ draw :: proc(state: ^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)
if panel.render != nil {
panel->render(state)
}
}
}
@ -161,7 +158,14 @@ expose_event_watcher :: proc "c" (state: rawptr, event: ^sdl2.Event) -> i32 {
}
main :: proc() {
ts.set_allocator()
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
defer {
}
ts.set_allocator()
_command_arena: mem.Arena
mem.arena_init(&_command_arena, make([]u8, 1024*1024));
@ -190,10 +194,6 @@ main :: proc() {
prev_elements = make([]ui.UI_Element, 8192),
}
// TODO: don't use this
mem.scratch_allocator_init(&scratch, 1024*1024);
scratch_alloc = mem.scratch_allocator(&scratch);
core.reset_input_map(&state)
// core.register_editor_command(
@ -278,13 +278,10 @@ main :: proc() {
if len(os.args) > 1 {
for arg in os.args[1:] {
panels.open_file_buffer_in_new_panel(&state, arg, 0, 0)
panels.open(&state, panels.make_file_buffer_panel(arg))
}
} else {
// buffer := core.new_virtual_file_buffer(context.allocator);
// panels.open(&state, panels.make_file_buffer_panel(len(state.buffers)))
// runtime.append(&state.buffers, buffer);
panels.open(&state, panels.make_file_buffer_panel(""))
}
if sdl2.Init({.VIDEO}) < 0 {
@ -393,7 +390,7 @@ main :: proc() {
if action, exists := state.current_input_map.ctrl_key_actions[key]; exists {
switch value in action.action {
case core.EditorAction:
value(state, &panel);
value(state, panel);
return true;
case core.InputActions:
state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions)
@ -404,7 +401,7 @@ main :: proc() {
if action, exists := state.current_input_map.key_actions[key]; exists {
switch value in action.action {
case core.EditorAction:
value(state, &panel);
value(state, panel);
return true;
case core.InputActions:
state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions)
@ -481,8 +478,8 @@ main :: proc() {
}
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)
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
panel->on_buffer_input(&state)
}
}
}
@ -509,4 +506,12 @@ main :: proc() {
}
sdl2.Quit();
if len(track.allocation_map) > 0 {
fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map))
for _, entry in track.allocation_map {
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
}
}
mem.tracking_allocator_destroy(&track)
}

497
src/panels/file_buffer.odin Normal file
View File

@ -0,0 +1,497 @@
package panels
import "base:runtime"
import "core:log"
import "core:fmt"
import "core:path/filepath"
import "vendor:sdl2"
import ts "../tree_sitter"
import "../core"
import "../ui"
make_file_buffer_panel :: proc(file_path: string, line: int = 0, col: int = 0) -> core.Panel {
return core.Panel {
type = core.FileBufferPanel {
file_path = file_path,
line = line,
col = col,
},
drop = proc(panel: ^core.Panel, state: ^core.State) {
panel_state := &panel.type.(core.FileBufferPanel)
ts.delete_state(&panel_state.buffer.tree)
},
create = proc(panel: ^core.Panel, state: ^core.State) {
context.allocator = panel.allocator
panel_state := &panel.type.(core.FileBufferPanel)
panel.input_map = core.new_input_map()
if len(panel_state.file_path) == 0 {
panel_state.buffer = core.new_virtual_file_buffer(panel.allocator)
} else {
buffer, err := core.new_file_buffer(panel.allocator, panel_state.file_path, state.directory)
if err.type != .None {
log.error("Failed to create file buffer:", err);
return;
}
panel_state.buffer = buffer
buffer.history.cursor.line = panel_state.line
buffer.history.cursor.col = panel_state.col
buffer.top_line = buffer.history.cursor.line
core.update_file_buffer_index_from_cursor(&buffer)
}
leader_actions := core.new_input_actions()
register_default_leader_actions(&leader_actions);
core.register_key_action(&panel.input_map.mode[.Normal], .SPACE, leader_actions, "leader commands");
core.register_ctrl_key_action(&panel.input_map.mode[.Normal], .W, core.new_input_actions(), "Panel Navigation")
register_default_panel_actions(&(&panel.input_map.mode[.Normal].ctrl_key_actions[.W]).action.(core.InputActions))
file_buffer_input_actions(&panel.input_map.mode[.Normal]);
file_buffer_visual_actions(&panel.input_map.mode[.Visual]);
file_buffer_text_input_actions(&panel.input_map.mode[.Normal]);
},
buffer = proc(panel: ^core.Panel, state: ^core.State) -> (buffer: ^core.FileBuffer, ok: bool) {
panel_state := &panel.type.(core.FileBufferPanel)
return &panel_state.buffer, true
},
render = proc(panel: ^core.Panel, state: ^core.State) -> (ok: bool) {
panel_state := &panel.type.(core.FileBufferPanel)
s := transmute(^ui.State)state.ui
render_file_buffer(state, s, &panel_state.buffer)
return true
}
}
}
open_file_buffer_in_new_panel :: proc(state: ^core.State, file_path: string, line, col: int) -> (panel_id: int, ok: bool) {
if panel_id, ok := open(state, make_file_buffer_panel(file_path, line, col)); ok {
return panel_id, true
}
return -1, false
}
render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) {
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y);
}
};
relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator)
ui.open_element(s, nil,
{
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}},
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4,
background_color = .Background1,
}
)
{
ui.open_element(s,
ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)buffer},
{
kind = {ui.Grow{}, ui.Grow{}}
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4,
background_color = .Background1,
}
)
ui.close_element(s)
ui.open_element(s, nil, {
kind = {ui.Grow{}, ui.Exact(state.source_font_height)}
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4,
}
)
{
ui.open_element(s, fmt.tprintf("%s", state.mode), {})
ui.close_element(s)
ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Grow{}}})
ui.close_element(s)
it := core.new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor)
ui.open_element(
s,
fmt.tprintf(
"%v:%v - Slice %v:%v - Char: %v",
buffer.history.cursor.line + 1,
buffer.history.cursor.col + 1,
buffer.history.cursor.index.chunk_index,
buffer.history.cursor.index.char_index,
core.get_character_at_iter(it)
),
{}
)
ui.close_element(s)
}
ui.close_element(s)
}
ui.close_element(s)
}
file_buffer_go_actions :: proc(input_map: ^core.InputActions) {
core.register_key_action(input_map, .H, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_start_of_line(buffer);
core.reset_input_map(state)
}, "move to beginning of line");
core.register_key_action(input_map, .L, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_end_of_line(buffer);
core.reset_input_map(state)
}, "move to end of line");
}
file_buffer_input_actions :: proc(input_map: ^core.InputActions) {
// Cursor Movement
{
core.register_key_action(input_map, .W, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_forward_start_of_word(buffer);
}, "move forward one word");
core.register_key_action(input_map, .E, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_forward_end_of_word(buffer);
}, "move forward to end of word");
core.register_key_action(input_map, .B, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_backward_start_of_word(buffer);
}, "move backward one word");
core.register_key_action(input_map, .K, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_up(buffer);
}, "move up one line");
core.register_key_action(input_map, .J, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_down(buffer);
}, "move down one line");
core.register_key_action(input_map, .H, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_left(buffer);
}, "move left one char");
core.register_key_action(input_map, .L, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.move_cursor_right(buffer);
}, "move right one char");
core.register_ctrl_key_action(input_map, .U, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.scroll_file_buffer(buffer, .Up);
}, "scroll buffer up");
core.register_ctrl_key_action(input_map, .D, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.scroll_file_buffer(buffer, .Down);
}, "scroll buffer up");
}
// Scale font size
{
core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^core.State, user_data: rawptr) {
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: ^core.State, user_data: rawptr) {
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");
}
// Save file
core.register_ctrl_key_action(input_map, .S, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
if err := core.save_buffer_to_disk(state, buffer); err != nil {
log.errorf("failed to save buffer to disk: %v", err)
}
}, "Save file")
core.register_key_action(input_map, .G, core.new_input_actions(), "Go commands");
file_buffer_go_actions(&(&input_map.key_actions[.G]).action.(core.InputActions));
core.register_key_action(input_map, .V, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
state.mode = .Visual;
core.reset_input_map(state)
buffer.selection = core.new_selection(buffer.history.cursor);
}, "enter visual mode");
}
file_buffer_visual_actions :: proc(input_map: ^core.InputActions) {
core.register_key_action(input_map, .ESCAPE, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
state.mode = .Normal;
core.reset_input_map(state)
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
}, "exit visual mode");
// Cursor Movement
{
core.register_key_action(input_map, .W, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.move_cursor_forward_start_of_word(buffer, cursor = &sel_cur.end);
}, "move forward one word");
core.register_key_action(input_map, .E, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.move_cursor_forward_end_of_word(buffer, cursor = &sel_cur.end);
}, "move forward to end of word");
core.register_key_action(input_map, .B, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.move_cursor_backward_start_of_word(buffer, cursor = &sel_cur.end);
}, "move backward one word");
core.register_key_action(input_map, .K, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.move_cursor_up(buffer, cursor = &sel_cur.end);
}, "move up one line");
core.register_key_action(input_map, .J, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.move_cursor_down(buffer, cursor = &sel_cur.end);
}, "move down one line");
core.register_key_action(input_map, .H, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.move_cursor_left(buffer, cursor = &sel_cur.end);
}, "move left one char");
core.register_key_action(input_map, .L, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.move_cursor_right(buffer, cursor = &sel_cur.end);
}, "move right one char");
core.register_ctrl_key_action(input_map, .U, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.scroll_file_buffer(buffer, .Up, cursor = &sel_cur.end);
}, "scroll buffer up");
core.register_ctrl_key_action(input_map, .D, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
sel_cur := &(buffer.selection.?);
core.scroll_file_buffer(buffer, .Down, cursor = &sel_cur.end);
}, "scroll buffer up");
}
// Text Modification
{
core.register_key_action(input_map, .D, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.push_new_snapshot(&buffer.history)
sel_cur := &(buffer.selection.?);
core.delete_content(buffer, sel_cur);
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
state.mode = .Normal
core.reset_input_map(state)
}, "delete selection");
core.register_key_action(input_map, .C, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.push_new_snapshot(&buffer.history)
sel_cur := &(buffer.selection.?);
core.delete_content(buffer, sel_cur);
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
state.mode = .Insert
core.reset_input_map(state, core.Mode.Normal)
sdl2.StartTextInput();
}, "change selection");
}
// Copy-Paste
{
core.register_key_action(input_map, .Y, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.yank_selection(state, buffer)
state.mode = .Normal;
core.reset_input_map(state)
buffer.selection = nil;
core.update_file_buffer_scroll(buffer)
}, "Yank Line");
core.register_key_action(input_map, .P, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.push_new_snapshot(&buffer.history)
if state.yank_register.whole_line {
core.insert_content(buffer, []u8{'\n'});
core.paste_register(state, state.yank_register, buffer)
core.insert_content(buffer, []u8{'\n'});
} else {
core.paste_register(state, state.yank_register, buffer)
}
core.reset_input_map(state)
}, "Paste");
}
}
file_buffer_text_input_actions :: proc(input_map: ^core.InputActions) {
core.register_key_action(input_map, .I, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.push_new_snapshot(&buffer.history)
state.mode = .Insert;
sdl2.StartTextInput();
}, "enter insert mode");
core.register_key_action(input_map, .A, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.push_new_snapshot(&buffer.history)
core.move_cursor_right(buffer, false);
state.mode = .Insert;
sdl2.StartTextInput();
}, "enter insert mode after character (append)");
core.register_key_action(input_map, .U, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.pop_snapshot(&buffer.history, true)
}, "Undo");
core.register_ctrl_key_action(input_map, .R, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.recover_snapshot(&buffer.history)
}, "Redo");
// TODO: add shift+o to insert newline above current one
core.register_key_action(input_map, .O, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.push_new_snapshot(&buffer.history)
if buffer := buffer; buffer != nil {
core.move_cursor_end_of_line(buffer, false);
runtime.clear(&buffer.input_buffer)
append(&buffer.input_buffer, '\n')
state.mode = .Insert;
sdl2.StartTextInput();
}
}, "insert mode on newline");
// Copy-Paste
{
{
yank_actions := core.new_input_actions()
defer core.register_key_action(input_map, .Y, yank_actions)
core.register_key_action(&yank_actions, .Y, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.yank_whole_line(state, buffer)
core.reset_input_map(state)
}, "Yank Line");
}
core.register_key_action(input_map, .P, proc(state: ^core.State, user_data: rawptr) {
buffer := &(&(transmute(^core.Panel)user_data).type.(core.FileBufferPanel)).buffer
core.push_new_snapshot(&buffer.history)
if state.yank_register.whole_line {
core.move_cursor_end_of_line(buffer, false);
core.insert_content(buffer, []u8{'\n'});
core.move_cursor_right(buffer, false);
} else {
core.move_cursor_right(buffer)
}
core.paste_register(state, state.yank_register, buffer)
core.move_cursor_start_of_line(buffer)
core.reset_input_map(state)
}, "Paste");
}
}

309
src/panels/grep.odin Normal file
View File

@ -0,0 +1,309 @@
package panels
import "base:runtime"
import "core:mem"
import "core:fmt"
import "core:strings"
import "core:log"
import "vendor:sdl2"
import ts "../tree_sitter"
import "../core"
import "../input"
import "../util"
import "../ui"
open_grep_panel :: proc(state: ^core.State) {
open(state, make_grep_panel())
state.mode = .Insert
sdl2.StartTextInput()
}
make_grep_panel :: proc() -> core.Panel {
run_query :: proc(panel_state: ^core.GrepPanel, query: string, directory: string) {
if panel_state.query_region.arena != nil {
mem.end_arena_temp_memory(panel_state.query_region)
}
panel_state.query_region = mem.begin_arena_temp_memory(&panel_state.query_arena)
context.allocator = mem.arena_allocator(&panel_state.query_arena)
rs_results := grep(
strings.clone_to_cstring(query),
strings.clone_to_cstring(directory)
);
panel_state.query_results = rs_grep_as_results(&rs_results)
panel_state.selected_result = 0
if len(panel_state.query_results) > 0 {
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}
return core.Panel {
type = core.GrepPanel {},
drop = proc(panel: ^core.Panel, state: ^core.State) {
panel_state := &panel.type.(core.GrepPanel)
ts.delete_state(&panel_state.buffer.tree)
},
create = proc(panel: ^core.Panel, state: ^core.State) {
context.allocator = panel.allocator
panel_state := &panel.type.(core.GrepPanel)
mem.arena_init(&panel_state.query_arena, make([]u8, 1024*1024*2))
panel.input_map = core.new_input_map()
panel_state.glyphs = core.make_glyph_buffer(256,256)
panel_state.buffer = core.new_virtual_file_buffer()
core.register_key_action(&panel.input_map.mode[.Normal], .ENTER, proc(state: ^core.State, user_data: rawptr) {
this_panel := transmute(^core.Panel)user_data
if panel_state, ok := &this_panel.type.(core.GrepPanel); ok {
if panel_state.query_results != nil {
selected_result := &panel_state.query_results[panel_state.selected_result]
if panel_id, ok := open_file_buffer_in_new_panel(state, selected_result.file_path, selected_result.line, selected_result.col); ok {
close(state, this_panel.id)
state.current_panel = panel_id
} else {
log.error("failed to open file buffer in new panel")
}
}
}
}, "Open File");
core.register_key_action(&panel.input_map.mode[.Normal], .I, proc(state: ^core.State, user_data: rawptr) {
state.mode = .Insert;
sdl2.StartTextInput();
}, "enter insert mode");
core.register_key_action(&panel.input_map.mode[.Normal], .K, proc(state: ^core.State, user_data: rawptr) {
this_panel := transmute(^core.Panel)user_data
if panel_state, ok := &this_panel.type.(core.GrepPanel); ok {
// TODO: bounds checking
panel_state.selected_result -= 1
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}, "move selection up");
core.register_key_action(&panel.input_map.mode[.Normal], .J, proc(state: ^core.State, user_data: rawptr) {
this_panel := transmute(^core.Panel)user_data
if panel_state, ok := &this_panel.type.(core.GrepPanel); ok {
// TODO: bounds checking
panel_state.selected_result += 1
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}, "move selection down");
core.register_key_action(&panel.input_map.mode[.Insert], .ESCAPE, proc(state: ^core.State, user_data: rawptr) {
state.mode = .Normal;
sdl2.StopTextInput();
}, "exit insert mode");
core.register_key_action(&panel.input_map.mode[.Normal], .ESCAPE, proc(state: ^core.State, user_data: rawptr) {
this_panel := transmute(^core.Panel)user_data
close(state, this_panel.id)
}, "close panel");
},
buffer = proc(panel: ^core.Panel, state: ^core.State) -> (buffer: ^core.FileBuffer, ok: bool) {
if panel_state, ok := &panel.type.(core.GrepPanel); ok {
return &panel_state.buffer, true
}
return
},
on_buffer_input = proc(panel: ^core.Panel, state: ^core.State) {
if panel_state, ok := &panel.type.(core.GrepPanel); ok {
run_query(panel_state, string(panel_state.buffer.input_buffer[:]), state.directory)
}
},
render = proc(panel: ^core.Panel, state: ^core.State) -> (ok: bool) {
if panel_state, ok := &panel.type.(core.GrepPanel); ok {
s := transmute(^ui.State)state.ui
ui.open_element(s, nil, {
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
})
{
// 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
query_result_container := ui.open_element(s, nil,
{
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4
}
)
{
container_height := query_result_container.layout.size.y
max_results := container_height / 16
for result, i in panel_state.query_results {
if i > max_results {
break
}
ui.open_element(s, nil, {
dir = .LeftToRight,
kind = {ui.Fit{}, ui.Fit{}},
})
{
defer ui.close_element(s)
ui.open_element(s, fmt.tprintf("%v:%v: ", result.line, result.col), {})
ui.close_element(s)
style := ui.UI_Style{}
if panel_state.selected_result == i {
style.background_color = .Background2
}
ui.open_element(s, result.file_path, {}, style)
ui.close_element(s)
}
}
}
ui.close_element(s)
// file contents
selected_result := &panel_state.query_results[panel_state.selected_result]
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)selected_result.file_context,
selected_result.line,
)
render_glyph_buffer(state, s, &panel_state.glyphs)
}
}
ui.close_element(s)
// text input
ui.open_element(s, nil, {
kind = {ui.Grow{}, ui.Exact(state.source_font_height)}
})
{
defer ui.close_element(s)
// render_raw_buffer(state, s, &state.buffers[panel_state.buffer])
}
}
ui.close_element(s)
return true
}
return false
}
}
}
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 {
context.allocator = allocator
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)) or_continue,
file_path = strings.clone_from_ptr(r.path, int(r.path_len)) or_continue,
line = int(r.line_number) - 1,
col = int(r.column) - 1,
}
}
free_grep_results(results^)
return query_results
}
render_raw_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) {
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y, false);
}
};
ui.open_element(s, ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)buffer}, {
kind = {ui.Grow{}, ui.Grow{}}
})
ui.close_element(s)
}
render_glyph_buffer :: proc(state: ^core.State, s: ^ui.State, glyphs: ^core.GlyphBuffer) {
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
glyphs := transmute(^core.GlyphBuffer)user_data;
if glyphs != nil {
glyphs.width = e.layout.size.x / state.source_font_width;
glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_glyph_buffer(state, glyphs, e.layout.pos.x, e.layout.pos.y, 0, true);
}
};
ui.open_element(s, ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)glyphs}, {
kind = {ui.Grow{}, ui.Grow{}}
})
ui.close_element(s)
}

View File

@ -2,7 +2,6 @@ package panels
import "base:runtime"
import "core:mem"
import "core:path/filepath"
import "core:fmt"
import "core:strings"
import "core:log"
@ -15,48 +14,6 @@ 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 {
context.allocator = allocator
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)) or_continue,
file_path = strings.clone_from_ptr(r.path, int(r.path_len)) or_continue,
line = int(r.line_number) - 1,
col = int(r.column) - 1,
}
}
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, user_data: rawptr) {
@ -106,9 +63,15 @@ register_default_panel_actions :: proc(input_map: ^core.InputActions) {
open :: proc(state: ^core.State, panel: core.Panel, make_active: bool = true) -> (panel_id: int, ok: bool) {
if panel_id, ok := util.append_static_list(&state.panels, panel).?; ok && make_active {
if panel_id, panel, ok := util.append_static_list(&state.panels, panel); ok && make_active {
panel.id = panel_id
state.current_panel = panel_id
mem.arena_init(&panel.arena, make([]u8, 1024*1024*4))
panel.allocator = mem.arena_allocator(&panel.arena)
panel->create(state)
core.reset_input_map(state)
return panel_id, true
@ -120,9 +83,11 @@ open :: proc(state: ^core.State, panel: core.Panel, make_active: bool = true) ->
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)
panel->drop(state)
}
mem.free(raw_data(panel.arena.data))
util.delete(&state.panels, panel_id)
// TODO: keep track of the last active panel instead of focusing back to the first one
@ -133,390 +98,3 @@ close :: proc(state: ^core.State, panel_id: int) {
core.reset_input_map(state)
}
}
open_file_buffer_in_new_panel :: proc(state: ^core.State, file_path: string, line, col: int) -> (panel_id, buffer_index: int, ok: bool) {
// FIXME: move into panel
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.history.cursor.line = line
buffer.history.cursor.col = col
buffer.top_line = buffer.history.cursor.line
core.update_file_buffer_index_from_cursor(&buffer)
// buffer_index = len(state.buffers)
// runtime.append(&state.buffers, buffer);
if panel_id, ok := open(state, make_file_buffer_panel(buffer_index)); ok {
return panel_id, buffer_index, true
}
return -1, -1, false
}
render_file_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) {
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y);
}
};
relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator)
ui.open_element(s, nil,
{
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}},
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4,
background_color = .Background1,
}
)
{
ui.open_element(s,
ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)buffer},
{
kind = {ui.Grow{}, ui.Grow{}}
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4,
background_color = .Background1,
}
)
ui.close_element(s)
ui.open_element(s, nil, {
kind = {ui.Grow{}, ui.Exact(state.source_font_height)}
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4,
}
)
{
ui.open_element(s, fmt.tprintf("%s", state.mode), {})
ui.close_element(s)
ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Grow{}}})
ui.close_element(s)
it := core.new_file_buffer_iter_with_cursor(buffer, buffer.history.cursor)
ui.open_element(
s,
fmt.tprintf(
"%v:%v - Slice %v:%v - Char: %v",
buffer.history.cursor.line + 1,
buffer.history.cursor.col + 1,
buffer.history.cursor.index.chunk_index,
buffer.history.cursor.index.char_index,
core.get_character_at_iter(it)
),
{}
)
ui.close_element(s)
}
ui.close_element(s)
}
ui.close_element(s)
}
render_raw_buffer :: proc(state: ^core.State, s: ^ui.State, buffer: ^core.FileBuffer) {
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
buffer := transmute(^core.FileBuffer)user_data;
if buffer != nil {
buffer.glyphs.width = e.layout.size.x / state.source_font_width;
buffer.glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_file_buffer(state, buffer, e.layout.pos.x, e.layout.pos.y, e.layout.size.x, e.layout.size.y, false);
}
};
ui.open_element(s, ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)buffer}, {
kind = {ui.Grow{}, ui.Grow{}}
})
ui.close_element(s)
}
render_glyph_buffer :: proc(state: ^core.State, s: ^ui.State, glyphs: ^core.GlyphBuffer) {
draw_func := proc(state: ^core.State, e: ui.UI_Element, user_data: rawptr) {
glyphs := transmute(^core.GlyphBuffer)user_data;
if glyphs != nil {
glyphs.width = e.layout.size.x / state.source_font_width;
glyphs.height = e.layout.size.y / state.source_font_height + 1;
core.draw_glyph_buffer(state, glyphs, e.layout.pos.x, e.layout.pos.y, 0, true);
}
};
ui.open_element(s, ui.UI_Element_Kind_Custom{fn = draw_func, user_data = transmute(rawptr)glyphs}, {
kind = {ui.Grow{}, ui.Grow{}}
})
ui.close_element(s)
}
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");
core.register_ctrl_key_action(&input_map.mode[.Normal], .W, core.new_input_actions(), "Panel Navigation")
register_default_panel_actions(&(&input_map.mode[.Normal].ctrl_key_actions[.W]).action.(core.InputActions))
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 },
input_map = input_map,
drop = proc(state: ^core.State, panel_state: ^core.PanelState) {
if panel_state, ok := &panel_state.(core.FileBufferPanel); ok {
// core.free_file_buffer(&state.buffers[panel_state.buffer_index])
}
},
render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) {
panel_state := panel_state.(core.FileBufferPanel) or_return;
// FIXME: use buffer from panel
// s := transmute(^ui.State)state.ui
// buffer := &state.buffers[panel_state.buffer_index]
// render_file_buffer(state, s, buffer)
return true
}
}
}
open_grep_panel :: proc(state: ^core.State) {
open(state, make_grep_panel(state))
state.mode = .Insert
sdl2.StartTextInput()
}
make_grep_panel :: proc(state: ^core.State) -> core.Panel {
query_arena: mem.Arena
mem.arena_init(&query_arena, make([]u8, 1024*1024*2, state.ctx.allocator))
glyphs := core.make_glyph_buffer(256,256, allocator = mem.arena_allocator(&query_arena))
input_map := core.new_input_map()
// FIXME: add to panel
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) {
if panel_state.query_region.arena != nil {
mem.end_arena_temp_memory(panel_state.query_region)
}
panel_state.query_region = mem.begin_arena_temp_memory(&panel_state.query_arena)
context.allocator = mem.arena_allocator(&panel_state.query_arena)
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)
free_grep_results(rs_results)
panel_state.selected_result = 0
if len(panel_state.query_results) > 0 {
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}
core.register_key_action(&input_map.mode[.Normal], .ENTER, proc(state: ^core.State, user_data: rawptr) {
this_panel := transmute(^core.Panel)user_data
if panel_state, ok := &this_panel.panel_state.(core.GrepPanel); ok {
if panel_state.query_results != nil {
selected_result := &panel_state.query_results[panel_state.selected_result]
if panel_id, buffer, ok := open_file_buffer_in_new_panel(state, selected_result.file_path, selected_result.line, selected_result.col); ok {
// FIXME: store panel_id in core.Panel
// close(state, this_panel)
state.current_panel = panel_id
} else {
log.error("failed to open file buffer in new panel")
}
}
}
}, "Open File");
core.register_key_action(&input_map.mode[.Normal], .I, proc(state: ^core.State, user_data: rawptr) {
state.mode = .Insert;
sdl2.StartTextInput();
}, "enter insert mode");
core.register_key_action(&input_map.mode[.Normal], .K, proc(state: ^core.State, user_data: rawptr) {
this_panel := transmute(^core.Panel)user_data
if panel_state, ok := &this_panel.panel_state.(core.GrepPanel); ok {
// TODO: bounds checking
panel_state.selected_result -= 1
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}, "move selection up");
core.register_key_action(&input_map.mode[.Normal], .J, proc(state: ^core.State, user_data: rawptr) {
this_panel := transmute(^core.Panel)user_data
if panel_state, ok := &this_panel.panel_state.(core.GrepPanel); ok {
// TODO: bounds checking
panel_state.selected_result += 1
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)panel_state.query_results[panel_state.selected_result].file_context,
panel_state.query_results[panel_state.selected_result].line,
)
}
}, "move selection down");
core.register_key_action(&input_map.mode[.Insert], .ESCAPE, proc(state: ^core.State, user_data: rawptr) {
state.mode = .Normal;
sdl2.StopTextInput();
}, "exit insert mode");
core.register_key_action(&input_map.mode[.Normal], .ESCAPE, proc(state: ^core.State, user_data: rawptr) {
if state.current_panel != nil {
// FIXME; store panel_id in core.Panel
// close(state, state.current_panel.?)
}
}, "close panel");
return core.Panel {
panel_state = core.GrepPanel {
query_arena = query_arena,
// buffer = len(state.buffers)-1,
query_results = nil,
glyphs = glyphs,
},
input_map = input_map,
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 {
// core.free_file_buffer(&state.buffers[panel_state.buffer])
delete(panel_state.query_arena.data, state.ctx.allocator)
}
},
render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) {
if panel_state, ok := &panel_state.(core.GrepPanel); ok {
s := transmute(^ui.State)state.ui
ui.open_element(s, nil, {
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
})
{
// 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
query_result_container := ui.open_element(s, nil,
{
dir = .TopToBottom,
kind = {ui.Grow{}, ui.Grow{}}
},
style = {
border = {.Left, .Right, .Top, .Bottom},
border_color = .Background4
}
)
{
container_height := query_result_container.layout.size.y
max_results := container_height / 16
for result, i in panel_state.query_results {
if i > max_results {
break
}
ui.open_element(s, nil, {
dir = .LeftToRight,
kind = {ui.Fit{}, ui.Fit{}},
})
{
defer ui.close_element(s)
ui.open_element(s, fmt.tprintf("%v:%v: ", result.line, result.col), {})
ui.close_element(s)
style := ui.UI_Style{}
if panel_state.selected_result == i {
style.background_color = .Background2
}
ui.open_element(s, result.file_path, {}, style)
ui.close_element(s)
}
}
}
ui.close_element(s)
// file contents
selected_result := &panel_state.query_results[panel_state.selected_result]
core.update_glyph_buffer_from_bytes(
&panel_state.glyphs,
transmute([]u8)selected_result.file_context,
selected_result.line,
)
render_glyph_buffer(state, s, &panel_state.glyphs)
}
}
ui.close_element(s)
// text input
ui.open_element(s, nil, {
kind = {ui.Grow{}, ui.Exact(state.source_font_height)}
})
{
defer ui.close_element(s)
// render_raw_buffer(state, s, &state.buffers[panel_state.buffer])
}
}
ui.close_element(s)
return true
}
return false
}
}
}

View File

@ -139,17 +139,17 @@ impl From<Match> for GrepResult {
// 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(),
text: Box::into_raw(value.text.into_boxed_slice()) as _,
path: Box::into_raw(value.path.into_bytes().into_boxed_slice()) as _,
}
}
}
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 text = Box::from_raw(std::slice::from_raw_parts_mut(value.text as *mut _, 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 = Box::from_raw(std::slice::from_raw_parts_mut(value.path as *mut _, value.path_len as usize)).to_vec();
let path = String::from_utf8_unchecked(path);
Self {
@ -188,17 +188,26 @@ extern "C" fn grep(
.into_iter()
.map(|sink| sink.matches.into_iter())
.flatten()
.map(Into::into)
.map(|v| GrepResult::from(v))
.collect::<Vec<_>>()
.into_boxed_slice();
let len = boxed.len() as u32;
GrepResults {
results: Box::leak(boxed).as_ptr(),
results: Box::into_raw(boxed) as _,
len,
}
}
#[unsafe(no_mangle)]
extern "C" fn free_grep_results(_: GrepResults) { }
extern "C" fn free_grep_results(results: GrepResults) {
unsafe {
let mut array = std::slice::from_raw_parts_mut(results.results as *mut GrepResult, results.len as usize);
let array = Box::from_raw(array);
for v in array {
let _ = Match::from(v);
}
}
}

View File

@ -223,6 +223,7 @@ make_state :: proc(type: LanguageType, allocator := context.allocator) -> State
}
delete_state :: proc(state: ^State) {
delete(state.highlights)
tree_cursor_delete(&state.cursor)
tree_delete(state.tree)
parser_delete(state.parser)

View File

@ -11,17 +11,17 @@ StaticListSlot :: struct($T: typeid) {
data: T,
}
append_static_list :: proc(list: ^StaticList($T), value: T) -> Maybe(int) {
append_static_list :: proc(list: ^StaticList($T), value: T) -> (id: int, panel: ^T, ok: bool) {
for i in 0..<len(list.data) {
if !list.data[i].active {
list.data[i].active = true
list.data[i].data = value
return i
return i, &list.data[i].data, true
}
}
return nil
return
}
append :: proc{append_static_list}

View File

@ -8,6 +8,7 @@
- Split grep search results into a table to avoid funky unaligned text
# Planned Features
- [ ] Jump List
- Use grouped lifetimes exclusively for memory allocation/freeing
- [ ] Highlight which panel is currently active
- [ ] Persist end of line cursor position