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_TIMEOUTfiredresetSequence()unconditionally, dismissing the popup even when partial completions existed. In editor mode, the which-key overlay stays open until the command completes (driven byvim-keypress/vim-command-doneevents, 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 beforeresetSequence)
- Plugin:
gtalways navigates to first tab instead of next tab in non-editor views — pressinggtwithout 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 usedthis.count || 1, making count 0 (no count typed) indistinguishable from count 1 (user typed1gt). Thegthandler'sif (count > 0)always triggeredgotoNthTab(app, 1). Fixed by passingthis.countdirectly tobuiltinhandlers, letting each handler decide its own default. Thegthandler already had the correct branching (count > 0→ nth tab, else → next tab). Other handlers (j/kscroll, hint actions) applycount || 1locally. (#97)- Plugin:
src/workspace/global-key-handler.ts(dispatch— rawthis.countfor builtin,this.count || 1for obcommand repeat),src/workspace/global-defaults.ts(localcount || 1in scroll/hint handlers)
- Plugin:
Ngt(count + gt) ignored count in editor views — pressing2gtor3gtin an editor view always went to the next tab instead of the Nth tab. Root cause: the editor-modegtwas mapped toworkspace:next-tabviacreateCommandAction, which ignoresactionArgs.repeatentirely. The count-awaregotoTabaction was only mapped tog<C-t>. Fixed by replacing thegtmapping with a newgtActionthat usesactionArgs.repeatIsExplicitto distinguish "no count typed" (next tab) from "count N typed" (go to tab N). (#97)- Plugin:
src/workspace/navigation.ts(gtAction—repeatIsExplicitcheck,gotoNthTabfor explicit count,workspace:next-tabfor no count)
- Plugin:
gotoNthTabcounted sidebar leaves in tab numbering —Ngtandg<C-t>counted all workspace leaves (including sidebar panes) when determining the Nth tab.3gtcould navigate to a sidebar pane instead of the 3rd editor tab. Fixed by filtering leaves withleaf.getRoot() === app.workspace.rootSplitto only count main editor area leaves, matching the existing pattern insrc/lua/loader.ts. (#97)- Plugin:
src/workspace/global-defaults.ts(gotoNthTab—rootSplitfilter),src/workspace/navigation.ts(createGotoTabAction—rootSplitfilter)
- Plugin:
- 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) Inembeddable-editor.ts, thebuiltinVimOnclosure variable capturedisVimEnabled(app)which returnstruewhen 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'sregisterEditorExtension()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-constructionensureVimExtension()safety net that checks for vim presence viagetCM()and appends it viaStateEffect.appendConfigonly if absent. (2) Inmanager.ts,openOil()calledgetLeaf(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(removedbuiltinVimOnclosure, removed dead vim push frombuildLocalExtensions, addedensureVimExtension()withgetCMcheck +StateEffect.appendConfigfallback, replacedisVimEnabledimport withisBuiltinVimEnabled+getCM),src/oil/manager.ts(openOil— leaf priming withsetViewState({ type: 'markdown' })when no active MarkdownView)
- Plugin:
- 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()calledcache.loadDirectory()three times during a single refresh cycle, causing buffer entry IDs to become out of sync with the cache. Entry lookup by ID returnedundefined, soopenEntryAtCursor()silently aborted. Fixed by passing the expected buffer content from the initial render as a parameter todiscoverAndMergeHidden(), eliminating the redundantrenderDirectoryToBuffer()call that triggered the thirdcache.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— acceptsexpectedContentparameter, removed redundantrenderDirectoryToBuffercall),src/oil/oil-view.ts(callers pass rendered content)
- Plugin:
- 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()andrefreshContent()updatedthis.dirPathbut never signaled Obsidian to re-readgetDisplayText(). Fixed by addingnotifyHeaderChanged()which callsleaf.updateHeader()(Obsidian internal) after dirPath changes, insetDirectory(),refreshContent(), andsetState(). (#93)- Plugin:
src/oil/oil-view.ts(notifyHeaderChangedprivate method, called fromsetDirectory,refreshContent,setState)
- Plugin:
- Hidden files toggle (
g.) has no effect — pressingg.in Oil to toggle hidden files did nothing. Root cause:this.settings.oilShowHiddenFiles ?? this.showHiddenused the nullish coalescing operator (??), butoilShowHiddenFilesis typed asboolean(defaultfalse), so??never fell through to the runtime togglethis.showHidden. Fixed by replacing the boolean field with ashowHiddenOverride: boolean | null(null = use setting) and agetEffectiveShowHidden()helper that prioritizes the override when set. (#93)- Plugin:
src/oil/manager.ts(showHiddenOverridefield,getEffectiveShowHidden()helper,toggleHidden()rewritten)
- Plugin:
<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()usedopenLinkText()which cannot replace a custom view type. Fixed by usingleaf.openFile()directly on the Oil leaf vianavigateWithJumpFile(), matching the pattern used bycloseOil(). (#93)- Plugin:
src/oil/manager.ts(openEntryAtCursorrewritten to useopenFileInLeaf, newopenFileInLeafprivate method),src/oil/keybindings.ts(oilOpenEntrydelegates tomanager.openEntryAtCursor())
- Plugin:
Added
- Oil
<C-t>open in new tab — new:oilopentabex 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)
- Plugin:
- Oil
<C-s>/<C-h>split open — new:oilopensvand:oilopenshex 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)
- Plugin:
- Oil
<C-c>close —<C-c>now maps to:oilclose, matching oil.nvim's default close binding.qremains as an additional close key.- Plugin:
src/oil/keybindings.ts(mapping)
- Plugin:
- Oil
gxopen in default app — new:oilopenexternalex command mapped togx, matching oil.nvim's default. Opens the file under cursor in the system's default application viaapp.openWithDefaultApp().- Plugin:
src/oil/manager.ts(openEntryExternalAtCursor),src/oil/keybindings.ts(mapping + action)
- Plugin:
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),getEffectiveShowHiddenoverride logic (5 tests),renderDirectoryat 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),gxmethod registration (1 test), Obsidian reload for split cleanup (1 test) Modalclass added totest/unit/__mocks__/obsidian.tsto unblock unit tests importingmanager.ts
Documentation
CHANGELOG.mdKNOWN_LIMITATIONS.md: Marked Oil non-editor context degradation as fixed; updated vim state per-editor note withensureVimExtension()safety netCONTRIBUTING.md: Updatedembeddable-editor.tsdescription (ensureVimExtension safety net) andmanager.tsdescription (leaf priming)AGENTS.md: Updated dual-vim architecture section with embedded editor vim injection and safety netdocs/features/oil-explorer.md: Added non-editor context opening noteKNOWN_LIMITATIONS.md: Added which-key popup timeout fix and gt/Ngt tab navigation fixesdocs/features/workspace-navigation.md: Added Ngt count support description and which-key timeout fix notedocs/reference/keybindings.md: Already hadNgtrow — no change neededCONTRIBUTING.md: Updatedglobal-key-handler.tsandglobal-defaults.tsdescriptionsKNOWN_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 keymapsdocs/features/ex-commands.md: Updated Oil ex commands table with new keymapsdocs/reference/keybindings.md: Updated Oil keybindings table with new keymapsCONTRIBUTING.md: Updated Oil keybindings descriptionREADME.md: Updated Oil feature description with oil.nvim-matching keybindings
Full Changelog: 0.90.0...0.91.0