fix: intercept Ctrl+B and app shortcuts in capture phase when terminal is focused

This commit is contained in:
2026-09-24 01:38:35 +08:00
parent 35452ef235
commit ebf454a250
2 changed files with 108 additions and 86 deletions
+100 -86
View File
@@ -217,97 +217,111 @@ class TerminalApp {
} }
initKeyboardShortcuts() { initKeyboardShortcuts() {
window.addEventListener('keydown', (e) => { window.addEventListener(
// Ctrl+Shift+P 或 F1: 命令面板 'keydown',
if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') || e.key === 'F1') { (e) => {
e.preventDefault(); // Ctrl+Shift+P 或 F1: 命令面板
this.commandPalette.toggle(); if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'p') || e.key === 'F1') {
return; e.preventDefault();
} e.stopPropagation();
this.commandPalette.toggle();
// Ctrl+B: 切换左侧工作区侧边栏 return;
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);
} }
return;
}
// Ctrl+Shift+E: 水平分屏 (左右) // Ctrl+B: 切换左侧工作区侧边栏
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'e') { if (e.ctrlKey && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'b') {
e.preventDefault(); e.preventDefault();
const tab = this.tabManager.getActiveTab(); e.stopPropagation();
if (tab) tab.splitTree.splitCurrent('horizontal'); this.workspaceManager?.toggleSidebar();
return; 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();
} }
return;
}
// Alt+1 .. Alt+9: 快速直达标签页 // Ctrl+Shift+T: 新建当前工作区标签页
if (e.altKey && e.key >= '1' && e.key <= '9') { if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 't') {
e.preventDefault(); e.preventDefault();
const index = parseInt(e.key, 10) - 1; e.stopPropagation();
const ids = Array.from(this.tabManager.tabs.keys()); const activeWs = this.workspaceManager ? this.workspaceManager.getActiveWorkspace() : null;
if (ids[index]) { const spawnOpts = this.workspaceManager ? this.workspaceManager.getSpawnOptions(activeWs?.id) : {};
this.tabManager.switchTab(ids[index]); 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() { async initStatusBar() {
+8
View File
@@ -75,6 +75,14 @@ export class TerminalView {
console.warn('WebGL addon not supported, fallback to default Canvas renderer:', e); 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) => { this.xterm.onData((data) => {
window.terminalAPI.writePty(this.id, data); window.terminalAPI.writePty(this.id, data);