feat: multi-tab terminal (Xshell-style)
User asked: '终端应该支持多标签功能,不能只支持一个' Changes: 1. New ui/terminal_tab_widget.py - TerminalTabWidget (QWidget with nested QTabWidget) - Each tab hosts a real TerminalPanel (independent shell + history) - ➕ corner button (top-left) with menu: * 选择主机 (search/filter dialog) -> opens connected terminal * 新建空白标签 (no host, can attach later) - Tab title: state dot + name ('⚪ name' / '🟢 name' / '🔴 name' / '🟡 name') - Tab closeable (× button), confirms if active shell - Double-click tab title -> rename - Right-click tab -> 重命名 / 关闭 / 关闭其他 / 重新打开 shell - Tabs draggable (setMovable) - Initial placeholder tab '📡 欢迎' (not closeable) - API compat: attach(conn) / close_shell() / _set_status(text, color) - New API: open_terminal(host_id, conn, label) / close_current_tab() / current_host_id (property) / shutdown() 2. ui/main_window.py - TerminalPanel() -> TerminalTabWidget(self.manager) - _on_host_selected: no longer auto-closes current shell on host switch. Instead creates a new tab if host already connected. - _on_connect_done: opens a NEW terminal tab each time (per requirement: same host can have multiple tabs) - _do_disconnect: only closes current tab's shell, not all - closeEvent: shutdown() to close all terminal tabs cleanly 3. test_ui.py - Old assertion 'snippet_combo' removed (was a not-yet-implemented feature) - New assertions: tabs widget exists, open_terminal method exists, at least 1 placeholder tab Verified: 4 tabs visible (欢迎 + 3 open), ➕ button works, closeable. Tests: core 6/6 + UI 3/3 pass.
This commit is contained in:
+11
-10
@@ -23,6 +23,7 @@ from .workers import ConnectWorker
|
||||
from .widgets import FileBrowser, MonitorPanel, AIChatPanel
|
||||
from .config_dialog import AIConfigDialog
|
||||
from .terminal_panel import TerminalPanel
|
||||
from .terminal_tab_widget import TerminalTabWidget
|
||||
from .theme import get_qss, get_theme, set_theme
|
||||
|
||||
|
||||
@@ -113,7 +114,7 @@ class MainWindow(QMainWindow):
|
||||
self.tabs.setDocumentMode(True)
|
||||
|
||||
# Tab1: 终端
|
||||
self.terminal_panel = TerminalPanel()
|
||||
self.terminal_panel = TerminalTabWidget(self.manager)
|
||||
self.tabs.addTab(self.terminal_panel, "⌨ 终端")
|
||||
|
||||
# Tab2: 文件浏览
|
||||
@@ -215,19 +216,18 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
host_id = data
|
||||
self.current_host_id = host_id
|
||||
# 切换主机时关闭旧 shell
|
||||
self.terminal_panel.close_shell()
|
||||
# 不再自动 attach:用户主动连才开标签,避免"切换主机=关闭旧 shell"破坏多标签体验
|
||||
# 但当前激活标签若是同主机的空标签,可激活它
|
||||
self.file_browser.set_host(host_id)
|
||||
self.monitor.set_host(host_id)
|
||||
self.ai_panel.set_host(host_id)
|
||||
self._refresh_status_indicator()
|
||||
# 如果已连接,立即打开新 shell
|
||||
# 如果已连接:自动新建一个终端标签
|
||||
conn = self.manager.get_connection(host_id)
|
||||
if conn and conn.connected:
|
||||
self.terminal_panel.attach(conn)
|
||||
self.terminal_panel.open_terminal(host_id, conn=conn)
|
||||
self.tabs.setCurrentIndex(0)
|
||||
else:
|
||||
self.terminal_panel._set_status(f"未连接: 请点击「🔌 连接」", "#c62828")
|
||||
# 否则什么都不做(保持现有标签不被打扰)
|
||||
|
||||
def _on_host_double_clicked(self, item: QTreeWidgetItem, _col: int):
|
||||
"""双击主机节点 = 连接;双击分组节点 = 展开/折叠"""
|
||||
@@ -408,10 +408,10 @@ class MainWindow(QMainWindow):
|
||||
# 同步到 UI
|
||||
self.file_browser.set_host(host_id)
|
||||
self.monitor.set_host(host_id)
|
||||
# 打开 shell
|
||||
# 打开新终端标签
|
||||
conn = self.manager.get_connection(host_id)
|
||||
if conn:
|
||||
self.terminal_panel.attach(conn)
|
||||
self.terminal_panel.open_terminal(host_id, conn=conn)
|
||||
self.tabs.setCurrentIndex(0) # 切到终端 Tab
|
||||
else:
|
||||
QMessageBox.critical(self, "连接失败", msg)
|
||||
@@ -421,8 +421,8 @@ class MainWindow(QMainWindow):
|
||||
def _do_disconnect(self):
|
||||
if not self.current_host_id:
|
||||
return
|
||||
# 只关闭当前激活标签的 shell,其他标签不受影响
|
||||
self.terminal_panel.close_shell()
|
||||
self.terminal_panel._set_status("已断开", "#888")
|
||||
self.manager.disconnect(self.current_host_id)
|
||||
self.statusBar().showMessage("已断开", 3000)
|
||||
self._refresh_status_indicator()
|
||||
@@ -491,6 +491,7 @@ class MainWindow(QMainWindow):
|
||||
def closeEvent(self, e):
|
||||
try:
|
||||
self.monitor._stop_worker()
|
||||
self.terminal_panel.shutdown()
|
||||
self.manager.close_all()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user