diff --git a/src/core/core.odin b/src/core/core.odin index df7d118..3eaa033 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -8,6 +8,36 @@ Mode :: enum { Insert, } +WindowDrawProc :: proc(win: ^Window, state: ^State); +WindowFreeProc :: proc(win: ^Window, state: ^State); +WindowGetBufferProc :: proc(win: ^Window) -> ^FileBuffer; +Window :: struct { + input_map: InputMap, + draw: WindowDrawProc, + free: WindowFreeProc, + + get_buffer: WindowGetBufferProc, + + // TODO: create hook for when mode changes happen +} +request_window_close :: proc(state: ^State) { + state.should_close_window = true; +} + +close_window_and_free :: proc(state: ^State) { + if state.window != nil { + if state.window.free != nil { + state.window->free(state); + } + + delete_input_map(&state.window.input_map); + free(state.window); + + state.window = nil; + state.current_input_map = &state.input_map; + } +} + State :: struct { mode: Mode, should_close: bool, @@ -22,10 +52,8 @@ State :: struct { current_buffer: int, buffers: [dynamic]FileBuffer, - // TODO: replace this with generic pointer to floating window - buffer_list_window_is_visible: bool, - buffer_list_window_selected_buffer: int, - buffer_list_window_input_map: InputMap, + window: ^Window, + should_close_window: bool, input_map: InputMap, current_input_map: ^InputMap, @@ -50,6 +78,10 @@ new_input_map :: proc() -> InputMap { return input_map; } +delete_input_map :: proc(input_map: ^InputMap) { + delete(input_map.key_actions); + delete(input_map.ctrl_key_actions); +} // NOTE(pcleavelin): might be a bug in the compiler where it can't coerce // `EditorAction` to `InputGroup` when given as a proc parameter, that is why there diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index fc7d560..a24cc47 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -86,8 +86,11 @@ iterate_file_buffer :: proc(it: ^FileBufferIter) -> (character: u8, idx: FileBuf } else if it.cursor.index.slice_index < len(it.buffer.content_slices)-1 { it.cursor.index.content_index = 0; it.cursor.index.slice_index += 1; - } else { + } else if it.hit_end { return character, it.cursor.index, false; + } else { + it.hit_end = true; + return character, it.cursor.index, true; } if character == '\n' { @@ -531,6 +534,31 @@ move_cursor_backward_end_of_word :: proc(buffer: ^FileBuffer) { update_file_buffer_scroll(buffer); } +new_virtual_file_buffer :: proc(allocator: mem.Allocator) -> FileBuffer { + context.allocator = allocator; + width := 256; + height := 256; + + buffer := FileBuffer { + allocator = allocator, + file_path = "virtual_buffer", + + original_content = slice.clone_to_dynamic([]u8{'\n'}), + added_content = make([dynamic]u8, 0, 1024*1024), + content_slices = make([dynamic][]u8, 0, 1024*1024), + + glyph_buffer_width = width, + glyph_buffer_height = height, + glyph_buffer = make([dynamic]Glyph, width*height, width*height), + + input_buffer = make([dynamic]u8, 0, 1024), + }; + + append(&buffer.content_slices, buffer.original_content[:]); + + return buffer; +} + new_file_buffer :: proc(allocator: mem.Allocator, file_path: string) -> (FileBuffer, Error) { context.allocator = allocator; @@ -567,6 +595,14 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string) -> (FileBuf } } +free_file_buffer :: proc(buffer: ^FileBuffer) { + delete(buffer.original_content); + delete(buffer.added_content); + delete(buffer.content_slices); + delete(buffer.glyph_buffer); + delete(buffer.input_buffer); +} + is_keyword :: proc(start: FileBufferIter, end: FileBufferIter) -> (matches: bool) { keywords := []string { "using", diff --git a/src/main.odin b/src/main.odin index 5e69265..bb4593b 100644 --- a/src/main.odin +++ b/src/main.odin @@ -19,8 +19,8 @@ FileBuffer :: core.FileBuffer; // TODO: use buffer list in state do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) { if state.current_input_map != nil { - if raylib.IsKeyDown(.ESCAPE) { - state.current_input_map = &state.input_map; + if raylib.IsKeyPressed(.ESCAPE) { + core.request_window_close(state); } else if raylib.IsKeyDown(.LEFT_CONTROL) { for key, action in &state.current_input_map.ctrl_key_actions { if raylib.IsKeyPressed(key) { @@ -87,9 +87,14 @@ switch_to_buffer :: proc(state: ^State, item: ^ui.MenuBarItem) { register_default_leader_actions :: proc(input_map: ^core.InputMap) { core.register_key_action(input_map, .B, proc(state: ^State) { - state.buffer_list_window_is_visible = true; - state.current_input_map = &state.buffer_list_window_input_map; + state.window = ui.create_buffer_list_window(); + state.current_input_map = &state.window.input_map; }, "show list of open buffers"); + core.register_key_action(input_map, .R, proc(state: ^State) { + state.window = ui.create_grep_window(); + state.current_input_map = &state.window.input_map; + state.mode = .Insert; + }, "live grep"); core.register_key_action(input_map, .Q, proc(state: ^State) { state.current_input_map = &state.input_map; }, "close this help"); @@ -107,62 +112,70 @@ register_default_go_actions :: proc(input_map: ^core.InputMap) { } register_default_input_actions :: proc(input_map: ^core.InputMap) { - core.register_key_action(input_map, .W, proc(state: ^State) { - core.move_cursor_forward_start_of_word(&state.buffers[state.current_buffer]); - }, "move forward one word"); - core.register_key_action(input_map, .E, proc(state: ^State) { - core.move_cursor_forward_end_of_word(&state.buffers[state.current_buffer]); - }, "move forward to end of word"); + // Cursor Movement + { + core.register_key_action(input_map, .W, proc(state: ^State) { + core.move_cursor_forward_start_of_word(&state.buffers[state.current_buffer]); + }, "move forward one word"); + core.register_key_action(input_map, .E, proc(state: ^State) { + core.move_cursor_forward_end_of_word(&state.buffers[state.current_buffer]); + }, "move forward to end of word"); - core.register_key_action(input_map, .B, proc(state: ^State) { - core.move_cursor_backward_start_of_word(&state.buffers[state.current_buffer]); - }, "move backward one word"); + core.register_key_action(input_map, .B, proc(state: ^State) { + core.move_cursor_backward_start_of_word(&state.buffers[state.current_buffer]); + }, "move backward one word"); - core.register_key_action(input_map, .K, proc(state: ^State) { - core.move_cursor_up(&state.buffers[state.current_buffer]); - }, "move up one line"); - core.register_key_action(input_map, .J, proc(state: ^State) { - core.move_cursor_down(&state.buffers[state.current_buffer]); - }, "move down one line"); - core.register_key_action(input_map, .H, proc(state: ^State) { - core.move_cursor_left(&state.buffers[state.current_buffer]); - }, "move left one char"); - core.register_key_action(input_map, .L, proc(state: ^State) { - core.move_cursor_right(&state.buffers[state.current_buffer]); - }, "move right one char"); + core.register_key_action(input_map, .K, proc(state: ^State) { + core.move_cursor_up(&state.buffers[state.current_buffer]); + }, "move up one line"); + core.register_key_action(input_map, .J, proc(state: ^State) { + core.move_cursor_down(&state.buffers[state.current_buffer]); + }, "move down one line"); + core.register_key_action(input_map, .H, proc(state: ^State) { + core.move_cursor_left(&state.buffers[state.current_buffer]); + }, "move left one char"); + core.register_key_action(input_map, .L, proc(state: ^State) { + core.move_cursor_right(&state.buffers[state.current_buffer]); + }, "move right one char"); - core.register_ctrl_key_action(input_map, .U, proc(state: ^State) { - core.scroll_file_buffer(&state.buffers[state.current_buffer], .Up); - }, "scroll buffer up"); - core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { - core.scroll_file_buffer(&state.buffers[state.current_buffer], .Down); - }, "scroll buffer up"); + core.register_ctrl_key_action(input_map, .U, proc(state: ^State) { + core.scroll_file_buffer(&state.buffers[state.current_buffer], .Up); + }, "scroll buffer up"); + core.register_ctrl_key_action(input_map, .D, proc(state: ^State) { + core.scroll_file_buffer(&state.buffers[state.current_buffer], .Down); + }, "scroll buffer up"); + } // Scale font size - core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) { - if state.source_font_height > 16 { - state.source_font_height -= 2; + { + core.register_ctrl_key_action(input_map, .MINUS, proc(state: ^State) { + if state.source_font_height > 16 { + state.source_font_height -= 2; + state.source_font_width = state.source_font_height / 2; + + state.font = raylib.LoadFontEx("/System/Library/Fonts/Supplemental/Andale Mono.ttf", i32(state.source_font_height*2), nil, 0); + raylib.SetTextureFilter(state.font.texture, .BILINEAR); + } + }, "increase font size"); + core.register_ctrl_key_action(input_map, .EQUAL, proc(state: ^State) { + state.source_font_height += 2; state.source_font_width = state.source_font_height / 2; state.font = raylib.LoadFontEx("/System/Library/Fonts/Supplemental/Andale Mono.ttf", i32(state.source_font_height*2), nil, 0); raylib.SetTextureFilter(state.font.texture, .BILINEAR); - } - }, "increase font size"); - core.register_ctrl_key_action(input_map, .EQUAL, proc(state: ^State) { - state.source_font_height += 2; - state.source_font_width = state.source_font_height / 2; + }, "decrease font size"); + } - state.font = raylib.LoadFontEx("/System/Library/Fonts/Supplemental/Andale Mono.ttf", i32(state.source_font_height*2), nil, 0); - raylib.SetTextureFilter(state.font.texture, .BILINEAR); - }, "decrease font size"); - - core.register_key_action(input_map, .I, proc(state: ^State) { - state.mode = .Insert; - }, "enter insert mode"); - core.register_key_action(input_map, .A, proc(state: ^State) { - core.move_cursor_right(&state.buffers[state.current_buffer], false); - state.mode = .Insert; - }, "enter insert mode after character (append)"); + // Inserting Text + { + core.register_key_action(input_map, .I, proc(state: ^State) { + state.mode = .Insert; + }, "enter insert mode"); + core.register_key_action(input_map, .A, proc(state: ^State) { + core.move_cursor_right(&state.buffers[state.current_buffer], false); + state.mode = .Insert; + }, "enter insert mode after character (append)"); + } core.register_key_action(input_map, .SPACE, core.new_input_map(), "leader commands"); register_default_leader_actions(&(&input_map.key_actions[.SPACE]).action.(core.InputMap)); @@ -171,44 +184,15 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) { register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputMap)); } -register_buffer_list_input_actions :: proc(input_map: ^core.InputMap) { - core.register_key_action(input_map, .K, proc(state: ^State) { - if state.buffer_list_window_selected_buffer > 0 { - state.buffer_list_window_selected_buffer -= 1; - } else { - state.buffer_list_window_selected_buffer = len(state.buffers)-1; - } - }, "move selection up"); - core.register_key_action(input_map, .J, proc(state: ^State) { - if state.buffer_list_window_selected_buffer >= len(state.buffers)-1 { - state.buffer_list_window_selected_buffer = 0; - } else { - state.buffer_list_window_selected_buffer += 1; - } - }, "move selection down"); - core.register_key_action(input_map, .ENTER, proc(state: ^State) { - state.current_buffer = state.buffer_list_window_selected_buffer; - - state.buffer_list_window_is_visible = false; - state.current_input_map = &state.input_map; - }, "switch to file"); - - core.register_key_action(input_map, .Q, proc(state: ^State) { - state.buffer_list_window_is_visible = false; - state.current_input_map = &state.input_map; - }, "close window"); -} - main :: proc() { state := State { source_font_width = 8, source_font_height = 16, input_map = core.new_input_map(), - buffer_list_window_input_map = core.new_input_map(), + window = nil, }; state.current_input_map = &state.input_map; register_default_input_actions(&state.input_map); - register_buffer_list_input_actions(&state.buffer_list_window_input_map); for arg in os.args[1:] { buffer, err := core.new_file_buffer(context.allocator, arg); @@ -323,8 +307,8 @@ main :: proc() { 0, theme.get_palette_raylib_color(.Background1)); - if state.buffer_list_window_is_visible { - ui.draw_buffer_list_window(&state); + if state.window != nil && state.window.draw != nil { + state.window->draw(&state); } if state.current_input_map != &state.input_map { @@ -377,9 +361,22 @@ main :: proc() { switch state.mode { case .Normal: - do_normal_mode(&state, buffer); + if state.window != nil && state.window.get_buffer != nil { + do_normal_mode(&state, state.window->get_buffer()); + } else { + do_normal_mode(&state, buffer); + } case .Insert: - do_insert_mode(&state, buffer); + if state.window != nil && state.window.get_buffer != nil { + do_insert_mode(&state, state.window->get_buffer()); + } else { + do_insert_mode(&state, buffer); + } + } + + if state.should_close_window { + state.should_close_window = false; + core.close_window_and_free(&state); } ui.test_menu_bar(&state, &menu_bar_state, 0,0, mouse_pos, raylib.IsMouseButtonReleased(.LEFT), state.source_font_height); diff --git a/src/ui/floating_window.odin b/src/ui/buffer_list_window.odin similarity index 59% rename from src/ui/floating_window.odin rename to src/ui/buffer_list_window.odin index 813f671..12869c5 100644 --- a/src/ui/floating_window.odin +++ b/src/ui/buffer_list_window.odin @@ -6,7 +6,60 @@ import "vendor:raylib" import "../core" import "../theme" -draw_buffer_list_window :: proc(state: ^core.State) { +BufferListWindow :: struct { + using window: core.Window, + + selected_buffer: int, +} + +create_buffer_list_window :: proc() -> ^BufferListWindow { + input_map := core.new_input_map(); + + core.register_key_action(&input_map, .K, proc(state: ^core.State) { + win := cast(^BufferListWindow)(state.window); + + if win.selected_buffer > 0 { + win.selected_buffer -= 1; + } else { + win.selected_buffer = len(state.buffers)-1; + } + + }, "move selection up"); + core.register_key_action(&input_map, .J, proc(state: ^core.State) { + win := cast(^BufferListWindow)(state.window); + + if win.selected_buffer >= len(state.buffers)-1 { + win.selected_buffer = 0; + } else { + win.selected_buffer += 1; + } + }, "move selection down"); + core.register_key_action(&input_map, .ENTER, proc(state: ^core.State) { + win := cast(^BufferListWindow)(state.window); + + state.current_buffer = win.selected_buffer; + + core.request_window_close(state); + }, "switch to file"); + + core.register_key_action(&input_map, .Q, proc(state: ^core.State) { + core.request_window_close(state); + }, "close window"); + + list_window := new(BufferListWindow); + list_window^ = BufferListWindow { + window = core.Window { + input_map = input_map, + draw = draw_buffer_list_window, + }, + }; + + return list_window; +} + +draw_buffer_list_window :: proc(win: ^core.Window, state: ^core.State) { + win := cast(^BufferListWindow)(win); + win_rec := raylib.Rectangle { x = f32(state.screen_width/8), y = f32(state.screen_height/8), @@ -37,7 +90,7 @@ draw_buffer_list_window :: proc(state: ^core.State) { text := raylib.TextFormat("%s:%d", buffer.file_path, buffer.cursor.line+1); text_width := raylib.MeasureTextEx(state.font, text, f32(state.source_font_height), 0); - if index == state.buffer_list_window_selected_buffer { + if index == win.selected_buffer { buffer.glyph_buffer_height = glyph_buffer_height; buffer.glyph_buffer_width = glyph_buffer_width; core.draw_file_buffer( diff --git a/src/ui/grep_window.odin b/src/ui/grep_window.odin new file mode 100644 index 0000000..ce1f33e --- /dev/null +++ b/src/ui/grep_window.odin @@ -0,0 +1,97 @@ +package ui; + +import "core:math" +import "vendor:raylib" + +import "../core" +import "../theme" + +GrepWindow :: struct { + using window: core.Window, + + input_buffer: core.FileBuffer, +} + +create_grep_window :: proc() -> ^GrepWindow { + input_map := core.new_input_map(); + + core.register_key_action(&input_map, .ENTER, proc(state: ^core.State) { + win := cast(^GrepWindow)(state.window); + + core.request_window_close(state); + }, "jump to location"); + + core.register_key_action(&input_map, .I, proc(state: ^core.State) { + state.mode = .Insert; + }, "enter insert mode"); + + grep_window := new(GrepWindow); + grep_window^ = GrepWindow { + window = core.Window { + input_map = input_map, + draw = draw_grep_window, + get_buffer = grep_window_get_buffer, + free = free_grep_window, + }, + + input_buffer = core.new_virtual_file_buffer(context.allocator), + }; + + return grep_window; +} + +free_grep_window :: proc(win: ^core.Window, state: ^core.State) { + win := cast(^GrepWindow)(win); + + core.free_file_buffer(&win.input_buffer); +} + +grep_window_get_buffer :: proc(win: ^core.Window) -> ^core.FileBuffer { + win := cast(^GrepWindow)(win); + + return &win.input_buffer; +} + +@private +grep_files :: proc(win: ^core.Window, state: ^core.State) { + // TODO: use rip-grep to search through files +} + +draw_grep_window :: proc(win: ^core.Window, state: ^core.State) { + win := cast(^GrepWindow)(win); + + win_rec := raylib.Rectangle { + x = f32(state.screen_width/8), + y = f32(state.screen_height/8), + width = f32(state.screen_width - state.screen_width/4), + height = f32(state.screen_height - state.screen_height/4), + }; + raylib.DrawRectangleRec( + win_rec, + theme.get_palette_raylib_color(.Background4)); + + win_margin := raylib.Vector2 { f32(state.source_font_width), f32(state.source_font_height) }; + + buffer_prev_width := (win_rec.width - win_margin.x*2) / 2; + buffer_prev_height := win_rec.height - win_margin.y*2; + + glyph_buffer_width := int(buffer_prev_width) / state.source_font_width - 1; + glyph_buffer_height := 1; + + raylib.DrawRectangle( + i32(win_rec.x + win_margin.x), + i32(win_rec.y + win_rec.height - win_margin.y * 2), + i32(buffer_prev_width), + i32(state.source_font_height), + theme.get_palette_raylib_color(.Background2)); + + win.input_buffer.glyph_buffer_height = glyph_buffer_height; + win.input_buffer.glyph_buffer_width = glyph_buffer_width; + core.draw_file_buffer( + state, + &win.input_buffer, + int(win_rec.x + win_margin.x), + int(win_rec.y + win_rec.height - win_margin.y * 2), + state.font, + show_line_numbers = false); +}