github saberzero1/motions 0.39.0

latest releases: 1.4.0, 1.3.1, 1.3.0...
3 months ago

Added

  • vim.ob.* API expansion (47 new functions across 4 sub-namespaces) — the vim.obsidian / vim.ob Lua namespace grows from 21 to 68 functions
    • Leaf introspection (Tier 1): vim.ob.get_leaf_type() returns the active view type string, vim.ob.get_active_leaf() returns {id, type, pinned, file_path} table, vim.ob.list_leaves() returns all open tabs, vim.ob.is_markdown_view() returns boolean
    • Command wrappers (Tier 2): vim.ob.follow_link(), vim.ob.backlinks(), vim.ob.daily(), vim.ob.search(), vim.ob.tags(), vim.ob.new_note(), vim.ob.rename(), vim.ob.toggle_checkbox(), vim.ob.template() — thin wrappers around Obsidian commands, silent no-op if required core plugin is disabled
    • Leaf management (Tier 3): vim.ob.focus(direction) navigates panes ("left", "right", "top", "bottom"), vim.ob.close_leaf() closes active tab, vim.ob.split(direction) splits vertically/horizontally, vim.ob.get_leaf_for_file(path) finds which leaf has a file open
    • vim.ob.meta.* sub-namespace (9 metadata query functions) — read-only access to note metadata via Obsidian's MetadataCache
      • vim.ob.meta.frontmatter(path?) — returns YAML frontmatter as a Lua table, or nil
      • vim.ob.meta.tags(path?) — returns combined body + frontmatter tags as string[]
      • vim.ob.meta.links(path?) — returns outgoing links as {link, display, original}[]
      • vim.ob.meta.backlinks(path?) — returns source file paths linking to this file as string[]
      • vim.ob.meta.headings(path?) — returns headings as {heading, level}[]
      • vim.ob.meta.embeds(path?) — returns embedded content as {link, display}[]
      • vim.ob.meta.aliases(path?) — returns YAML aliases as string[]
      • vim.ob.meta.tasks(path?) — returns checklist items as {text, status, line}[]
      • vim.ob.meta.lists(path?) — returns all list items as {text, line, indent}[]
      • All functions default to the current file when path is omitted
      • Plugin: src/lua/obsidian-api.ts, src/lua/api.ts, src/lua/loader.ts
    • vim.ob.fs.* sub-namespace (11 vault filesystem functions) — read and write vault files with config-dir guards
      • Read: vim.ob.fs.files(pattern?), vim.ob.fs.all_files(), vim.ob.fs.folders(), vim.ob.fs.exists(path), vim.ob.fs.stat(path?)
      • Write: vim.ob.fs.create(path, content?), vim.ob.fs.write(content) or vim.ob.fs.write(path, content), vim.ob.fs.append(content) or vim.ob.fs.append(path, content)
      • Management: vim.ob.fs.rename(new_path) or vim.ob.fs.rename(path, new_path), vim.ob.fs.move(dest) or vim.ob.fs.move(path, dest) (detects folder dest and appends filename), vim.ob.fs.trash(path?)
      • Write/rename/move/trash operations silently reject paths inside the vault config directory (app.vault.configDir)
      • rename uses fileManager.renameFile() which updates backlinks; trash uses fileManager.trashFile() which respects the user's trash preference
      • Write operations are fire-and-forget (async internally, Lua returns immediately)
      • All write operations default to the current file when path is omitted
      • Plugin: src/lua/obsidian-api.ts, src/lua/api.ts, src/lua/loader.ts
    • vim.ob.ui.* sub-namespace (4 UI control functions) — control Obsidian UI from Lua
      • vim.ob.ui.sidebar(side, state?) — toggle/open/close sidebar ("left"/"right", optional "open"/"close"/"toggle")
      • vim.ob.ui.command_palette() — open command palette
      • vim.ob.ui.quickswitch() — open quick switcher
      • vim.ob.ui.notice(msg) — alias for vim.notify (convenience for staying in vim.ob namespace)
      • Plugin: src/lua/obsidian-api.ts
    • vim.ob editor state and convenience functions — cursor, selection, mode, and notification access
      • vim.ob.get_cursor() — returns {line, col} (1-indexed, Lua/Neovim convention)
      • vim.ob.set_cursor(line, col) — sets cursor position (1-indexed)
      • vim.ob.get_selection() — returns visual selection text or nil
      • vim.ob.mode() — alias for vim.fn.mode() (convenience)
      • vim.ob.notice(msg) — alias for vim.notify (convenience)
      • Plugin: src/lua/obsidian-api.ts, src/lua/api.ts, src/lua/loader.ts
  • 3 new autocmd events — LeafEnter, LeafLeave, FileType (total: 15 events)
    • LeafEnter — fires when a new leaf gains focus (debounced 50ms), event data includes {type, leaf_id} in ev.data
    • LeafLeave — fires when a leaf loses focus (immediate, before LeafEnter)
    • FileType — fires after BufEnter with ev.match set to detected filetype from file extension (.md → "markdown", .ts → "typescript", etc.)
    • Enables Neovim-style per-filetype keymaps: vim.api.nvim_create_autocmd("FileType", { pattern = "markdown", callback = function() ... end })
    • Plugin: src/lua/autocmd.ts (fireFileType, onActiveLeafChange extension), src/main.ts (leaf info passthrough)
  • workspaceNavViewTypes setting — comma-separated list of view types where scroll and count keys are intercepted. Defaults to markdown,graph,pdf,canvas,empty,image. Plugin views not in this list receive their own keystrokes. Configurable via Settings → Vim Motions → Workspace navigation view types, vimrc (set workspacenavviewtypes=...), or Lua (vim.opt.workspacenavviewtypes = "..." or vim.opt.workspacenavviewtypes = {"markdown", "graph", "pdf"})
    • Plugin: src/settings.ts, src/vim/options.ts, src/vimrc/loader.ts
  • vim.opt table (array) support for string options — string-type options can now be set using Lua tables: vim.opt.workspacenavviewtypes = {"markdown", "graph", "pdf"} is equivalent to vim.opt.workspacenavviewtypes = "markdown,graph,pdf". Elements are joined with commas. Applies to all string-type options.
    • Plugin: src/lua/api.ts (vim.opt.__newindex table handling)

