Compare commits
4 Commits
e412dbe7a2
...
cdadbbef18
Author | SHA1 | Date |
---|---|---|
|
cdadbbef18 | |
|
0a0681c704 | |
|
df283ee49b | |
|
9ace98118d |
|
@ -74,33 +74,43 @@ EditorCommandArgument :: union #no_nil {
|
||||||
i32
|
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 {
|
Panel :: struct {
|
||||||
is_floating: bool,
|
using vtable: Panel_VTable,
|
||||||
panel_state: PanelState,
|
arena: mem.Arena,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
|
||||||
|
id: int,
|
||||||
|
type: PanelType,
|
||||||
input_map: InputMap,
|
input_map: InputMap,
|
||||||
buffer_proc: PanelBufferProc,
|
is_floating: bool,
|
||||||
on_buffer_input_proc: PanelBufferInputProc,
|
|
||||||
render_proc: PanelRenderProc,
|
|
||||||
drop: PanelDropProc,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
FileBufferPanel,
|
||||||
GrepPanel,
|
GrepPanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
FileBufferPanel :: struct {
|
FileBufferPanel :: struct {
|
||||||
buffer_index: int,
|
buffer: FileBuffer,
|
||||||
|
|
||||||
|
// only used for initialization
|
||||||
|
file_path: string,
|
||||||
|
line, col: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
GrepPanel :: struct {
|
GrepPanel :: struct {
|
||||||
query_arena: mem.Arena,
|
query_arena: mem.Arena,
|
||||||
query_region: mem.Arena_Temp_Memory,
|
query_region: mem.Arena_Temp_Memory,
|
||||||
buffer: int,
|
buffer: FileBuffer,
|
||||||
selected_result: int,
|
selected_result: int,
|
||||||
search_query: string,
|
search_query: string,
|
||||||
query_results: []GrepQueryResult,
|
query_results: []GrepQueryResult,
|
||||||
|
@ -114,7 +124,20 @@ GrepQueryResult :: struct {
|
||||||
col: int,
|
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) {
|
yank_whole_line :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||||
|
context.allocator = buffer.allocator
|
||||||
|
|
||||||
if state.yank_register.data != nil {
|
if state.yank_register.data != nil {
|
||||||
delete(state.yank_register.data)
|
delete(state.yank_register.data)
|
||||||
state.yank_register.data = nil
|
state.yank_register.data = nil
|
||||||
|
@ -190,7 +213,9 @@ Action :: struct {
|
||||||
description: string,
|
description: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
new_input_map :: proc() -> InputMap {
|
new_input_map :: proc(allocator := context.allocator) -> InputMap {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
input_map := InputMap {
|
input_map := InputMap {
|
||||||
mode = make(map[Mode]InputActions),
|
mode = make(map[Mode]InputActions),
|
||||||
}
|
}
|
||||||
|
@ -205,7 +230,9 @@ new_input_map :: proc() -> InputMap {
|
||||||
return input_map;
|
return input_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_input_actions :: proc() -> InputActions {
|
new_input_actions :: proc(allocator := context.allocator) -> InputActions {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
input_actions := InputActions {
|
input_actions := InputActions {
|
||||||
key_actions = make(map[Key]Action),
|
key_actions = make(map[Key]Action),
|
||||||
ctrl_key_actions = make(map[Key]Action),
|
ctrl_key_actions = make(map[Key]Action),
|
||||||
|
|
|
@ -662,7 +662,7 @@ selection_length :: proc(buffer: ^FileBuffer, selection: Selection) -> int {
|
||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer {
|
new_virtual_file_buffer :: proc(allocator := context.allocator) -> FileBuffer {
|
||||||
context.allocator = allocator;
|
context.allocator = allocator;
|
||||||
width := 256;
|
width := 256;
|
||||||
height := 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);
|
extension := filepath.ext(fi.fullpath);
|
||||||
|
|
||||||
if original_content, success := os.read_entire_file_from_handle(fd); success {
|
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;
|
width := 256;
|
||||||
height := 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
|
// TODO: derive language type from extension
|
||||||
tree = ts.make_state(.Odin),
|
tree = ts.make_state(.Odin),
|
||||||
history = make_history(original_content),
|
history = make_history(content),
|
||||||
|
|
||||||
glyphs = make_glyph_buffer(width, height),
|
glyphs = make_glyph_buffer(width, height),
|
||||||
input_buffer = make([dynamic]u8, 0, 1024),
|
input_buffer = make([dynamic]u8, 0, 1024),
|
||||||
|
@ -820,8 +825,8 @@ color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x, y, w, h: int, show_line_numbers: bool = true, show_cursor: bool = true) {
|
draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x, y, w, h: int, show_line_numbers: bool = true, show_cursor: bool = true) {
|
||||||
glyph_width := math.min(256, int((w - state.source_font_width) / state.source_font_width));
|
glyph_width := math.min(256, int(w / state.source_font_width));
|
||||||
glyph_height := math.min(256, int((h - state.source_font_height*2) / state.source_font_height)) + 1;
|
glyph_height := math.min(256, int(h / state.source_font_height)) + 1;
|
||||||
|
|
||||||
update_glyph_buffer(buffer, glyph_width, glyph_height);
|
update_glyph_buffer(buffer, glyph_width, glyph_height);
|
||||||
|
|
||||||
|
@ -840,7 +845,7 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x, y, w, h: int, sh
|
||||||
if show_cursor {
|
if show_cursor {
|
||||||
if state.mode == .Normal {
|
if state.mode == .Normal {
|
||||||
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Background4);
|
draw_rect(state, cursor_x, cursor_y, state.source_font_width, state.source_font_height, .Background4);
|
||||||
} else if state.mode == .Visual {
|
} else if state.mode == .Visual && buffer.selection != nil {
|
||||||
start_sel_x := x + padding + buffer.selection.?.start.col * state.source_font_width;
|
start_sel_x := x + padding + buffer.selection.?.start.col * state.source_font_width;
|
||||||
start_sel_y := y + buffer.selection.?.start.line * state.source_font_height;
|
start_sel_y := y + buffer.selection.?.start.line * state.source_font_height;
|
||||||
|
|
||||||
|
@ -932,7 +937,9 @@ update_file_buffer_scroll :: proc(buffer: ^FileBuffer, cursor: Maybe(^Cursor) =
|
||||||
cursor = &buffer.history.cursor;
|
cursor = &buffer.history.cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
if buffer.glyphs.height < 5 {
|
||||||
|
buffer.top_line = cursor.?.line
|
||||||
|
} else if cursor.?.line > (buffer.top_line + buffer.glyphs.height - 5) {
|
||||||
buffer.top_line = math.max(cursor.?.line - buffer.glyphs.height + 5, 0);
|
buffer.top_line = math.max(cursor.?.line - buffer.glyphs.height + 5, 0);
|
||||||
} else if cursor.?.line < (buffer.top_line + 5) {
|
} else if cursor.?.line < (buffer.top_line + 5) {
|
||||||
buffer.top_line = math.max(cursor.?.line - 5, 0);
|
buffer.top_line = math.max(cursor.?.line - 5, 0);
|
||||||
|
@ -973,6 +980,8 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end:
|
||||||
update_file_buffer_index_from_cursor(buffer);
|
update_file_buffer_index_from_cursor(buffer);
|
||||||
move_cursor_right(buffer, false, amt = len(to_be_inserted) - 1);
|
move_cursor_right(buffer, false, amt = len(to_be_inserted) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
||||||
|
@ -991,6 +1000,8 @@ delete_content_from_buffer_cursor :: proc(buffer: ^FileBuffer, amount: int) {
|
||||||
buffer.history.cursor.line = it.cursor.line
|
buffer.history.cursor.line = it.cursor.line
|
||||||
buffer.history.cursor.col = it.cursor.col
|
buffer.history.cursor.col = it.cursor.col
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection) {
|
||||||
|
@ -998,6 +1009,8 @@ delete_content_from_selection :: proc(buffer: ^FileBuffer, selection: ^Selection
|
||||||
delete_text_in_span(buffer_piece_table(buffer), &selection.start.index, &selection.end.index)
|
delete_text_in_span(buffer_piece_table(buffer), &selection.start.index, &selection.end.index)
|
||||||
|
|
||||||
buffer.history.cursor.index = selection.start.index
|
buffer.history.cursor.index = selection.start.index
|
||||||
|
|
||||||
|
ts.parse_buffer(&buffer.tree, tree_sitter_file_buffer_input(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};
|
delete_content :: proc{delete_content_from_buffer_cursor, delete_content_from_selection};
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import "core:log"
|
import "core:log"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
FileHistory :: struct {
|
FileHistory :: struct {
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
|
||||||
piece_table: PieceTable,
|
piece_table: PieceTable,
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
|
|
||||||
|
@ -20,6 +23,7 @@ make_history_with_data :: proc(initial_data: []u8, starting_capacity: int = 1024
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
return FileHistory {
|
return FileHistory {
|
||||||
|
allocator = allocator,
|
||||||
piece_table = make_piece_table(initial_data, starting_capacity = starting_capacity),
|
piece_table = make_piece_table(initial_data, starting_capacity = starting_capacity),
|
||||||
snapshots = make([]Snapshot, starting_capacity),
|
snapshots = make([]Snapshot, starting_capacity),
|
||||||
next = 0,
|
next = 0,
|
||||||
|
@ -31,6 +35,7 @@ make_history_empty :: proc(starting_capacity: int = 1024, allocator := context.a
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
return FileHistory {
|
return FileHistory {
|
||||||
|
allocator = allocator,
|
||||||
piece_table = make_piece_table(starting_capacity = starting_capacity),
|
piece_table = make_piece_table(starting_capacity = starting_capacity),
|
||||||
snapshots = make([]Snapshot, starting_capacity),
|
snapshots = make([]Snapshot, starting_capacity),
|
||||||
next = 0,
|
next = 0,
|
||||||
|
@ -54,6 +59,8 @@ free_history :: proc(history: ^FileHistory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
push_new_snapshot :: proc(history: ^FileHistory) {
|
push_new_snapshot :: proc(history: ^FileHistory) {
|
||||||
|
context.allocator = history.allocator
|
||||||
|
|
||||||
if history.snapshots[history.next].chunks != nil {
|
if history.snapshots[history.next].chunks != nil {
|
||||||
delete(history.snapshots[history.next].chunks)
|
delete(history.snapshots[history.next].chunks)
|
||||||
}
|
}
|
||||||
|
@ -65,6 +72,8 @@ push_new_snapshot :: proc(history: ^FileHistory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pop_snapshot :: proc(history: ^FileHistory, make_new_snapshot: bool = false) {
|
pop_snapshot :: proc(history: ^FileHistory, make_new_snapshot: bool = false) {
|
||||||
|
context.allocator = history.allocator
|
||||||
|
|
||||||
new_next, _ := next_indexes(history, backward = true)
|
new_next, _ := next_indexes(history, backward = true)
|
||||||
if new_next == history.next do return
|
if new_next == history.next do return
|
||||||
|
|
||||||
|
@ -81,6 +90,8 @@ pop_snapshot :: proc(history: ^FileHistory, make_new_snapshot: bool = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
recover_snapshot :: proc(history: ^FileHistory) {
|
recover_snapshot :: proc(history: ^FileHistory) {
|
||||||
|
context.allocator = history.allocator
|
||||||
|
|
||||||
new_next, _ := next_indexes(history)
|
new_next, _ := next_indexes(history)
|
||||||
if history.snapshots[new_next].chunks == nil do return
|
if history.snapshots[new_next].chunks == nil do return
|
||||||
history.next = new_next
|
history.next = new_next
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -23,9 +23,6 @@ import ts "tree_sitter"
|
||||||
State :: core.State;
|
State :: core.State;
|
||||||
FileBuffer :: core.FileBuffer;
|
FileBuffer :: core.FileBuffer;
|
||||||
|
|
||||||
// TODO: should probably go into state
|
|
||||||
scratch: mem.Scratch;
|
|
||||||
scratch_alloc: runtime.Allocator;
|
|
||||||
state := core.State {};
|
state := core.State {};
|
||||||
|
|
||||||
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||||
|
@ -69,8 +66,8 @@ draw :: proc(state: ^State) {
|
||||||
{
|
{
|
||||||
for i in 0..<len(state.panels.data) {
|
for i in 0..<len(state.panels.data) {
|
||||||
if panel, ok := util.get(&state.panels, i).?; ok {
|
if panel, ok := util.get(&state.panels, i).?; ok {
|
||||||
if panel.render_proc != nil {
|
if panel.render != nil {
|
||||||
panel.render_proc(state, &panel.panel_state)
|
panel->render(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,10 +187,6 @@ main :: proc() {
|
||||||
prev_elements = make([]ui.UI_Element, 8192),
|
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.reset_input_map(&state)
|
||||||
|
|
||||||
// core.register_editor_command(
|
// core.register_editor_command(
|
||||||
|
@ -278,13 +271,10 @@ main :: proc() {
|
||||||
|
|
||||||
if len(os.args) > 1 {
|
if len(os.args) > 1 {
|
||||||
for arg in 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 {
|
} else {
|
||||||
// buffer := core.new_virtual_file_buffer(context.allocator);
|
panels.open(&state, panels.make_file_buffer_panel(""))
|
||||||
|
|
||||||
// panels.open(&state, panels.make_file_buffer_panel(len(state.buffers)))
|
|
||||||
// runtime.append(&state.buffers, buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sdl2.Init({.VIDEO}) < 0 {
|
if sdl2.Init({.VIDEO}) < 0 {
|
||||||
|
@ -393,7 +383,7 @@ main :: proc() {
|
||||||
if action, exists := state.current_input_map.ctrl_key_actions[key]; exists {
|
if action, exists := state.current_input_map.ctrl_key_actions[key]; exists {
|
||||||
switch value in action.action {
|
switch value in action.action {
|
||||||
case core.EditorAction:
|
case core.EditorAction:
|
||||||
value(state, &panel);
|
value(state, panel);
|
||||||
return true;
|
return true;
|
||||||
case core.InputActions:
|
case core.InputActions:
|
||||||
state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions)
|
state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions)
|
||||||
|
@ -404,7 +394,7 @@ main :: proc() {
|
||||||
if action, exists := state.current_input_map.key_actions[key]; exists {
|
if action, exists := state.current_input_map.key_actions[key]; exists {
|
||||||
switch value in action.action {
|
switch value in action.action {
|
||||||
case core.EditorAction:
|
case core.EditorAction:
|
||||||
value(state, &panel);
|
value(state, panel);
|
||||||
return true;
|
return true;
|
||||||
case core.InputActions:
|
case core.InputActions:
|
||||||
state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions)
|
state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions)
|
||||||
|
@ -481,8 +471,8 @@ main :: proc() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_panel, ok := state.current_panel.?; ok {
|
if current_panel, ok := state.current_panel.?; ok {
|
||||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input_proc != nil {
|
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||||
panel.on_buffer_input_proc(&state, &panel.panel_state)
|
panel->on_buffer_input(&state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,496 @@
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
ts.parse_buffer(&buffer.tree, core.tree_sitter_file_buffer_input(buffer))
|
||||||
|
}, "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)
|
||||||
|
ts.parse_buffer(&buffer.tree, core.tree_sitter_file_buffer_input(buffer))
|
||||||
|
}, "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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,310 @@
|
||||||
|
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 "../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)
|
||||||
|
|
||||||
|
arena_bytes, err := make([]u8, 1024*1024*2)
|
||||||
|
if err != nil {
|
||||||
|
log.errorf("failed to allocate arena for grep panel: '%v'", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mem.arena_init(&panel_state.query_arena, arena_bytes)
|
||||||
|
|
||||||
|
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, &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 {
|
||||||
|
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)
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package panels
|
||||||
|
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:path/filepath"
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
|
@ -11,52 +10,9 @@ import "vendor:sdl2"
|
||||||
|
|
||||||
import ts "../tree_sitter"
|
import ts "../tree_sitter"
|
||||||
import "../core"
|
import "../core"
|
||||||
import "../input"
|
|
||||||
import "../util"
|
import "../util"
|
||||||
import "../ui"
|
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
|
// 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) {
|
register_default_leader_actions :: proc(input_map: ^core.InputActions) {
|
||||||
core.register_key_action(input_map, .Q, proc(state: ^core.State, user_data: rawptr) {
|
core.register_key_action(input_map, .Q, proc(state: ^core.State, user_data: rawptr) {
|
||||||
|
@ -106,9 +62,22 @@ 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) {
|
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
|
state.current_panel = panel_id
|
||||||
|
|
||||||
|
arena_bytes, err := make([]u8, 1024*1024*8)
|
||||||
|
if err != nil {
|
||||||
|
log.errorf("failed to allocate memory for panel: '%v'", err)
|
||||||
|
util.delete(&state.panels, panel_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.arena_init(&panel.arena, arena_bytes)
|
||||||
|
panel.allocator = mem.arena_allocator(&panel.arena)
|
||||||
|
|
||||||
|
panel->create(state)
|
||||||
|
|
||||||
core.reset_input_map(state)
|
core.reset_input_map(state)
|
||||||
|
|
||||||
return panel_id, true
|
return panel_id, true
|
||||||
|
@ -120,9 +89,11 @@ open :: proc(state: ^core.State, panel: core.Panel, make_active: bool = true) ->
|
||||||
close :: proc(state: ^core.State, panel_id: int) {
|
close :: proc(state: ^core.State, panel_id: int) {
|
||||||
if panel, ok := util.get(&state.panels, panel_id).?; ok {
|
if panel, ok := util.get(&state.panels, panel_id).?; ok {
|
||||||
if panel.drop != nil {
|
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)
|
util.delete(&state.panels, panel_id)
|
||||||
|
|
||||||
// TODO: keep track of the last active panel instead of focusing back to the first one
|
// TODO: keep track of the last active panel instead of focusing back to the first one
|
||||||
|
@ -133,390 +104,3 @@ close :: proc(state: ^core.State, panel_id: int) {
|
||||||
core.reset_input_map(state)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -139,17 +139,17 @@ impl From<Match> for GrepResult {
|
||||||
// this won't totally bite me later
|
// this won't totally bite me later
|
||||||
text_len: value.text.len() as u32,
|
text_len: value.text.len() as u32,
|
||||||
path_len: value.path.len() as u32,
|
path_len: value.path.len() as u32,
|
||||||
text: Box::leak(value.text.into_boxed_slice()).as_ptr(),
|
text: Box::into_raw(value.text.into_boxed_slice()) as _,
|
||||||
path: Box::leak(value.path.into_bytes().into_boxed_slice()).as_ptr(),
|
path: Box::into_raw(value.path.into_bytes().into_boxed_slice()) as _,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<GrepResult> for Match {
|
impl From<GrepResult> for Match {
|
||||||
fn from(value: GrepResult) -> Self {
|
fn from(value: GrepResult) -> Self {
|
||||||
unsafe {
|
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);
|
let path = String::from_utf8_unchecked(path);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -188,17 +188,26 @@ extern "C" fn grep(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|sink| sink.matches.into_iter())
|
.map(|sink| sink.matches.into_iter())
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(Into::into)
|
.map(|v| GrepResult::from(v))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
|
|
||||||
let len = boxed.len() as u32;
|
let len = boxed.len() as u32;
|
||||||
|
|
||||||
GrepResults {
|
GrepResults {
|
||||||
results: Box::leak(boxed).as_ptr(),
|
results: Box::into_raw(boxed) as _,
|
||||||
len,
|
len,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ new_test_editor :: proc() -> core.State {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete_editor :: proc(e: ^core.State) {
|
||||||
|
util.delete(&e.panels)
|
||||||
|
}
|
||||||
|
|
||||||
buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string {
|
buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string {
|
||||||
if buffer == nil {
|
if buffer == nil {
|
||||||
log.error("nil buffer")
|
log.error("nil buffer")
|
||||||
|
@ -84,9 +88,7 @@ input_text :: proc(text: string) -> ArtificialTextInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_empty_buffer :: proc(state: ^core.State) {
|
setup_empty_buffer :: proc(state: ^core.State) {
|
||||||
buffer := core.new_virtual_file_buffer(context.allocator);
|
panels.open(state, panels.make_file_buffer_panel(""))
|
||||||
panels.open(state, panels.make_file_buffer_panel(len(state.buffers)))
|
|
||||||
runtime.append(&state.buffers, buffer);
|
|
||||||
|
|
||||||
core.reset_input_map(state)
|
core.reset_input_map(state)
|
||||||
}
|
}
|
||||||
|
@ -135,8 +137,12 @@ expect_cursor_index :: proc(t: ^testing.T, cursor: core.Cursor, chunk_index, cha
|
||||||
insert_from_empty_no_newlines :: proc(t: ^testing.T) {
|
insert_from_empty_no_newlines :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
inputted_text := "Hello, world!"
|
inputted_text := "Hello, world!"
|
||||||
expected_text := fmt.aprintf("%v\n", inputted_text)
|
expected_text := fmt.aprintf("%v\n", inputted_text)
|
||||||
|
@ -146,6 +152,8 @@ insert_from_empty_no_newlines :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 12)
|
expect_cursor_index(t, buffer.history.cursor, 0, 12)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +161,12 @@ insert_from_empty_no_newlines :: proc(t: ^testing.T) {
|
||||||
insert_from_empty_with_newline :: proc(t: ^testing.T) {
|
insert_from_empty_with_newline :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
inputted_text := "Hello, world!\nThis is a new line"
|
inputted_text := "Hello, world!\nThis is a new line"
|
||||||
expected_text := fmt.aprintf("%v\n", inputted_text)
|
expected_text := fmt.aprintf("%v\n", inputted_text)
|
||||||
|
@ -164,6 +176,8 @@ insert_from_empty_with_newline :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 31)
|
expect_cursor_index(t, buffer.history.cursor, 0, 31)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,8 +185,12 @@ insert_from_empty_with_newline :: proc(t: ^testing.T) {
|
||||||
insert_in_between_text :: proc(t: ^testing.T) {
|
insert_in_between_text :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
inputted_text := "Hello, world!"
|
inputted_text := "Hello, world!"
|
||||||
expected_text := "Hello, beautiful world!\n"
|
expected_text := "Hello, beautiful world!\n"
|
||||||
|
@ -188,6 +206,8 @@ insert_in_between_text :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 1, 9)
|
expect_cursor_index(t, buffer.history.cursor, 1, 9)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +215,12 @@ insert_in_between_text :: proc(t: ^testing.T) {
|
||||||
insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
|
insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
inputted_text := "Hello, world!"
|
inputted_text := "Hello, world!"
|
||||||
expected_text := "Well, Hello, beautiful world!\n"
|
expected_text := "Well, Hello, beautiful world!\n"
|
||||||
|
@ -215,6 +239,8 @@ insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 0, 5)
|
expect_cursor_index(t, buffer.history.cursor, 0, 5)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,8 +248,12 @@ insert_before_slice_at_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
insert_before_slice :: proc(t: ^testing.T) {
|
insert_before_slice :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
inputted_text := "Hello, world!"
|
inputted_text := "Hello, world!"
|
||||||
expected_text := "Hello, beautiful rich world!\n"
|
expected_text := "Hello, beautiful rich world!\n"
|
||||||
|
@ -243,6 +273,8 @@ insert_before_slice :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 2, 4)
|
expect_cursor_index(t, buffer.history.cursor, 2, 4)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,8 +282,12 @@ insert_before_slice :: proc(t: ^testing.T) {
|
||||||
delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
|
delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "Hello, world!")
|
run_text_insertion(&e, "Hello, world!")
|
||||||
|
|
||||||
|
@ -283,8 +319,12 @@ delete_last_content_slice_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
delete_in_slice :: proc(t: ^testing.T) {
|
delete_in_slice :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
inputted_text := "Hello, world!"
|
inputted_text := "Hello, world!"
|
||||||
expected_text := "Hello, beautiful h world!\n"
|
expected_text := "Hello, beautiful h world!\n"
|
||||||
|
@ -310,6 +350,8 @@ delete_in_slice :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 3, 0)
|
expect_cursor_index(t, buffer.history.cursor, 3, 0)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,8 +359,12 @@ delete_in_slice :: proc(t: ^testing.T) {
|
||||||
delete_across_slices :: proc(t: ^testing.T) {
|
delete_across_slices :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
inputted_text := "Hello, world!"
|
inputted_text := "Hello, world!"
|
||||||
expected_text := "Hello, beautiful world!\n"
|
expected_text := "Hello, beautiful world!\n"
|
||||||
|
@ -352,6 +398,8 @@ delete_across_slices :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 2, 0)
|
expect_cursor_index(t, buffer.history.cursor, 2, 0)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,10 +407,14 @@ delete_across_slices :: proc(t: ^testing.T) {
|
||||||
move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
|
move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
is_ctrl_pressed := false
|
is_ctrl_pressed := false
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "012345678\n0")
|
run_text_insertion(&e, "012345678\n0")
|
||||||
|
|
||||||
|
@ -383,10 +435,14 @@ move_down_next_line_has_shorter_length :: proc(t: ^testing.T) {
|
||||||
move_down_on_last_line :: proc(t: ^testing.T) {
|
move_down_on_last_line :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
is_ctrl_pressed := false
|
is_ctrl_pressed := false
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "012345678")
|
run_text_insertion(&e, "012345678")
|
||||||
|
|
||||||
|
@ -402,8 +458,12 @@ move_down_on_last_line :: proc(t: ^testing.T) {
|
||||||
move_left_at_beginning_of_file :: proc(t: ^testing.T) {
|
move_left_at_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "01234")
|
run_text_insertion(&e, "01234")
|
||||||
// Move cursor from --------^
|
// Move cursor from --------^
|
||||||
|
@ -425,10 +485,14 @@ move_left_at_beginning_of_file :: proc(t: ^testing.T) {
|
||||||
move_right_at_end_of_file :: proc(t: ^testing.T) {
|
move_right_at_end_of_file :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
is_ctrl_pressed := false
|
is_ctrl_pressed := false
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "01234")
|
run_text_insertion(&e, "01234")
|
||||||
|
|
||||||
|
@ -447,10 +511,14 @@ move_right_at_end_of_file :: proc(t: ^testing.T) {
|
||||||
move_to_end_of_line_from_end :: proc(t: ^testing.T) {
|
move_to_end_of_line_from_end :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
is_ctrl_pressed := false
|
is_ctrl_pressed := false
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "01234\n01234")
|
run_text_insertion(&e, "01234\n01234")
|
||||||
|
|
||||||
|
@ -468,10 +536,14 @@ move_to_end_of_line_from_end :: proc(t: ^testing.T) {
|
||||||
move_to_end_of_line_from_middle :: proc(t: ^testing.T) {
|
move_to_end_of_line_from_middle :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
is_ctrl_pressed := false
|
is_ctrl_pressed := false
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "01234\n01234")
|
run_text_insertion(&e, "01234\n01234")
|
||||||
|
|
||||||
|
@ -492,10 +564,14 @@ move_to_end_of_line_from_middle :: proc(t: ^testing.T) {
|
||||||
move_to_beginning_of_line_from_middle :: proc(t: ^testing.T) {
|
move_to_beginning_of_line_from_middle :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
is_ctrl_pressed := false
|
is_ctrl_pressed := false
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "01234\n01234")
|
run_text_insertion(&e, "01234\n01234")
|
||||||
|
|
||||||
|
@ -516,10 +592,14 @@ move_to_beginning_of_line_from_middle :: proc(t: ^testing.T) {
|
||||||
move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
|
move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
is_ctrl_pressed := false
|
is_ctrl_pressed := false
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "01234\n01234")
|
run_text_insertion(&e, "01234\n01234")
|
||||||
|
|
||||||
|
@ -540,8 +620,12 @@ move_to_beginning_of_line_from_start :: proc(t: ^testing.T) {
|
||||||
append_end_of_line :: proc(t: ^testing.T) {
|
append_end_of_line :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
run_text_insertion(&e, "hello")
|
run_text_insertion(&e, "hello")
|
||||||
|
|
||||||
|
@ -562,8 +646,12 @@ append_end_of_line :: proc(t: ^testing.T) {
|
||||||
insert_line_under_current :: proc(t: ^testing.T) {
|
insert_line_under_current :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
initial_text := "Hello, world!\nThis is a new line"
|
initial_text := "Hello, world!\nThis is a new line"
|
||||||
run_text_insertion(&e, initial_text)
|
run_text_insertion(&e, initial_text)
|
||||||
|
@ -590,6 +678,8 @@ insert_line_under_current :: proc(t: ^testing.T) {
|
||||||
expect_cursor_index(t, buffer.history.cursor, 1, 23)
|
expect_cursor_index(t, buffer.history.cursor, 1, 23)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,8 +687,12 @@ insert_line_under_current :: proc(t: ^testing.T) {
|
||||||
yank_and_paste_whole_line :: proc(t: ^testing.T) {
|
yank_and_paste_whole_line :: proc(t: ^testing.T) {
|
||||||
e := new_test_editor()
|
e := new_test_editor()
|
||||||
setup_empty_buffer(&e)
|
setup_empty_buffer(&e)
|
||||||
|
defer {
|
||||||
|
panels.close(&e, 0)
|
||||||
|
delete_editor(&e)
|
||||||
|
}
|
||||||
|
|
||||||
buffer := &e.buffers[0]
|
buffer := core.current_buffer(&e)
|
||||||
|
|
||||||
initial_text := "Hello, world!\nThis is a new line"
|
initial_text := "Hello, world!\nThis is a new line"
|
||||||
run_text_insertion(&e, initial_text)
|
run_text_insertion(&e, initial_text)
|
||||||
|
@ -617,22 +711,22 @@ yank_and_paste_whole_line :: proc(t: ^testing.T) {
|
||||||
expect_line_col(t, buffer.history.cursor, 1, 0)
|
expect_line_col(t, buffer.history.cursor, 1, 0)
|
||||||
|
|
||||||
contents := buffer_to_string(core.current_buffer(&e))
|
contents := buffer_to_string(core.current_buffer(&e))
|
||||||
|
defer delete(contents)
|
||||||
|
|
||||||
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
testing.expectf(t, contents == expected_text, "got '%v', expected '%v'", contents, expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pressed: ^bool) {
|
run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pressed: ^bool) {
|
||||||
log.infof("running input: %v", input)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
run_key_action := proc(state: ^core.State, control_key_pressed: bool, key: core.Key) -> bool {
|
run_key_action := proc(state: ^core.State, control_key_pressed: bool, key: core.Key) -> bool {
|
||||||
log.info("key_action")
|
if current_panel, ok := state.current_panel.?; ok {
|
||||||
|
panel := util.get(&state.panels, current_panel).?
|
||||||
|
|
||||||
if state.current_input_map != nil {
|
|
||||||
if control_key_pressed {
|
if control_key_pressed {
|
||||||
if action, exists := state.current_input_map.ctrl_key_actions[key]; exists {
|
if action, exists := state.current_input_map.ctrl_key_actions[key]; exists {
|
||||||
switch value in action.action {
|
switch value in action.action {
|
||||||
case core.EditorAction:
|
case core.EditorAction:
|
||||||
value(state);
|
value(state, panel);
|
||||||
return true;
|
return true;
|
||||||
case core.InputActions:
|
case core.InputActions:
|
||||||
state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions)
|
state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions)
|
||||||
|
@ -643,7 +737,7 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
||||||
if action, exists := state.current_input_map.key_actions[key]; exists {
|
if action, exists := state.current_input_map.key_actions[key]; exists {
|
||||||
switch value in action.action {
|
switch value in action.action {
|
||||||
case core.EditorAction:
|
case core.EditorAction:
|
||||||
value(state);
|
value(state, panel);
|
||||||
return true;
|
return true;
|
||||||
case core.InputActions:
|
case core.InputActions:
|
||||||
state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions)
|
state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions)
|
||||||
|
@ -651,8 +745,6 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.info("current_input_map is null")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -661,8 +753,6 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
||||||
switch state.mode {
|
switch state.mode {
|
||||||
case .Visual: fallthrough
|
case .Visual: fallthrough
|
||||||
case .Normal: {
|
case .Normal: {
|
||||||
log.info("it's normal/visual mode")
|
|
||||||
|
|
||||||
if key, ok := input.(ArtificialKey); ok {
|
if key, ok := input.(ArtificialKey); ok {
|
||||||
if key.is_down {
|
if key.is_down {
|
||||||
if key.key == .LCTRL {
|
if key.key == .LCTRL {
|
||||||
|
@ -678,8 +768,6 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .Insert: {
|
case .Insert: {
|
||||||
log.info("it's insert mode")
|
|
||||||
|
|
||||||
buffer := core.current_buffer(state);
|
buffer := core.current_buffer(state);
|
||||||
|
|
||||||
if key, ok := input.(ArtificialKey); ok {
|
if key, ok := input.(ArtificialKey); ok {
|
||||||
|
@ -710,24 +798,20 @@ run_editor_frame :: proc(state: ^core.State, input: ArtificialInput, is_ctrl_pre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("before text input")
|
|
||||||
if text_input, ok := input.(ArtificialTextInput); ok {
|
if text_input, ok := input.(ArtificialTextInput); ok {
|
||||||
log.infof("attempting to append '%v' to buffer", text_input)
|
|
||||||
|
|
||||||
for char in text_input.text {
|
for char in text_input.text {
|
||||||
if char < 1 {
|
if char < 1 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == '\n' || (char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1) {
|
if char == '\n' || (char >= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1) {
|
||||||
log.infof("appening '%v' to buffer", char)
|
|
||||||
append(&buffer.input_buffer, u8(char));
|
append(&buffer.input_buffer, u8(char));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_panel, ok := state.current_panel.?; ok {
|
if current_panel, ok := state.current_panel.?; ok {
|
||||||
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input_proc != nil {
|
if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input != nil {
|
||||||
panel.on_buffer_input_proc(state, &panel.panel_state)
|
panel->on_buffer_input(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,12 +223,17 @@ make_state :: proc(type: LanguageType, allocator := context.allocator) -> State
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_state :: proc(state: ^State) {
|
delete_state :: proc(state: ^State) {
|
||||||
|
delete(state.highlights)
|
||||||
tree_cursor_delete(&state.cursor)
|
tree_cursor_delete(&state.cursor)
|
||||||
tree_delete(state.tree)
|
tree_delete(state.tree)
|
||||||
parser_delete(state.parser)
|
parser_delete(state.parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_buffer :: proc(state: ^State, input: Input) {
|
parse_buffer :: proc(state: ^State, input: Input) {
|
||||||
|
if state.parser == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
old_tree := state.tree
|
old_tree := state.tree
|
||||||
if old_tree != nil {
|
if old_tree != nil {
|
||||||
defer tree_delete(old_tree)
|
defer tree_delete(old_tree)
|
||||||
|
|
|
@ -11,17 +11,17 @@ StaticListSlot :: struct($T: typeid) {
|
||||||
data: T,
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
append_static_list :: proc(list: ^StaticList($T), value: T) -> Maybe(int) {
|
append_static_list :: proc(list: ^StaticList($T), value: T) -> (id: int, item: ^T, ok: bool) {
|
||||||
for i in 0..<len(list.data) {
|
for i in 0..<len(list.data) {
|
||||||
if !list.data[i].active {
|
if !list.data[i].active {
|
||||||
list.data[i].active = true
|
list.data[i].active = true
|
||||||
list.data[i].data = value
|
list.data[i].data = value
|
||||||
|
|
||||||
return i
|
return i, &list.data[i].data, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
append :: proc{append_static_list}
|
append :: proc{append_static_list}
|
||||||
|
|
||||||
|
@ -89,4 +89,8 @@ delete_static_list_elem :: proc(list: ^StaticList($T), index: int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete :: proc{delete_static_list_elem}
|
delete_static_list :: proc(list: ^StaticList($T)) {
|
||||||
|
runtime.delete(list.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete :: proc{delete_static_list_elem, delete_static_list}
|
4
todo.md
4
todo.md
|
@ -1,5 +1,4 @@
|
||||||
# Bugs
|
# Bugs
|
||||||
- Memory Leak
|
|
||||||
- Fix jumping forward a word jumping past consecutive brackets
|
- Fix jumping forward a word jumping past consecutive brackets
|
||||||
- Odd scrolling behavior on small screen heights
|
- Odd scrolling behavior on small screen heights
|
||||||
- Scrolling past end/beginning of results panics
|
- Scrolling past end/beginning of results panics
|
||||||
|
@ -8,7 +7,8 @@
|
||||||
- Split grep search results into a table to avoid funky unaligned text
|
- Split grep search results into a table to avoid funky unaligned text
|
||||||
|
|
||||||
# Planned Features
|
# Planned Features
|
||||||
- Use grouped lifetimes exclusively for memory allocation/freeing
|
- [ ] Jump List
|
||||||
|
- [x] Use grouped lifetimes exclusively for memory allocation/freeing
|
||||||
- [ ] Highlight which panel is currently active
|
- [ ] Highlight which panel is currently active
|
||||||
- [ ] Persist end of line cursor position
|
- [ ] Persist end of line cursor position
|
||||||
- Testing Harness
|
- Testing Harness
|
||||||
|
|
Loading…
Reference in New Issue