feat: add literal buffer search and match navigation
This commit is contained in:
@@ -22,22 +22,28 @@ Without a file, vamp opens an unnamed buffer. A nonexistent file opens an empty
|
||||
| Normal | `h`/`j`/`k`/`l` or arrows; `0`/`$` or Home/End |
|
||||
| Normal | `J` (Shift+j) join the current line with the next line |
|
||||
| Normal | `x`/Delete delete; `u` undo; `U` (Shift+u) redo; `:` enter command mode |
|
||||
| Normal | `/` opens search; `n` finds the next match; `N` (Shift+n) finds the previous match |
|
||||
| Insert | Type, Enter, Tab, Backspace/Delete, arrows, Home/End; Esc returns to normal |
|
||||
| Command | Enter executes; Backspace edits; Esc cancels |
|
||||
| Search | Type a literal query; Enter finds the next match; Backspace edits; Esc cancels |
|
||||
|
||||
`J` removes the next line's leading spaces/tabs and preserves existing trailing whitespace. It normally adds one space, two after `.`, `?`, or `!` (Vim's default), and none before `)` or when either side is empty. The cursor lands at the join point; the join is one undo action. On the last line, `J` does nothing.
|
||||
|
||||
Search is case-sensitive and single-line, with no regular expressions or Unicode normalization. Matches start and end on whole grapheme boundaries, including combining marks and emoji. Next/previous navigation skips the current position and wraps through the buffer; a sole match wraps back to itself. The active match is highlighted yellow.
|
||||
|
||||
Submitting an empty search repeats the last query. Esc cancels without moving the cursor or replacing the previous query/match. A missing match reports the query, clears the highlight, and leaves the cursor unchanged; searching without a previous query also leaves the cursor unchanged. Search never edits text or adds undo history. Buffer edits and undo/redo clear the highlight but retain the query, so subsequent navigation searches the current text again.
|
||||
|
||||
Commands: `:w [path]`, `:q`, `:q!`, `:wq [path]`, `:plugins`, and `:plugin-command [arguments]`.
|
||||
|
||||
`:q` refuses unsaved changes. `:wq` quits only after a successful save. Saves use a same-directory temporary file and rename, preserve existing permissions, and reject target symlinks. UTF-8 text is edited by extended grapheme; tabs render at four-cell stops. LF and trailing-newline structure are preserved; CRLF and CR are normalized to LF. Binary/terminal-control-containing text is rejected. Bracketed paste inserts only in Insert mode and never executes pasted commands in Normal mode.
|
||||
|
||||
Paste is backend input: it is ignored in Normal and Command modes, and inserts literal text in Insert mode, with CRLF/CR normalization and a 4 MiB limit. Pasted text is one undo action, separate from typing before and after it. Rejected paste reports an error without changing text, cursor, mode, or pending insert history.
|
||||
Paste is backend input: it is ignored in Normal, Command, and Search modes, and inserts literal text in Insert mode, with CRLF/CR normalization and a 4 MiB limit. Pasted text is one undo action, separate from typing before and after it. Rejected paste reports an error without changing text, cursor, mode, or pending insert history.
|
||||
|
||||
## Module seams
|
||||
|
||||
One crate, four modules, no workspace or frontend-specific types in the editing backend:
|
||||
|
||||
- `editor`: owns buffer, cursor, modes, key notation and binding resolution, modal paste policy, undo/redo, saving, command ownership, capabilities, and validated edit effects. Its low-level interface returns an `Invocation` when an external command is selected.
|
||||
- `editor`: owns buffer, cursor, modes, search state/navigation and grapheme-coordinate match presentation, key notation and binding resolution, modal paste policy, undo/redo, saving, command ownership, capabilities, and validated edit effects. Its low-level interface returns an `Invocation` when an external command is selected.
|
||||
- `session`: `EditorSession` owns the editor and its Ruby hosts together. It loads configuration, registers eligible plugins before installing bindings, executes selected commands, and retires both host and command ownership on invocation or effect-validation failure.
|
||||
- `tui`: translates terminal events to session input, renders read-only editor state, and advertises `text` and `styled_text` capabilities. It 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.
|
||||
@@ -62,10 +68,12 @@ Vamp.configure do |config|
|
||||
config.bind "ctrl+r", config.core.redo
|
||||
config.bind "shift+j", config.core.join_lines
|
||||
config.bind "ctrl+c", config.core.normal, mode: config.modes.insert
|
||||
config.bind "ctrl+f", config.core.search
|
||||
config.bind "ctrl+c", config.core.normal, mode: config.modes.search
|
||||
end
|
||||
```
|
||||
|
||||
Plugin paths resolve relative to the config file, not the shell's working directory. `config.bind key, target, mode: config.modes.normal` overrides one key; available modes are `config.modes.normal`, `config.modes.insert`, and `config.modes.command`. Omitting `mode` selects Normal.
|
||||
Plugin paths resolve relative to the config file, not the shell's working directory. `config.bind key, target, mode: config.modes.normal` overrides one key; available modes are `config.modes.normal`, `config.modes.insert`, `config.modes.command`, and `config.modes.search`. Omitting `mode` selects Normal.
|
||||
|
||||
`config.core` exposes the built-in actions below as immutable `Action` values; `config.modes` exposes immutable `Mode` values. Misspelled methods fail during configuration evaluation, and `bind` rejects the wrong value type. Built-in action strings and mode strings are not accepted. Registered Ex/plugin commands still use exact string names (`"w"`, `"greet"`, etc., without `:` or arguments) and can only be bound in Normal mode. Ruby provides runtime checks, not compile-time checking; the editor still validates action/mode compatibility.
|
||||
|
||||
@@ -81,10 +89,11 @@ Terminals share `ctrl+h` with `backspace`, `ctrl+i` with `tab`, and `ctrl+j`/`ct
|
||||
| --- | --- |
|
||||
| Normal | `insert`, `insert_first_nonblank`, `append`, `append_line_end`, `open_below`, `open_above` |
|
||||
| Normal | `join_lines`, `undo`, `redo`, `command` |
|
||||
| Normal | `search`, `search_next`, `search_previous` |
|
||||
| Normal, Insert | `left`, `down`, `up`, `right`, `line_start`, `line_end`, `delete` |
|
||||
| Insert, Command | `normal`, `backspace` |
|
||||
| Insert, Command, Search | `normal`, `backspace` |
|
||||
| Insert | `newline` |
|
||||
| Command | `submit` |
|
||||
| Command, Search | `submit` |
|
||||
|
||||
Adding a binding keeps the original key. Pass `nil` to disable a key, including literal typing in Insert/Command mode:
|
||||
|
||||
@@ -99,7 +108,7 @@ end
|
||||
|
||||
The last valid declaration for a key/mode wins. Invalid keys, incompatible action/mode pairs, or unknown command names leave that binding unchanged and appear in the status message; other valid bindings still load. Ruby syntax errors, unknown methods, and type errors disable the entire configuration and fall back to defaults. One plugin's load failure does not prevent other plugins loading. Disabling a plugin removes its bindings, without restoring any default keys they replaced.
|
||||
|
||||
Bindings are resolved inside the editor module, not the terminal adapter. Bracketed paste remains literal and bypasses keybindings. If configuration disables your keys for leaving Insert/Command mode or entering commands, restart with `--no-config` to restore defaults.
|
||||
Bindings are resolved inside the editor module, not the terminal adapter. Bracketed paste remains literal and bypasses keybindings. If configuration disables your keys for leaving Insert/Command/Search mode or entering commands, restart with `--no-config` to restore defaults.
|
||||
|
||||
## Ruby plugins
|
||||
|
||||
@@ -129,7 +138,7 @@ JSON uses Ruby's standard library. Protocol Buffers is not installed or required
|
||||
|
||||
## Current limits
|
||||
|
||||
- One text buffer; Normal, Insert, and Command modes. No Visual mode, Vim counts/operators, splits, search, LSP, or graphical frontend.
|
||||
- One text buffer; Normal, Insert, Command, and Search modes. No Visual mode, Vim counts/operators, splits, regex/multiline search, LSP, or graphical frontend.
|
||||
- Undo uses full-buffer snapshots and movement scans the current line. This favors straightforward correctness over large-file performance.
|
||||
- Plugin invocation is synchronous and may pause input for up to 500 ms before a host timeout. Config/plugin startup has a 2-second deadline per host. Buffer context must fit within a 4 MiB JSON request; a too-large request disables that plugin, not editing.
|
||||
- Disabling a plugin removes its commands and bindings until the next launch. There is no hot reload.
|
||||
|
||||
Reference in New Issue
Block a user