0.112.0
Fixed
- Cursor shape dropdowns always disabled in Settings UI — the 5 cursor shape dropdowns (Normal, Insert, Visual, Replace, Operator-pending) on the Appearance page were permanently disabled even when Obsidian's built-in Vim mode was off. Root cause: Obsidian's
addSettingTab()immediately callsgetSettingDefinitions()and caches the result for rendering and search indexing. Inonload(),addSettingTab()ran beforecreateBundledVimExtension(), so thedisabledcallbacks closed overforkActive = false(aconstcaptured at the top ofgetSettingDefinitions()). The callbacks always returnedtrue(disabled) regardless of the actual fork activation state. Fixed by replacing the capturedforkActiveconst in all 5disabledcallbacks with a directisBundledVimActive()call, so Obsidian'srefreshDomState()always evaluates the current state. Additionally,this.declarativeSettingTab.update()is now called aftercreateBundledVimExtension()to refresh the cachedgetSettingDefinitions()result — this updates the static description text which cannot use a callback. (#128)- Plugin:
src/settings.ts(5 cursor shapedisabledcallbacks —!forkActive→!isBundledVimActive()) - Plugin:
src/main.ts(store setting tab reference asdeclarativeSettingTab; calldeclarativeSettingTab.update()aftercreateBundledVimExtension())
- Plugin:
- Animated cursor suppression not synced on
reloadFeatures()—setCursorSuppressed(this.settings.animatedCursor)was only called during initial plugin load (onload()), not duringreloadFeatures(). Any runtime setting change that calledreloadFeatures()(settings UI toggle, vimrcset smoothcursor, Luavim.opt.smoothcursor) did not update the global cursor suppression flag in the codemirror-vim fork. The animated cursor canvas would draw but the native CM6 block cursor was not suppressed, causing both cursors to render simultaneously. Also fixed the born-brokentable-cursor-suppression.e2e.tstest (5 of 6 failures since commit99e5fea) whoseenableAnimatedCursor()helper set the setting and calledreloadFeatures()but never triggered the global suppression. (#127)- Plugin:
src/main.ts(reloadFeatures()— addedsetCursorSuppressed(this.settings.animatedCursor)call)
- Plugin:
- Doubled cursors when animated cursor is disabled — when animated cursor was disabled, the native CM6 text caret (thin blinking bar) appeared alongside the fork's vim cursor (block/hollow) in normal, operator-pending, and replace modes after entering and leaving insert mode. Root cause: the fork's
BlockCursorPlugin.update()relied on a CSSbaseThemerule to hide native cursor layers, but mode transitions (insert removes.cm-vimMode, normal re-adds it) and CM6'sdrawSelectionextension left native layers visible due to CSS specificity conflicts. Fixed in the fork by unconditionally hiding native CM6 cursor layers and settingcaretColorto match the vim cursor color (var(--interactive-accent)) in insert mode. (#129)- Fork:
~/Repos/codemirror-vim/src/block-cursor.ts(BlockCursorPlugin.update()— unconditional native layer hiding, mode-awarecaretColor) - Fork:
~/Repos/codemirror-vim/DIFFERENCES.md(updatedsetCursorSuppressedAPI section)
- Fork:
- Doubled cursors in embedded editors (textarea vim overlay) — in the textarea vim overlay,
caretColorwas the accent color instead of transparent in normal mode, causing the native text caret to appear alongside the fork's block cursor. Root cause:BlockCursorPlugin.update()checked the.cm-vimModeDOM class to determine insert/normal mode, but CM6 ViewPlugin update ordering meant the class wasn't yet present when the block cursor plugin ran. Fixed in the fork by checkingthis.cm.state.vim.insertModedirectly instead of the DOM class. Also usessetProperty("caret-color", ..., "important")for CSS specificity robustness. (#130)- Fork:
~/Repos/codemirror-vim/src/block-cursor.ts(BlockCursorPlugin.update()— vim-state-basedcaretColorinstead of DOM class check) - Fork:
~/Repos/codemirror-vim/DIFFERENCES.md(added "Vim-state-based caretColor" subsection)
- Fork:
- Escape does not close footnote popover — pressing Escape twice (insert→normal, then idle normal) in the footnote popover editor did not close the popover. The user had to click outside to dismiss it. Root cause: the fork's
findKeyconsumed<Esc>unconditionally in idle normal mode, preventing the event from reaching Obsidian's popover close handler. Fixed with a two-part approach: (1) the fork now exposessetIdleEscapeCallback(fn)which fires when Escape is pressed in idle normal mode, and (2) the plugin registers a callback (installEscapeGuard) that dismisses the popover viaHoverPopover.hide()for non-workspace-leaf editors while silently consuming Escape in workspace-leaf editors (preventing Obsidian hotkey interference). (#130)- Fork:
~/Repos/codemirror-vim/src/vim.js(setIdleEscapeCallbackAPI,wasIdleNormalpre-capture infindKey) - Fork:
~/Repos/codemirror-vim/DIFFERENCES.md(addedsetIdleEscapeCallbackAPI section) - Plugin:
src/vim/escape-guard.ts(NEW —installEscapeGuardwithHoverPopover.hide()dismissal) - Plugin:
src/main.ts(installEscapeGuard(this.app)call in feature registration)
- Fork:
- Invisible cursor in footnote popover with animated cursor enabled — the animated cursor canvas (
z-index: 15) renders behind Obsidian's popover (z-index: 30). The fork's vim cursor was also suppressed (globalsetCursorSuppressed(true)), resulting in no visible cursor. Fixed by detecting editors inside.popoveror.modal-containerin theCursorControllerand un-suppressing the fork's vim cursor for those views (setCursorSuppressedForView(view, false)). The animated cursortick()skips rendering for above-canvas editors. (#130)- Plugin:
src/vim/animated-cursor/controller.ts(isAboveCanvasflag, per-view un-suppression for popover/modal editors,tick()early return)
- Plugin:
- Stale cursor suppression after animated cursor toggle — when animated cursor was disabled at runtime,
CursorController.update()returned early without clearing the per-view suppression override set in the constructor, leaving the fork's vim cursor hidden. Also, the constructor unconditionally suppressed the cursor regardless ofconfig.enabled. Fixed by gating constructor suppression onconfig.enabledand callingclearCursorSuppressedForView()in the disabled early-return path. (#130)- Plugin:
src/vim/animated-cursor/controller.ts(constructor gates onconfig.enabled,update()clears per-view override when disabled)
- Plugin:
Changed
- Internal API type safety — obsidian-typings migration (round 2) — eliminated 23 additional
as unknown ascasts across 16 source files by leveraging@obsidian-typings/obsidian-public-latestv6.32.0 typed APIs. Totalas unknown ascount reduced from 90 → 67. The remaining 67 casts are inherent to plugin architecture (dynamic settings indexing, codemirror-vim fork adapter access, external plugin window globals, fengari Lua bridge, minAppVersion compatibility guards).src/util/commands.ts:app.commands.executeCommandById()andapp.commands.commandsaccessed directly via typedCommandsinterface; customObsidianCommandnarrowed toPick<Command, 'id' | 'name'>src/util/leaf.ts:leaf.idandleaf.pinnedused directly (required properties viaWorkspaceItem/WorkspaceLeafaugmentation);getViewFilePath()/getViewFileBasename()useinstanceof FileViewguard instead ofas unknown as { file? }castsrc/util/vault.ts:ConfigItemimported from@obsidian-typings/obsidian-public-latestreplacing customVaultConfigKeytype inferencesrc/workspace/global-defaults.ts:mdView.getMode()called directly (typed asMarkdownViewModeType)src/editors/embeddable-editor.ts:app.embedRegistryaccessed directly;editorApp.scopeaccessed directly (official API);workspace.activeEditorassignment typed viaMarkdownFileInfosrc/oil/keybindings.ts,src/oil/manager.ts:app.internalPlugins.getEnabledPluginById('file-explorer')returns typedFileExplorerPluginInstancewithrevealInFolder(item: TAbstractFile)src/oil/oil-view.ts:this.leaf.updateHeader()called directly (typed onWorkspaceLeafaugmentation)src/oil/manager.ts:app.openWithDefaultApp(path)called directly (typed onAppaugmentation)src/vim/native-table-adapter.ts:EditModetype extendsMarkdownEditViewinstead ofRecord<string, unknown>;view.editModeaccessed directly;isInLivePreview()usesview.getMode()+editMode.sourceModeinstead ofgetState()cast;getEditModeForView()usesinstanceof MarkdownViewguardsrc/vim/table-cell-cursor-guard.ts:mdView.editor.cmaccessed directly (typed asEditorViewviaEditoraugmentation)src/ui/global-ex-command.ts:this.inputElaccessed directly (officialSuggestModal.inputEl)src/lua/loader.ts:view.getViewType()called directly (officialViewAPI) — 3 instancessrc/settings.ts:this.display()andthis.refreshDomState()— reverted, casts retained to bypassobsidianmd/no-unsupported-apiand@typescript-eslint/no-deprecatedlint rules (pluginminAppVersionis 1.7.2; these APIs require/deprecate at 1.13.0)src/picker/sources/tasks.ts:app.plugins.plugins['obsidian-tasks-plugin']accessed directly via typedPluginsinterface
Documentation
CHANGELOG.mdKNOWN_LIMITATIONS.md: Updated cursor shapes section withaddSettingTab()caching fix (#128); added doubled cursors fix (#129); added #130 fixes (doubled cursors in embedded editors, Escape popover dismiss, invisible cursor in popovers, stale cursor suppression)AGENTS.md: Updated codemirror-vim fork cursor suppression description (vim-state-basedcaretColor,setIdleEscapeCallbackAPI)CONTRIBUTING.md: Addedescape-guard.tsto codebase structure; updatedcontroller.tsdescription withisAboveCanvasflag and popover/modal fallbackdocs/features/animated-cursor.md: Updated embeddable editors section with popover/modal fallback and z-index explanation- Fork
DIFFERENCES.md: AddedsetIdleEscapeCallbackAPI section; added "Vim-state-based caretColor" subsection; updatedfindKeyEscape handling description
Full Changelog: 0.111.0...0.112.0