feat: add agent panel, file panel, context menu, fix agy cursor, and bump to v1.1.0
Build and Release / Build & Release DEB Package (push) Successful in 3m27s
Build and Release / Build & Release DEB Package (push) Successful in 3m27s
- Agent panel: dedicated panel per tab with compact icon-only agent tabs (agy with Google colors, codex with ChatGPT icon, claude) - Agent lifecycle: auto-close tab and panel when agent process exits without leaving a shell - Fix cursor jumping: disable convertEol and ensure precise viewport size calculation - File browser panel: single instance per tab, directory navigation, hidden files toggle - Context menu: terminal right-click menu with split options, agent launcher, and close pane - Terminal shortcuts: Ctrl+Shift+C / Ctrl+Shift+V copy and paste support - Test suite: 35 automated end-to-end tests covering all new features
This commit is contained in:
+93
-4
@@ -3,6 +3,7 @@ import { TabManager } from './tab-manager.js';
|
||||
import { WorkspaceManager } from './workspace-manager.js';
|
||||
import { CommandPalette } from './command-palette.js';
|
||||
import { SettingsModal } from './settings.js';
|
||||
import { ContextMenu } from './context-menu.js';
|
||||
|
||||
class TerminalApp {
|
||||
constructor() {
|
||||
@@ -10,6 +11,7 @@ class TerminalApp {
|
||||
this.initTheme();
|
||||
this.initTabManager();
|
||||
this.initWorkspaceManager();
|
||||
this.initContextMenu();
|
||||
this.initModals();
|
||||
this.initSearch();
|
||||
this.initWindowControls();
|
||||
@@ -59,6 +61,10 @@ class TerminalApp {
|
||||
if (activeTerm && this.statusShell) {
|
||||
this.statusShell.textContent = activeTerm.title || 'bash';
|
||||
}
|
||||
this.updateSplitButtonsState(activeTerm);
|
||||
},
|
||||
onContextMenu: (e, term) => {
|
||||
this.contextMenu?.show(e, term);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -75,12 +81,43 @@ class TerminalApp {
|
||||
});
|
||||
}
|
||||
|
||||
updateSplitButtonsState(activeTerm) {
|
||||
const isAgent = !!(
|
||||
activeTerm?.isAgent ||
|
||||
activeTerm?.element?.closest?.('.agent-panel') ||
|
||||
activeTerm?.terminalWrapper?.closest?.('.agent-panel')
|
||||
);
|
||||
const btnH = document.getElementById('btn-split-h');
|
||||
const btnV = document.getElementById('btn-split-v');
|
||||
if (btnH) {
|
||||
btnH.disabled = isAgent;
|
||||
btnH.classList.toggle('disabled', isAgent);
|
||||
btnH.title = isAgent ? 'Agent 面板不可分屏' : '水平分屏 (Ctrl+Shift+E)';
|
||||
}
|
||||
if (btnV) {
|
||||
btnV.disabled = isAgent;
|
||||
btnV.classList.toggle('disabled', isAgent);
|
||||
btnV.title = isAgent ? 'Agent 面板不可分屏' : '垂直分屏 (Ctrl+Shift+O)';
|
||||
}
|
||||
}
|
||||
|
||||
initWorkspaceManager() {
|
||||
this.workspaceManager = new WorkspaceManager({
|
||||
tabManager: this.tabManager,
|
||||
});
|
||||
}
|
||||
|
||||
initContextMenu() {
|
||||
this.contextMenu = new ContextMenu({
|
||||
tabManager: this.tabManager,
|
||||
workspaceManager: this.workspaceManager,
|
||||
});
|
||||
|
||||
window.addEventListener('terminal-contextmenu', (e) => {
|
||||
this.contextMenu?.show(e.detail.event, e.detail.termView);
|
||||
});
|
||||
}
|
||||
|
||||
initModals() {
|
||||
this.commandPalette = new CommandPalette({
|
||||
overlay: this.commandPaletteOverlay,
|
||||
@@ -109,12 +146,20 @@ class TerminalApp {
|
||||
|
||||
document.getElementById('btn-split-h')?.addEventListener('click', () => {
|
||||
const tab = this.tabManager.getActiveTab();
|
||||
if (tab) tab.splitTree.splitCurrent('horizontal');
|
||||
if (tab && !tab.splitTree.activeTerminal?.isAgent) {
|
||||
tab.splitTree.splitCurrent('horizontal');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btn-split-v')?.addEventListener('click', () => {
|
||||
const tab = this.tabManager.getActiveTab();
|
||||
if (tab) tab.splitTree.splitCurrent('vertical');
|
||||
if (tab && !tab.splitTree.activeTerminal?.isAgent) {
|
||||
tab.splitTree.splitCurrent('vertical');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btn-file-browser')?.addEventListener('click', () => {
|
||||
this.toggleFileBrowser();
|
||||
});
|
||||
|
||||
document.getElementById('btn-search')?.addEventListener('click', () => {
|
||||
@@ -176,6 +221,12 @@ class TerminalApp {
|
||||
}
|
||||
}
|
||||
|
||||
toggleFileBrowser(initialDir = null) {
|
||||
const tab = this.tabManager?.getActiveTab();
|
||||
if (!tab || !tab.splitTree) return null;
|
||||
return tab.splitTree.toggleFilePanel(initialDir);
|
||||
}
|
||||
|
||||
initWindowControls() {
|
||||
document.getElementById('win-min')?.addEventListener('click', () => {
|
||||
window.terminalAPI.minimize();
|
||||
@@ -241,6 +292,32 @@ class TerminalApp {
|
||||
return;
|
||||
}
|
||||
|
||||
const isEditingText = (el) => {
|
||||
if (!el) return false;
|
||||
const tag = el.tagName?.toLowerCase();
|
||||
return (tag === 'input' || tag === 'textarea') && !el.classList.contains('xterm-helper-textarea');
|
||||
};
|
||||
|
||||
// Ctrl+Shift+C: 复制当前选中终端文本
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'c') {
|
||||
if (isEditingText(e.target)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const activeTerm = this.tabManager.getActiveTerminal();
|
||||
if (activeTerm) activeTerm.copySelection();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Shift+V: 粘贴剪贴板文本到当前终端
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'v') {
|
||||
if (isEditingText(e.target)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const activeTerm = this.tabManager.getActiveTerminal();
|
||||
if (activeTerm) activeTerm.pasteFromClipboard();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Shift+T: 新建当前工作区标签页
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 't') {
|
||||
e.preventDefault();
|
||||
@@ -273,7 +350,9 @@ class TerminalApp {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const tab = this.tabManager.getActiveTab();
|
||||
if (tab) tab.splitTree.splitCurrent('horizontal');
|
||||
if (tab && !tab.splitTree.activeTerminal?.isAgent) {
|
||||
tab.splitTree.splitCurrent('horizontal');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -282,7 +361,9 @@ class TerminalApp {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const tab = this.tabManager.getActiveTab();
|
||||
if (tab) tab.splitTree.splitCurrent('vertical');
|
||||
if (tab && !tab.splitTree.activeTerminal?.isAgent) {
|
||||
tab.splitTree.splitCurrent('vertical');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -294,6 +375,14 @@ class TerminalApp {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Shift+U 或 Alt+F: 切换文件浏览面板
|
||||
if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'u') || (e.altKey && !e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'f')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleFileBrowser();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+,: 设置
|
||||
if (e.ctrlKey && e.key === ',') {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user