- Undo texts of 4 KiB or more (Insert/Paste from the buffer, Delete from
the spans the piece table remembers for its last large deletion) are
held as TextSpan references into the piece table's append-only
storage, after checking they hold exactly the recorded bytes. Undo and
redo insert them with Buffer::insert_spans, which adds pieces over the
stored bytes instead of copying them into storage again (the journal
still gets the bytes, as one record). A replace-all session on a 26 MB
file peaks at 181 MB instead of 332 MB; undo/redo cycles of a 6.6 MB
kill no longer grow memory by 6.6 MB each.
- UndoSystem keeps a byte budget (default max(64 MiB, twice the buffer
size); SetByteBudget). Over it, history is pruned to 75% of the budget:
branches off the current path, then the oldest edits (an undo group
goes whole, and never the current state's group), then the redo line.
When the saved state is dropped the buffer stays modified until saved.
- replace_all_bytes clears undo history, which was recorded against (and
may reference the storage of) the previous content.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
Undo/redo:
- cmd_newline recorded the undo node's position after the cursor moved past
the split point, so undoing Enter joined the wrong pair of lines.
- Backspace/Delete line-joins recorded UndoType::Newline, whose apply()
semantics are inverted for a join (forward=split); add UndoType::JoinLines
with correct forward/backward behavior.
- Regex replace-all, indent/unindent region, kill-to-eol, kill-line,
kill-region, and delete-word-prev/next mutated the buffer via the
no-undo-recording raw APIs; they now record undo, grouped atomically via a
shared UndoGroupGuard.
- Plain-text replace-all with an empty replacement advanced the scan
position one character too far, skipping adjacent/overlapping matches.
Crash recovery / memory safety:
- Buffer's move ctor/assignment never carried over swap_rec_,
on_disk_identity_, or the visual-line-mode fields, so the crash-recovery
journal silently stopped tracking a buffer whenever Editor's
std::vector<Buffer> reallocated or shifted (e.g. opening/closing files).
Added SwapManager::Rehome() plus Flush()-before-mutate in
Editor::AddBuffer/CloseBuffer so the journal's Buffer* key and the
background writer thread never reference a stale address.
- UndoTree leaked its entire node graph (including all edit text) on every
buffer close/reload; added ~UndoTree() to free it.
- main.cc didn't restore the terminal if an exception escaped the run loop;
added an RAII guard around Frontend::Shutdown().
Input handling:
- Terminal frontend used getch() instead of get_wch(), silently dropping
non-ASCII keyboard input despite linking wide-char ncurses.
- KKeymap's C-k lookup switches on an already-lowercased key, making
`case 'E'` unreachable dead code; C-k Shift-E silently aliased to C-k e.
- ImGui file picker wasn't modal: keystrokes typed while it was open still
reached the buffer underneath as edit commands, and Escape didn't close it.
- Gated ImGuiInputHandler's unconditional fprintf/fflush diagnostics behind
an IMGUI_IH_DEBUG macro, matching QtInputHandler's existing convention.
Syntax highlighting:
- Go/Rust/SQL highlighters weren't stateful, so multi-line /* */ comments
mis-highlighted as code starting on the second line.
- Python's triple-quoted-string handler didn't re-scan the remainder of a
line after a string closed mid-line, missing a same-line reopen.
- HighlighterEngine's state_last_contig_ field was declared but unused,
forcing an O(n) scan of state_cache_ on every stateful lookup; wired it up
as a real fast path and fixed InvalidateFrom() to keep it in sync.
- kge's :syntax off was silently undone the next frame because
apply_syntax_to_buffer() re-applied the config default unconditionally
every frame; added a user-override flag mirroring the existing
edit-mode-detection guard.
Added regression tests for all of the above (test_undo.cc, test_kkeymap.cc,
test_command_semantics.cc, test_search_replace_flow.cc, and a new
test_syntax_highlighting.cc). Full suite (163 tests) passes, including under
AddressSanitizer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Added `test_reflow_undo.cc` to validate undo/redo workflows for reflow operations.
- Introduced `UndoType::InsertRow` in `UndoSystem` for tracking row insertion changes in undo history.
- Updated `UndoNode.h` and `UndoSystem.cc` to support row insertion as a standalone undo step.
- Enhanced reflow paragraph functionality to properly record undo/redo actions for both row deletion and insertion.
- Enabled legacy/extended undo tests in `test_undo.cc` for comprehensive validation.
- Updated `CMakeLists.txt` to include new test file in the build target.
- Introduced SwapManager for sidecar journaling of buffer mutations, with a safe recovery mechanism.
- Added group undo/redo functionality, allowing atomic grouping of related edits.
- Implemented `SwapRecorder` and integrated it as a callback interface for mutations.
- Added unit tests for swap journaling (save/load/replay) and undo grouping.
- Refactored undo to support group tracking and ID management.
- Updated CMake to include the new tests and swap journaling logic.
- Added branching model for undo/redo, enabling multiple redo paths and branch selection.
- Updated `UndoNode` to include `parent` and refined hierarchical navigation.
- Extended `UndoSystem` with branching logic for redo operations, supporting sibling branch selection.
- Overhauled tests to validate branching behavior and tree invariants.
- Refined editor command logic for undo/redo with repeat counts and branch selection.
- Enabled test-only introspection hooks for undo tree validation.
- Updated CMake to include test definitions (`KTE_TESTS` flag).
- Replace header include guards with `#pragma once` and perform minor optimizations.
- Replaced traditional include guards with `#pragma once` for simplicity and to reduce boilerplate in all headers.
- Improved CLI line number handling with clamping and error messaging.
- Enhanced `chdir` error handling for macOS GUI builds.
- Removed redundant logic for GUI builds.
- Adjusted font constructor and registry to handle `const` data pointers consistently.
- Add visual file picker for GUI with toggle support.
- Introduce `GUIConfig` class for loading GUI settings from configuration file.
- Refactor window initialization to support dynamic sizing based on configuration.
- Add macOS-specific handling for fullscreen behavior.
- Improve header inclusion order and minor code cleanup.