Files
clide/assets/keymaps/vim.yaml
T
jpmschweitzerandClaude Opus 4.8 e54d5263e0 feat(vim): ex command-line overlay (:w :q :wq :x :e :N, ZZ) (T-407)
Under the Vim preset, `:` opens a transient one-line ex overlay running a
fixed v1 table; ZZ runs :wq directly. Completes the last built child of the
T-403 cross-pane vim layer (T-405 part 2 gt/gT still open).

- ExLineController + parseExCommand grammar + editor-targeted executors
  (lib/kernel/src/ex_line.dart); the overlay (lib/widgets/src/ex_line_overlay
  .dart) reuses the quick-open chrome, mounts in the root_shell Stack, and
  publishes the exline.open scope flag. Unknown commands flash + stay open;
  with no active buffer every command no-ops (2026-06-13 decision).
- :q closes the active tab via editor.close on its id — the registry promotes
  the next buffer and the split self-collapses on the last (2026-06-12
  decision); :w/:wq/:x/ZZ save (+close) the active buffer.
- :e <path> seeds quick-open (new QuickOpenController.open(seed:)); :N adds the
  editor.goto-line IPC/CLI verb (reuses _offsetForLine). Goto needs caret sync:
  EditorController now handles editor.selection-changed and the editor view
  moves the caret on a selection-only change.
- `:` and ZZ are typed intents; the editor matcher and PaneKeyNav now bubble
  unhandled typed intents to the app-root Actions, so they fire from any focus.
  vim.yaml binds `:`, ZZ (shift+z shift+z), and Esc-dismiss.

Tests: parser/controller/executors, editor.goto-line daemon tests,
selection-changed (controller + view), full overlay widget test. make test
green; analyze + format clean.

Also files T-441 (drop bold from the ticket-id card label) and T-442
(sub-agent renders as 3 cards instead of one bundle) under the T-276 UI epic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:44:48 +02:00

253 lines
8.3 KiB
YAML

