mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
feat(quick-switcher): derive workspace dir from active tab
The explorer panel's explorerCurrentDir is hardcoded to null, so the Quick Switcher's workspace toggle was a no-op. Rather than fix the broken explorer flow, derive the workspace from the active tab's filePath's parent directory — the most reliable source we have without restructuring that panel. deriveWorkspaceDir() handles POSIX and Windows paths (including mixed separators and trailing separators), returns null for untitled / empty inputs so the workspace toggle silently stays off in those cases. 10 new tests cover POSIX, Windows, mixed-separator, trailing separator, UNC, and invalid input paths. Amit Haridas
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Workspace dir resolver for the Quick Switcher overlay.
|
||||||
|
*
|
||||||
|
* Derives a workspace directory from a single file path by returning its
|
||||||
|
* parent directory. Auto-detects path separator (Windows backslash vs
|
||||||
|
* POSIX forward slash).
|
||||||
|
*
|
||||||
|
* Returns null when no usable file path is supplied (untitled tab, no
|
||||||
|
* active tab) — the renderer then surfaces only recent + open tabs in
|
||||||
|
* the Quick Switcher, and the workspace toggle is a no-op.
|
||||||
|
*
|
||||||
|
* @module workspace-dir-resolver
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|null|undefined} filePath
|
||||||
|
* @returns {string|null} parent directory, or null if not derivable
|
||||||
|
*/
|
||||||
|
function deriveWorkspaceDir(filePath) {
|
||||||
|
if (typeof filePath !== 'string' || filePath.length === 0) return null;
|
||||||
|
// Trim trailing separators (so a path like '/foo/bar/' resolves to '/foo',
|
||||||
|
// not '/foo/bar').
|
||||||
|
let end = filePath.length;
|
||||||
|
while (end > 0 && (filePath[end - 1] === '/' || filePath[end - 1] === '\\')) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
if (end === 0) return null;
|
||||||
|
const trimmed = filePath.slice(0, end);
|
||||||
|
// Find the last separator of either kind — mixed paths are ambiguous
|
||||||
|
// but taking the last one of either kind matches what users mean when
|
||||||
|
// they paste a path with mixed separators.
|
||||||
|
const lastSlash = trimmed.lastIndexOf('/');
|
||||||
|
const lastBack = trimmed.lastIndexOf('\\');
|
||||||
|
const lastSep = Math.max(lastSlash, lastBack);
|
||||||
|
if (lastSep <= 0) return null;
|
||||||
|
return trimmed.slice(0, lastSep);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { deriveWorkspaceDir };
|
||||||
+22
-4
@@ -223,10 +223,15 @@ function getCreateQuickSwitcherOverlay() {
|
|||||||
let _createInlineAiController;
|
let _createInlineAiController;
|
||||||
function getCreateInlineAiController() {
|
function getCreateInlineAiController() {
|
||||||
if (!_createInlineAiController)
|
if (!_createInlineAiController)
|
||||||
_createInlineAiController = require('./renderer/inline-ai-controller')
|
_createInlineAiController = require('./renderer/inline-ai-controller').createInlineAiController;
|
||||||
.createInlineAiController;
|
|
||||||
return _createInlineAiController;
|
return _createInlineAiController;
|
||||||
}
|
}
|
||||||
|
let _deriveWorkspaceDir;
|
||||||
|
function getDeriveWorkspaceDir() {
|
||||||
|
if (!_deriveWorkspaceDir)
|
||||||
|
_deriveWorkspaceDir = require('./quick-switcher/workspace-dir-resolver').deriveWorkspaceDir;
|
||||||
|
return _deriveWorkspaceDir;
|
||||||
|
}
|
||||||
function getPrintPreview() {
|
function getPrintPreview() {
|
||||||
if (!_PrintPreview) _PrintPreview = require('./print-preview').PrintPreview;
|
if (!_PrintPreview) _PrintPreview = require('./print-preview').PrintPreview;
|
||||||
return _PrintPreview;
|
return _PrintPreview;
|
||||||
@@ -2578,7 +2583,18 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// v4.13.0 — explorer-driven workspace dir is the next iteration;
|
// v4.13.0 — explorer-driven workspace dir is the next iteration;
|
||||||
// for now the workspace toggle is a no-op and we surface only
|
// for now the workspace toggle is a no-op and we surface only
|
||||||
// recent files + open tabs.
|
// recent files + open tabs.
|
||||||
getWorkspaceDir: () => null,
|
getWorkspaceDir: () => {
|
||||||
|
// Derive workspace dir from the active tab's file (parent dir).
|
||||||
|
// The explorer panel's currentDir is hardcoded to null, so this is
|
||||||
|
// the most reliable source we have without restructuring that
|
||||||
|
// panel. Returns null for untitled tabs, which makes the workspace
|
||||||
|
// search toggle a no-op (recent + open tabs still surface).
|
||||||
|
const derive = getDeriveWorkspaceDir();
|
||||||
|
const active =
|
||||||
|
tabManager.activeTab ||
|
||||||
|
(tabManager.activeTabId && tabManager.tabs.get(tabManager.activeTabId));
|
||||||
|
return active ? derive(active.filePath) : null;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return quickSwitcherOverlayInstance;
|
return quickSwitcherOverlayInstance;
|
||||||
}
|
}
|
||||||
@@ -2593,7 +2609,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const create = getCreateInlineAiController();
|
const create = getCreateInlineAiController();
|
||||||
inlineAiControllerInstance = create({
|
inlineAiControllerInstance = create({
|
||||||
getEditorView: () => {
|
getEditorView: () => {
|
||||||
const active = tabManager.activeTab || (tabManager.activeTabId && tabManager.tabs.get(tabManager.activeTabId));
|
const active =
|
||||||
|
tabManager.activeTab ||
|
||||||
|
(tabManager.activeTabId && tabManager.tabs.get(tabManager.activeTabId));
|
||||||
return active ? active.editorView : null;
|
return active ? active.editorView : null;
|
||||||
},
|
},
|
||||||
electronAPI: window.electronAPI,
|
electronAPI: window.electronAPI,
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* @jest-environment node
|
||||||
|
*
|
||||||
|
* Workspace dir resolver — derive parent dir from a file path.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { deriveWorkspaceDir } = require('../src/quick-switcher/workspace-dir-resolver');
|
||||||
|
|
||||||
|
describe('deriveWorkspaceDir', () => {
|
||||||
|
test('returns parent dir for a POSIX path', () => {
|
||||||
|
expect(deriveWorkspaceDir('/home/user/notes/readme.md')).toBe('/home/user/notes');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns parent dir for a Windows path', () => {
|
||||||
|
expect(deriveWorkspaceDir('C:\\Users\\me\\notes\\readme.md')).toBe('C:\\Users\\me\\notes');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns parent dir for a nested POSIX path', () => {
|
||||||
|
expect(deriveWorkspaceDir('/a/b/c/d/e.md')).toBe('/a/b/c/d');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto-detects separator when path contains both', () => {
|
||||||
|
// A path with both — backslash takes precedence (Windows convention)
|
||||||
|
expect(deriveWorkspaceDir('C:\\foo/bar.txt')).toBe('C:\\foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for a root-level POSIX file', () => {
|
||||||
|
expect(deriveWorkspaceDir('readme.md')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for an empty string', () => {
|
||||||
|
expect(deriveWorkspaceDir('')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for null / undefined / non-string', () => {
|
||||||
|
expect(deriveWorkspaceDir(null)).toBeNull();
|
||||||
|
expect(deriveWorkspaceDir(undefined)).toBeNull();
|
||||||
|
expect(deriveWorkspaceDir(42)).toBeNull();
|
||||||
|
expect(deriveWorkspaceDir({})).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for a single-character filename', () => {
|
||||||
|
expect(deriveWorkspaceDir('/x')).toBeNull();
|
||||||
|
expect(deriveWorkspaceDir('\\x')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preserves trailing separators', () => {
|
||||||
|
expect(deriveWorkspaceDir('/home/user/notes/')).toBe('/home/user');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles UNC-style Windows path', () => {
|
||||||
|
expect(deriveWorkspaceDir('\\\\server\\share\\file.md')).toBe('\\\\server\\share');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user