A polished, Neovim-native experience inside Obsidian. Vim Motions adds what's missing from Obsidian's built-in Vim mode: Markdown-aware text objects, structural navigation, hard-wrap formatting, workspace keyboard control, EasyMotion, Lua configuration with vim.keymap.set / vim.opt / vim.fn / vim.api / vim.tbl_* / autocommands / timers / highlight groups, and a built-in .obsidian.vimrc loader.
- Markdown text objects — operate on bold, italic, code, math, links, blockquotes, code blocks, callouts, tags, table cells, subwords, numbers, quotes, wikilinks, URLs, arguments, and indentation with
d,c,y,v - Structural navigation — jump between headings, lists, links, and buffers with
]h,]l,]n,]b - Lua configuration —
.obsidian.init.luawith conditional logic, function keymaps,vim.vpredefined variables (count,count1,register,operator,searchforward,maxcol, constants),{ expr = true }expression mappings,vim.fn.*(92 real implementations with async callbacks, including byte/character/display-column helpers, line byte offsets,deletebufline, and CM6 viewport geometry),vim.api.*(69 realnvim_*implementations: buffer, cursor, marks, keymaps, options, commands, highlights, namespaces, extmarks, autocommands, mode query, vvars, byte offsets, and synthetic current-window calls/dimensions/identity),vim.o/vim.goglobal option fallbacks,vim.iter(26 methods;rpop,count, andsizeare extensions),vim.on_key(pre-mapping physical-key observation), Neovim key-byte conversion vianvim_replace_termcodesand decoding innvim_feedkeys,vim.tbl_*,vim.snippet.*,vim.json,vim.inspect,vim.regex(Vim patterns),vim.validate(full Neovim spec),vim.version(parse, compare, range),vim.keycode,vim.schedule/vim.defer_fn/vim.uvtimers, autocommands (19 events, mode events fire per-view across all editors),vim.obsidiannamespace (includingvim.obsidian.imfor input method control), buffer-local keymaps, async file reading (vim.ob.fs.read), async key input (vim.fn.getcharstr), async user prompts (vim.fn.input), regex buffer search (vim.fn.searchpos), extmarks (nvim_buf_set_extmark), multi-file configs viarequire()(withinit.luafallback, resolved synchronously from an in-memory snapshot so lazyrequireworks inside keymap callbacks), plugin management with automatic GitHub fetching (vim.plugins.add, retaining Lua and.scmquery files),collectgarbage()support,__gcuserdata finalization,vim.o.operatorfuncsupport forg@in bundled fork mode (also viavim.opt,vim.go, and global option APIs),vim.treesitter(backed byweb-tree-sitterWASM —get_parser,get_node,query.parse/get/set/get_files, user/plugin/bundled queries with extension and inheritance modelines,Query:iter_captures/iter_matches, LanguageTree, 31 TSNode methods, 8 built-in predicates, 4 directives), five real string-coordinate helpers (str_byteindex,str_utfindex,str_utf_start,str_utf_end,str_utf_pos), and config hot-reload on save (.scmedits require a configuration reload). Coordinate correctness is scoped to 23 enumerated APIs, now including text, legacy positions and extmark columns. Interior-byte cursor/text/extmark writes normalize instead of preserving Neovim's byte remainders; text reads preserve exact bytes, and cursor goals use the fork's partial state. Other coordinate seams remain deferred. See known limitations. - Neovim backend with Obsidian bridge — optional desktop-only connection to a user-supplied Neovim 0.12+ binary over msgpack-RPC. Neovim receives ordinary Markdown-editor keys through
nvim_input, owns text, mode, cursor, registers, undo, folds, dot-repeat, macros, persistent extmarks, and floating windows, and mirrors line events back into an input-inert CM6 editor. A cursor-positioned input outside CM6 owns native IME composition and forwards only committed text throughnvim_input, preserving undo and dot-repeat. Visible extmarks and redraw-time fold state render as matching CM6 decorations and folds. Floats render as Obsidian overlays with their buffer content, extmarks, border presence, and z-index; row, column, width, and height use measured CM6 cell metrics, so terminal-grid placement on proportional Markdown typography is approximate. Registry-generated callbacks restore all built-in picker leader actions and picker ex commands,:Oil, every Harpoon action and ex callback, host-owned cross-note<C-o>/<C-i>,:marks/:delmarks/:jumps, workspace navigation, go-to-definition variants, and the three undo-tree sidebar commands. Structural heading, list, and link motions and Markdown text objects execute inside Neovim as buffer-local companion mappings backed by native treesitter, with bounded operator ranges, visual selections, registers, and count handling matching the fork; nativegq/gwuses the mirrored buffer's configuredtextwidthand stock Markdown ftplugin. The sidebar reads Neovim's nativeundotree()data; fold and undo commands remain native Neovim operations. Oil's embedded editor is deliberately outside RPC key delegation, so all 16 Oil mappings continue through its bundled Vim engine without duplicating actions or leaking keys into Neovim. Slot strings, counts, picker queries, named sources, and resume state cross the general payloads; cross-note actions re-seed the active note and restore its stored cursor in CM6 and Neovim. Lowercase within-buffer mark motions remain Neovim-native; uppercase cross-file mark motions are deferred. Lowercase bridged commands use guarded command-line abbreviations and do not expand inside substitutions. Both Properties in document modes are supported: Source frontmatter stays navigable, while rendered frontmatter is protected by a closed Neovim fold and properties-widget input remains owned by Obsidian. The mirror is anacwritebuffer::wuses Obsidian's active-editor save command, while:eand:e!re-seed from the current Obsidian document instead of reading the file behind Obsidian's back. Fold persistence and thei=/a=highlight object are unavailable under RPC; the bundled Markdown parser exposes no highlight node. M7 latency is certified: over 500 measured keystrokes per condition on a ~2000-line note, RPC measured p95 36.2 ms against the fork's 39.5 ms (p95 delta −3.3 ms, p99 −4.4 ms), within the ≤25 ms/≤60 ms budget; the round-trip cost is visible at p50, where the fork leads by about 2 ms. An optional configuration path can load a minimal Obsidian-specificinit.luaunder--clean; leaving it empty loads your normal Neovim setup. Multi-leaf buffer ownership is deferred. Enabling it runs the binary and configuration you supply as arbitrary code. That code may load native libraries through LuaJIT FFI and read or write files outside the vault. No sandbox is provided. Vim Motions neither downloads nor installs Neovim or its plugins. - Neovim external UI — RPC-mode errors, warnings, notifications, echoes, Lua prints, and shell output appear as severity-styled, duplicate-limited Obsidian Notices; routine undo and search messages remain silent. A byte-correct, nested external command line renders
:,/, and?input plusvim.ui.inputandvim.ui.selectprompts. The external popup menu shows command-line wildmenu and insert completion with live selection, and Neovim's own mode output owns the plugin status bar until RPC disconnects. - Built-in vimrc —
.obsidian.vimrcloader with 100+ configurable settings, which-key support with Lucide icons, and hot-reload on save - Flash motions — enhanced
f/F/t/Twith labels on all visible matches (flash.nvim-inspired). Auto-jumps on single match, count prefix honored (3f{char}jumps to 3rd match without labels). Operator-pending (df,cf,yf), visual mode, multi-line search. Incrementalsjump mode (type multiple chars to narrow, labels update live), post-commit//?search labels, clever-f repetition, label conflict skipping,[3/15]search match counter. Dynamically sized match highlights and labels positioned after matched text (flash.nvim parity) - EasyMotion / Hop — jump to any visible position with two keystrokes, with operator-pending support
- Workspace keyboard control — navigate panes, tabs, and sidebar without a mouse (
<C-w>,gt/gT/Ngt,:sp/:vs). Built-in hotkey conflict detection with resolution wizard - Surround — add, change, or delete surrounding delimiters (vim-surround with Markdown support, including
dsf/csffor function calls, dot-repeat foryswith text objects, insert-mode<C-G>swith both delimiters inserted up front and full dot-repeat support) - Hard-wrap formatting — Markdown-aware
gq/gwoperators with prefix preservation - Replace-with-register —
gr{motion}replaces text with register contents without clobbering the register (vim-ReplaceWithRegister parity) - Yank-ring paste cycling — cycle through numbered register history with
<C-p>/<C-n>after pasting. Wraps around registers"1–"9. Cancels on any non-cycling command. Dot-repeat (.) replays the final cycled text (yanky.nvim parity). - Table editing — cell navigation, text objects, manipulation commands, format-on-exit auto-alignment, and native table editor integration with vim-enabled per-cell editing, cross-cell
h/j/k/lnavigation, and optional table-nav overlay with direct table manipulation (o,dd,J/K,H/L,=). Three modes: native with nav overlay (default), native without overlay, or raw markdown - Oil explorer — oil.nvim-inspired file manager: edit directories as buffers, create/rename/delete files with vim commands, nested path creation (
newfolder/notes.mdcreates both directory and file). Matching oil.nvim keybindings:<CR>opens in same leaf,<C-t>new tab,<C-s>/<C-h>vertical/horizontal split,<C-p>preview toggle,<C-c>/qclose,gxopen in default app,g.toggle hidden files (blocked when unsaved changes exist), visual mode multi-select (V+<CR>opens all selected files) - Telescope-style picker — fuzzy finder with 14 built-in sources (files, buffers, commands, headings, outline, grep, live grep, marks, registers, tags, backlinks, recent, harpoon, snippets), preview pane, frecency scoring, bundled integrations for Omnisearch, Obsidian Tasks, and Dataview, and a provider API for external plugin integration
- Snippets — VS Code-compatible snippet expansion with tabstop navigation, 37 variables (full VSCode spec +
$VISUAL/$WORDvim aliases), choice nodes, context filtering. 60+ bundled Obsidian snippets. User-defined snippets via JSON files or LuaSnip-inspired Lua DSL with reactivef()/d()nodes - 100+ ex commands —
:sp,:vs,:e,:grep,:ob,:Oil,:sidebar,:move,:copy, navigation/action aliases, and more - Vimium-style hints — navigate the entire Obsidian UI with keyboard hints (
f,F,yf,df,gffor context menu) - Line numbers — configurable line number gutter with absolute, relative, and hybrid modes. Neovim-compatible
statuscolumnAPI for custom gutter layouts (vim.opt.statuscolumn = "%s %l %r %C"). Cursor line highlight with Neovim's fullcursorlineoptgrammar (line,screenline,number,both, and comma lists —screenlinehighlights only the cursor's display row of a wrapped line), configurable number width, mobile-responsive gutter, and Obsidian's native line numbers suppressed when active - Marks — dedicated sign column gutter showing mark letters next to marked lines, configurable via
signcolumn(auto/always/off), consistent font size regardless of content, gutter layout matching Neovim (sign column → line numbers → fold column), global mark persistence across files and sessions (A–Z), and a grouped marks picker with cross-file navigation - Harpoon — pin files to numbered slots for instant switching (
<leader>1–<leader>9), cursor position tracking, persistence across sessions, auto-updating on file rename/delete - Fully remappable keybindings — every keybinding can be customized via Lua or vimrc across all contexts (editor, oil explorer, picker, workspace)
- Folding — full Neovim-style fold commands:
zf/zF(create),zd/zD(delete, recursive),zE(eliminate all),zo/zO/zc/zC/za/zA(open/close/toggle, with recursive variants),zm/zM/zr/zR(incremental and global level),zn/zN/zi(fold enable/disable/toggle),zv(reveal cursor),zx/zX(reapply fold level),zj/zk(fold motion navigation with hierarchical sibling-fold semantics),[z/]z(enclosing fold boundary navigation). Custom heading fold provider trims trailing blank lines for Neovim-accurate fold ranges. Custom fold providers for frontmatter and callouts, descriptive fold placeholder text, Neovim-compatiblefoldopenoption (structural motions like]h,%,/auto-unfold;j/kleave folds closed — configurable viaset foldopen=…), cross-session fold persistence,set foldenabletoggle, and optional fold column gutter (set foldcolumn) with click-to-fold - Input method switching — automatic IM switching for CJK users when entering/leaving insert mode. Supports macism, im-select, fcitx5-remote, ibus, and any external binary. Platform presets for one-click setup, per-view state across all editors (split panes, popovers, canvas cards) with session persistence, composition guard,
:IMToggle/:IMStatusex commands, Lua API (vim.obsidian.im). Desktop only. - Vim in text areas — focused
<textarea>elements in modals and plugin UIs are replaced with a vim-enabled editor overlay. Starts in insert mode for transparent typing; press Escape for normal mode, second Escape returns to modal. Experimental, disabled by default. Desktop only. - Cross-note jump list —
<C-o>and<C-i>navigate backward/forward through jump history across notes. Jumps recorded ongd, picker selection, harpoon, oil, EasyMotion, and 100+ other navigation paths. Persists across sessions.:jumpsdisplays the list.set jumplist/set jumplistsizefor configuration - Undo tree — undotree-style branching undo history visualization.
g-/g+navigate chronologically across all branches with buffer content restoration.:earlier/:laterby count, time, or save point.:undolistmodal. Sidebar view (:UndoTreeToggle) with tree rendering, keyboard nav, collapse/expand, diff preview.vim.fn.undotree()Lua API. Optional persistence (set undofile). 5 settings:enableUndoTree,undoTreeMaxNodes,undoTreePosition,undoTreeAutoOpen,undoFile - Animated cursor — canvas-based smooth cursor movement and smear-cursor.nvim-style spring-damper smear trail. Per-mode cursor shapes, configurable stiffness/damping/smoothness,
prefers-reduced-motionsupport, cross-platform resilience (3-gear frame governor, heartbeat safety net, error recovery, visibility-change wakeup, scroll tracking, dirty-rect clearing, fractional DPI rounding), and full vimrc/Lua configuration (set smoothcursor/vim.opt.smoothcursor). Disabled by default. 8 settings:animatedCursor,smoothCursor,cursorSmoothness,smearTrail,smearStiffness,smearTrailingStiffness,smearDamping,smearMaxLength - Subword motions — spider.nvim-style
w/b/e/geoverride stopping at camelCase, snake_case, and kebab-case boundaries. Full Unicode support (Arabic, CJK, accented Latin, and other non-ASCII scripts). Opt-in setting. - Enhanced increment/decrement — dial.nvim-style
<C-a>/<C-x>cycling hex colors, booleans, dates, CSS values, and checkboxes - Custom text objects — define delimiter-pair text objects from Lua via
vim.textobject.add()+vim.gen_spec.pair() - External grep — optional ripgrep or GNU grep binary for native-speed vault search in the picker. Desktop only with in-memory fallback.
- Quality of life: Neovim defaults (
Y/Q/g&/gM/K/]<Space>/[<Space>/v_*/v_#/g<C-A>/g<C-X>), 12 configurable Neovim options (ignorecase,smartcase,hlsearch,incsearch,wrapscan,gdefault,startofline,whichwrap,virtualedit,joinspaces,shiftround,nrformats), vim toggle commands (toggle-vim-mode,enable-vim-mode,disable-vim-mode), yank highlight, smart list continuation, scrolloff, insert escape sequences, chord display, powerline status bar, Neovim option compatibility (every Neovim option recognized — typos produce warnings, irrelevant options are silently accepted), and settings hot-reload
Third-party Lua compatibility: API counts measure registered surface, not semantic correctness; repairing already-real handlers leaves those counts unchanged. mini.surround and mini.splitjoin remain audit-blocked after the coordinate fixes; their behavior suites remain gated. mini.splitjoin's string expression mapping requires unavailable Vimscript evaluation, an architectural constraint rather than a missing-function to-do. The built-in surround feature above is separate. Existing mini.comment tests cover specific operations and now pin commit 27a29d6b949b9497f80a0a03421e89fed71d8c37 for reproducibility. flash.nvim is blocked by unavailable LuaJIT FFI. See API status for the audited blockers.
Search for "Vim Motions" in Settings → Community plugins → Browse.
- Download
main.js,manifest.json, andstyles.cssfrom the latest release. - Create a folder
vim-motionsin<your-vault>/.obsidian/plugins/. - Copy the downloaded files into that folder.
- Restart Obsidian and enable the plugin in Settings → Community plugins.
Disable Obsidian's built-in Vim mode (Settings → Editor → Vim key bindings → off). Vim Motions provides its own enhanced vim engine — a fork of codemirror-vim — with Neovim-correct behavior, async motion support, correct cursor positioning in Live Preview, and theme-aligned styling.
The plugin also works with built-in vim mode enabled, but the fork provides a more accurate Vim experience. See the recommended setup guide for details.
Full documentation: https://saberzero1.github.io/motions
- Getting started
- Features
- Lua configuration
- Settings reference
- Keybinding cheat sheet
- Remapping guide
- Known limitations
- Changelog
- Obsidian v1.7.2 or later
- Desktop or mobile (physical keyboard recommended on mobile)
npm install # Install dependencies
npm run dev # Development build (watch mode)
npm run build:dev # Development build (one-shot, with __DEV__ assertions)
npm run build # Production build
npm run lint # Lint
npm run test:unit # Unit tests (Vitest)
npm run test:e2e # E2E tests (requires nix develop)See CONTRIBUTING.md for the full development guide, testing strategy, and contribution guidelines.
MIT — Emile Bangma