Add TestFrontend documentation and UndoSystem buffer reference update.
- Document `TestFrontend` for programmatic testing, including examples and usage details. - Add `UpdateBufferReference` to `UndoSystem` to support updating buffer associations.
This commit is contained in:
105
docs/TestFrontend.md
Normal file
105
docs/TestFrontend.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# TestFrontend - Headless Frontend for Testing
|
||||
|
||||
## Overview
|
||||
|
||||
`TestFrontend` is a headless implementation of the `Frontend` interface designed to facilitate programmatic testing of editor features. It allows you to queue commands and text input manually, execute them step-by-step, and inspect the editor/buffer state.
|
||||
|
||||
## Components
|
||||
|
||||
### TestInputHandler
|
||||
A programmable input handler that uses a queue-based system:
|
||||
- `QueueCommand(CommandId id, const std::string &arg = "", int count = 0)` - Queue a specific command
|
||||
- `QueueText(const std::string &text)` - Queue text for insertion (character by character)
|
||||
- `Poll(MappedInput &out)` - Returns queued commands one at a time
|
||||
- `IsEmpty()` - Check if the input queue is empty
|
||||
|
||||
### TestRenderer
|
||||
A minimal no-op renderer for testing:
|
||||
- `Draw(Editor &ed)` - No-op implementation, just increments draw counter
|
||||
- `GetDrawCount()` - Returns the number of times Draw() was called
|
||||
- `ResetDrawCount()` - Resets the draw counter
|
||||
|
||||
### TestFrontend
|
||||
The main frontend class that integrates TestInputHandler and TestRenderer:
|
||||
- `Init(Editor &ed)` - Initializes the frontend (sets editor dimensions to 24x80)
|
||||
- `Step(Editor &ed, bool &running)` - Processes one command from the queue and renders
|
||||
- `Shutdown()` - Cleanup (no-op for TestFrontend)
|
||||
- `Input()` - Access the TestInputHandler
|
||||
- `Renderer()` - Access the TestRenderer
|
||||
|
||||
## Usage Example
|
||||
|
||||
```cpp
|
||||
#include "Editor.h"
|
||||
#include "TestFrontend.h"
|
||||
#include "Command.h"
|
||||
|
||||
int main() {
|
||||
// IMPORTANT: Install default commands first!
|
||||
InstallDefaultCommands();
|
||||
|
||||
Editor editor;
|
||||
TestFrontend frontend;
|
||||
|
||||
// Initialize
|
||||
frontend.Init(editor);
|
||||
|
||||
// Setup: create a buffer (open a file or add buffer)
|
||||
std::string err;
|
||||
if (!editor.OpenFile("/tmp/test.txt", err)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Queue some commands
|
||||
frontend.Input().QueueText("Hello");
|
||||
frontend.Input().QueueCommand(CommandId::Newline);
|
||||
frontend.Input().QueueText("World");
|
||||
|
||||
// Execute queued commands
|
||||
bool running = true;
|
||||
while (!frontend.Input().IsEmpty() && running) {
|
||||
frontend.Step(editor, running);
|
||||
}
|
||||
|
||||
// Inspect results
|
||||
Buffer *buf = editor.CurrentBuffer();
|
||||
if (buf && buf->Rows().size() >= 2) {
|
||||
std::cout << "Line 1: " << std::string(buf->Rows()[0]) << "\n";
|
||||
std::cout << "Line 2: " << std::string(buf->Rows()[1]) << "\n";
|
||||
}
|
||||
|
||||
frontend.Shutdown();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Programmable Input**: Queue any sequence of commands or text programmatically
|
||||
2. **Step-by-Step Execution**: Run the editor one command at a time
|
||||
3. **State Inspection**: Access and verify editor/buffer state between commands
|
||||
4. **No UI Dependencies**: Headless operation, no terminal or GUI required
|
||||
5. **Integration Testing**: Test command sequences, undo/redo, multi-line editing, etc.
|
||||
|
||||
## Available Commands
|
||||
|
||||
All commands from `CommandId` enum can be queued, including:
|
||||
- `CommandId::InsertText` - Insert text (use `QueueText()` helper)
|
||||
- `CommandId::Newline` - Insert newline
|
||||
- `CommandId::Backspace` - Delete character before cursor
|
||||
- `CommandId::DeleteChar` - Delete character at cursor
|
||||
- `CommandId::MoveLeft`, `MoveRight`, `MoveUp`, `MoveDown` - Cursor movement
|
||||
- `CommandId::Undo`, `CommandId::Redo` - Undo/redo operations
|
||||
- `CommandId::Save`, `CommandId::Quit` - File operations
|
||||
- And many more (see Command.h)
|
||||
|
||||
## Integration
|
||||
|
||||
TestFrontend is built into both `kte` and `kge` executables as part of the common source files. You can create standalone test programs by linking against the same source files and ncurses.
|
||||
|
||||
## Notes
|
||||
|
||||
- Always call `InstallDefaultCommands()` before using any commands
|
||||
- Buffer must be initialized (via `OpenFile()` or `AddBuffer()`) before queuing edit commands
|
||||
- Undo/redo requires the buffer to have an UndoSystem attached
|
||||
- The test frontend sets editor dimensions to 24x80 by default
|
||||
12
docs/ke.md
12
docs/ke.md
@@ -14,7 +14,7 @@ someone's writeup of the process of writing a text editor from scratch.
|
||||
It has keybindings inspired by VDE (and the Wordstar family) and emacs;
|
||||
its spiritual parent is mg(1).
|
||||
|
||||
## KEYBINDINGS
|
||||
## KEYBINDINGS
|
||||
|
||||
K-command mode is entered using C-k. This is taken from
|
||||
Wordstar and just so happens to be blessed with starting with a most
|
||||
@@ -30,6 +30,8 @@ k-command mode can be exited with ESC or C-g.
|
||||
* C-k SPACE: Toggle the mark.
|
||||
* C-k -: If the mark is set, unindent the region.
|
||||
* C-k =: If the mark is set, indent the region.
|
||||
* C-k a: Set the mark at the beginning of the file, then jump to the end of
|
||||
the file.
|
||||
* C-k b: Switch to a buffer.
|
||||
* C-k c: Close the current buffer. If no other buffers are open, an empty
|
||||
buffer will be opened. To exit, use C-k q.
|
||||
@@ -39,16 +41,16 @@ k-command mode can be exited with ESC or C-g.
|
||||
* C-k f: Flush the kill ring.
|
||||
* C-k g: Go to a specific line.
|
||||
* C-k j: Jump to the mark.
|
||||
* C-k l: List the number of lines of code in a saved file.
|
||||
* C-k l: Reload the current buffer from disk.
|
||||
* C-k m: Run make(1), reporting success or failure.
|
||||
* C-k p: Switch to the next buffer.
|
||||
* C-k q: Exit the editor. If the file has unsaved changes, a warning will
|
||||
be printed; a second C-k q will exit.
|
||||
* C-k C-q: Immediately exit the editor.
|
||||
* C-k C-r: Reload the current buffer from disk.
|
||||
* C-k l: Reload the current buffer from disk.
|
||||
* C-k s: Save the file, prompting for a filename if needed.
|
||||
* C-k u: Undo.
|
||||
* C-k U: Redo changes (not implemented; marking this k-command as taken).
|
||||
* C-k r: Redo changes.
|
||||
* C-k x: save the file and exit. Also C-k C-x.
|
||||
* C-k y: Yank the kill ring.
|
||||
* C-k \\: Dump core.
|
||||
@@ -67,6 +69,8 @@ k-command mode can be exited with ESC or C-g.
|
||||
* ESC b: Move to the previous word.
|
||||
* ESC d: Delete the next word.
|
||||
* ESC f: Move to the next word.
|
||||
* ESC q: Reflow the paragraph to 72 columns or the value of the universal
|
||||
argument.
|
||||
* ESC w: Save the region (if the mark is set) to the kill ring.
|
||||
|
||||
## FIND
|
||||
|
||||
@@ -93,3 +93,36 @@ Owner pointers & file locations
|
||||
- Undo batching entry points: Command.cc (cmd_insert_text, cmd_backspace, cmd_delete_char, cmd_newline)
|
||||
|
||||
End of snapshot — safe to resume from here.
|
||||
|
||||
---
|
||||
|
||||
RESOLUTION (2025-11-30)
|
||||
|
||||
Root Cause Identified and Fixed
|
||||
The undo system failure was caused by incorrect timing of UndoSystem::Begin() and Append() calls relative to buffer modifications in Command.cc.
|
||||
|
||||
Problem:
|
||||
- In cmd_insert_text, cmd_backspace, cmd_delete_char, and cmd_newline, the undo recording (Begin/Append) was called BEFORE the actual buffer modification and cursor update.
|
||||
- UndoSystem::Begin() checks the current cursor position to determine if it can batch with the pending node.
|
||||
- For Insert type: Begin() checks if col == pending->col + pending->text.size()
|
||||
- For Delete type: Begin() checks if the cursor is at the expected position based on whether it's forward delete or backspace
|
||||
- When Begin/Append were called before cursor updates, the batching condition would fail on the second character because the cursor hadn't moved yet from the first insertion.
|
||||
- This caused each character to create a separate batch, but since commit() was never called between characters (only at k-prefix or undo), the pending node would be overwritten rather than committed, resulting in no undo history.
|
||||
|
||||
Fix Applied:
|
||||
- cmd_insert_text: Moved Begin/Append to AFTER buffer insertion (lines 854-856) and cursor update (line 857).
|
||||
- cmd_backspace: Moved Begin/Append to AFTER character deletion (lines 1024-1025) and cursor decrement (line 1026).
|
||||
- cmd_delete_char: Moved Begin/Append to AFTER character deletion (lines 1074-1076).
|
||||
- cmd_newline: Moved Begin/commit to AFTER line split (lines 956-966) and cursor update (lines 963-967).
|
||||
|
||||
Result:
|
||||
- Begin() now sees the correct cursor position after each edit, allowing proper batching of consecutive characters.
|
||||
- Typing "Hello" will now create a single pending batch with all 5 characters that can be undone as one unit.
|
||||
- The fix applies to both terminal (kte) and GUI (kge) builds.
|
||||
|
||||
Testing Recommendation:
|
||||
- Type several characters (e.g., "Hello")
|
||||
- Press C-k u to undo - the entire word should disappear
|
||||
- Press C-k U to redo - the word should reappear
|
||||
- Test backspace batching: type several characters, then backspace multiple times, then undo - should undo the backspace batch
|
||||
- Test delete batching similarly
|
||||
|
||||
Reference in New Issue
Block a user