Closes out the T-403 cross-pane vim layer. gt/gT bind to the existing workspace.tab.next/previous commands (also on ctrl+pagedown/up for every preset), resolved by the focused editor's matcher or a pane's PaneKeyNav — bare-g sequences stay editor/pane-local (T-404's global matcher only engages on modified-chord prefixes), so no global-matcher surgery. - vim.yaml: g t -> command:workspace.tab.next, g shift+t -> .previous (vim.normal); shares the `g` prefix with `g g` (docStart / nav.top), distinguished by the final chord. - PaneKeyNav now EXECUTES non-editor.vim.* command intents (e.g. workspace.tab.*) instead of swallowing all command intents, so gt/gT work from a focused pane; editor.vim.* buffer edits stay blocked in panes. Tests: vim-preset resolution (gt/gT bind, gg intact), PaneKeyNav executes the command from a pane, and editor.vim.* is never run from a pane. make test green; analyze + format clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
266 lines
8.9 KiB
YAML
266 lines
8.9 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"
|
|
|
|
# ---- Tab motions (T-405) ---------------------------------------------
|
|
# gt / gT cycle the workspace tab strip via the preset-neutral
|
|
# workspace.tab.* commands (also on ctrl+pagedown/up everywhere). Bare-`g`
|
|
# sequences are editor/pane-local — the focused editor's matcher or a pane's
|
|
# PaneKeyNav resolves them and runs the command; they share the `g` prefix
|
|
# with `g g` (docStart / nav.top), distinguished by the final chord.
|
|
- intent: command:workspace.tab.next
|
|
keys: g t # gt
|
|
when: vim.normal
|
|
- intent: command:workspace.tab.previous
|
|
keys: g shift+t # gT
|
|
when: vim.normal
|
|
|
|
# ---- 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
|