0.71.0
Added
- Yank-ring paste cycling — after
p/P, pressing<C-p>replaces the pasted text with the previous numbered register ("1–"9).<C-n>reverses direction. Cycling wraps. Any non-cycling command cancels state;<C-p>/<C-n>then revert tok/j. Gated byenableYankRingsetting (default: on). Usesvim-keypressevent detection andaddToHistory.of(false)for single-undo-group cycling.- Plugin:
src/vim/yank-ring.ts(new),src/settings.ts(enableYankRing),src/main.ts(lifecycle integration)
- Plugin:
- Indentation text object (
ii/ai) —iiselects contiguous lines with same-or-greater indentation.aiadds the parent line above and trailing blank lines. Zero-indentation and blank lines return no match. Column-aware tab handling via CM6state.tabSize. Gated by existingenableTextObjectssetting.- Plugin:
src/text-objects/indentation.ts(new),src/text-objects/register.ts
- Plugin:
grblockwise visual mode —<C-V>block selection +grnow replaces each line in the block with corresponding register content. Single-line registers duplicate to all block rows; multi-line registers apply line-by-line; excess register lines truncate to block height. Cursor lands at top-left of block. Previously returned early (no-op).- Plugin:
src/operators/replace-with-register.ts(blockwise branch)
- Plugin:
Changed
- EasyMotion label positioning — labels now appear one character to the right of the target character (after the target) instead of on top of it. This prevents labels from obscuring the character they target. The change applies to all EasyMotion motions (word, char, line, search). Match highlights now also appear behind EasyMotion labels.
Fixed
- Flash highlight rectangles hardcoded to 8×16px — the
.vim-motions-flash-matchhighlight boxes used a fixedwidth: 8px; height: 16pxregardless of actual character dimensions, breaking with proportional fonts, different font sizes, and CJK characters. Now dynamically measured viacoordsAtPos()for both start and end of each match. CSS dimensions use custom properties (--vim-motions-flash-w,--vim-motions-flash-h) with fallbacks. (#75) - Flash labels obscure matched text — labels were positioned at the match START coordinate, rendering on top of the matched characters. Labels are now positioned at the END of the matched text (one character past the last matched character), matching flash.nvim's default
after = truebehavior. Matched text remains visible with a colored highlight underneath. (#75) - Flash match highlights missing during label phase — when labels appeared (pattern met
minPatternLength), match highlights disappeared. NowshowOverlayrenders both match highlights AND labels simultaneously (flash.nvim parity). Match highlights persist during label narrowing — only labels update when typing a label prefix. (#75) - Flash jump-mode label narrowing destroyed match highlights — in jump mode (
s), typing a label prefix character destroyed the entire overlay and recreated it with only remaining targets, losing match highlights for non-matching targets. Now usesupdateLabels()to narrow labels while preserving all match highlights. (#75)- Plugin:
src/easymotion/overlay.ts(extractedmeasureTarget+measureLabelAnchor+renderHighlightSpansshared helpers, addedrenderHighlightstoshowOverlay, label positioning at end-of-match),src/easymotion/types.ts(matchLength?: numberonTarget),src/easymotion/targets.ts(matchLengthinfindSubstringTargets),src/flash/search-mode.ts(matchLengthinfindSearchMatchTargets),src/flash/jump-mode.ts(widenedcurrentOverlaytype toOverlayHandle, label narrowing viaupdateLabels),styles.css(CSS custom properties for.vim-motions-flash-matchdimensions)
- Plugin:
- Flash jump mode two-character label premature exit — when flash jump mode (
s) displayed two-character labels (28+ matches with default 27-char label alphabet), typing the first character of a two-char label either jumped to the wrong single-char target or appended the character to the search pattern, destroying the label state. The same bug affected post-//?search labels. Fixed by adding prefix accumulation with label narrowing: typed characters are checked as label matches first (exact → jump, prefix → narrow and update overlay), then fall back to extending the search pattern. Extracted sharedwaitForFlashLabel()intosrc/flash/label-input.tsfor reuse bychar-mode.ts. (#76)- Plugin:
src/flash/label-input.ts(new: shared label selection state machine),src/flash/jump-mode.ts(prefix accumulation + label narrowing),src/flash/search-mode.ts(prefix accumulation + label narrowing),src/flash/char-mode.ts(imports sharedwaitForFlashLabel)
- Plugin:
- Flash jump
sconflicting with surroundcs/ys/dsin operator-pending mode — when flash jump mode was enabled withsas the key, surround operations (cs",ds",ysiw") were intercepted by flash because motions take precedence over partial action matches in operator-pending mode. Fixed by implementing an operator-prefix shadow resolver in the codemirror-vim fork: when an operator is pending and the next key fully matches a motion but also partially matches anoperatorPendingaction (e.g., surround'ss<character>), the resolver defers to the partial match, waiting for the next character to disambiguate. A configurable timeout (operatorshadowtimeout, default 1000ms) falls back to executing the deferred motion if no next key arrives. (#76)- Fork:
~/Repos/codemirror-vim/src/vim.js(shadow resolver inmatchCommand(), timer inhandleKeyNonInsertMode(), cleanup inclearInputState()+ teardown,operatorshadowtimeoutoption) - Fork:
~/Repos/codemirror-vim/src/types.ts(_shadowTimeronvimState) - Plugin:
src/settings.ts(operatorshadowtimeoutsetting + Settings UI),src/vimrc/loader.ts(operatorshadowtimeout/ostinKNOWN_SET_OPTIONS)
- Fork:
Tests
- 5 unit tests in
test/unit/flash-targets.test.ts:findSubstringTargetssetsmatchLength(5-char, 1-char, 2-char patterns), empty pattern returns empty,findCharTargetsdoes not setmatchLength - 2 e2e tests in
test/specs/flash-jump-mode.e2e.ts: two-char label narrowing (labels persist after typing first char of two-char label), single-char label immediate jump - 3 e2e tests in
test/specs/flash-jump-mode.e2e.ts: shadow resolver surround coexistence (cs"',ds",ysiw"with flashsenabled) - 10 tests in
~/Repos/codemirror-vim/test/vim_test.js: shadow resolver (cs,ds,ysiwsurround wins,dd/ccregression,dw/cwno-activation, Escape clears timer,g~regression,ost=0disables) - 6 e2e tests for
grblockwise intest/specs/operators.e2e.ts(4 unskipped + register preservation + cursor position) - 13 e2e tests in
test/specs/indentation-textobj.e2e.ts(inner/around selection, operators, zero-indent, blank lines, nesting, yank, cursor position, mode verification) - 8 e2e tests in
test/specs/yank-ring.e2e.ts(cycling, reversal, cancellation, fallback to k/j, paste variants, register preservation)
Documentation
CHANGELOG.mdREADME.mdCONTRIBUTING.md: Updated overlay.ts description; addedlabel-input.tsto codebase structureAGENTS.md: Updated codemirror-vim fork description with operator-prefix shadow resolver and test countKNOWN_LIMITATIONS.md: Updated flash motions section with highlight sizing and label positioning fixes; added operator-prefix key dispatch section (Implemented); updatedskey / surround conflict bullet to reference resolverdocs/features/flash.md: Added highlight and label rendering behavior documentation; updated surround conflict note to document automatic resolution via shadow resolver; added two-char label behavior documentationdocs/features/easymotion.md: Added note about label positioning changedocs/features/text-objects.mddocs/features/quality-of-life.mddocs/reference/keybindings.mddocs/configuration/settings.md: Addedoperatorshadowtimeoutto Vim engine settings groupdocs/configuration/vimrc.md: Addedoperatorshadowtimeout/ostto numeric options tabledocs/configuration/lua-config.md: Addedvim.opt.operatorshadowtimeoutentry~/Repos/codemirror-vim/DIFFERENCES.md: Added operator-prefix shadow resolver section
Full Changelog: 0.70.0...0.71.0