From 6b4d9f0cda2fa6b9cc9eb2b3d591f9afccfaf13e Mon Sep 17 00:00:00 2001 From: Patrick Cleaveliln Date: Thu, 10 Jul 2025 06:24:05 +0000 Subject: [PATCH] test harness --- Makefile | 3 + src/core/file_buffer.odin | 2 +- src/main.odin | 2 +- src/tests/tests.odin | 622 ++++++++++++++++++++++++++++++++++++++ todo.md | 18 +- 5 files changed, 637 insertions(+), 10 deletions(-) create mode 100644 src/tests/tests.odin diff --git a/Makefile b/Makefile index 004a5dd..866b113 100644 --- a/Makefile +++ b/Makefile @@ -8,3 +8,6 @@ editor: grep src/**/*.odin grep: cargo build --manifest-path "src/pkg/grep_lib/Cargo.toml" + +test: src/**/*.odin + odin test src/tests/ -all-packages -debug -out:bin/test_runner \ No newline at end of file diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index 8900a99..4782796 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -1073,7 +1073,7 @@ insert_content :: proc(buffer: ^FileBuffer, to_be_inserted: []u8, append_to_end: if !append_to_end { update_file_buffer_index_from_cursor(buffer); - move_cursor_right(buffer, false, amt = len(to_be_inserted)); + move_cursor_right(buffer, false, amt = len(to_be_inserted) - 1); } } diff --git a/src/main.odin b/src/main.odin index a162476..91ffc5f 100644 --- a/src/main.odin +++ b/src/main.odin @@ -320,7 +320,7 @@ main :: proc() { } else { buffer := core.new_virtual_file_buffer(context.allocator); - util.append_static_list(&state.panels, panels.make_file_buffer_panel(len(state.buffers))) + panels.open(&state, panels.make_file_buffer_panel(len(state.buffers))) runtime.append(&state.buffers, buffer); } diff --git a/src/tests/tests.odin b/src/tests/tests.odin new file mode 100644 index 0000000..c0cd4bd --- /dev/null +++ b/src/tests/tests.odin @@ -0,0 +1,622 @@ +package tests + +import "base:runtime" +import "core:testing" +import "core:fmt" +import "core:mem" +import "core:log" + +import "../core" +import "../panels" +import "../util" + +new_test_editor :: proc() -> core.State { + state := core.State { + ctx = context, + screen_width = 640, + screen_height = 480, + source_font_width = 8, + source_font_height = 16, + + panels = util.make_static_list(core.Panel, 128), + + directory = "test_directory", + }; + + return state +} + +buffer_to_string :: proc(buffer: ^core.FileBuffer) -> string { + if buffer == nil { + log.error("nil buffer") + } + + length := 0 + for content_slice in buffer.content_slices { + length += len(content_slice) + } + + buffer_contents := make([]u8, length) + + offset := 0 + for content_slice in buffer.content_slices { + for c in content_slice { + buffer_contents[offset] = c + offset += 1 + } + } + + return string(buffer_contents) +} + +ArtificialInput :: union { + ArtificialKey, + ArtificialTextInput, +} + +ArtificialKey :: struct { + is_down: bool, + key: core.Key, +} + +ArtificialTextInput :: struct { + text: string, +} + +press_key :: proc(key: core.Key) -> ArtificialKey { + return ArtificialKey { + is_down = true, + key = key + } +} + +release_key :: proc(key: core.Key) -> ArtificialKey { + return ArtificialKey { + is_down = false, + key = key + } +} + +input_text :: proc(text: string) -> ArtificialTextInput { + return ArtificialTextInput { + text = text + } +} + +setup_empty_buffer :: proc(state: ^core.State) { + buffer := core.new_virtual_file_buffer(context.allocator); + panels.open(state, panels.make_file_buffer_panel(len(state.buffers))) + runtime.append(&state.buffers, buffer); + + core.reset_input_map(state) +} + +run_inputs :: proc(state: ^core.State, inputs: []ArtificialInput) { + is_ctrl_pressed := false + + for input in inputs { + run_editor_frame(state, input, &is_ctrl_pressed) + } +} + +run_input_multiple :: proc(state: ^core.State, input: ArtificialInput, amount: int) { + is_ctrl_pressed := false + + for _ in 0.. bool { + log.info("key_action") + + if state.current_input_map != nil { + if control_key_pressed { + if action, exists := state.current_input_map.ctrl_key_actions[key]; exists { + switch value in action.action { + case core.EditorAction: + value(state); + return true; + case core.InputActions: + state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputActions) + return true; + } + } + } else { + if action, exists := state.current_input_map.key_actions[key]; exists { + switch value in action.action { + case core.EditorAction: + value(state); + return true; + case core.InputActions: + state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputActions) + return true; + } + } + } + } else { + log.info("current_input_map is null") + } + + return false + } + + switch state.mode { + case .Visual: fallthrough + case .Normal: { + log.info("it's normal/visual mode") + + if key, ok := input.(ArtificialKey); ok { + if key.is_down { + if key.key == .LCTRL { + is_ctrl_pressed^ = true; + } else { + run_key_action(state, is_ctrl_pressed^, key.key) + } + } else { + if key.key == .LCTRL { + is_ctrl_pressed^ = false; + } + } + } + } + case .Insert: { + log.info("it's insert mode") + + buffer := core.current_buffer(state); + + if key, ok := input.(ArtificialKey); ok { + if key.is_down { + // TODO: make this work properly + if true || !run_key_action(state, is_ctrl_pressed^, key.key) { + #partial switch key.key { + case .ESCAPE: { + state.mode = .Normal; + + core.insert_content(buffer, buffer.input_buffer[:]); + runtime.clear(&buffer.input_buffer); + } + case .TAB: { + // TODO: change this to insert a tab character + for _ in 0..<4 { + append(&buffer.input_buffer, ' '); + } + } + case .BACKSPACE: { + core.delete_content(buffer, 1); + } + case .ENTER: { + append(&buffer.input_buffer, '\n'); + } + } + } + } + } + + log.info("before text input") + if text_input, ok := input.(ArtificialTextInput); ok { + log.infof("attempting to append '%v' to buffer", text_input) + + for char in text_input.text { + if char < 1 { + break; + } + + 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)); + } + } + + if current_panel, ok := state.current_panel.?; ok { + if panel, ok := util.get(&state.panels, current_panel).?; ok && panel.on_buffer_input_proc != nil { + panel.on_buffer_input_proc(state, &panel.panel_state) + } + } + } + } + } + } + + // TODO: share this with the main application + do_insert_mode :: proc(state: ^core.State, buffer: ^core.FileBuffer) { + key := 0; + + for key > 0 { + if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 { + append(&buffer.input_buffer, u8(key)); + } + + key = 0; + } + } + + switch state.mode { + case .Normal: + // buffer := core.current_buffer(state); + // do_normal_mode(state, buffer); + case .Insert: + buffer := core.current_buffer(state); + do_insert_mode(state, buffer); + case .Visual: + // buffer := core.current_buffer(state); + // do_visual_mode(state, buffer); + } + + runtime.free_all(context.temp_allocator); +} diff --git a/todo.md b/todo.md index 7becd7e..9ec2eb3 100644 --- a/todo.md +++ b/todo.md @@ -2,22 +2,27 @@ - Fix crash when cursor is over a new-line - Fix jumping forward a word jumping past consecutive brackets - Odd scrolling behavior on small screen heights +- Closing the only panel crashes # Planned Features +- Testing Harness + - [x] Replay user inputs and assert buffer contents/changes + - [ ] Finish writing tests for all current user actions +- Vim-like Macro replays +- [ ] Simple File Search (vim /) +- [ ] Auto-indent +- Modify input system to allow for keybinds that take input + - Vim's f and F movement commands + - Vim's r command - Save/Load files - [x] Save - [ ] Load when changed on disk -- [ ] Simple File Search (vim /) -- [ ] Auto-indent -- Testing Harness - - [ ] Replay user inputs and assert buffer contents/changes - LSP Integration - [ ] Language Server Configurations - [ ] Diagnostics - [ ] In-line errors - [ ] Go-to Definition/ - [ ] Find references -- Vim-like Macro replays - Re-implement lost features from Plugins - [ ] Syntax Highlighting - [ ] Integrate tree-sitter @@ -45,9 +50,6 @@ - [ ] Change inside delimiter - Virtual Whitespace - Allow any-sized tabs -- Modify input system to allow for keybinds that take input - - Vim's f and F movement commands - - Vim's r command - Command Search and Execution - Refactor to remove generics added specifically for plugins - Palette based UI?