run_search() replaces collecting every match in the buffer (and building
the full list again for every next/previous):
- Plain search finds forward from the search origin (then the current
match) in the materialized text, or backwards through 1 MiB blocks
searched forward (string_view::rfind scans a byte at a time: 511 ms
per previous-match on 105 MB, now 6 ms). Regex search walks lines
from the position with early exit.
- The i/N count stops at 1,000 matches, and for regex after 4 MiB; the
status says 'N+' when capped.
- The first match is the first at or after the cursor (it was the first
in the buffer). Extending the query keeps the current match while it
still matches. Left/Right also step through matches in the regex
replace prompt; Up/Down in a regex prompt use the regex.
On a 26 MB file: plain keystroke 30 ms -> 0.1 ms, next 24 -> 3 ms, regex
keystroke 434 -> 1.6 ms, regex next 420 -> 66 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
- 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
- main: when the environment's locale is not UTF-8 (LANG unset, C/POSIX),
switch LC_CTYPE to a UTF-8 locale so ncurses delivers and draws
multibyte characters instead of dropping them.
- Buffer::Id(): a process-unique id that survives moves and is fresh for
copies. The terminal renderer's skip and cursor caches and the reload
confirmation now key on it rather than the buffer's address, which is
reused after a buffer is closed.
- The reload confirmation is cancelled by any other command, like the
quit confirmation.
- TerminalFrontend redraws when a deferred open starts a prompt or sets a
status (e.g. the swap recovery question), not only when a buffer opens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
- Redoing an insert and undoing a delete put the cursor at col + size
on the start row even when the text spans lines, i.e. past the end of
the line. Later edits and their undo records then went to the wrong
place (a join recorded at column 2 of 'a' re-split the text wrongly)
and delete-word-prev read past the line (heap-buffer-overflow under
ASan). The cursor now goes after the text's last newline, and undo and
redo clamp it to the buffer.
- A recovered buffer stays modified in every undo state
(mark_base_unsaved): one edit and its undo made it look clean, and
closing it then discarded the recovered text and its journal.
- Save-and-quit records the saved undo state.
- Visual-line selections are clamped to the buffer when read; the
visual newline counts the cursor's own line only if it was selected.
A stale selection put the cursor rows past the end, where typed text
could not be undone.
- The yank size cap accounts for visual-line yanks (once per line).
- After an exception escapes a command, open undo groups are closed so
later edits do not join them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
- kte refuses to start when stdin is not a terminal: piped bytes were
executed as editor commands (C-k s plus a path writes a file), and
after EOF the loop spun at full CPU with no way to quit.
- The terminal frontend handles all pending input before drawing (a
paste used to cost a frame and a full redraw per character) and
redraws only after input, a resize or deferred opens, else once a
second. The renderer caches, per row and buffer version, where the
horizontally scrolled part of a line starts and the cursor's render
column, instead of rescanning the line every frame. Idle CPU with the
cursor at the end of a 20 MB line: one full core -> ~1%.
- Buffers with a virtual name (+HELP+) are not journaled and saving one
asks for a file name; it used to write ./+HELP+ and journal under
that path.
- Yank with a repeat count is capped at 256 MiB in total instead of
reserving count x size bytes (C-u 1000000 C-y of a large kill).
- Remove an overload clang reports as unused (-Werror on macOS).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
Every edit bumped the buffer version (through SetDirty) and called
InvalidateFrom(0), so all cached spans and line states were discarded
and stateful highlighters re-lexed from row 0 to the viewport on each
keystroke.
Buffer mutators now report the row they changed (edited_at_), which
bumps the version and tells the engine to drop only rows from there
down; a row's highlighting depends only on rows above it. A version
change that arrives without that notice still clears everything, so a
missed path cannot show stale spans. The engine keeps states as a
contiguous vector and caches spans for every row it lexes. SetDirty no
longer touches the version: the edits themselves do, including undo
and redo back to the saved state (D9 still holds; its test now checks
the highlighting itself).
Typing near the end of a 100k-line C++ file: 46.3 ms -> 0.14 ms of
highlighting work per keystroke.
Audit finding P3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
SetDirty(false) did not bump the version, so undo back to the saved
state (and reload, and replace_all_bytes) left the highlighter serving
spans for the old text. Reloading a file deleted on disk now also
clears undo history recorded against the old content.
Audit finding D9.
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>
apply_syntax_to_buffer() was called every frame and unconditionally reset
edit mode from the file extension, making it impossible to toggle out of
writing mode for .txt/.md files. Add edit_mode_detected_ flag to Buffer so
auto-detection runs once per buffer. Writing mode now also disables syntax
highlighting as intended. Propagate edit_mode_ through Buffer copy/move ops.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add three proportional serif fonts: Crimson Pro, ET Book, Spectral
- Fix text rendering for variable-width fonts: selection, cursor,
mouse click mapping, search highlights, and syntax-colored text
now use pixel-accurate measurement via ImGui::CalcTextSize()
- Add per-buffer edit mode (code/writing) with auto-detection from
file extension (.txt, .md, .rst, .org, .tex default to writing)
- Add C-k m keybinding and :mode command to toggle edit modes
- Switch config format from INI to TOML (kge.toml), with legacy
INI fallback; vendor toml++ v3.4.0
- New config keys: font.code and font.writing for per-mode defaults
- Add font tab completion for ImGui builds
- Add tab completion for :mode command
- Update help text, themes.md, and add CONFIG.md
- Bump version to 1.10.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add benchmarks for core operations, migration edge case tests, improved
buffer I/O tests, and developer guide
- Introduced `test_benchmarks.cc` for performance benchmarking of key
operations in `PieceTable` and `Buffer`, including syntax highlighting
and iteration patterns.
- Added `test_migration_coverage.cc` to provide comprehensive tests for
migration of `Buffer::Rows()` to `PieceTable` APIs, with edge cases,
boundary handling, and consistency checks.
- Enhanced `test_buffer_io.cc` with additional cases for save/load
workflows, file handling, and better integration with the core API.
- Documented architectural details and core concepts in a new
`DEVELOPER_GUIDE.md`. Highlighted design principles, code
organization, and contribution workflows.
- Added `test_reflow_indented_bullets.cc` to verify correct reflow handling for indented bullet points.
- Enhanced undo system with additional tests for cursor adjacency, explicit grouping, branching, newline independence, and dirty-state tracking.
- Introduced external modification detection for files and required confirmation before overwrites.
- Refactored buffer save logic to use atomic writes and track on-disk identity.
- Updated CMake to include new test files and bumped version to 1.6.4.
- Added detailed journaling system (`SwapManager`) for crash recovery, including edit recording and replay.
- Integrated recovery prompts for handling swap files during file open flows.
- Implemented swap file cleanup, checkpointing, and compaction mechanisms.
- Added extensive unit tests for swap-related behaviors such as recovery prompts, file pruning, and corruption handling.
- Updated CMake to include new test files.
- 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.
- Introduced visual-line mode for multi-line selection and edits.
- Implemented commands, rendering, and keyboard shortcuts.
- Added tests for broadcast operations in visual-line mode.
- Created `piece-table-migration.md` outlining the steps to transition from GapBuffer to a unified PieceTable architecture.
- Included phased approach: extending PieceTable, Buffer adapter layer, command updates, and renderer changes.
- Detailed API changes, file updates, testing strategy, risk assessment, and timeline for each migration phase.
- Document serves as a reference for architecture goals and implementation details.
- Deleted `GapBuffer` class and its API implementations.
- Removed `AppendBuffer` selector and conditional `KTE_USE_PIECE_TABLE` macros.
- Eliminated legacy support in buffer APIs, file I/O, benchmarks, and correctness tests.
- Updated guidelines and comments to reflect PieceTable as the default and only buffer backend.
- Added `PieceTable` class for efficient text manipulation and implemented core editing APIs (`Insert`, `Delete`, `Find`, etc.).
- Integrated `PieceTable` into `Buffer` class with an adapter for rows caching.
- Enabled seamless switching between legacy row-based and new PieceTable-backed editing via `KTE_USE_BUFFER_PIECE_TABLE`.
- Updated file I/O, line-based queries, and cursor operations to support PieceTable-based storage.
- Lazy rebuilding of line index and improved management of edit state for performance.
- 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.
- Introduced `HighlighterRegistry` with support for multiple language highlighters (e.g., JSON, Markdown, Python).
- Added `JsonHighlighter` implementation for basic JSON syntax highlighting.
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
- Expanded help text and command documentation with detailed keybinding descriptions.
- Added theme customization support to GUIConfig (Nord default, light/dark variants).
- Adjusted for consistent indentation and debug instrumentation in undo system.
- Enhanced test cases for multi-line, UTF-8, and branching scenarios.
- 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.
- Document `TestFrontend` for programmatic testing, including examples and usage details.
- Add `UpdateBufferReference` to `UndoSystem` to support updating buffer associations.