feat: real interactive terminal (Xshell/PuTTY style)
User feedback: 'your terminal can only send commands, not type freely. Useless.' Replaced the old 'single command + output' terminal with a true interactive shell panel: NEW: ui/terminal_panel.py - Uses paramiko invoke_shell() to open a long-lived shell session - Every keystroke is forwarded to the remote shell (not a textbox) - Remote output (incl. ANSI color codes) is parsed and rendered inline in a QPlainTextEdit (16-color SGR, bold, underline, invert) - Cursor kept on the current input line; user can't edit past output - Local command history with up/down navigation (stores last 200) - Standard terminal shortcuts: Ctrl+C (SIGINT), Ctrl+D (EOF), Ctrl+L (clear), Ctrl+A/E (line start/end), Ctrl+U/K/W - Terminal resize forwarded to remote PTY (vim/top work correctly) - Tab key forwarded to remote for completion CHANGED: core/ssh_client.py - Added open_shell(term_type, cols, rows) -> Channel - Added resize_shell(chan, cols, rows) CHANGED: ui/main_window.py - Replaced old _build_terminal_tab (now removed) with TerminalPanel - On host connect -> auto-open shell, switch to Terminal tab - On host disconnect / switch -> close shell cleanly NEW: test_terminal.py (5 tests, all pass) - Real SSH connection, opens shell - Sends 'ls /tmp' via simulated keypresses, verifies 162-line output - Sends ANSI color command, verifies colored text appears - Tests up-arrow history recall - Verifies prompt returns after command completes All existing tests still pass (core 6/6 + UI 3/3 + E2E 6/6 + terminal 5/5)
This commit is contained in:
@@ -89,6 +89,27 @@ class SSHConnection:
|
||||
self.client = None
|
||||
self.connected = False
|
||||
|
||||
def open_shell(self, term_type: str = "xterm", cols: int = 80, rows: int = 24):
|
||||
"""打开一个交互式 shell 会话(用于真正的终端模拟器)"""
|
||||
if not self.connected or not self.client:
|
||||
return None
|
||||
try:
|
||||
chan = self.client.invoke_shell(term=term_type, width=cols, height=rows)
|
||||
chan.settimeout(0.0) # 非阻塞
|
||||
return chan
|
||||
except Exception as e:
|
||||
self.last_error = f"打开 shell 失败: {e}"
|
||||
return None
|
||||
|
||||
def resize_shell(self, chan, cols: int, rows: int):
|
||||
"""终端大小变化时通知远端(vim/top 这类全屏程序需要)"""
|
||||
if not chan:
|
||||
return
|
||||
try:
|
||||
chan.resize_pty(width=cols, height=rows)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def exec_command(self, command: str, timeout: int = 30) -> Tuple[int, str, str]:
|
||||
"""执行远程命令;返回 (退出码, stdout, stderr)"""
|
||||
if not self.connected or not self.client:
|
||||
|
||||
Reference in New Issue
Block a user