Emile Bangma9k downloadsFull Neovim experience inside Obsidian with Lua scripting, Flash/EasyMotion jumps, Telescope-style picker, Oil explorer, snippets, surround, animated cursor, undo tree, and Markdown text objects.
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.
d, c, y, v]h, ]l, ]n, ]b.obsidian.init.lua with conditional logic, function keymaps, vim.v predefined 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 real nvim_* 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.go global option fallbacks, vim.iter (26 methods; rpop, count, and size are extensions), vim.on_key (pre-mapping physical-key observation), Neovim key-byte conversion via nvim_replace_termcodes and decoding in nvim_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.uv timers, autocommands (19 events, mode events fire per-view across all editors), vim.obsidian namespace (including vim.obsidian.im for 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 via require() (with init.lua fallback, resolved synchronously from an in-memory snapshot so lazy require works inside keymap callbacks), plugin management with automatic GitHub fetching (vim.plugins.add, retaining Lua and .scm query files), collectgarbage() support, __gc userdata finalization, vim.o.operatorfunc support for g@ in bundled fork mode (also via vim.opt, vim.go, and global option APIs), vim.treesitter (backed by web-tree-sitter WASM — 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 (.scm edits 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.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 through nvim_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; native gq/gw uses the mirrored buffer's configured textwidth and stock Markdown ftplugin. The sidebar reads Neovim's native undotree() 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 an acwrite buffer: :w uses Obsidian's active-editor save command, while :e and :e! re-seed from the current Obsidian document instead of reading the file behind Obsidian's back. Fold persistence and the i=/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 p50 8.2 ms / p95 23.5 ms / p99 28.9 ms against the fork's 16.2 / 43.4 / 54.0 ms (deltas −8.0 / −19.9 / −25.1 ms), inside the ≤25 ms/≤60 ms budget. RPC leads at every percentile and in all four measured command classes, because a pipe round-trip to a native process costs less than running a full vim implementation in the renderer over a note that size. An optional configuration path can load a minimal Obsidian-specific init.lua under --clean; leaving it empty loads your normal Neovim setup. Multi-leaf buffer ownership is deferred. Because Neovim owns editor keys, editor keymaps from the plugin's own Lua and vimrc config do not fire in this mode; their equivalents belong in your Neovim config. 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 never downloads or installs Neovim itself. It can ask your Neovim to install or update the plugins it generates configuration for, but only when you press the button and confirm the preview.:, /, and ? input plus vim.ui.input and vim.ui.select prompts. 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..obsidian.vimrc loader with 100+ configurable settings, which-key support with Lucide icons, and hot-reload on savef/F/t/T with 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. Incremental s jump 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)<C-w>, gt/gT/Ngt, :sp/:vs), including native File Explorer navigation with h/j/k/l. Built-in hotkey conflict detection with resolution wizarddsf/csf for function calls, dot-repeat for ys with text objects, insert-mode <C-G>s with both delimiters inserted up front and full dot-repeat support). Every pair is user-definable, including the built-in ones — rebind ( to drop the inner spaces, " to curly quotes, or t/f/< away from their tag and function promptsgq/gw operators with prefix preservationgr{motion} replaces text with register contents without clobbering the register (vim-ReplaceWithRegister parity)<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).h/j/k/l navigation, 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 markdownnewfolder/notes.md creates 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>/q close, gx open in default app, g. toggle hidden files (blocked when unsaved changes exist), visual mode multi-select (V + <CR> opens all selected files)$VISUAL/$WORD vim aliases), choice nodes, context filtering. 60+ bundled Obsidian snippets. User-defined snippets via JSON files or LuaSnip-inspired Lua DSL with reactive f()/d() nodes:sp, :vs, :e, :grep, :ob, :Oil, :sidebar, :move, :copy, navigation/action aliases, and moref, F, yf, df, gf for context menu)statuscolumn API for custom gutter layouts (vim.opt.statuscolumn = "%s %l %r %C"). Cursor line highlight with Neovim's full cursorlineopt grammar (line, screenline, number, both, and comma lists — screenline highlights only the cursor's display row of a wrapped line), configurable number width, mobile-responsive gutter, and Obsidian's native line numbers suppressed when activesigncolumn (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<leader>1–<leader>9), cursor position tracking, persistence across sessions, auto-updating on file rename/deletezf/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-compatible foldopen option (structural motions like ]h, %, / auto-unfold; j/k leave folds closed — configurable via set foldopen=…), cross-session fold persistence, set foldenable toggle, and optional fold column gutter (set foldcolumn) with click-to-fold:IMToggle/:IMStatus ex commands, Lua API (vim.obsidian.im). Desktop only.<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.<C-o> and <C-i> navigate backward/forward through jump history across notes. Jumps recorded on gd, picker selection, harpoon, oil, EasyMotion, and 100+ other navigation paths. Persists across sessions. :jumps displays the list. set jumplist/set jumplistsize for configurationg-/g+ navigate chronologically across all branches with buffer content restoration. :earlier/:later by count, time, or save point. :undolist modal. 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, undoFileprefers-reduced-motion support, 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, smearMaxLengthw/b/e/ge override stopping at camelCase, snake_case, and kebab-case boundaries. Full Unicode support (Arabic, CJK, accented Latin, and other non-ASCII scripts). Opt-in setting.<C-a>/<C-x> cycling hex colors, booleans, dates, CSS values, and checkboxesvim.textobject.add() + vim.gen_spec.pair()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-reloadThird-party Lua compatibility (bundled runtime only): this paragraph describes the bundled fengari Lua shim, which is what runs on mobile and in bundled-fork mode. It does not describe the Neovim backend — that runs your own Neovim, so LuaJIT FFI is available and flash.nvim works there. See Neovim plugin compatibility for the backend's own boundary, which is decided by whether a plugin draws with extmarks or with screen cells. 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 in the bundled runtime by unavailable LuaJIT FFI. See API status for the audited blockers.
Search for "Vim Motions" in Settings → Community plugins → Browse.
main.js, manifest.json, and styles.css from the latest release.vim-motions in <your-vault>/.obsidian/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
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