Files
mim/KEYMAP.md
T
2026-06-21 21:20:29 +02:00

18 KiB

KEYMAP.md

This is the durable v1 keymap and operation grammar for mim. It is design authority, not a claim that every operation is implemented today. Implementation slices should cite the relevant requirements and keep the grammar source-patched, replayable, and terminal-trace-backed.

Product stance

mim is mobile-first, not phone-only. The same command grammar must work when SSHing from an iPhone software keyboard, a QWERTZ thumb keyboard, or an attached physical keyboard. Physical keyboards may add faster direct keys, but they must not become the only way to perform core editing, navigation, tool, or recovery actions.

The keymap is optimized for:

  • narrow terminal viewports;
  • mnemonic commands that can be discovered from the screen;
  • common operations as a mode plus one key, or at most a two-key sequence;
  • an orthogonal grammar where verbs, objects, modes, panels, and tool operations compose instead of becoming one-off bindings;
  • avoiding required Esc/Ctrl/Alt/function-key chords for core actions;
  • replayable terminal events separated from layout profiles and editor intents;
  • code editing workflows, including diagnostics, LSP, lint, format, build/test, and local trusted tools.

Input layers

The input path has three distinct layers:

  1. Terminal event: bytes or key events observed from the terminal.
  2. Layout profile: source-patched keyboard tables backed by recorded traces such as iOS QWERTZ and physical keyboard traces.
  3. Editor intent: semantic operations such as delete line, select indent, format buffer, or next diagnostic.

No command should inspect layout quirks directly. A new keyboard layout is a new source-patched profile and trace fixture, not an ad-hoc branch in command code.

Orthogonality rules

The keymap should feel small because its parts compose:

  • modes decide how raw input is interpreted;
  • verbs decide the operation: delete, change, yank, select, format, explain;
  • objects decide the range: word, line, indent block, delimiter pair, parameter, diagnostic, function, panel item;
  • counts/repetition decide how often a movement or previous operation applies;
  • providers decide the source of language/tool data, not the user-facing command;
  • panels decide presentation and filtering, not the operation semantics.

A new operation is suspect if it needs a bespoke binding that cannot reuse this shape. Prefer adding one verb, one object, or one provider rule over adding many special-case bindings. Reuse is not allowed to hide ambiguity: if the same key can mean two things, the current mode or visible rail label must make the meaning obvious.

Examples:

  • d + object deletes; c + object changes; y + object copies; s + object selects. The object grammar is shared.
  • Space l operates on language features; Space t operates on jobs/tools; both can target buffer, selection, project, or provider without inventing a separate mode.
  • Formatting a selection, buffer, or project should be one format command plus a scope/provider choice, not unrelated shortcuts.

Counts and repetition

Counts and repetition are orthogonal modifiers, not separate commands for every operation.

  • A numeric prefix in Normal, Select, or Panel mode applies to the next movement, object, or panel navigation where that operation is repeatable.
  • Physical keyboards can type digits directly: 3 j, 2 w, 4 d w.
  • Mobile users can open the repeat rail with Space r, enter a small count in a prompt, then choose the movement/object/operation. This avoids requiring a comfortable number row for common editing.
  • . repeats the last mutating edit when the key is available; Space r . is the mobile canonical repeat-last path.
  • Counts do not apply to unsafe broad tool operations such as format project, fix-all, build, test, or external terminal commands unless a visible prompt confirms the affected scope.
  • A count is shown in the status/rail while pending and is cleared after one operation, cancel, or mode switch.

Modes

Normal mode

Default navigation and command mode. Text input does not happen here except via explicit symbol/punctuation insertion commands. Normal mode owns movement, selection entry, leader rails, tool commands, save/quit, and recoverability.

Insert mode

Text-entry mode for ordinary typing. mim starts in Normal mode; Insert mode is entered explicitly with i, a, o, or another visible command. In Insert mode, Space commits a literal space immediately and Tab inserts spaces by default (expandtab, matching the dotfiles Neovim baseline). Physical keyboards may use conventional direct keys such as Escape as aliases back to Normal, but those aliases are optional accelerators, not required controls.

Select mode

Selection-building mode. Movement extends the active selection by default. Vertical movement preserves a preferred cursor column across ragged lines, clamping only to the current line length until horizontal/editing motion resets that preference. Semantic object selections live behind the match/object rail (m w, m l, m i, m p, etc.) so bare h/j/k/l, word motions, arrows, and mobile line aliases keep behaving as selection-extending motions. Line object selection includes the line's newline boundary when present, matching Vim-like linewise edit semantics. Select mode can apply operations such as delete, replace, copy, format, code action, or explain to the selection.

Panel mode

Transient-panel mode for file picker, project search, diagnostics, git, LSP, build/test output, terminal escape hatch, and Pi/tool context. Panels share one navigation grammar: move, open, preview, filter, act, close, and return.

Prompt mode

