From b70dce957407aa4c9325611da780d24124f75207 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Wed, 30 Sep 2026 20:01:16 +0530 Subject: [PATCH] feat(quick-switcher): wire Cmd+P into File menu + renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/main.js | 73 ++++++++----------------------------------------- src/preload.js | 5 ++++ src/renderer.js | 46 +++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/src/main.js b/src/main.js index 59a8225..9eec8e4 100644 --- a/src/main.js +++ b/src/main.js @@ -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 diff --git a/src/preload.js b/src/preload.js index feb9a72..bfde2e1 100644 --- a/src/preload.js +++ b/src/preload.js @@ -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'), diff --git a/src/renderer.js b/src/renderer.js index 5055444..7cbf971 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -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