diff --git a/KEYMAP.md b/KEYMAP.md new file mode 100644 index 0000000..df40d0c --- /dev/null +++ b/KEYMAP.md @@ -0,0 +1,402 @@ +# 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. Insert mode preserves terminal text input +and exposes a visible return path to Normal through a non-modifier command rail. + +In Insert mode, `Space` enters a pending-space state instead of immediately +committing ambiguity. If the next key is ordinary text, `mim` commits the literal +space and the next character. If the user pauses after `Space`, `mim` opens the +Insert rail. The canonical mobile path back to Normal is `Space` pause, then +`n` for "normal". `Space` pause, then `Space` commits a literal space from the +rail. Physical keyboards may use conventional direct keys such as Escape as +aliases, but those aliases are optional accelerators, not required controls. + +### Select mode + +Selection-building mode. Movement extends the active selection by default. +Selection commands are semantic: select word, line, current indent block, +matching delimiter pair, enclosing text object, parameter, diagnostic range, and +visible panel item. 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. Pressing and pausing on Space opens the command +rail. 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 | +| `0` `$` | line start/end when available; `Space g` `l` opens line targets | +| `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 | +| Insert `Space` pause, `n` | return from Insert to Normal without Esc/Ctrl/Alt | + +## 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 i` | symbol insertion rail for coding punctuation | +| `Space i` `p` | insert paired delimiter, then place cursor inside | +| `Space i` `q` | insert paired 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; +- basic trace commands for open, insert, save, panels, diagnostics, repo files, + 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; +- lint/format/build/test command execution through the job surface; +- source-profile format-on-save policy and one-shot save-without-format; +- contextual rails for all prefixes listed in this document; +- Insert pending-space rail, including `Space` pause then `n` to Normal; +- counts/repetition grammar with direct digit and mobile repeat-rail paths; +- physical-keyboard trace fixtures and alias table. + +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. diff --git a/PRODUCT.md b/PRODUCT.md index 87db49a..b391b91 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -2,7 +2,7 @@ ## Product thesis -`mim` is a mobile-first terminal code editor for SSH sessions from an iPhone. It runs as one server-side binary and is designed around narrow viewports, QWERTZ/thumb input, source patching, and real coding workflows rather than desktop Vim compatibility. +`mim` is a mobile-first terminal code editor for SSH sessions from an iPhone. It runs as one server-side binary and is designed around narrow viewports, QWERTZ/thumb input, attachable physical keyboards, source patching, and real coding workflows rather than desktop Vim compatibility. ## User job @@ -14,7 +14,7 @@ The user opens Vim or Neovim over SSH and tolerates bindings, ex commands, split ## Mechanism -`mim` is protocol-first: the editor core owns buffers, selections, panels, jobs, diagnostics, and session state behind a local socket, while the terminal UI is the first thin client over that protocol. The UI provides a thumb-native leader-key command grammar, transient panels instead of desktop splits, UTF-8-aware editing, Tree-sitter highlighting, built-in LSP, git, file search/tree, project text search, build output, terminal, and optional Pi assistant integration. The socket path is exported in the session environment so trusted local tools like `pi` can inspect editor state or send explicit commands through the same small protocol. Defaults live in source and are changed by patching and rebuilding, not runtime config files. +`mim` is protocol-first: the editor core owns buffers, selections, panels, jobs, diagnostics, and session state behind a local socket, while the terminal UI is the first thin client over that protocol. The UI provides a thumb-native leader-key command grammar that remains efficient with an attached physical keyboard, transient panels instead of desktop splits, UTF-8-aware editing, Tree-sitter highlighting, built-in LSP, git, file search/tree, project text search, build output, terminal, and optional Pi assistant integration. The socket path is exported in the session environment so trusted local tools like `pi` can inspect editor state or send explicit commands through the same small protocol. Defaults live in source and are changed by patching and rebuilding, not runtime config files. ## Feature-complete boundary diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 8076b70..86c2800 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -16,6 +16,8 @@ Rows are redgate TSV requirements: `ringidsummary [tag]`. 1 003 Frequent coding punctuation SHALL have editor-native insertion paths for mobile keyboards. [mobile] 1 004 Input handling SHALL separate terminal key events, keyboard layout profiles, and editor command intents. [mobile] 2 005 Keyboard layout profiles SHALL be source-patched tables backed by recorded terminal traces, starting with iOS QWERTZ. [mobile] +1 006 Physical keyboard input SHALL be a first-class terminal path with discoverable shortcuts and no loss of mobile no-required-modifier command access. [mobile] +1 007 The v1 keymap SHALL keep modes, verbs, objects, counts/repetition, panels, and tool operations orthogonal so operations compose instead of multiplying bindings. [mobile] ## ui @@ -27,8 +29,10 @@ Rows are redgate TSV requirements: `ringidsummary [tag]`. 0 001 Buffer and rendering code SHALL preserve UTF-8 boundaries and terminal cell width semantics. [core] 1 002 Tree-sitter syntax highlighting SHALL be built in with source-patched language tables. [syntax] 1 003 LSP support SHALL be built in and spawn existing language servers without installing them. [lsp] -1 004 LSP hover and signature help SHALL use viewport-aware presentation that remains usable on narrow terminals. [lsp] +1 004 LSP hover and signature help SHALL use compact-first viewport-aware presentation with source labels, scrolling/search, and explicit expansion for long content. [lsp] 1 005 When syntax or LSP data is available, `mim` SHALL provide mobile-friendly navigation between parameters in function calls. [lsp] +1 006 Lint, format, format-on-save, organize-imports, code-action, build, test, and check workflows SHALL be first-class editor commands rather than hidden side effects. [tools] +1 007 When multiple LSPs or tools answer the same request, `mim` SHALL preserve provider/source identity and require explicit selection for ambiguous mutating operations. [tools] ## repo