github saberzero1/motions 0.90.0

latest releases: 0.150.0, 0.149.0, 0.148.0...
one month ago

Fixed

  • Note freezes in Reading Mode after closing Oil explorer — closing the Oil explorer view (via q, :q, :wq, or Lua vim.ob.oil.close()) reopened the previous file in Obsidian's default mode (often Reading/Preview) instead of the mode the user was in when they opened Oil. Root cause: openOil() captured previousFile (path only) but not the editor's view mode. Fixed by capturing previousViewMode (the MarkdownView state: source mode, live preview, or reading mode) when opening Oil and restoring it via leaf.openFile(file, { state: previousViewMode }) on close. All 4 close paths (keybindings q, ex commands :q/:wq, and Lua API vim.ob.oil.close()) are unified into a single closeOil() method on OilManager. (#93)
    • Plugin: src/oil/oil-view.ts (previousViewMode field, getState/setState extended, getPreviousViewMode getter), src/oil/manager.ts (openOil captures mode via MarkdownView.getState(), new closeOil() shared method with mode restoration), src/oil/keybindings.ts (oilClose delegates to manager.closeOil()), src/workspace/commands.ts (closeOilView delegates to oilManager.closeOil()), src/main.ts (Lua API oilClose delegates to oilMgr.closeOil())
  • Cursor focus lost when switching back to Oil tab — after opening a file from Oil and then switching back to the Oil tab via gT or Obsidian's tab navigation, the cursor focus was missing. Keystrokes were not captured by the Oil editor until the user clicked with the mouse. Root cause: Oil's editor focus was set only once in onOpen() and never re-applied when switching back. Fixed by adding a focusEditor() method to OilView and calling it from OilKeybindingManager.onActiveLeafChange() when switching into an Oil view. (#93)
    • Plugin: src/oil/oil-view.ts (focusEditor() public method), src/oil/keybindings.ts (onActiveLeafChange calls view.focusEditor() when switching to Oil)
  • :Oil . opens current file's directory instead of vault root — running :Oil . opened the directory containing the current active file rather than the vault root. In oil.nvim, . means current working directory, which maps to the vault root in Obsidian. Root cause: the condition if (!dirPath || dirPath === '.' || dirPath === '/') treated . identically to an empty argument. Fixed by separating . and / into their own branch that resolves to vault root (""), while the empty-argument case continues to resolve to the current file's parent directory. Both the ex command handler (commands.ts) and global ex command handler (global-ex-command.ts) are updated. (#93)
    • Plugin: src/workspace/commands.ts (:Oil ex command path resolution), src/ui/global-ex-command.ts (global ex command path resolution)
  • Hidden files (dotfiles) not shown in Oil explorer — hidden files and folders (e.g., .gitignore, .git/) were not visible in Oil even with "Show hidden files" enabled. Root cause: app.vault.getFiles() and app.vault.getAllFolders() only return Obsidian-indexed files, and Obsidian does not index dotfiles. Fixed by adding a two-pass rendering approach: the initial sync render uses the Vault API (unchanged), then an async second pass discovers hidden entries via app.vault.adapter.list() (which returns all filesystem entries including dotfiles) and merges them into the listing. A race condition guard prevents overwriting user edits during the async merge. Hidden files are currently view-only — CRUD operations on dotfiles may fail because they lack TFile/TFolder objects in the Vault index. (#93)
    • Plugin: src/oil/render.ts (exported getParentPath/isInConfigDir, new discoverHiddenEntries() function), src/oil/manager.ts (new discoverAndMergeHidden() method with race condition guard), src/oil/oil-view.ts (setEditorContent() method, async trigger in onOpen() and refreshContent())
  • Inconsistent behavior when deleting surroundings with doubled symmetric delimitersds$ on $$example$$ did nothing instead of deleting the innermost $ pair to produce $example$. Same failure for ds" on ""hi"", cs$ on $$example$$, and other symmetric (same open/close) surround characters when doubled. Root cause: findSurroundingQuotes() in the codemirror-vim fork paired all quote positions sequentially at even/odd indices (i += 2). For $$example$$ with positions [0, 1, 9, 10], this created pairs (0,1) and (9,10) — the two adjacent $$ on each side — leaving the cursor between them with no match. Fixed by replacing the sequential pairing with cursor-expansion: search backward from cursor for the nearest quote character (open), then forward for the next one (close). This correctly handles both doubled delimiters ($$example$$ → finds inner pair (1, 9)) and adjacent pairs ("hello" "world" → finds pair around cursor). (#96)
    • Fork: ~/Repos/codemirror-vim/src/vim.js (findSurroundingQuotes — cursor-expansion algorithm replacing sequential i += 2 pairing)
    • Fork: ~/Repos/codemirror-vim/DIFFERENCES.md (added "Symmetric surround quote matching" section)
  • Snippet ex commands do not work after vimrc/Lua config reload:snippet <name> and :snippets ex commands silently stopped working after any reloadFeatures() cycle (triggered by vimrc loading, Lua config loading, or settings changes). Root cause: registerSnippetCommands() was called only in onload(), but reloadFeatures() calls unregisterAll() which replaces all registered ex commands with no-ops — and snippet commands were never re-registered. The Picker-based snippet insertion was unaffected because it uses a separate pickerRegistry not managed by VimRegistration. Fixed by adding registerSnippetCommands() to reloadFeatures(), matching the pattern used by all other feature registrations. (#95)
    • Plugin: src/main.ts (reloadFeatures — added registerSnippetCommands call gated by enableSnippets)
  • Which-key shows EasyMotion commands incorrectly with space leader — EasyMotion commands (prefixed with <leader><leader>) appeared at the wrong level in the which-key popup when using space as the leader key. Two root causes: (1) LeaderRegistry.addBinding() stripped the leader prefix using the raw leader key (" "), but onKeyPressLeaderOnly() compared against normalized keys ("<Space>" from vim-keypress events). The stored binding keys (" f") never matched the normalized drill-down prefix ("<Space>"). Similarly, addGroupLabel() stored the group label key in raw format, causing getRelativeGroupLabels() lookups to miss. Fixed by normalizing both lhs and prefix via normalizeVimKey() at storage time in addBinding() and addGroupLabel(). (2) In grouped mode, buildNextKeyEntries() called isSpecialKey() to filter out non-typeable keys like <CR>, <Left>, etc. — but <Space> was also treated as special, causing all EasyMotion bindings (whose first key after leader-stripping is <Space>) to be silently dropped from the grouping. Fixed by exempting <Space> from the special key check. (#94)
    • Plugin: src/ui/which-key.ts (LeaderRegistry.addBinding — normalize lhs and leader before stripping; LeaderRegistry.addGroupLabel — normalize prefix before storing; isSpecialKey — exempt <Space> from special key filtering)

Tests

  • 6 fork tests in ~/Repos/codemirror-vim/test/vim_test.js: ds_doubled_dollar_deletes_inner, ds_doubled_quote_deletes_inner, cs_doubled_dollar_changes_inner, ds_single_dollar_pair, ds_adjacent_dollar_pairs, ds_dollar_cursor_on_delimiter
  • 5 e2e tests in test/specs/surround.e2e.ts (doubled symmetric delimiters — #96): ds$ on $$example$$ in Live Preview, ds" on ""hi"", cs$ on $$example$$, ds$ on single $hello$, ds$ on adjacent $hello$ $world$
  • 28 unit tests in test/unit/which-key.test.ts: LeaderRegistry normalization (raw space leader, pre-normalized leader, format consistency, non-leader rejection, bare-leader rejection, deduplication, backslash leader, comma leader), group label normalization (raw vs normalized prefix, cross-format consistency), clearBuiltinBindings with normalized keys, double-leader drill-down (issue #94 scenario — EasyMotion bindings filterable by <Space> prefix, single-leader bindings excluded), isSpecialKey (<Space> exempt, other angle-bracket keys special, plain keys not special)
  • 2 e2e tests unskipped in test/specs/snippets/snippet-variables.e2e.ts: :snippet command expands by name, :snippets opens picker
  • 1 e2e test in test/specs/settings-reload.e2e.ts: snippet ex commands survive reloadFeatures() (regression test for #95)
  • 17 unit tests in test/unit/oil-render.test.ts: getParentPath (4 tests), isInConfigDir (4 tests), discoverHiddenEntries (9 tests — dotfiles, dot-folders, index exclusion, config dir exclusion, non-dotfile exclusion, adapter.list failure graceful fallback, nested paths, mixed entries, empty results)
  • 5 e2e tests in test/specs/oil-poc.e2e.ts (Oil explorer #93): :Oil . opens vault root, :Oil / opens vault root, closing oil restores source mode, closing oil restores live preview mode, closeOil() restores previous file

Documentation

  • CHANGELOG.md
  • KNOWN_LIMITATIONS.md: Added surround doubled symmetric delimiter fix
  • AGENTS.md: Updated fork test count (1882)
  • DIFFERENCES.md (fork): Added "Symmetric surround quote matching" section
  • docs/features/surround.md: Added doubled delimiter behavior note
  • KNOWN_LIMITATIONS.md: Added hidden files view-only limitation to Oil section; marked Reading Mode freeze, focus loss, and :Oil . path resolution as fixed
  • CONTRIBUTING.md: Updated Oil codebase structure descriptions (oil-view.ts, manager.ts, render.ts)
  • docs/features/oil-explorer.md: Updated with mode restoration on close, focus restoration on tab switch, :Oil ./:Oil / path semantics, hidden files via adapter API, view-only dotfile limitation
  • docs/features/ex-commands.md: Updated :Oil argument description
  • docs/reference/keybindings.md: Updated :Oil description with .// path support
  • KNOWN_LIMITATIONS.md: Marked ex command snippet expansion as fixed; added which-key EasyMotion double-leader fix to which-key overlay section
  • docs/features/snippets.md: Updated ex command trigger description noting reload survival
  • docs/configuration/which-key.md: Added note about double-leader prefix grouping for EasyMotion

Full Changelog: 0.89.0...0.90.0

Don't miss a new motions release

NewReleases is sending notifications on new releases.