Short text prompt mode for current-file search, project search, rename, command filtering, and tool arguments. Prompt mode is not an ex-command language; it is a small typed argument surface entered from a visible command.

Leader keys and rails

Space is the primary leader in Normal mode. In Insert mode, Space inserts a literal space immediately; terminals do not give mim reliable key-up/hold semantics, so hold-to-leader is intentionally not a v1 path. The rail shows mnemonic groups and available next keys. A command either executes after one key or enters a second-level group; common paths should not go deeper than two keys after the mode/leader.

m is the match/object prefix in Normal and Select modes. It is deliberately not Vim %: % remains a physical-keyboard alias where available, while m is the mobile mnemonic path.

g is the go/navigation prefix. It is used for jumps whose target is elsewhere: definition, references, next/previous diagnostic, file symbol, project symbol, and matching syntax target when a direct object prefix would be ambiguous.

Space Space reopens the last command rail/panel context. It is the universal "where was I?" recovery path.

Mnemonic command grammar

The grammar is verb-first for editing and noun/group-first for panels:

  • i: insert;
  • a: append/after;
  • o: open/new line or open item depending on context;
  • s: select/search depending on mode/rail group;
  • d: delete;
  • c: change;
  • r: replace;
  • y: yank/copy;
  • p: paste/put;
  • u: undo;
  • R: redo where uppercase is available, otherwise Space u then r;
  • f: find/format/file depending on rail group label;
  • l: lint/LSP language tools depending on rail group label;
  • t: tools/tasks/tests;
  • b: buffer/build depending on rail group label;
  • q: quit/close;
  • w: write/save, shown as "write/save" in rails for mnemonic continuity.

Letter reuse is allowed only when the current mode or visible rail label makes the meaning obvious. Invisible overloading is not allowed.

Core Normal bindings

These are the mobile canonical paths. Physical keyboards may add direct aliases such as arrow keys, Home/End, PageUp/PageDown, Escape, or %.

Keys Intent
i enter Insert before cursor
a enter Insert after cursor
o open line below and enter Insert
O or Space o a open line above and enter Insert
h j k l left/down/up/right where comfortable; arrows are aliases
w b e word forward/back/end
gg G document start / bottom line
Ctrl-u Ctrl-d half-page up/down when the SSH client exposes Ctrl; g u / g d are mobile fallback aliases
H L mobile-reachable line start/end aliases
0 ^ $ attached/Vim-style line start/first-nonblank/end aliases; Space g l opens line targets
. repeat the last repeatable local edit (insert/change/delete/put/open-line subset)
u undo
Space u r redo
Space w write/save current buffer
Space q close/quit current surface, with dirty-buffer protection
Space ? show contextual help/command rail
Normal i / a / o enter Insert explicitly; mim starts in Normal mode

Editing operations

Keys Intent
d d delete current line
d then motion/object delete range
c c change current line
c then motion/object change range and enter Insert
r key/symbol replace character or active selection
s enter Select mode
y then motion/object yank/copy range
p paste/put after cursor or replace active selection
Space p symbol insertion rail for coding punctuation: p (), b [], c {}, s /, f //, x *, a &, h #, d $, r @, v `
Space p p / b / c / q / e / t insert paired delimiters or quotes, then place cursor inside

Delete/change/yank all accept the same object grammar. This keeps operations orthogonal: verbs decide what happens; objects decide what range is affected.

Object and match grammar

Objects are shared by Normal and Select mode.

