feat: add syntax highlighting to text buffers

This commit is contained in:
Valdemar Lorenzen
2026-10-09 10:51:33 +02:00
parent 981011096e
commit 7c1e4c3ca0
7 changed files with 1359 additions and 54 deletions
+13 -2
View File
@@ -200,13 +200,14 @@ Leader bindings require Normal/Visual and the target's existing mode eligibility
## Module seams
One crate, six modules, no workspace or terminal-coordinate types in the editing backend:
One crate, seven modules, no workspace or terminal-coordinate types in the editing backend:
- `editor`: owns buffer lifecycle and per-buffer text, saved state, primary/selection set, modes, search, and undo/redo; session-wide clipboard policy, independent registers and owned-system fragment metadata, semantic native repeat, action metadata, command-palette and file-picker focus/dispatch, key notation, leader/count prefixes and binding resolution, modal paste policy, saving, command ownership, capabilities, and validated edit effects. Its low-level interface returns an `Invocation` when an external command is selected.
- `files`: owns the gitignore-aware directory/file catalog, authoritative OS paths, compact tree projection, query filtering, browse/search focus, expansion/selection restoration, navigation, and scan/error state shared by floating and docked presentations.
- `clipboard`: owns native plain-text provider selection and bounded subprocess I/O; it never computes selections or editing offsets.
- `syntax`: owns bundled grammar detection, semantic byte spans, multiline parser checkpoints, and changed-suffix highlighting; it has no terminal color/geometry or editing dependency.
- `session`: `EditorSession` owns the editor and its Ruby hosts together. It loads configuration once, registers eligible plugins before installing shared bindings, executes selected commands against the active buffer, and retires both host and command ownership on invocation or effect-validation failure.
- `tui`: translates terminal events to session input, renders read-only active-buffer state, keeps frontend-owned viewports by stable buffer ID, and advertises `text` and `styled_text` capabilities. It has no plugin lifecycle callback, host storage, or paste acceptance policy.
- `tui`: translates terminal events to session input, renders read-only active-buffer state, keeps frontend-owned viewports and syntax caches by stable buffer ID, and advertises `text` and `styled_text` capabilities. It maps semantic tokens to terminal colors and has no plugin lifecycle callback, host storage, or paste acceptance policy.
- `ruby`: evaluates configuration and runs isolated plugin hosts. Protocol, deadlines, and process cleanup stay inside this module.
- The executable selects paths and frontend capabilities, opens an editor session, and launches the TUI.
@@ -225,6 +226,16 @@ The TUI uses Ratatui constraint-based layout for a flexible buffer viewport, a o
The buffer viewport includes a fixed left gutter with right-aligned hybrid line numbers: the highlighted primary cursor line shows its one-based absolute number; every other line shows its distance from the primary. Secondary carets do not establish separate numbering origins. Gutter width follows the total line count plus one separator cell, including an empty final row after a trailing newline. Horizontal scrolling affects only text; search/selection highlights and carets never paint over the gutter. Rows beyond the buffer have no number. The gutter is hidden when it would leave no text column.
### Syntax highlighting
Buffers highlight automatically using [Syntect's bundled Sublime grammars](https://docs.rs/syntect/5.3.0/syntect/). Detection uses the supplied filename/extension and the buffer's own first line/shebang, without rereading the file from disk. Tested languages include Rust, Ruby, Python, JSON, shell, Makefile, and Markdown; other bundled grammars are available. Unknown and Plain Text buffers keep the terminal's normal foreground. The detected language appears in status, and saving under a different extension redetects it.
Colors use the terminal palette rather than a fixed background/theme: comments are dark gray, strings green, keywords magenta, numbers yellow, types/markup cyan, and functions light blue. Multiline/nested comments and strings retain parser scope across lines, including when their opening is above the viewport. Highlighting styles whole graphemes and expanded tab cells; partially clipped wide characters keep the existing blank-placeholder behavior. Search, Visual selections, and secondary carets paint afterward and retain their foreground/background precedence.
Each buffer has a frontend-owned cache. Text revisions distinguish edits/plugin effects/undo/redo from cursor and explorer changes; unchanged revision/path skips comparison and parsing. Changed text resumes at the first changed line's cached parser/scope state and reparses the suffix, preserving prefix work. Buffer switching restores its cache, and closing retires it. Real parser/scope errors clear syntax spans, retain readable/editable plain text, and report a status error. No custom lexer, runtime grammar downloads, or editor-offset changes are involved.
### Core feature registry
Built-in Ex commands register as the protected `core` feature in the same command registry used by plugins. External plugins cannot claim core status, replace existing commands, or disable core. User configuration can override any keybinding without replacing the core implementation. `:plugins` lists active features and frontend capabilities.
A feature declares all capabilities it needs. Registration is atomic: if any requirement is unavailable or any command conflicts, none of its commands are installed. For example, `Vamp.plugin "canvas", requires: ["graphics"]` will be refused by this TUI. Capability names are extensible strings, not a terminal-vs-GUI switch. Capability declarations express compatibility; they are not security permissions.