package panels import "base:runtime" import "core:mem" import "core:path/filepath" import "core:fmt" import "core:strings" import "core:log" import "vendor:sdl2" import "../core" import "../input" import "../util" import "../ui" foreign import grep_lib "../pkg/grep_lib/target/debug/libgrep.a" @(default_calling_convention = "c") foreign grep_lib { grep :: proc (pattern: cstring, directory: cstring) -> RS_GrepResults --- free_grep_results :: proc(results: RS_GrepResults) --- } RS_GrepResults :: struct { results: [^]RS_GrepResult, len: u32, } RS_GrepResult :: struct { line_number: u64, column: u64, text_len: u32, path_len: u32, text: [^]u8, path: [^]u8, } @(private) rs_grep_as_results :: proc(results: ^RS_GrepResults, allocator := context.allocator) -> []core.GrepQueryResult { context.allocator = allocator query_results := make([]core.GrepQueryResult, results.len) for i in 0.. (panel_id: int, ok: bool) { if panel_id, ok := util.append_static_list(&state.panels, panel).?; ok && make_active { state.current_panel = panel_id core.reset_input_map(state) return panel_id, true } return -1, false } close :: proc(state: ^core.State, panel_id: int) { if panel, ok := util.get(&state.panels, panel_id).?; ok { if panel.drop != nil { panel.drop(state, &panel.panel_state) } util.delete(&state.panels, panel_id) // TODO: keep track of the last active panel instead of focusing back to the first one if first_active, ok := util.get_first_active_index(&state.panels).?; ok { state.current_panel = first_active } 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) { 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.cursor.line = line buffer.cursor.col = col buffer.top_line = buffer.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); } }; 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{}}, }) { 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) ui.open_element(s, nil, { kind = {ui.Grow{}, ui.Exact(state.source_font_height)} }) { 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.cursor) ui.open_element( s, fmt.tprintf( "%v:%v - Slice %v:%v - Char: %v", buffer.cursor.line + 1, buffer.cursor.col + 1, buffer.cursor.index.chunk_index, buffer.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, 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, buffer_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (buffer: ^core.FileBuffer, ok: bool) { panel_state := panel_state.(core.FileBufferPanel) or_return; return &state.buffers[panel_state.buffer_index], true }, render_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (ok: bool) { panel_state := panel_state.(core.FileBufferPanel) or_return; s := transmute(^ui.State)state.ui buffer := &state.buffers[panel_state.buffer_index] render_file_buffer(state, s, buffer) return true } } } 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() 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) { if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { this_panel := state.current_panel.? if panel_state, ok := ¤t_panel.panel_state.(core.GrepPanel); ok { if panel_state.query_results != nil { selected_result := &panel_state.query_results[panel_state.selected_result] if panel_id, buffer, ok := open_file_buffer_in_new_panel(state, selected_result.file_path, selected_result.line, selected_result.col); ok { close(state, this_panel) state.current_panel = panel_id state.current_buffer = buffer } 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) { state.mode = .Insert; sdl2.StartTextInput(); }, "enter insert mode"); core.register_key_action(&input_map.mode[.Normal], .K, proc(state: ^core.State) { // NOTE: this is really jank, should probably update the input // action stuff to allow panels to be passed into these handlers if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { if panel_state, ok := ¤t_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) { // NOTE: this is really jank, should probably update the input // action stuff to allow panels to be passed into these handlers if current_panel, ok := util.get(&state.panels, state.current_panel.? or_else -1).?; ok { if panel_state, ok := ¤t_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) { state.mode = .Normal; sdl2.StopTextInput(); }, "exit insert mode"); core.register_key_action(&input_map.mode[.Normal], .ESCAPE, proc(state: ^core.State) { if state.current_panel != nil { close(state, state.current_panel.?) } }, "close panel"); 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, buffer_proc = proc(state: ^core.State, panel_state: ^core.PanelState) -> (buffer: ^core.FileBuffer, ok: bool) { panel_state := panel_state.(core.GrepPanel) or_return; return &state.buffers[panel_state.buffer], true }, on_buffer_input_proc = proc(state: ^core.State, panel_state: ^core.PanelState) { if panel_state, ok := &panel_state.(core.GrepPanel); ok { buffer := &state.buffers[panel_state.buffer] run_query(panel_state, string(buffer.input_buffer[:]), state.directory) } }, drop = proc(state: ^core.State, panel_state: ^core.PanelState) { if panel_state, ok := &panel_state.(core.GrepPanel); ok { delete(panel_state.query_arena.data, 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{}} }) { 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) // TODO: when styling is implemented, make this look better if panel_state.selected_result == i { ui.open_element(s, fmt.tprintf("%s <--", result.file_path), {}) ui.close_element(s) } else { ui.open_element(s, result.file_path, {}) ui.close_element(s) } } } } ui.close_element(s) // 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 } } }