github saberzero1/motions 0.91.0

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

Fixed

  • Which-key popup disappears quickly in non-editor views — in non-editor views (reading view, graph, canvas, etc.), the which-key popup appeared and vanished after ~500ms instead of staying visible until the user completed the key sequence. Root cause: the global key handler's 1000ms SEQUENCE_TIMEOUT fired resetSequence() unconditionally, dismissing the popup even when partial completions existed. In editor mode, the which-key overlay stays open until the command completes (driven by vim-keypress/vim-command-done events, not a fixed timer). Fixed by checking for partial matches when the timeout fires — if the current key buffer has pending completions in the registry, the timeout restarts instead of resetting. The popup now stays alive until the user completes or abandons the sequence. (#97)
    • Plugin: src/workspace/global-key-handler.ts (startTimeout — partial-match check before resetSequence)
  • gt always navigates to first tab instead of next tab in non-editor views — pressing gt without a count prefix in non-editor views (graph, canvas, reading view) always jumped to the first tab instead of cycling to the next tab. Root cause: dispatch() in the global key handler used this.count || 1, making count 0 (no count typed) indistinguishable from count 1 (user typed 1gt). The gt handler's if (count > 0) always triggered gotoNthTab(app, 1). Fixed by passing this.count directly to builtin handlers, letting each handler decide its own default. The gt handler already had the correct branching (count > 0 → nth tab, else → next tab). Other handlers (j/k scroll, hint actions) apply count || 1 locally. (#97)
    • Plugin: src/workspace/global-key-handler.ts (dispatch — raw this.count for builtin, this.count || 1 for obcommand repeat), src/workspace/global-defaults.ts (local count || 1 in scroll/hint handlers)
  • Ngt (count + gt) ignored count in editor views — pressing 2gt or 3gt in an editor view always went to the next tab instead of the Nth tab. Root cause: the editor-mode gt was mapped to workspace:next-tab via createCommandAction, which ignores actionArgs.repeat entirely. The count-aware gotoTab action was only mapped to g<C-t>. Fixed by replacing the gt mapping with a new gtAction that uses actionArgs.repeatIsExplicit to distinguish "no count typed" (next tab) from "count N typed" (go to tab N). (#97)
    • Plugin: src/workspace/navigation.ts (gtActionrepeatIsExplicit check, gotoNthTab for explicit count, workspace:next-tab for no count)
  • gotoNthTab counted sidebar leaves in tab numberingNgt and g<C-t> counted all workspace leaves (including sidebar panes) when determining the Nth tab. 3gt could navigate to a sidebar pane instead of the 3rd editor tab. Fixed by filtering leaves with leaf.getRoot() === app.workspace.rootSplit to only count main editor area leaves, matching the existing pattern in src/lua/loader.ts. (#97)
    • Plugin: src/workspace/global-defaults.ts (gotoNthTabrootSplit filter), src/workspace/navigation.ts (createGotoTabActionrootSplit filter)
  • Oil editor degraded when opened from non-editor context — opening Oil from an empty pane, settings view, graph view, or any non-markdown context produced a broken editor: keybindings (g?, <CR>, q) didn't work, which-key popup didn't appear, and the cursor could move through concealed icon ranges character by character. Two root causes: (1) In embeddable-editor.ts, the builtinVimOn closure variable captured isVimEnabled(app) which returns true when the bundled fork is active — making the guard !builtinVimOn && isBundledVimActive() always false and the explicit vim extension push dead code. The embedded editor relied entirely on Obsidian's registerEditorExtension() injection to receive vim, which could fail on leaves that had never hosted a MarkdownView. Fixed by removing the dead guard and adding a post-construction ensureVimExtension() safety net that checks for vim presence via getCM() and appends it via StateEffect.appendConfig only if absent. (2) In manager.ts, openOil() called getLeaf(false) which reuses the current leaf — when that leaf was a non-editor view (empty pane, settings), it lacked initialized editor infrastructure. Fixed by priming the leaf with a temporary markdown view state (setViewState({ type: 'markdown' })) before switching to the Oil view type when no MarkdownView is active.
    • Plugin: src/editors/embeddable-editor.ts (removed builtinVimOn closure, removed dead vim push from buildLocalExtensions, added ensureVimExtension() with getCM check + StateEffect.appendConfig fallback, replaced isVimEnabled import with isBuiltinVimEnabled + getCM), src/oil/manager.ts (openOil — leaf priming with setViewState({ type: 'markdown' }) when no active MarkdownView)
  • Cannot open files/folders from Oil explorer at vault root — after the v0.90.0 fix, pressing <CR> on any file or folder in the Oil explorer did nothing. Root cause: discoverAndMergeHidden() called cache.loadDirectory() three times during a single refresh cycle, causing buffer entry IDs to become out of sync with the cache. Entry lookup by ID returned undefined, so openEntryAtCursor() silently aborted. Fixed by passing the expected buffer content from the initial render as a parameter to discoverAndMergeHidden(), eliminating the redundant renderDirectoryToBuffer() call that triggered the third cache.loadDirectory(). The cache is now updated exactly once per merge. Confirmed by spike unit test demonstrating ID desync (buffer IDs [1,2] vs cache IDs [6,7]). (#93)
    • Plugin: src/oil/manager.ts (discoverAndMergeHidden — accepts expectedContent parameter, removed redundant renderDirectoryToBuffer call), src/oil/oil-view.ts (callers pass rendered content)
  • Oil explorer title bar does not update when navigating directories — after navigating from one directory to another, the tab header continued to show the original directory name. Root cause: setDirectory() and refreshContent() updated this.dirPath but never signaled Obsidian to re-read getDisplayText(). Fixed by adding notifyHeaderChanged() which calls leaf.updateHeader() (Obsidian internal) after dirPath changes, in setDirectory(), refreshContent(), and setState(). (#93)
    • Plugin: src/oil/oil-view.ts (notifyHeaderChanged private method, called from setDirectory, refreshContent, setState)
  • Hidden files toggle (g.) has no effect — pressing g. in Oil to toggle hidden files did nothing. Root cause: this.settings.oilShowHiddenFiles ?? this.showHidden used the nullish coalescing operator (??), but oilShowHiddenFiles is typed as boolean (default false), so ?? never fell through to the runtime toggle this.showHidden. Fixed by replacing the boolean field with a showHiddenOverride: boolean | null (null = use setting) and a getEffectiveShowHidden() helper that prioritizes the override when set. (#93)
    • Plugin: src/oil/manager.ts (showHiddenOverride field, getEffectiveShowHidden() helper, toggleHidden() rewritten)
  • <CR> in Oil opens file in new tab instead of replacing Oil view — pressing Enter on a file in Oil opened it in a new tab, leaving the Oil view in the original tab. In oil.nvim, <CR> (select) opens the file in the same window, replacing the oil buffer. Root cause: navigateWithJump() used openLinkText() which cannot replace a custom view type. Fixed by using leaf.openFile() directly on the Oil leaf via navigateWithJumpFile(), matching the pattern used by closeOil(). (#93)
    • Plugin: src/oil/manager.ts (openEntryAtCursor rewritten to use openFileInLeaf, new openFileInLeaf private method), src/oil/keybindings.ts (oilOpenEntry delegates to manager.openEntryAtCursor())

Added

  • Oil <C-t> open in new tab — new :oilopentab ex command mapped to <C-t>, matching oil.nvim's default. Opens the file under cursor in a new tab while keeping the Oil view in the current tab.
    • Plugin: src/oil/manager.ts (openEntryAtCursorInNewTab), src/oil/keybindings.ts (mapping + action)
  • Oil <C-s> / <C-h> split open — new :oilopensv and :oilopensh ex commands mapped to <C-s> (vertical split) and <C-h> (horizontal split), matching oil.nvim's defaults. Opens the file under cursor in a split pane alongside the Oil view.
    • Plugin: src/oil/manager.ts (openEntryAtCursorInSplit), src/oil/keybindings.ts (mappings + actions)
  • Oil <C-c> close<C-c> now maps to :oilclose, matching oil.nvim's default close binding. q remains as an additional close key.
    • Plugin: src/oil/keybindings.ts (mapping)
  • Oil gx open in default app — new :oilopenexternal ex command mapped to gx, matching oil.nvim's default. Opens the file under cursor in the system's default application via app.openWithDefaultApp().
    • Plugin: src/oil/manager.ts (openEntryExternalAtCursor), src/oil/keybindings.ts (mapping + action)

Tests

  • 13 unit tests in test/unit/global-key-handler.test.ts: dispatch count for builtin actions (count=0, count=1, count=3, count reset after dispatch), dispatch count for obcommand actions (once without count, N times with count), gt tab navigation issue #97 (gt without count → next tab, 3gt → nth tab, 1gt → nth tab), sequence timeout with partial matches (keeps alive, dispatches after restart, resets on no match, no lingering after exact match)
  • 4 unit tests in test/unit/global-defaults.test.ts: gotoNthTab via gt mapping (skips sidebar leaves, first root tab for count=1, no-op when count exceeds tabs, workspace:next-tab for count=0)
  • 11 e2e tests in test/specs/global-nav.e2e.ts (issue #97): editor-mode Ngt (gt without count → next tab not first, 1gt → first, 2gt → second, 3gt → third, 9gt → stays), non-editor-mode Ngt (gt → next, 1gt → first, 2gt → second, 3gt → third, 9gt → stays), sequence timeout updated (partial match keeps sequence alive)
  • 12 unit tests in test/unit/oil-cache-sync.test.ts: cache ID synchronization after render (5 tests), getEffectiveShowHidden override logic (5 tests), renderDirectory at vault root (2 tests)
  • 11 e2e tests in test/specs/oil-poc.e2e.ts: vault root folder navigation (2 tests), title bar update on directory change (2 tests), hidden files toggle (1 test), same-leaf file open (1 test), <C-t> keymap registration (1 test), vertical and horizontal split open (2 tests), gx method registration (1 test), Obsidian reload for split cleanup (1 test)
  • Modal class added to test/unit/__mocks__/obsidian.ts to unblock unit tests importing manager.ts

Documentation

  • CHANGELOG.md
  • KNOWN_LIMITATIONS.md: Marked Oil non-editor context degradation as fixed; updated vim state per-editor note with ensureVimExtension() safety net
  • CONTRIBUTING.md: Updated embeddable-editor.ts description (ensureVimExtension safety net) and manager.ts description (leaf priming)
  • AGENTS.md: Updated dual-vim architecture section with embedded editor vim injection and safety net
  • docs/features/oil-explorer.md: Added non-editor context opening note
  • KNOWN_LIMITATIONS.md: Added which-key popup timeout fix and gt/Ngt tab navigation fixes
  • docs/features/workspace-navigation.md: Added Ngt count support description and which-key timeout fix note
  • docs/reference/keybindings.md: Already had Ngt row — no change needed
  • CONTRIBUTING.md: Updated global-key-handler.ts and global-defaults.ts descriptions
  • KNOWN_LIMITATIONS.md: Marked Oil cache desync, title bar, and hidden toggle as fixed; added <CR> same-leaf fix; added new keymaps (<C-t>, <C-s>, <C-h>, <C-c>, gx)
  • docs/features/oil-explorer.md: Updated Oil ex commands table with new keymaps
  • docs/features/ex-commands.md: Updated Oil ex commands table with new keymaps
  • docs/reference/keybindings.md: Updated Oil keybindings table with new keymaps
  • CONTRIBUTING.md: Updated Oil keybindings description
  • README.md: Updated Oil feature description with oil.nvim-matching keybindings

Full Changelog: 0.90.0...0.91.0

Don't miss a new motions release

NewReleases is sending notifications on new releases.