diff --git a/src/renderer/js/app.js b/src/renderer/js/app.js index f38ecb3..58ee3e2 100644 --- a/src/renderer/js/app.js +++ b/src/renderer/js/app.js @@ -217,97 +217,111 @@ class TerminalApp { } initKeyboardShortcuts() { - window.addEventListener('keydown', (e) => { - // Ctrl+Shift+P 或 F1: 命令面板 - if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') || e.key === 'F1') { - e.preventDefault(); - this.commandPalette.toggle(); - return; - } - - // Ctrl+B: 切换左侧工作区侧边栏 - if (e.ctrlKey && e.key.toLowerCase() === 'b') { - e.preventDefault(); - this.workspaceManager?.toggleSidebar(); - return; - } - - // Ctrl+Shift+T: 新建当前工作区标签页 - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 't') { - e.preventDefault(); - const activeWs = this.workspaceManager ? this.workspaceManager.getActiveWorkspace() : null; - const spawnOpts = this.workspaceManager ? this.workspaceManager.getSpawnOptions(activeWs?.id) : {}; - this.tabManager.createTab({ - workspaceId: activeWs?.id, - title: spawnOpts.title, - cwd: spawnOpts.cwd, - shell: spawnOpts.shell, - args: spawnOpts.args, - }); - return; - } - - // Ctrl+Shift+W: 关闭窗格 - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'w') { - e.preventDefault(); - const tab = this.tabManager.getActiveTab(); - if (tab && tab.splitTree.activeTerminal) { - tab.splitTree.closeTerminal(tab.splitTree.activeTerminal.id); + window.addEventListener( + 'keydown', + (e) => { + // Ctrl+Shift+P 或 F1: 命令面板 + if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') || e.key === 'F1') { + e.preventDefault(); + e.stopPropagation(); + this.commandPalette.toggle(); + return; } - return; - } - // Ctrl+Shift+E: 水平分屏 (左右) - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'e') { - e.preventDefault(); - const tab = this.tabManager.getActiveTab(); - if (tab) tab.splitTree.splitCurrent('horizontal'); - return; - } - - // Ctrl+Shift+O: 垂直分屏 (上下) - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'o') { - e.preventDefault(); - const tab = this.tabManager.getActiveTab(); - if (tab) tab.splitTree.splitCurrent('vertical'); - return; - } - - // Ctrl+Shift+F: 搜索 - if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'f') { - e.preventDefault(); - this.toggleSearch(); - return; - } - - // Ctrl+,: 设置 - if (e.ctrlKey && e.key === ',') { - e.preventDefault(); - this.settings.open(); - return; - } - - // Ctrl+Tab / Ctrl+Shift+Tab: 标签页切换 - if (e.ctrlKey && e.key === 'Tab') { - e.preventDefault(); - if (e.shiftKey) { - this.tabManager.prevTab(); - } else { - this.tabManager.nextTab(); + // Ctrl+B: 切换左侧工作区侧边栏 + if (e.ctrlKey && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'b') { + e.preventDefault(); + e.stopPropagation(); + this.workspaceManager?.toggleSidebar(); + return; } - return; - } - // Alt+1 .. Alt+9: 快速直达标签页 - if (e.altKey && e.key >= '1' && e.key <= '9') { - e.preventDefault(); - const index = parseInt(e.key, 10) - 1; - const ids = Array.from(this.tabManager.tabs.keys()); - if (ids[index]) { - this.tabManager.switchTab(ids[index]); + // Ctrl+Shift+T: 新建当前工作区标签页 + if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 't') { + e.preventDefault(); + e.stopPropagation(); + const activeWs = this.workspaceManager ? this.workspaceManager.getActiveWorkspace() : null; + const spawnOpts = this.workspaceManager ? this.workspaceManager.getSpawnOptions(activeWs?.id) : {}; + this.tabManager.createTab({ + workspaceId: activeWs?.id, + title: spawnOpts.title, + cwd: spawnOpts.cwd, + shell: spawnOpts.shell, + args: spawnOpts.args, + }); + return; } - } - }); + + // Ctrl+Shift+W: 关闭窗格 + if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'w') { + e.preventDefault(); + e.stopPropagation(); + const tab = this.tabManager.getActiveTab(); + if (tab && tab.splitTree.activeTerminal) { + tab.splitTree.closeTerminal(tab.splitTree.activeTerminal.id); + } + return; + } + + // Ctrl+Shift+E: 水平分屏 (左右) + if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'e') { + e.preventDefault(); + e.stopPropagation(); + const tab = this.tabManager.getActiveTab(); + if (tab) tab.splitTree.splitCurrent('horizontal'); + return; + } + + // Ctrl+Shift+O: 垂直分屏 (上下) + if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'o') { + e.preventDefault(); + e.stopPropagation(); + const tab = this.tabManager.getActiveTab(); + if (tab) tab.splitTree.splitCurrent('vertical'); + return; + } + + // Ctrl+Shift+F: 搜索 + if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'f') { + e.preventDefault(); + e.stopPropagation(); + this.toggleSearch(); + return; + } + + // Ctrl+,: 设置 + if (e.ctrlKey && e.key === ',') { + e.preventDefault(); + e.stopPropagation(); + this.settings.open(); + return; + } + + // Ctrl+Tab / Ctrl+Shift+Tab: 标签页切换 + if (e.ctrlKey && e.key === 'Tab') { + e.preventDefault(); + e.stopPropagation(); + if (e.shiftKey) { + this.tabManager.prevTab(); + } else { + this.tabManager.nextTab(); + } + return; + } + + // Alt+1 .. Alt+9: 快速直达标签页 + if (e.altKey && e.key >= '1' && e.key <= '9') { + e.preventDefault(); + e.stopPropagation(); + const index = parseInt(e.key, 10) - 1; + const ids = Array.from(this.tabManager.tabs.keys()); + if (ids[index]) { + this.tabManager.switchTab(ids[index]); + } + } + }, + true // 启用捕获阶段,确保终端即使在聚焦/输入状态下应用级快捷键也能被优先拦截响应 + ); } async initStatusBar() { diff --git a/src/renderer/js/terminal-view.js b/src/renderer/js/terminal-view.js index 503df40..a6fc868 100644 --- a/src/renderer/js/terminal-view.js +++ b/src/renderer/js/terminal-view.js @@ -75,6 +75,14 @@ export class TerminalView { console.warn('WebGL addon not supported, fallback to default Canvas renderer:', e); } + // 自定义按键过滤:允许应用级全局快捷键(如 Ctrl+B 切换侧边栏)穿透 + this.xterm.attachCustomKeyEventHandler((event) => { + if (event.ctrlKey && !event.shiftKey && !event.altKey && event.key.toLowerCase() === 'b') { + return false; + } + return true; + }); + // 监听输入 this.xterm.onData((data) => { window.terminalAPI.writePty(this.id, data);