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>
kte - Kyle's Text Editor
Vision
kte is a small, fast, and understandable text editor with a terminal-first UX and an optional ImGui GUI. It modernizes the original ke editor while preserving its familiar WordStar/VDE‑style command model and Emacs‑influenced ergonomics. The focus is on simplicity of design, excellent latency, and pragmatic features you can learn and keep in your head.
I am experimenting with using Jetbrains Junie to assist in development, largely as a way to learn the effective use of agentic coding. I worked with the agent by feeding it notes that I've been taking about text editors for the last few years, as well as the sources from the original ke editor that is all handwritten C.
Project Goals
- Keep the core minimal and readable; favor straightforward data structures (gap buffer, piece table) and incremental evolution.
- Round‑trip editing of large files with low latency in a terminal environment.
- Preserve ke keybindings and command semantics wherever sensible; smooth migration for ke users.
- Provide a clean separation between core model, input, and rendering so a GUI can grow independently of the TUI.
- Minimize dependencies; the GUI layer remains optional and isolated.
Keybindings
kte maintains ke’s command model while internals evolve. Highlights ( subject to refinement):
- K‑command prefix:
C-kenters k‑command mode; exit withESCorC-g. - Save/Exit:
C-k s(save),C-k xorC-k C-x(save and exit),C-k q(quit with confirm),C-k C-q(quit immediately). - Editing:
C-k d(kill to EOL),C-k C-d(kill line),C-w(kill region),C-y(yank),C-u(universal argument). - Navigation/Search:
C-s(incremental find),C-r(regex search),ESC f/b(word next/prev),ESC BACKSPACE(delete previous word). - Buffers/Files:
C-k e(open),C-k b/C-k p(switch),C-k c(close),C-k l(reload). - Misc:
C-l(refresh),C-g(cancel),C-k g(goto line).
See ke.md for the canonical ke reference retained for now.
Build and Run
Prerequisites: C++20 compiler, CMake, and ncurses development headers/libs.
Dependencies by platform
-
macOS (Homebrew)
- Terminal (default):
brew install ncurses
- Optional GUI (enable with
-DBUILD_GUI=ON):brew install sdl2 freetype- OpenGL is provided by the system framework on macOS; no package needed.
- Terminal (default):
-
Debian/Ubuntu
- Terminal (default):
sudo apt-get install -y libncurses5-dev libncursesw5-dev
-
Optional GUI (enable with
-DBUILD_GUI=ON):sudo apt-get install -y libsdl2-dev libfreetype6-dev mesa-common-dev- The
mesa-common-devpackage provides OpenGL headers/libs (libGL).
- The
- Terminal (default):
-
NixOS/Nix
- Terminal (default):
- Ad-hoc shell:
nix-shell -p cmake gcc ncurses
- Ad-hoc shell:
- Optional GUI (enable with
-DBUILD_GUI=ON):- Ad-hoc shell:
nix-shell -p cmake gcc ncurses SDL2 freetype libGL
- Ad-hoc shell:
- With flakes/devshell (example
flake.nixinputs not provided): includencursesfor TUI, andSDL2,freetype,libGLfor GUI in your devShell.
- Terminal (default):
Notes
- The GUI is OFF by default to keep SDL/OpenGL/Freetype optional. Enable
it by
configuring with
-DBUILD_GUI=ONand ensuring the GUI deps above are installed for your platform. - If you previously configured with GUI ON and want to disable it,
reconfigure
the build directory with
-DBUILD_GUI=OFF.
Example build:
cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build cmake-build-debug
Run:
./cmake-build-debug/kte [files]
If you configured the GUI, you can also run the GUI-first target (when
built as kge) or request the GUI from kte:
./cmake-build-debug/kte --gui [files]
# or if built/installed as a separate GUI target
./cmake-build-debug/kge [files]
GUI build example
To build with the optional GUI (after installing the GUI dependencies listed above):
cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug -DBUILD_GUI=ON
cmake --build cmake-build-debug
./cmake-build-debug/kte --gui [files]
Status
- This project is a hobby text editor meant to be my personal editor. I do not warrant its suitability for anyone else.
