Fixed
let mapleader = ","(comma) and other keys with default Vim bindings not working as leader for EasyMotion —unmapDefaultBindingnow passes{ includeDefaults: true }tovim.unmap(), so built-in codemirror-vim bindings (e.g.,→repeatLastCharacterSearch,;→ forward repeat) are actually removed before registering EasyMotionmapCommandmulti-key sequences. Previously,vim.unmap()silently skipped_isDefaultkeymap entries, meaning the default single-key binding consumed the first keystroke before the multi-key sequence (e.g.,,w) could accumulate. Space as leader was unaffected because the default<Space>binding uses angle-bracket notation which doesn't collide with literal space incommandMatch. (#6)gg/Gand other keymaps intermittently stop working — comprehensive vim state hardening across the codemirror-vim fork and plugin to prevent keymaps from breaking until app reload. Root causes identified and fixed: stale normal-mode key prefix state persisting across focus changes, global singleton keymap corruption viaunmap()removing default entries, incompleteleaveVimMode()cleanup leaking insert-mode listeners, and async motion race conditions. (#18)- Fork: blur handler resets partial key prefixes — the CM6 ViewPlugin now registers a
blurlistener oncontentDOMthat callsclearInputState()when the editor loses focus in normal mode. A stale prefix likegno longer persists across tab switches or modal opens, preventing the next keystroke from being silently swallowed. - Fork:
leaveVimMode()cleanup hardened — now removes insert-modechange/keydownlisteners if the editor was destroyed while in insert mode, clears the globallastInsertModeKeyTimer, clearsvirtualPrompt, and resetsinputStatebefore nullingcm.state.vim. - Fork: default keymaps protected from
unmap()— default keymap entries are tagged with_isDefaultand a frozen snapshot is stored at module init.unmap()now skips default entries unless explicitly requested. NewVim.resetKeymap()API restores defaults from the snapshot while preserving user mappings.mapclear()updated to use the_isDefaultflag instead of fragile index-based partitioning. - Fork: async motion generation tracking —
_commandGenerationcounter on vim state prevents stale async motion callbacks from executing after a newer command has already run. Protects EasyMotion operator-pending mode (d+ easymotion) from race conditions. - Plugin: pane-switch state reset —
active-leaf-changehandler now clears pending vim input state on all editors when switching panes, preventing partial commands from leaking across editors. - Plugin:
resetKeymap()on load — callsVim.resetKeymap()during pluginonload()to ensure a clean keymap baseline on plugin enable/reload, recovering from any prior corruption in the same app session.
- Fork: blur handler resets partial key prefixes — the CM6 ViewPlugin now registers a
Added
- Which-key leader grouping — leader key bindings in the which-key overlay are now grouped by prefix key, matching Neovim's which-key plugin behavior. When grouping is enabled (default), pressing the leader key shows collapsed groups (e.g.
t→Table (+11),\→EasyMotion (+17)) instead of listing every binding individually. Pressing a group key drills down to show only bindings within that group. Configurable via Settings → Vim Motions → Which-key leader grouping (grouped / flat). (#27)- Groups are sorted first in the overlay, followed by ungrouped single-key bindings
- Group rows are visually distinct (accent color, italic) via the
.vim-motions-which-key-groupCSS class - Grouping applies to all completions in "all partial keys" mode, not just leader-scoped bindings — any multi-key prefix (
g,z,[,], custom mappings) can be grouped - Drill-down works in both "leader key only" and "all partial keys" which-key modes
- Which-key group labels — configurable names for key groups in the which-key overlay. Prefix keys can be labeled (e.g.
\t→Table,gr→LSP) instead of showing the generic+N keystext. Built-in features register default labels (Table, EasyMotion) that can be overridden. Labels support<leader>token expansion (e.g.<leader>tresolves to the actual leader key +t). Configurable via Settings → Vim Motions → Which-key group labels. - E2E test suite
test/specs/vim-state-hardening.e2e.tswith 7 tests: blur prefix recovery,gg/Gafter plugin reload, keymap protection viaunmap(),resetKeymap()recovery after force-unmap,leaveVimModecleanup from insert mode - Fork unit tests: 10 new tests for async motion generation tracking (superseded motion discarded, superseded delete discarded), keymap protection (
unmapskips defaults,unmapremoves user mapping preserving default,unmap ggpreserves default,resetKeymaprestores after force-unmap,resetKeymappreserves user mappings,mapclearpreserves defaults),leaveVimModecleanup (clears input state, cleanup from insert mode) - Fork test count: 1672 (up from 1660)
Documentation
DIFFERENCES.md(fork): added sections for blur handler,leaveVimModecleanup hardening, default keymap protection (_isDefaulttagging,resetKeymap(),mapclear()update), async motion generation tracking,clearInputStateAPI exposureKNOWN_LIMITATIONS.md: added "Vim state hardening" section documenting the multi-layered defense against intermittent keymap breakageREADME.md: added "Improved vim state reliability" bullet to recommended setup section
Full Changelog: 0.19.0...0.20.0