Commit Graph
15 Commits
Author SHA1 Message Date
Claude 3be85ef4ad Review fixes: exception-safe AdoptOriginal; Qt draws cells straddling the left edge
AdoptOriginal cleared the table before allocating its piece list, so a
bad_alloc left a live buffer with the file's size and no pieces;
OpenFromFile no longer caught it either. The piece list is now built
before anything is touched (the rest cannot throw) and OpenFromFile
again reports "File too large to load" with the buffer unchanged.

Qt skipped every character starting left of the horizontal scroll
offset, so a tab or wide character straddling it left blank columns
while selection and cursor still used the real ones. Such a character
is now drawn at its own column and clipped by the viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 12:19:59 -07:00
Claude d490245f30 Highlighter engine returns cached spans by reference; drop unneeded mutexes
HighlighterEngine::GetLine copied the span vector on every call (twice
on a miss) and did a dynamic_cast per call; it now returns the cached
entry (valid until the next call) and dispatches on Stateful().

PieceTable and HighlighterEngine took a mutex in their read paths while
their writes took none, and PieceTable.h claimed concurrent reads were
safe; they are not (reads update caches). Every Buffer is used from the
main thread only (the swap writer thread receives copies; the regex
large-stack thread runs while its caller waits), so the mutexes are
removed and the headers say so. ASan and the threaded tests under TSan
are clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 12:19:59 -07:00
Claude 95bcf8ef09 PieceTable: binary-search piece lookup; lazy line-start shift
Each edit shifted every later line start in the line index, O(lines) per
keystroke (0.5 ms near the top of a 1M-line file). The shift is now kept
pending ("from entry k, add d") and applied on read; further edits at the
same line only adjust it, and it is folded in when an edit lands
elsewhere.

locate() walked the piece list for every lookup (two per rendered line
per frame on an edited buffer); it now binary-searches prefix sums
rebuilt once per modification (keyed on version_, which every mutation
bumps; AdoptOriginal now bumps it after installing its piece).

Typing on line 10 of a 1M-line file with a frame's worth of line reads
per keystroke: 0.52 ms -> 0.017 ms. A new randomized test checks every
read path against a string model with consolidation forced; it fails
under each of three deliberate breakages of the new bookkeeping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 12:19:59 -07:00
Claude 7dae9cc305 Load into the piece table's original store; save and copy without materializing
A loaded file's bytes are adopted as the piece table's original storage
instead of being appended to the add buffer: the first keystroke no
longer reallocates and copies the whole file (140 ms at 200 MB), and the
read buffer is not copied again.

Save writes the pieces in order instead of materializing the buffer (a
second full copy that then stayed resident). Buffer::Bytes() assembles a
copy from the pieces and replaces four hand-rolled copies (swap
snapshots, recovery comparison, Command.cc's per-row join, the tests'
accessor). A single-piece table (an unedited file) is viewed in place;
GetLineView serves a line from its piece when it lies within one, so it
no longer copies the whole buffer after every edit. Code that needed the
whole text contiguous says so with ContentView().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 12:19:59 -07:00
Claude 306a337516 PieceTable: newline test from the line index, memchr rebuild; remove dead code
GetLine and LineColToByteOffset fetched each line's last byte (a piece
lookup, an allocation and a cache write) to see whether it was a newline;
the line index already says so: every line but the last ends at one.
LineColToByteOffset runs on every insert. RebuildLineIndex scans with
memchr instead of byte by byte.

Removed, having no callers: OptimizedSearch (built only into its own
test), UndoNodePool.h, SwapManager::RecordSplit/RecordJoin (replay still
reads SPLIT/JOIN), PieceTable::Reserve/Capacity and its capacity
constructor, Buffer's write-only render column, Editor's mode, no-kill,
dirtyex and repeatable flags and the UCount getter, and
RetryPolicy::Conservative.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 12:19:59 -07:00
Claude 404b983009 Undo: hold large texts as storage spans; byte budget with pruning
- 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
2026-09-24 09:16:04 -07:00
Claude 4c42761fc0 Perf: update the line index in place; renderers read only visible rows
- PieceTable::Insert/Delete adjust a valid line index directly (shift
  later line starts, add or drop starts for inserted or deleted
  newlines) instead of invalidating it, which forced a byte-by-byte
  rescan of the whole buffer on the next line query. On a 38 MB,
  1M-line buffer, insert + line query drops from 14.7 ms to 0.28 ms.
- The terminal and ImGui renderers fetch only the rows they draw
  (Nrows + GetLineString) instead of Buffer::Rows(), which rebuilt a
  string for every line of the file on the first frame after each edit.
  Qt's line-count uses switch to Nrows().

ImGuiRenderer.cc was syntax-checked with g++ and clang++ against the
vendored ImGui headers; the GUI targets could not be built here (no
SDL2 or Qt6).

Audit findings P1, P2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
2026-09-24 09:16:04 -07:00
kyle 8ec0d6ac41 Add benchmarks, migration tests, and dev guide
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.
2026-02-17 16:08:23 -08:00
kyle d2d155f211 Fix data race.
+ Add thread-safety with mutexes in `PieceTable` and `Buffer`
+ Bump version to 1.5.9
2026-01-28 01:03:58 -08:00
kyle f6f0c11be4 Add PieceTable-based buffer tests and improvements for file I/O and editing.
- Introduced comprehensive tests:
  - `test_buffer_open_nonexistent_save.cc`: Save after opening a non-existent file.
  - `test_buffer_save.cc`: Save buffer contents to disk.
  - `test_buffer_save_existing.cc`: Save after opening existing files.
- Implemented `PieceTable::WriteToStream()` to directly stream content without full materialization.
- Updated `Buffer::Save` and `Buffer::SaveAs` to use efficient streaming via `PieceTable`.
- Enhanced editing commands (`Insert`, `Delete`, `Replace`, etc.) to use PieceTable APIs, ensuring proper undo and save functionality.
2025-12-07 00:30:11 -08:00
kyle 3f4c60d311 Add detailed migration plan for PieceTable-based buffer architecture.
- 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.
2025-12-05 20:53:04 -08:00
kyle afb6888c31 Introduce PieceTable-based buffer backend (Phase 1)
- 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.
2025-12-05 20:53:04 -08:00
kyle 45b2b88623 Code quality, safety, stability, and cleanups.
- 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.
2025-12-03 14:02:54 -08:00
kyle e4cd4877cc Introduce file picker and GUI configuration with enhancements.
- 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.
2025-11-30 18:35:12 -08:00
kyle 46c7a4e8a2 Initial import. 2025-11-29 16:57:01 -08:00