# clide Vim keymap preset (T-65).
#
# Modal editing built on the keymap sequence layer (D-82). The Vim mode
# service (builtin.vim) publishes vim.normal / vim.insert / vim.visual
# scope flags; bindings here are guarded on them. Motions/edits resolve to
# `command:editor.vim.<action>`, which the editor applies to the buffer
# (read-only in command mode so keys never type); mode changes resolve to
# `command:vim.mode.<mode>`.
#
# Notation (D-82): `+` joins a chord, a space sequences (`d d`), a YAML
# list alternates (`[d, x]`). Repeat counts (`5j`) are captured by the
# matcher — no binding needed.
#
# Scope: the muscle-memory set, not bit-exact Vim. cw/$/^ are
# approximations; see vim_edit_ops.dart.
name: vim
bindings:
# ---- App shortcuts (shared with the default preset) -----------------
# Modified chords bubble past the editor to the global handler, so these
# keep working with the editor focused and under every mode.
- intent: palette.open
keys: [ctrl+shift+p, meta+shift+p]
- intent: palette.selectNext
keys: [down, ctrl+n]
when: palette.open
- intent: palette.selectPrevious
keys: [up, ctrl+p]
when: palette.open
- intent: palette.accept
keys: enter
when: palette.open
- intent: dismiss
keys: escape
when: palette.open
# `shift shift` (double-tap) aliases to quick-open across presets (T-341).
- intent: quickOpen.open
keys: [ctrl+p, meta+p, shift shift]
when: "!palette.open"
- intent: quickOpen.selectNext
keys: [down, ctrl+n]
when: quickOpen.open
- intent: quickOpen.selectPrevious
keys: up
when: quickOpen.open
- intent: quickOpen.accept
keys: enter
when: quickOpen.open
- intent: dismiss
keys: escape
when: quickOpen.open
# Ex command-line overlay (T-407): Esc dismisses it back to normal mode (the
# overlay's own EditableText handles Enter via onSubmitted).
- intent: dismiss
keys: escape
when: exline.open
- intent: findInFiles.open
keys: [ctrl+shift+f, meta+shift+f]
- intent: focus.nextPanel
keys: f6
- intent: focus.previousPanel
keys: shift+f6
- intent: text.scaleIncrease
keys: [ctrl+equal, ctrl+shift+equal, meta+equal, meta+shift+equal]
- intent: text.scaleDecrease
keys: [ctrl+minus, meta+minus]
- intent: text.scaleReset
keys: [ctrl+0, meta+0]
# ---- Pane navigation (non-editor panes) ------------------------------
# When a non-editor pane holds focus (file tree, conversation, lists), the
# same motion keys mean NAVIGATION, not buffer edits (T-406). The
# `!editor.focused` guard keeps these out of the editor's way; the editor
# publishes `editor.focused` while it has focus. These MUST precede the
# editor motions below — the resolver takes the first matching binding in
# file order, so with a pane focused (editor.focused false) nav wins, and
# with the editor focused the `!editor.focused` clause fails and the buffer
# motion below wins. Each pane runs its own SequenceMatcher (PaneKeyNav).
- intent: nav.down
keys: j
when: "vim.normal && !editor.focused"
- intent: nav.up
keys: k
when: "vim.normal && !editor.focused"
- intent: nav.pageDown
keys: ctrl+d
when: "vim.normal && !editor.focused"
- intent: nav.pageUp
keys: ctrl+u
when: "vim.normal && !editor.focused"
- intent: nav.top
keys: "g g" # gg
when: "vim.normal && !editor.focused"
- intent: nav.bottom
keys: shift+g # G
when: "vim.normal && !editor.focused"
- intent: nav.expandOrRight
keys: l
when: "vim.normal && !editor.focused"
- intent: nav.collapseOrLeft
keys: h
when: "vim.normal && !editor.focused"
- intent: nav.activate
keys: [o, enter]
when: "vim.normal && !editor.focused"
# ---- ctrl+w window-command family (T-404) ----------------------------
# Multi-chord sequences resolved by the GLOBAL matcher (root_shell), so they
# work from any focus. Bare ctrl+w still closes the editor after the ambiguity
# timeout (the editor.close binding below / contributions layer). The 3-column
# clide layout approximates vim's window grid: h/l focus left/right panels,
# j toggles the dock, o is "only" (focus mode), q/c close the editor.
- intent: command:panel.focus.left
keys: ctrl+w h
when: "vim.normal || vim.visual"
- intent: command:panel.focus.right
keys: ctrl+w l
when: "vim.normal || vim.visual"
- intent: command:dock.toggle
keys: ctrl+w j
when: "vim.normal || vim.visual"
- intent: focus.nextPanel
keys: [ctrl+w w, ctrl+w ctrl+w]
when: "vim.normal || vim.visual"
- intent: focus.previousPanel
keys: ctrl+w shift+w # ctrl+w W
when: "vim.normal || vim.visual"
- intent: command:panel.focusMode
keys: ctrl+w o
when: "vim.normal || vim.visual"
- intent: command:editor.close
keys: [ctrl+w q, ctrl+w c]
when: "vim.normal || vim.visual"
# ---- Ex command-line (T-407) -----------------------------------------
# `:` opens the transient ex overlay (`:w` `:q` `:wq` `:x` `:e <path>` `:N`);
# `ZZ` runs `:wq` directly. Both are typed app intents the editor's command
# matcher and a pane's nav matcher bubble to the app-root Actions, so they
# fire from any focus. Editor-targeted: no-op when no buffer is active.
- intent: exline.open
keys: shift+semicolon # :
when: vim.normal
- intent: exline.writeQuit
keys: shift+z shift+z # ZZ
when: vim.normal
# ---- Mode transitions ------------------------------------------------
- intent: command:vim.mode.visual
keys: v
when: vim.normal
- intent: command:vim.mode.normal
keys: escape
when: "vim.insert || vim.visual"
# ---- Insert entry (normal → insert), positioned by the editor --------
- intent: command:editor.vim.insert
keys: i
when: vim.normal
- intent: command:editor.vim.append
keys: a
when: vim.normal
- intent: command:editor.vim.insertLineStart
keys: shift+i # I
when: vim.normal
- intent: command:editor.vim.appendLineEnd
keys: shift+a # A
when: vim.normal
- intent: command:editor.vim.openBelow
keys: o
when: vim.normal
- intent: command:editor.vim.openAbove
keys: shift+o # O
when: vim.normal
# ---- Motions (normal + visual) ---------------------------------------
- intent: command:editor.vim.left
keys: h
when: "vim.normal || vim.visual"
- intent: command:editor.vim.down
keys: j
when: "vim.normal || vim.visual"
- intent: command:editor.vim.up
keys: k
when: "vim.normal || vim.visual"
- intent: command:editor.vim.right
keys: l
when: "vim.normal || vim.visual"
- intent: command:editor.vim.wordForward
keys: w
when: "vim.normal || vim.visual"
- intent: command:editor.vim.wordBackward
keys: b
when: "vim.normal || vim.visual"
- intent: command:editor.vim.wordEnd
keys: e
when: "vim.normal || vim.visual"
- intent: command:editor.vim.lineStart
keys: "0"
when: "vim.normal || vim.visual"
- intent: command:editor.vim.lineEnd
keys: shift+4 # $
when: "vim.normal || vim.visual"
- intent: command:editor.vim.firstNonBlank
keys: shift+6 # ^
when: "vim.normal || vim.visual"
- intent: command:editor.vim.docStart
keys: "g g" # gg
when: "vim.normal || vim.visual"
- intent: command:editor.vim.docEnd
keys: shift+g # G
when: "vim.normal || vim.visual"
# ---- Normal-mode edits ----------------------------------------------
- intent: command:editor.vim.deleteChar
keys: x
when: vim.normal
- intent: command:editor.vim.deleteLine
keys: "d d"
when: vim.normal
- intent: command:editor.vim.deleteWord
keys: "d w"
when: vim.normal
- intent: command:editor.vim.deleteToEnd
keys: shift+d # D
when: vim.normal
- intent: command:editor.vim.changeLine
keys: "c c"
when: vim.normal
- intent: command:editor.vim.changeWord
keys: "c w"
when: vim.normal
- intent: command:editor.vim.yankLine
keys: "y y"
when: vim.normal
- intent: command:editor.vim.paste
keys: p
when: vim.normal
- intent: command:editor.vim.pasteBefore
keys: shift+p # P
when: vim.normal
# ---- Visual-mode operators ------------------------------------------
- intent: command:editor.vim.visualDelete
keys: [d, x]
when: vim.visual
- intent: command:editor.vim.visualYank
keys: y
when: vim.visual
- intent: command:editor.vim.visualChange
keys: c
when: vim.visual