Added
New Vim commands
Q— replay last recorded macro (Neovim default, maps to@@)Y— yank to end of line (Neovim default, maps toy$; overrides CM Vim'syybehavior)ga— show character info under cursor (codepoint, hex, octal) via Noticegp— paste and move cursor past pasted textgn/gN— select next/previous search match (CM Vim native, now tested)g;/g,— jump to older/newer change position (changelist navigation)zO/zC/zA— recursive fold open/close/toggle (maps to Obsidian's fold commands)it/at— HTML/XML tag text objects, implemented via raw text scanning since CM Vim's built-inexpandToTagis inactive in Markdown mode. Supports single-line, multiline, and nested tags.<C-v>— visual block mode (CM Vim native, now tested)
New Ex commands
:e {file}/:edit {file}— open file by name in vault:e!/:edit!— revert current file to saved version:enew— create new untitled note:saveas {file}— save current buffer as new file:update/:up— save current file (alias for:w):x/:xit— write-if-modified and close:xa/:xall— write-if-modified all and close all:find {file}/:fin— find and open file by partial name match:read {file}/:r— insert file contents at cursor position:b {name}/:buffer {name}— switch to tab matching name:bf/:bfirst— go to first tab:bl/:blast— go to last tab:bw/:bwipeout— close current tab:sp/:split— horizontal split:vs/:vsplit— vertical split:new— horizontal split with new note:vnew— vertical split with new note:tabnew/:tabedit— open new tab (optionally with file):tabclose/:tabc— close current tab:tabonly/:tabo— close all other tabs:tabfirst/:tabrewind— go to first tab:tablast/:tabl— go to last tab:version/:ve— show plugin version:delmarks {marks}— delete specified marks:changes— show change list in modal
Test infrastructure
- Shared test helpers module (
test/helpers.ts) withsetupEditor,getCursorPos,getEditorValue,getRegisterContent,getVimMode,vimKeys, and timing constants unsupported()anddeviation()test helpers for documenting known limitations and behavioral differences in test reports- Neovim command index manifest (
test/neovim-command-index.yaml) tracking 227 commands with tier classification, test status, and test file references - Coverage report script (
test/coverage-report.ts) — run vianpm run test:coverage - 16 new test files in
test/specs/vim-builtin/covering normal mode motions, search, editing, yank/put, insert entry, scroll, marks/jumps, g-commands, z-commands, bracket commands, text objects, operators, visual mode, insert mode, and Ex commands - 7 spike tests for register access, paste marks, editor extensions, tag text objects, CM Vim Ex command probing, Ex command conflict checking, and vimrc mapping diagnostics
- Comprehensive E2E test coverage for
<C-w>h/j/k/lpane focus,H/M/Lscreen-relative motions,?backward search,zO/zC/zArecursive folds, and all new Ex commands - E2E test for scrolloff hot-reload: verifies scroll margins update when
scrolloffLineschanges - E2E test for
Y/Qindependence from workspace navigation: verifiesYstill yanks to end of line when workspace nav is disabled - GitHub issue templates (bug report, feature request) with required KNOWN_LIMITATIONS.md checklist
Fixed
- Scrolloff now works correctly — previously used CSS
scroll-paddingwhich CodeMirror 6 ignores (it uses manual scroll calculations, notElement.scrollIntoView). Replaced withEditorView.scrollMarginsfacet, which CM6 respects when scrolling the cursor into view - Scrolloff setting now applies immediately when changed in settings — previously required a plugin reload because the slider's
onChangehandler did not triggerreloadFeatures()andreloadFeatures()itself had no scrolloff handling - Removed deprecated
setDynamicTooltip()call on scrolloff slider — the value is now always shown inline by Obsidian Y(y$) andQ(@@) Neovim default remaps now work regardless of the "Workspace navigation" toggle — previously these were registered insideregisterWorkspaceNavigation()and would stop working when workspace nav was disabled- Vimrc loader now shows a Notice on load: reports the number of commands applied on success, warns when the file is not found, and warns when the file contains no commands
- Vimrc commands are now processed through codemirror-vim's Ex command handler (
handleEx) instead of the programmatic API, matching obsidian-vimrc-support's approach for improved compatibility - ESLint
import/no-extraneous-dependencieserror on@codemirror/view— addedimport/core-modulessetting andpeerDependenciesfor@codemirror/*packages provided by Obsidian at runtime - Removed unused variables:
totalLinesintag.ts,openEndIndex/closeStartIndexintag.ts,activeincommands.ts,newLeafincommands.ts
Changed
- Scrolloff implementation rewritten from CSS
scroll-paddinginline styles toEditorView.scrollMarginsextension registered viaregisterEditorExtension. TheScrolloffManagerclass no longer manages event listeners or DOM manipulation — it updates a shared margin variable read by the CM6 facet callback. - Refactored 8 existing test files to use shared helpers from
test/helpers.tsinstead of locally definedgetEditorValue,getCursorLine, andvimKeysfunctions - Test-vault
hotkeys.jsonnow unbinds Obsidian shortcuts that conflict with Vim commands (Ctrl+W,Ctrl+N,Ctrl+P,Ctrl+S,Ctrl+O) - Tag text objects (
it/at) changed fromunsupportedskip to working plugin-implemented text objects ChangeListclass gainsgetEntries()andgetIndex()public accessors for the:changesEx commandYandQNeovim default remaps moved fromregisterWorkspaceNavigation()to the always-on initialization path inonload()andreloadFeatures()- Vimrc loader's
loadVimrc()now returns aVimrcLoadResultwithfound,commandCount,path, andmapsfields - Vimrc loader refactored to use
vim.handleEx()for command application instead of directvim.map()/vim.setOption()API calls, improving compatibility with obsidian-vimrc-support configurations - Vimrc loader now collects parsed map commands as
DeferredMapentries and re-applies them viavim.map()/vim.noremap()on subsequentactive-leaf-changeevents, attempting to restore mappings that CM Vim may lose during editor reinitialization - Vimrc loader intercepts
set textwidth=N/set tw=Nlines and directly updates the plugin's internaltextwidthValue, bypassing CM Vim's option callback chain getTextwidth()now reads from CM Vim's option viaVim.getOption('textwidth')as a fallback when the plugin's internal value hasn't been updated- Vimrc loading deferred to first
active-leaf-changeevent to guarantee editor availability, matching obsidian-vimrc-support's loading strategy
Documentation
- README: added
:wa/:wallto Ex commands table,g<C-t>to workspace keybindings table - README: corrected
set textwidth=Nclaim — now notes the known limitation and provides the runtime workaround via developer console KNOWN_LIMITATIONS.mdexpanded with comprehensive "Neovim Ex commands not applicable in Obsidian" section covering 30+ commands across 8 categories (shell, quickfix, tags, scripting, diff, etc.) with specific reasoningKNOWN_LIMITATIONS.mdexpanded with "Behavioral deviations" section documenting 6 commands that work differently from Neovim (Y,Q,:wall,gf,zO/zC/zA,it/at)KNOWN_LIMITATIONS.md: added "nmap L $does not work via vimrc" section with full diagnostic findingsKNOWN_LIMITATIONS.md: added "set textwidthvia vimrc does not affectgq" section with root cause analysisKNOWN_LIMITATIONS.md: replaced "Scrolloff cleanup on disable" section with "Scrolloff line height assumption" (22px hardcoded)
Full Changelog: 0.4.0...0.5.0