Keys Object or jump
m m jump to matching delimiter or quote
m s select inside matching delimiter/string pair
m a select around matching delimiter/string pair
m ( jump/select () pair, depending on mode
m { jump/select {} pair, depending on mode
m [ jump/select [] pair, depending on mode
m " / m ' / m ` jump/select quoted string where parseable
s w select word
s l select line
s i select current indent block
s p select current parameter/argument
s f select current function or syntactic enclosing form where available
s d select current diagnostic range

Physical keyboards may expose % as an alias for m m. The mobile canonical path stays m m because % is awkward on software keyboards and QWERTZ.

Search and navigation

Keys Intent
/ or Space s f search in current file
n / N next/previous search result; rail aliases exist if uppercase is awkward
Space f file finder
Space s p project text search
Space s s symbol search in current file
Space g d go to definition
Space g r references
Space g i implementation
Space g t type definition
Space g p previous cursor location
Space g n next cursor location
Space g e next diagnostic/error
Space g E or Space g p from diagnostics rail previous diagnostic/error
Space g a next parameter/argument in call
Space g A previous parameter/argument in call

Search results and navigation targets use transient panels on narrow viewports. Opening a result returns to the editor without leaving behind permanent splits.

Panels

Space plus a group opens a panel or rail:

Keys Surface
Space f files: file tree/finder/recent
Space s search: file/project/symbol
Space d diagnostics
Space g go/navigation
Space l language/LSP tools
Space t tasks: build/test/check/lint jobs
Space b buffers/build output depending on rail label
Space v version control/git workbench
Space ! terminal escape hatch/job command
Space p Pi/local trusted tool context

Panel controls are consistent:

Key Intent
j/k or arrows move item
o or Enter open/accept item
p preview item
/ filter/search within panel
a act on item; opens contextual action rail
q close panel
Space Space restore last panel/rail

Coding tools and language operations

Tool operations are first-class commands, not hidden side effects.

Keys Intent
Space l h hover: compact card first, expandable panel for full text
Space l s signature help: active parameter first, expandable panel for overloads/docs
Space l a code actions for cursor/selection/diagnostic
Space l r rename
Space l o organize imports
Space l f format current buffer
Space l F or Space l w toggle/show format-on-save policy for this source build
Space l d show provider details for symbol/diagnostic under cursor
Space t l lint current file
Space t L lint project/workspace
Space t b build
Space t t test
Space t c check/typecheck
Space d diagnostics panel
Space d n next diagnostic
Space d p previous diagnostic
Space d s filter diagnostics by source/provider

Format-on-save is a policy command, not a runtime config file. Defaults live in source profiles. The rail must show whether save will format, which formatter or LSP provider will run, and how to perform a one-shot save without formatting when needed. If no formatter/linter/server exists, mim reports a clear missing-tool or unsupported-capability diagnostic instead of installing anything.

Hover, signature help, and long content

Hover responses are often too large for an iPhone terminal. The default answer is not to dump all text inline.

Space l h shows a compact hover card first:

  • symbol/type/signature headline;
  • shortest useful documentation excerpt that fits the viewport;
  • provider/source label when more than one provider could answer;
  • more, open, search, copy, and pin actions in the rail.

Expanding hover opens a transient scrollable panel. The panel supports search, line wrapping by terminal cell width, source/provider switching, copy/yank, and closing back to the original cursor location. Long hovers never permanently steal editor space, and they should not make diagnostics or signature context unreachable.

Signature help follows the same rule: show the active overload and parameter first; move between parameters with the parameter navigation grammar; expand only when the user asks to read all overloads or full docs.

Multiple providers

Several LSPs, linters, formatters, or build tools may apply to one buffer. mim must keep provider identity visible when ambiguity matters:

  • diagnostics carry source/provider and severity;
  • hover and signature help show provider when multiple providers answer;
  • formatting chooses a source-profile default and offers provider selection when multiple formatters are available;
  • code actions show provider/source in the action list;
  • lint/build/test jobs show command/provider, status, exit code, and captured diagnostics;
  • provider conflicts are surfaced in the language rail instead of silently choosing surprising behavior.

Safe default: use the source-profile-preferred provider when one exists; otherwise ask through a transient provider picker for mutating operations such as format, organize imports, rename, or fix-all. Non-mutating operations may merge results while preserving source labels.

Discoverability and feedback

  • Pausing after Space, m, g, d, c, y, or s shows the valid next keys for the current mode and selection.
  • Rails use verbs and nouns, not internal names: "format buffer", "lint file", "select indent", "matching brace".
  • Dangerous or broad operations show the affected scope before execution.
  • Dirty-buffer, missing-tool, provider-conflict, and unsupported-profile states appear as diagnostics/panel messages, not silent failures.
  • Help is contextual: Space ? in Insert, Select, Normal, and Panel mode shows different high-value commands.

Implementation gap as of this design

Backed by current code/tests:

  • source-patched symbol insertion and keyboard-layout acceptance fixtures;
  • leader rail seed behavior and command-intent separation;
  • transient panel/rendering foundations;
  • protocol/session/job/LSP/diagnostic skeletons;
  • Space t lint/build/test/check job rail backed by source-patched command profiles, captured output panels, missing-tool/cancel/timeout rows, and jump/yank actions;
  • physical-keyboard parity aliases for arrows, Home/End, PageUp/PageDown, Escape, direct digit counts, % match jumping, and panel page keys while preserving mobile Space paths;
  • basic trace commands for open, insert, save, panels, diagnostics, repo files, physical key aliases, and local context.

Design-only and needing future implementation slices:

  • explicit modal state machine for Normal/Insert/Select/Panel/Prompt;
  • orthogonal command parser for shared verb + object + scope/provider grammar;
  • object grammar shared by delete/change/yank/select;
  • matching delimiter and quote object behavior;
  • select current indent block and syntactic enclosing form;
  • current-file search prompt and result navigation;
  • parameter navigation in function calls;
  • compact-first hover/signature cards with expandable scroll/search panels;
  • full provider arbitration for multiple LSP/tool sources;
  • source-profile-backed format, organize-imports, code-action edit application;
  • format-on-save policy and one-shot save-without-format;
  • contextual rails for all prefixes listed in this document;
  • explicit mobile-safe Normal/Insert transitions without required Esc/Ctrl/Alt.

Future slices should implement one vertical behavior at a time with replay or headless tests: for example, m m delimiter jump, s i indent selection, current-file search, or Space l f format buffer with provider diagnostics.