Added
set nopcre— Vim-style regular expressions — users can now switch from JavaScript/PCRE regexps to Vim-style regex syntax in search and substitution viaset nopcre(vimrc),vim.opt.pcre = false(Lua), or the Settings UI toggle (Settings → Vim Motions → Vim engine → PCRE). The codemirror-vim fork already implemented the fullpcreoption (regex translation with magic modes,\</\>word boundaries,\zs/\ze, backreference conversion); this change wires it into the plugin's option tracking, settings UI, and documentation. Default:true(JavaScript regexps, no behavior change for existing users). (#111)- Plugin:
src/settings.ts(pcre: booleaninVimMotionsSettings,pcre: trueinDEFAULT_SETTINGS, toggle in both declarative and imperative settings UI — General page, Vim engine group) - Plugin:
src/vimrc/loader.ts(pcreadded toKNOWN_SET_OPTIONSandKNOWN_CM_VIM_OPTIONS) - Plugin:
src/main.ts(initialization sync —vim.setOption('pcre', false)when user has disabled PCRE) - Plugin:
test/unit/known-set-options.test.ts(pcreadded tonewOptionstest array)
- Plugin:
- 37 snippet variables (up from 16 documented) — expanded the snippet variable system to cover the full VSCode snippet specification, plus vim-ecosystem aliases. New variables:
$TM_SELECTED_TEXT(wired — was stubbed),$VISUAL(alias for$TM_SELECTED_TEXT, vim convention),$TM_CURRENT_LINE,$TM_CURRENT_WORD,$WORD(alias for$TM_CURRENT_WORD, vim convention),$TM_LINE_NUMBER(1-based),$TM_LINE_INDEX(0-based),$CLIPBOARD(wired via cache-ahead pattern — was stubbed),$RELATIVE_FILEPATH,$WORKSPACE_NAME,$WORKSPACE_FOLDER,$CURSOR_INDEX,$CURSOR_NUMBER,$CURRENT_MILLISECOND,$CURRENT_MILLISECONDS_UNIX,$CURRENT_TIMEZONE_NAME, plus previously undocumented$CURRENT_YEAR_SHORT,$CURRENT_MONTH_NAME_SHORT,$CURRENT_DAY_NAME_SHORT,$CURRENT_SECONDS_UNIX,$CURRENT_TIMEZONE_OFFSET.$CLIPBOARDuses a cache-ahead pattern (refreshed onwindow focusandvisibilitychange) to avoid making the synchronous snippet pipeline async. On mobile,$CLIPBOARDresolves to empty due to browser clipboard API restrictions.$TM_SELECTED_TEXT/$VISUALresolve to the editor selection at expansion time; in tab-expand mode, selection is not available (tab expansion requires an empty selection). (#110)- Plugin:
src/snippets/types.ts(PreprocessContext— addedcurrentLine,currentWord,lineNumber,lineIndex,workspaceNamefields) - Plugin:
src/snippets/variables.ts(added 15 new variable entries includingVISUAL,WORD,TM_CURRENT_LINE,TM_CURRENT_WORD,TM_LINE_NUMBER,TM_LINE_INDEX,RELATIVE_FILEPATH,WORKSPACE_NAME,WORKSPACE_FOLDER,CURSOR_INDEX,CURSOR_NUMBER,CURRENT_MILLISECOND,CURRENT_MILLISECONDS_UNIX,CURRENT_TIMEZONE_NAME; addedpad3()andgetTimezoneName()helpers) - Plugin:
src/main.ts(_clipboardCachefield,refreshClipboardCache()method, clipboard cache listeners onwindow focus+visibilitychange+ initial population;getSnippetPreprocessContext()rewritten to populate all fields from the active editor including selection, current line/word, line number, and workspace name)
- Plugin:
Fixed
- EasyMotion operator-pending inclusivity — EasyMotion motions (
f,t,e,s,ge,E,gE) now correctly include the target character in operator-pending mode, matching native Vim semantics. Previously, all EasyMotion motions were registered with emptymotionArgs, so the fork treated them as exclusive —y<leader><leader>fk{label}excluded the targetkfrom the yank. Visual mode was unaffected (it extends the selection directly without consulting theinclusiveflag). Backward motions (F,T) remain exclusive, matching native Vim. (#109)- Plugin:
src/easymotion/register.ts(EasyMotionDefinterface — addedmotionArgs?: Record<string, unknown>;EASYMOTION_DEFS— addedmotionArgs: { inclusive: true }to 8 of 17 defs matching Vim's native inclusivity; registration loop — passesdef.motionArgs ?? {}tomapCommand)
- Plugin:
- Cursor stuck below YAML frontmatter in Live Preview with "Properties in document: Source" —
k,gk, and<Up>could not move into the frontmatter region when the editor was in Live Preview mode and Obsidian's "Properties in document" setting was set to "Source". In this configuration, the.metadata-containerDOM element exists but is hidden (display: none). The fork'sfocusBeforecallback found the hidden element viaquerySelector, focused it (no visible effect), andmoveByLines/moveByDisplayLinesreturned the original cursor position — leaving the cursor stuck. Fixed by adding asetPropertiesSource(fn: () => boolean)API to the fork, parallel tosetLivePreviewField. When the callback returnstrue, the frontmatter interception block is skipped entirely and the cursor moves through raw frontmatter text normally. The plugin passes() => getVaultConfig(app, 'propertiesInDocument') === 'source', evaluated per cursor movement so runtime setting changes take effect immediately. (#77)- Fork:
~/Repos/codemirror-vim/src/cm_adapter.ts(setPropertiesSourceAPI,_propertiesSourceFngate infindPosV) - Fork:
~/Repos/codemirror-vim/src/index.ts(exportsetPropertiesSource) - Fork:
~/Repos/codemirror-vim/DIFFERENCES.md(addedsetPropertiesSourceAPI section, updated "Properties navigation" section with two-level gate) - Plugin:
src/vim/bundled-vim.ts(createBundledVimExtensionacceptsisPropertiesSourcecallback, callssetPropertiesSource) - Plugin:
src/main.ts(passespropertiesInDocument === 'source'callback) - Plugin:
src/types/codemirror-vim.d.ts(addedsetPropertiesSourcetype declaration)
- Fork:
- Escape in operator-pending mode exits embedded text area editor — pressing
dthenEscapein the textarea vim overlay exited the editor instead of clearing the pending operator. The Escape handler checkedvim.mode === 'normal'without accounting for operator-pending, surround, partial key sequences, and literal-character-await sub-states. Additionally, the CM6 keymap handler could never run because vim'seventObservers.keydowncallede.preventDefault()before CM6 keymaps processed the event. Fixed by moving Escape handling to an ObsidianScope.registerhandler (fires before vim's observer) with a newisVimIdle()check covering all compound-command sub-states. (#112)- Plugin:
src/editors/embeddable-editor.ts(isVimIdlehelper,VimIdleStateinterface, Scope-based Escape handler replacing CM6 keymap handler)
- Plugin:
- Keydown events leak from embedded text area editor to parent modals — typing keys (e.g., Space) in insert mode inside the textarea vim overlay propagated
keydownevents to the parent modal, triggering unintended actions in third-party plugins (e.g., Spaced Repetition). Fixed with a new opt-inisolateKeyEventsoption onEmbeddableEditorOptionsthat stopskeydownandkeyuppropagation via CM6domEventHandlers. Only enabled for textarea-vim overlays; Oil and table-cell editors are unaffected. (#112)- Plugin:
src/editors/embeddable-editor.ts(isolateKeyEventsoption,domEventHandlerswithstopPropagation) - Plugin:
src/vim/textarea-vim-manager.ts(isolateKeyEvents: true)
- Plugin:
- Unmatched
<Space>inserted as text after failed multi-key sequence — pressing an unmapped key after a partial multi-key sequence (e.g.,<leader><leader><Space>where no EasyMotion motion matches) inserted a literal space character into the document. Root cause: the fork'sfindKeyusedkey.length === 1to suppress unmatched single-character keys in normal mode, butvimKeyFromEventconverts Space to"<Space>"(7 characters) via thespecialKeymap, bypassing the guard. The function returnedundefinedinstead of a consuming no-op, letting the keydown propagate to CM6's text input handler. Fixed by replacing the guard withkey.length === 1 || /^<.+>$/.test(key)to match both plain characters and angle-bracket notation keys. (#112)- Fork:
~/Repos/codemirror-vim/src/vim.js(findKey— generalized key length guard) - Fork:
~/Repos/codemirror-vim/DIFFERENCES.md(added "Unmatched angle-bracket keys consumed in normal mode" section)
- Fork:
Tests
- 4 e2e tests in
test/specs/easymotion-comprehensive.e2e.ts(issue #109): inclusivefyank includes target character, inclusiveedelete includes end-of-word character, exclusivewyank excludes target (regression), visual modefyank includes target (regression) - 1 unit test in
test/unit/known-set-options.test.ts(issue #111):pcreoption registered inKNOWN_SET_OPTIONSwith correct type and settingsKey, default value verified - 49 unit tests in
test/unit/snippets/variables.test.ts:resolveVariables()coverage for all 37 variables (selection/content, file/path, workspace/cursor, date/time, random), syntax variants ($VARand${VAR}), alias parity ($VISUAL=$TM_SELECTED_TEXT,$WORD=$TM_CURRENT_WORD,$RELATIVE_FILEPATH=$TM_FILEPATH,$WORKSPACE_FOLDER=$WORKSPACE_NAME), edge cases (empty fields, unknown variables, tabstop defaults, adjacent variables) - 20 e2e tests in
test/specs/snippets/snippet-variables-integration.e2e.ts(issue #110): file/path variables against liveWelcome.md(5 tests), editor content variables with cursor positioning (4 tests), line number variables 1-based/0-based (3 tests), workspace name and alias (2 tests), cursor index/number constants (2 tests), selection variable resolution viagetSnippetPreprocessContext()(3 tests), combined multi-variable expansion (1 test) - 3 e2e tests in
test/specs/vim-builtin/g-commands.e2e.ts(issue #77):kmoves up through source-rendered frontmatter,knavigates through multiple frontmatter properties,gkmoves up through source-rendered frontmatter. Tests setpropertiesInDocumentto'source'and ensure Live Preview mode, with save/restore of the original setting. - 13 unit tests in
test/unit/embedded-editor-idle.test.ts(issue #112):isVimIdlecoverage for null/undefined, idle normal, insert/visual/replace modes, operator pending, surround state, partial key buffer, expectLiteralNext, multiple sub-states, missing inputState, missing keyBuffer - 3 e2e tests in
test/specs/textarea-vim.e2e.ts(issue #112): operator-pending Escape does not exit overlay, idle normal Escape exits overlay, insert-mode typing does not leak keydown to parent modal
Documentation
CHANGELOG.mdKNOWN_LIMITATIONS.md: UpdatedKNOWN_CM_VIM_OPTIONSlist insetoption scope section to includepcre; added snippet variable limitations section ($CLIPBOARDmobile restriction,$TM_SELECTED_TEXTtab-expand limitation,snip.envdeferred, comment variables deferred); updated properties navigation section with "Properties in document: Source" edge case fix and updated test coverage; marked EasyMotion operator-pending inclusivity as fixed; added 4 new known limitations (linewisej/k,motionArgs.forward/clipToLine,EXTRA_DEFSbidirectional motions,easyMotionRepeatoperator-pending)CONTRIBUTING.md: Updatedvariables.tsdescription in codebase structure; updatedbundled-vim.tsdescription withsetPropertiesSourcewiring; updatedeasymotion/register.tsdescription with per-motionmotionArgsfor operator-pending inclusivityREADME.md: Updated Snippets feature line with variable count and vim-ecosystem aliasesAGENTS.md: Updated codemirror-vim fork description withsetPropertiesSourceAPIdocs/features/snippets.md: Expanded variable table from 16 to 37 entries organized into sections (selection/content, file/path, workspace/cursor, date/time, random) with info callout about selection and clipboard behaviordocs/features/easymotion.md: Updated operator-pending section with inclusivity semantics; fixed stale dot-repeat notedocs/configuration/vimrc.md: Addedpcrerow to boolean options tabledocs/configuration/settings.md: Addedpcrerow to Vim engine settings tabledocs/configuration/lua-config.md: Addedpcrerow tovim.optoptions tableDIFFERENCES.md(fork): AddedsetPropertiesSourceAPI section, updated "Properties navigation" section with two-level gateKNOWN_LIMITATIONS.md: Updated embedded editor Escape handler description with Scope-based approach andisVimIdlesub-state detection; added key event isolation noteCONTRIBUTING.md: Updatedembeddable-editor.tsdescription withisVimIdlehelper, Scope-based Escape, andisolateKeyEventsoptionAGENTS.md: Updated dual-vim architecture section with Scope-based Escape handling for embedded editorsDIFFERENCES.md(fork): Added "Unmatched angle-bracket keys consumed in normal mode" section under Behavioral fixes
Full Changelog: 0.98.0...0.99.0