Added
- Mobile support — the plugin is no longer desktop-only. Changed
isDesktopOnlytofalseinmanifest.json. EasyMotion and hint mode are disabled on mobile viaPlatform.isMobileguards because they depend onactiveDocument/activeWindow(desktop-only Obsidian globals). All other features (core vim, text objects, navigation, workspace commands, vimrc, status bar, tables, surround) work on mobile. (#30)src/main.ts: addedPlatform.isMobileguards to skip EasyMotion and hint mode registration on mobile (inonload,reloadFeatures, andreregisterLeaderFeatures)eslint.config.mts: added@codemirror/*and@lezer/*toimport/no-nodejs-modulesallow list —eslint-plugin-obsidianmdenables this rule whenisDesktopOnly: false
Fixed
- EasyMotion big-WORD regex crashes on iOS < 16.4 —
BIG_WORD_START_REused a lookbehind assertion ((?<=\s|^)\S) which is not supported on iOS versions before 16.4. Rewritten as a two-pass scanner: first checks start-of-line for non-whitespace, then finds\s\Stransitions mid-line. Theobsidianmd/regex-lookbehindlint rule (enabled whenisDesktopOnly: false) caught this. (#30) import/no-nodejs-modulesfalse positives on@codemirror/*imports —eslint-plugin-obsidianmdenables this rule whenisDesktopOnly: falseinmanifest.json. The existingimport/core-modulessetting does not affect this rule's allow list. Added explicitallowentries for all@codemirror/*and@lezer/*packages to the rule configuration.- Configurable insert mode escape timeout —
set insertmodeescapetimeout=N(aliasimet, range 100–5000ms, default: 1000ms) controls how long the plugin waits between keystrokes when matching theinsertmodeescapesequence (e.g.jk). Matches Neovim'stimeoutlendefault of 1000ms. Previously hardcoded at 200ms — too tight for normal typing. Configurable via vimrc, Settings UI (Settings → Vim Motions → Vim engine → Insert mode escape timeout), or runtimeVim.setOption('insertmodeescapetimeout', 500). (#31) - Vimrc ↔ Settings parity — all plugin settings are now configurable via
.obsidian.vimrcin addition to the Settings UI. When vimrc is enabled (the default), vimrc values override the corresponding Settings UI values. Settings overridden by vimrc are shown as disabled controls in the settings tab with a note indicating the vimrc directive that set them (e.g., "Set by vimrc:set scrolloff=10").- Boolean feature toggles via
set/set no:textobjects,navigation,hardwrap,listcontinuation,tablenav,workspacenav,easymotion,easymotiondimming,hintmode,statusbar,chorddisplay,powerline - Number options via
set <option>=<value>:scrolloff(0–20),scanlimit(5–200),labelfontsize(10–20) - String options:
easymotionlabels,hintlabels - Enum options:
tablewidget(off/cursor/always),whichkey(off/leader/all),whichkeygrouping(flat/grouped) - Mode prompt customization via
let g:mode_prompt_normal = "N"(and insert/visual/replace) - Which-key group labels via
whichkeygroup <leader>t Table— name key prefix groups in the which-key popup - Which-key command labels via
whichkeylabel <leader>w Save file— describe individual bindings in the which-key popup - Reverse-direction settings — clipboard, tabstop, shiftwidth, expandtab, insertmodeescape, and textwidth now have Settings UI controls (previously vimrc-only)
- Priority rule: vimrc values override Settings UI values when
enableVimrcis true. Overrides are in-memory only — the on-disk settings file always reflects UI-set values. Changing an overridden setting in the UI clears the override for the current session. - List merge: which-key group labels and command labels from vimrc are merged with labels configured in Settings. Vimrc entries appear as read-only rows; the "Add" button remains active for user additions. Vimrc wins on conflict.
sourcedirective fix: settings andguicursorin sourced vimrc files now propagate correctly (pre-existing bug whereonCursorShapeChangewas not passed to recursiveloadVimrcFilecalls)vimrcLoadingflag fix: the flag is now reset tofalseafter successful vimrc load, enabling runtime:setcommands to trigger immediatereloadFeatures()
- Boolean feature toggles via
- Vimrc
setcommand routing — all knownsetoptions are now handled directly in the vimrc loader via aKNOWN_SET_OPTIONSmapping table, callingonSettingOverridedirectly instead of relying ondefineOptioncallback dispatch throughvim.handleEx. This ensures reliable settings override regardless of codemirror-vim initialization order. Unknown options fall through tohandleExfor forward compatibility. - Spurious
defineOptioncallback prevention —registerVimOptionsnow uses aregisteredflag to preventdefineOptioncallbacks from firing during initial option registration (codemirror-vim callssetOption(name, defaultValue)internally duringdefineOption). Without this guard, every option with a truthy default would spuriously populatevimrcOverridesand triggerreloadFeaturesduring plugin startup. - E2E test suite
test/specs/vimrc-settings.e2e.tswith 11 tests covering boolean/number/string/enum option overrides, mode prompts, which-key labels, override tracking, and combined overrides set insertmodeescape=jknot working (frame-perfect timing required) — theInsertEscapeHandlerlistened tovim-keypressevents, which only fire for keys processed by codemirror-vim as vim commands. In insert mode, regular character keys bypass vim entirely and go through CM6's text input pipeline — the handler never saw them. Rewrote to use DOMkeydownevents on the editor element, correctly intercepting keystrokes in insert mode. Also fixed theinsertmodeescapevim option not storing its value forgetOption()retrieval (callback returnedundefinedinstead of the stored value). (#31)dknot deleting in operator-pending mode —dk(delete current and previous line) was a no-op becausetableAwareMoveUpwas registered withcontext: 'normal', causing it to be filtered out in operator-pending mode. CM Vim's keymap search then failed to fall through to the defaultkmotion. Removed the context restriction since the motion already handles operator-pending mode internally via itshasOperatorcheck.- Cursor snaps to formatting mark boundary in Live Preview — placing the cursor inside formatted text (
*italic*,**bold**,`code`,~~strike~~,==highlight==) would snap to the delimiter boundary instead of the intended position. Obsidian's Live Preview usesDecoration.replace({})to hide formatting marks on inactive lines, creating zero-width gaps that cause CM6's position mapping to collapse. Fixed by interceptingDecoration.replace({})viaRangeSetBuilder.prototype.addpatching (same pattern as the table widget suppressor) and suppressing empty replace decorations whose text matches known formatting marks (*,**,_,__,`,~~,==). AStateFieldtracks the current document for text extraction. CSS styling (color: transparent) hides the now-visible marks on inactive lines while preserving their positional space. %bracket matching skips brackets in strings and comments — the fork'sscanForBracketfallback now callsgetTokenTypeAt()for each bracket candidate and skips brackets inside"string"or"comment"tokens. Previously, positional stack counting would match a bracket inside a string literal. Note: in Markdown mode, Lezer does not classify double-quoted text as string tokens, so this primarily benefits languages with proper syntax trees.<</>>indent respectsshiftwidthandexpandtab— the fork's indent operator now reads the vim optionsshiftwidthandexpandtab(viagetOption()) before falling back to CM6'stabSizeandindentWithTabs. Whenset shiftwidth=2orset expandtabis set in.obsidian.vimrc, the indent operator uses those values for both visual-block and line-by-line indentation.Vlinewise visual cursor at end of line instead of column 0 — linewise visual mode (V,Vj, etc.) now positions the cursor at column 0 of the head line, matching Neovim. The fork'smakeCmSelectionwas settinghead.ch = lineLength(line)for display, which placed the cursor at the end of the line. AViewPluginwithDecoration.linenow provides the full-line visual highlight independently of the CM6 selection head position.- Vimrc map re-application — vimrc key mappings are now re-applied 200ms after initial load as a safety net against CM Vim initialization timing. If the initial
applyVimrcMapscall runs before the CM6 vim extension has fully settled, the delayed retry ensures mappings take effect.
Changed
minAppVersionbumped to 1.2.3 — required forsetDisabled()API on settings controls (used to disable vimrc-overridden settings in the UI). Obsidian 1.2.3 was released March 2023.
Documentation
KNOWN_LIMITATIONS.md: replaced "Desktop only" section with "Mobile support" section documentingPlatform.isMobileguards and feature-by-platform compatibility matrixREADME.md: updated Requirements from "Desktop only" to "Desktop and mobile" with link to known limitationsKNOWN_LIMITATIONS.md: added "Insert mode escape" section documenting thekeydown-based handler, configurable timeout, and thevim-keypressevent limitation; updatedvi*single-character status to fixed via formatting mark suppression; updated%+ strings to note Lezer limitation in Markdown; updated<<unindent entry to note fork fix; removedVlinewise cursor deviation; updatednmap L $section with investigation findings; added "Formatting mark suppression" sectionREADME.md: addedinsertmodeescapetimeoutto number options table and vimrc example; added insert mode escape timeout to settings listDIFFERENCES.md(fork): added sections forscanForBracketstring/comment awareness, indent operatorshiftwidth/expandtabsupport, linewise visual cursor positioning with decoration-based highlight
Full Changelog: 0.21.2...0.22.0