feat(quick-switcher): wire Cmd+P into File menu + renderer

End-to-end wiring of the Quick Switcher overlay:
  - main.js File menu: new 'Quick Switcher...' item with Cmd+P
    accelerator (moved from Print Preview; Print Preview stays in
    File > Print submenu but loses its shortcut). Recent Files
    submenu and its buildRecentFilesMenu() helper removed.
  - new 'recent-files:get' invoke IPC handler returns the recent
    files array on demand.
  - preload.js: 'recent-files:get' + 'show-quick-switcher' added
    to channel whitelists; quickSwitcher.getRecentFiles() exposed.
  - renderer.js: lazy-mounts the overlay on first 'show-quick-switcher'
    message, fetches recent files via IPC, and shows the overlay.
    File-open goes through the existing 'open-file-path' channel.

Workspace dir is intentionally null for v4.13.0 — the workspace
search toggle is wired but a no-op until the explorer panel exposes
its current directory (next iteration).

Amit Haridas
This commit is contained in:
Amit Haridas
2026-09-30 20:01:16 +05:30
parent 3bd958c064
commit b70dce9574
3 changed files with 63 additions and 61 deletions
+12 -61
View File
@@ -684,62 +684,6 @@ function createWindow() {
// Wait for the page to fully load before sending file data
mainWindow.webContents.on('did-finish-load', () => {});
}
function buildRecentFilesMenu() {
const recentFiles = getRecentFiles();
if (recentFiles.length === 0) {
return [
{
label: 'No recent files',
enabled: false,
},
];
}
const recentFileItems = recentFiles.map((filePath) => ({
label: filePath.split(/[\\/]/).pop(),
// Get filename only
click: () => {
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
if (stats.size > MAX_FILE_SIZE) {
dialog.showErrorBox(
'File Too Large',
`File exceeds the ${MAX_FILE_SIZE_MB}MB size limit.`
);
return;
}
currentFile = filePath;
const content = fs.readFileSync(filePath, 'utf-8');
mainWindow.webContents.send('file-opened', {
path: filePath,
content,
});
} else {
dialog.showErrorBox(
'File Not Found',
sanitizeErrorMessage(`The file "${filePath}" could not be found.`)
);
}
},
toolTip: filePath, // Show full path in tooltip
}));
return [
...recentFileItems,
{
type: 'separator',
},
{
label: 'Clear Recent Files',
click: () => {
try {
clearRecentFilesOnDisk();
mainWindow.webContents.send('recent-files-cleared');
} catch (error) {
console.error('Error clearing recent files:', error);
}
},
},
];
}
function getRecentFiles() {
try {
const recentFiles = JSON.parse(
@@ -783,12 +727,19 @@ function createMenu() {
{
type: 'separator',
},
{
// v4.13.0 — Quick Switcher replaces the Recent Files submenu.
// Cmd+P moved here from Print Preview; the Print Preview menu
// entry is still reachable via File → Print but lost its shortcut.
label: 'Quick Switcher...',
accelerator: 'CmdOrCtrl+P',
click: () => mainWindow.webContents.send('show-quick-switcher'),
},
{
label: 'Print',
submenu: [
{
label: 'Print Preview',
accelerator: 'CmdOrCtrl+P',
click: () => mainWindow.webContents.send('print-preview'),
},
{
@@ -800,10 +751,6 @@ function createMenu() {
{
type: 'separator',
},
{
label: 'Recent Files',
submenu: buildRecentFilesMenu(),
},
{
type: 'separator',
},
@@ -4957,6 +4904,10 @@ ipcMain.on('clear-recent-files', (event) => {
}
});
// Quick-switcher (v4.13.0): renderer asks for the recent-files list when
// the Cmd+P overlay opens. Read-only — write paths remain send-only.
ipcMain.handle('recent-files:get', () => getRecentFiles());
// Plugins (loaded in the renderer) report the export formats they've
// registered; rebuild the Export menu so they show up as entries.
// createMenu() is idempotent and already re-invoked elsewhere (e.g. after
+5
View File
@@ -191,6 +191,7 @@ const ALLOWED_SEND_CHANNELS = [
// Quick-switcher (Cmd+P workspace file picker)
'quick-switcher:list-files',
'recent-files:get',
// Document version history
'version-history:list',
@@ -330,6 +331,9 @@ const ALLOWED_RECEIVE_CHANNELS = [
'toggle-sidebar-panel',
'toggle-bottom-panel',
// v4.13.0 — quick-switcher overlay trigger
'show-quick-switcher',
// Plugin export formats
'run-plugin-export-format',
];
@@ -557,6 +561,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
// fuzzy matcher (src/quick-switcher/fuzzy-matcher.js) does the ranking.
quickSwitcher: {
listFiles: (dir, options) => ipcRenderer.invoke('quick-switcher:list-files', { dir, options }),
getRecentFiles: () => ipcRenderer.invoke('recent-files:get'),
},
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
+46
View File
@@ -176,6 +176,7 @@ let _SidebarManager,
_renderGitPanel,
_renderSnippetsPanel;
let _ReplPanel, _CommandPalette, _PrintPreview, _createWelcomeContent;
let _createQuickSwitcherOverlay;
function getSidebarManager() {
if (!_SidebarManager) _SidebarManager = require('./sidebar/sidebar-manager').SidebarManager;
return _SidebarManager;
@@ -213,6 +214,12 @@ function getCommandPalette() {
if (!_CommandPalette) _CommandPalette = require('./command-palette').CommandPalette;
return _CommandPalette;
}
function getCreateQuickSwitcherOverlay() {
if (!_createQuickSwitcherOverlay)
_createQuickSwitcherOverlay =
require('./quick-switcher/quick-switcher-overlay').createQuickSwitcherOverlay;
return _createQuickSwitcherOverlay;
}
function getPrintPreview() {
if (!_PrintPreview) _PrintPreview = require('./print-preview').PrintPreview;
return _PrintPreview;
@@ -2541,6 +2548,33 @@ document.addEventListener('DOMContentLoaded', async () => {
// Initialize command palette
const CommandPalette = getCommandPalette();
const commandPalette = new CommandPalette();
// Quick-switcher (Cmd+P) overlay — lazy-mounted on first trigger.
// Workspace dir is sourced from the explorer panel when it has one open;
// for the initial v4.13.0 release we ship without workspace tracking and
// the "Search workspace" toggle silently stays off.
let quickSwitcherOverlayInstance = null;
function ensureQuickSwitcherOverlay() {
if (quickSwitcherOverlayInstance) return quickSwitcherOverlayInstance;
const create = getCreateQuickSwitcherOverlay();
const host = document.createElement('div');
host.id = 'quick-switcher-host';
document.body.appendChild(host);
quickSwitcherOverlayInstance = create(host, {
getOpenTabPaths: () =>
Array.from(tabManager.tabs.values())
.map((t) => t.filePath)
.filter(Boolean),
listWorkspaceFiles: (dir, options) =>
window.electronAPI.quickSwitcher.listFiles(dir, options),
onOpenFile: (filePath) => ipcRenderer.send('open-file-path', filePath),
// v4.13.0 — explorer-driven workspace dir is the next iteration;
// for now the workspace toggle is a no-op and we surface only
// recent files + open tabs.
getWorkspaceDir: () => null,
});
return quickSwitcherOverlayInstance;
}
const pluginRegistry = new PluginRegistry({
sidebar: sidebarManager,
commands: commandPalette,
@@ -2934,6 +2968,18 @@ document.addEventListener('DOMContentLoaded', async () => {
}
});
// File menu: Cmd+P opens the Quick Switcher overlay (v4.13.0).
ipcRenderer.on('show-quick-switcher', async () => {
const overlay = ensureQuickSwitcherOverlay();
let recent = [];
try {
recent = (await window.electronAPI.quickSwitcher.getRecentFiles()) || [];
} catch {
recent = [];
}
overlay.show({ recent });
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Ctrl+Shift+P — Command Palette