Fixed

  • Surround opening bracket semantics (ds(/ds[/ds{/cs({) — findSurroundingBrackets received swapped parameters when the target was an opening bracket ((, [, {, <), causing the backward search to look for the wrong bracket character. ds( on ( hello world ) was a no-op because the search looked for ) going backward. Fixed by detecting opening bracket targets and swapping parameters so the closing bracket is always passed as the forward-search character. Also fixes cs({, ds( on nested/multiline content, and ds<. (#41)
    • Fork: src/vim.js — findSurroundingPair bracket parameter ordering
  • Surround cursor position after ys/yss/visual S — addSurroundToRange placed the cursor at from.ch + pair.open.length (after the opening delimiter). nvim-surround places it at from.ch (on the opening delimiter). Fixed by removing + pair.open.length. The _surroundSelOffset.chDelta used for dot-repeat now adds pair.open.length at recording time to compensate, preserving correct visual surround replay ranges. (#41)
    • Fork: src/vim.js — addSurroundToRange cursor, surroundVisual offset recording
  • Visual-block cursor displaced rightward at end-of-line — in visual-block mode (<C-v>), selecting to the end of a line (via $ or l to EoL) caused the block cursor to render one position past the last visible character. The measureCursor() function in the fork's block-cursor.ts had a guard (!vim.visualBlock) that prevented the EOL step-back for visual-block mode. This guard was originally correct when makeCmSelection produced toCh + 1 without clamping, but after the per-line clamping fix (issue #38), block selection heads legitimately land on newline positions and need the step-back. Fixed by removing the !vim.visualBlock exclusion. (#41)
    • Fork: src/block-cursor.ts — measureCursor() EOL adjustment guard
  • Visual-block A skips short lines instead of padding — in visual-block mode, A (append) on a block spanning lines shorter than the block column skipped those lines entirely. Neovim pads short lines with spaces to reach the block's right edge before appending. Fixed by adding a padShortLines parameter to selectForInsert in the fork — A pads, I still skips (matching Neovim). (#41)
    • Fork: src/vim.js — selectForInsert() padding, enterInsertMode passes flag for endOfSelectedArea
  • Visual charwise r replaces one fewer character across line boundary — the replace action in the fork used curEnd = selEnd (the inclusive head position) for charwise visual mode, but cm.getRange() treats the end as exclusive. This caused r <Space> across a line boundary to replace one fewer character than the visual selection covered. Fixed by using selEnd.ch + 1 for the exclusive end. (#41)
    • Fork: src/vim.js — actions.replace charwise visual branch
  • set insertmodeescape=jk leaves j in buffer after escaping insert mode — the InsertEscapeHandler sent vim.handleKey(adapter, '<BS>') to delete typed characters before sending <Esc>, but codemirror-vim does not handle <BS> in insert mode (returns false, expecting the browser default action). Since handleKey is called programmatically with no DOM event, the backspace had no effect and the first character(s) of the escape sequence remained in the buffer. Fixed by replacing the handleKey('<BS>') loop with a direct adapter.replaceRange() call that deletes exactly escapeSeq.length - 1 characters before the cursor (the last key in the sequence is already intercepted by preventDefault and never enters the document). The native imap jk <Esc> mapping (via vimrc or vim.map()) was unaffected — codemirror-vim's changeQueue cleanup handles that path correctly.
    • Plugin: src/vim/insert-escape.ts (onKeyDown method)
  • scrolloff values above ~30 pin view at bottom of document — high scrolloff values (e.g., set scrolloff=999 to center the cursor) caused the viewport to pin at the top or bottom instead of centering. The scroll margin was passed to CodeMirror's EditorView.scrollMargins unclamped, producing a target rect taller than the viewport. CM6's scrollRectIntoView resolved the conflicting top/bottom constraints by favoring one side based on cursor direction. Fixed by clamping the margin to half the viewport height, mirroring Vim's silent cap of scrolloff to (window_height - 1) / 2. (#48)
    • Plugin: src/vim/scrolloff.ts — createScrolloffExtension() viewport-relative clamp
  • Workspace navigation intercepting keystrokes in plugin leaves — when workspace navigation was enabled, the global key handler consumed keystrokes (1, 2, 3, 0, j, k, etc.) in non-editor plugin views (Spaced Repetition, Excalidraw, etc.) before the plugin could process them. Fixed with a three-gate interception system: structural keys (<C-w>*, gt/gT, <C-o>/<C-i>, :) always work in non-editor views, content keys (scroll, digits, tab shortcuts) only intercept in whitelisted view types, and plugin views receive their own keystrokes. (#47)
    • Plugin: src/workspace/global-mapping-registry.ts (GlobalMapGate → 'standard' | 'hint' | 'structural'), src/workspace/global-defaults.ts (gate assignments), src/workspace/global-key-handler.ts (three-gate onKeydown rewrite, GLOBAL_NAV_VIEW_TYPES whitelist, shouldInterceptContent/shouldInterceptStructural methods)

Changed

  • minAppVersion bumped from 1.4.10 to 1.6.6 — required for Vault.getAllFolders() used by vim.ob.fs.folders()
  • vim.obsidian.* namespace extracted to dedicated module — the Obsidian-specific Lua API is now in src/lua/obsidian-api.ts (extracted from api.ts), following the pattern of fn.ts, stdlib.ts, timers.ts, highlight.ts. No behavioral change. api.ts shrinks by ~504 lines.

Documentation

  • docs/configuration/lua-config.md: added vim.ob.meta.* (9 functions), vim.ob.fs.* (11 functions), vim.ob.ui.* (4 functions), editor state functions (5 functions) with API tables and examples
  • KNOWN_LIMITATIONS.md: added "Workspace navigation in plugin views" section documenting three-gate interception and the gg-in-plugin-leaf trade-off; marked #47 as fixed; added "Neovim golden test coverage gaps" section documenting non-verifiable areas (scroll/viewport, fold, jumplist, cursor rendering); updated visual mode EOL cursor section with visual-block A padding and visual r off-by-one fixes
  • Neovim golden test coverage expansion — 106 new golden comparison test cases across 6 suites, recorded against Neovim 0.12.2:
    • surround (74 cases): comprehensive nvim-surround parity — ds/cs/ys/yss/visual S with all delimiter types, count-prefixed operations (2dsb, 2csbB), dot-repeat (ysiwb.., dsb.., csba..), tag surround (dst, cst), function surround (dsf), ysa (around surround), empty content, whitespace cascade (ds{ strips / ds} preserves), motion-based (ys$, ysjb), newline variants (ySS, VSB), angle brackets, arbitrary delimiters (|, ^), multiline, nesting, and cursor positioning. Ground truth shifted from tpope/vim-surround to nvim-surround — better maintained, comprehensive test suite, Lua-native, superset of tpope behavior. 54 pass, 20 tracked deviations.
    • dot-repeat (17 cases): . after 2dw, dd, 3i, 3o, cw, R, 2dl, d2w, g~2w, V>, 3J, 3I, visual block ~, o
    • select-mode-extended (6 cases): gh/gH enter select, type replaces, <BS> deletes, <Esc> exits, <C-g> toggles visual↔select
    • ex-sort (6 cases): :sort, :sort!, :sort i, :sort u, :sort n, :2,3sort
    • ex-global (3 cases): :g/pattern/d, :v/pattern/d, :g/a/s/a/x/
    • upstream-gaps (7 cases): dip paragraph, backward block A, block A short-line padding, visual r cross-line, block↔char/line mode switch, macro replay
    • Test infrastructure: SuiteDefinition.nvimSetup field for per-suite Neovim commands (loads nvim-surround for surround suite), NeovimClient.executeCommand() method
    • Total golden test coverage: 276 → 382 cases across 28 suites
  • docs/configuration/settings.md: added Workspace navigation view types to Vim features table
  • docs/configuration/vimrc.md: added workspacenavviewtypes (wnvt alias) to string options table
  • docs/configuration/lua-config.md: added 17 new vim.ob.* functions, 3 new autocmd events (LeafEnter, LeafLeave, FileType), workspacenavviewtypes option
  • docs/features/workspace-navigation.md: added "Plugin view compatibility" section with key passthrough table and whitelist customization
  • docs/guides/ecosystem-compatibility.md: added "Plugin leaf key passthrough" section
  • docs/configuration/lua-config.md: added table (array) syntax tip for string options with example

Full Changelog: 0.38.0...0.39.0

Don't miss a new motions release

NewReleases is sending notifications on new releases.