# Vim Mode Gaps
This document compares Alma's current normal and visual mode behavior with the
same families of behavior in Neovim. It is intentionally implementation-facing:
the goal is not full Vim compatibility all at once, but a clear map of which
gaps matter and how to close them without weakening Alma's architecture.
The comparison was made from the current working tree and a few representative
local Neovim checks with `nvim --headless --clean -n`. Alma already has a good
core for movement, search, leader actions, command-line write/quit, viewport
placement, and visual selection movement. The major discrepancy is that Alma can
navigate and select, but cannot yet operate on text in the way normal and visual
mode users expect.
## Compatibility Snapshot
| Normal motions | Supports `h/j/k/l`, word and WORD motions, `e/E/ge/gE`, columns, line addresses, pages, paragraphs, `f/F/t/T`, `;`, `,` | Broadly comparable for this subset | Validate edge cases and keep adding golden coverage |
| Normal operators | `Operator::{Delete,Yank,Change}` exists in the grammar type, but normal dispatch ignores operators | `dw`, `d$`, `yy`, `cw`, counts, and linewise/charwise targets mutate or yank text | High-priority functional gap |
| Insert/change entry | No insert mode, append, open-line, or change-to-insert transition | `i/a/I/A/o/O`, `c{motion}`, `cc`, visual `c` enter insert mode | High-priority mode gap |
| Visual movement | Supports characterwise and linewise visual selection, toggles, `o`/`O`, and motion reuse | Comparable for basic selection movement | Needs more parity fixtures around inclusive selection boundaries |
| Visual operators | Visual selections do not support `d`, `y`, `c`, `x`, `>`, `<`, `~`, etc. | Operators act on selected text and usually return to normal or insert mode | High-priority functional gap |
| Text objects | Not implemented | `iw`, `aw`, `ip`, `ap`, quotes/brackets, etc. work in normal operators and visual mode | Important after basic operators |
| Registers | Not implemented | Deletes/yanks/changes update registers; paste reads registers | Needed for credible operator support |
| Paste/undo/redo | Not implemented | `p/P`, `u`, `<C-r>` are central normal-mode editing commands | Important editing foundation |
| Search | Supports `/`, `?`, `n`, `N`, status errors, highlighting | Similar subset in Neovim | Needs option parity and richer patterns only if desired |
| Ex commands | Supports write/quit family | Neovim supports a huge command language | Keep deliberately narrow for now |
Representative Neovim checks:
- `normal! 0dw` on `one two` leaves `two`.
- `normal! 0yw` yanks `one ` into register `0`.
- `normal! 0vwd` on `one two` leaves `wo` because the visual range is inclusive.
- `normal! 0viwy` yanks `one` through a text object.
Those examples are not implemented by Alma today. They show the shape of the
missing model: motions are not enough; Alma needs operator targets, selection
kinds, registers, and mode transitions.
## Highest-Value Gaps
1. Implement operator execution before adding many more motions.
Alma already has a typed `Operator` enum, but `NormalState::apply_command`
currently treats `NormalCommand::Operator { .. }` as a no-op. The next natural
step is to turn operator commands into typed edit effects. Start with `d`, `y`,
and `c`, because they force the right abstractions: range computation, register
writes, buffer mutation, and optional insert-mode transition.
2. Add a first-class edit target model.
Do not let every operator compute byte ranges ad hoc. Introduce a pure target
type that can represent at least characterwise, linewise, and blockwise-future
targets. Normal operators should resolve a motion into one of these targets;
visual mode should resolve the active selection into the same target type. This
keeps normal and visual behavior consistent.
3. Add registers as explicit editor state.
Yank/delete/change should not be "just edits." Neovim's observable behavior is
that these operations update registers. Alma should add a small register model
with unnamed and numbered/register-0 behavior before attempting paste parity.
Keep it pure and testable, then attach it to view-local or editor-global ECS
state deliberately.
4. Add insert mode deliberately.
`c{motion}`, visual `c`, `i`, `a`, `o`, and `O` need somewhere to go. Insert mode
should not be patched into `features/vim/input.rs` as a special case. Model mode
state and text insertion as a pure input-to-action layer, then emit
`BufferEditRequested` from the adapter.
5. Split the Vim adapter before it absorbs more behavior.
`features/vim/input.rs` is already handling input normalization, command prompt,
search prompt, normal dispatch, visual dispatch, leader state, repeat state,
status emission, and ECS effect emission. Adding operators and insert mode there
without decomposition would make future parity work brittle.
## Zellij-Inspired Implementation Practices
Zellij's useful lesson for Alma is not any one API; it is the habit of keeping
protocols explicit. Alma should extend Vim behavior through small data types and
clear action boundaries, the same way a terminal workspace benefits from typed
actions, owned state, and deterministic tests.
Apply that here:
- Treat Vim commands as actions, not incidental key handlers.
- Keep pure state machines separate from Bevy systems.
- Emit typed effects at boundaries rather than mutating unrelated ECS state.
- Make ownership of state visible: buffers own text, views own modal state,
registers should have one clear home.
- Prefer replayable fixtures and golden traces for compatibility behavior.
- Use small capability modules instead of one growing adapter file.
- Add behavior by expanding contracts, not by relying on schedule side effects.
## Proposed Direction
The next implementation slice should be narrow but architecture-setting:
1. Add a pure `vim::operator` or equivalent module with:
- `OperatorTarget` for characterwise and linewise byte ranges.
- range resolution from `(text, cursor, motion, count)`.
- range resolution from visual selection and visual mode.
- tests against UTF-8 boundaries, line endings, inclusive visual ranges, and
representative Neovim cases.
2. Add a pure `vim::register` module with:
- unnamed register storage.
- yank register storage for `y`.
- enough structure to add numbered and named registers later without
changing operator APIs.
3. Teach normal mode to execute `d{motion}` and `y{motion}` first.
- `d{motion}` resolves a target, writes the deleted text to registers, emits
a buffer edit, and returns to normal mode with the cursor clamped.
- `y{motion}` resolves a target, writes registers, does not mutate the
buffer, and reports status only if Alma wants a visible confirmation.
- `c{motion}` can follow once insert mode exists.
4. Teach visual mode to execute `d` and `y` using the same target model.
- Characterwise visual selections must be inclusive like Neovim.
- Linewise selections should operate on whole touched lines, including
newline policy chosen and tested explicitly.
- Visual operations should leave visual mode after completion.
5. Add compatibility fixtures.
- Keep the existing golden style.
- Add a small Neovim comparison table in fixture comments or test names for
`dw`, `d$`, `dd`, `yw`, `yy`, `vwd`, `Vy`, and UTF-8 selection cases.
- Add property tests for target ranges staying valid UTF-8 slices.
## Lower-Priority Gaps
- Text objects: add after operator targets exist, because text objects are
target producers.
- Paste: add after registers exist.
- Undo/redo: add after edits are represented as reversible transactions or an
edit history boundary is chosen.
- Dot-repeat: add after actions and edit transactions are explicit enough to
replay safely.
- Blockwise visual mode: defer until characterwise and linewise operators are
solid.
- Rich Ex commands and pattern syntax: keep narrow unless they unlock real
editor workflows.
## Non-Goals For The Next Slice
- Full Neovim parity.
- Embedding Neovim as an oracle in normal CI.
- Expanding `features/vim/input.rs` with more branches before extracting
smaller adapters.
- Letting UI or render systems mutate buffer text directly.
- Adding text-object support before normal and visual operators share one
tested target model.
The architectural bar for closing these gaps is simple: a behavior should be
expressed as pure typed intent first, covered by replayable tests, and only then
wired into ECS through Alma's existing message boundaries.