fix: terminal command output not displaying - add diagnostics

User reported: '已连接, 但终端啥也不显示'
Likely cause: command ran but stdout was empty / scrolled to wrong place
/ output widget didn't refresh before worker finished.

Changes:
- Eagerly show the typed command in the output area (\$ <cmd>) so user
  sees the input was received even if the worker hangs/fails
- Show '(stdout 为空)' placeholder when worker returns no output, so it's
  clear the worker actually ran
- Force output.repaint() and ensureCursorVisible() after append
- Add 2 diagnostic buttons in terminal bottom bar:
    * [🔍 诊断]  prints current SSH connection / worker state
    * [▶ 测试命令]  pre-fills a known-good test command
- Make CommandWorker explicitly check conn.connected before exec
- Surface full traceback in stderr on exception instead of bare str(e)

All 15 existing tests still pass (core 6/6 + UI 3/3 + E2E 6/6).
This commit is contained in:
Hermes
2026-07-28 21:18:21 +08:00
parent 01c8d3581a
commit 8b5dacda58
2 changed files with 59 additions and 7 deletions
+54 -6
View File
@@ -183,9 +183,17 @@ class MainWindow(QMainWindow):
self.btn_clear_out.clicked.connect(self.output.clear)
self.btn_copy = QPushButton("复制输出")
self.btn_copy.clicked.connect(lambda: QApplication.clipboard().setText(self.output.toPlainText()))
self.btn_debug = QPushButton("🔍 诊断")
self.btn_debug.setToolTip("打印 SSH 连接和 worker 状态")
self.btn_debug.clicked.connect(self._debug_state)
self.btn_test = QPushButton("▶ 测试命令")
self.btn_test.setToolTip("执行 'echo hello-sshclient && pwd && uname -a' 验证连通性")
self.btn_test.clicked.connect(lambda: self.cmd_input.setText("echo hello-sshclient && pwd && uname -a"))
bottom.addWidget(self.btn_clear_out)
bottom.addWidget(self.btn_copy)
bottom.addWidget(self.btn_debug)
bottom.addStretch(1)
bottom.addWidget(self.btn_test)
v.addLayout(bottom)
return w
@@ -364,31 +372,71 @@ class MainWindow(QMainWindow):
if not conn or not conn.connected:
QMessageBox.warning(self, "提示", "当前主机未连接")
return
self._append_out(f"\n$ {cmd}\n")
# 先在输出区显示命令(即使后续 worker 失败也能看到用户输入了啥)
self._append_out(f"\n$ {cmd}")
self.cmd_input.clear()
self.btn_run.setEnabled(False)
self.btn_run.setText("执行中...")
timeout = self.timeout_spin.value()
self.cmd_worker = CommandWorker(conn, cmd, timeout)
self.cmd_worker.finished_with.connect(self._on_cmd_done)
self.cmd_worker.start()
# 强制刷新滚动到底
self.output.repaint()
def _on_cmd_done(self, code: int, out: str, err: str):
self.btn_run.setEnabled(True)
self.btn_run.setText("执行")
if out:
self._append_out(out)
else:
self._append_out("(stdout 为空)")
if err:
self._append_out_err(err)
self._append_out(f"[exit={code}]\n")
self._append_out(f"[exit={code}]")
self.statusBar().showMessage(f"命令完成 (退出码 {code})", 3000)
# 强制刷新 + 滚到底
self.output.repaint()
def _append_out(self, text: str):
self.output.appendPlainText(text.rstrip("\n"))
# 不再 rstrip("\n"),保留原样;appendPlainText 本身会处理换行
self.output.appendPlainText(text)
# 滚到底
sb = self.output.verticalScrollBar()
sb.setValue(sb.maximum())
self.output.ensureCursorVisible()
def _append_out_err(self, text: str):
self.output.appendPlainText("[ERR] " + text.replace("\n", "\n[ERR] "))
sb = self.output.verticalScrollBar()
sb.setValue(sb.maximum())
def _append_out_err(self, text: str):
# 简单的 ANSI/颜色:stderr 用红色(PlainTextEdit 不支持富文本,所以加 [ERR] 前缀)
self.output.appendPlainText("[ERR] " + text.rstrip("\n").replace("\n", "\n[ERR] "))
def _debug_state(self):
"""诊断按钮:打印当前 SSH 连接和 worker 状态"""
lines = ["=== Debug State ==="]
lines.append(f"current_host_id: {self.current_host_id}")
if self.current_host_id:
h = self.manager.get_host(self.current_host_id)
lines.append(f"host config: {h}")
conn = self.manager.get_connection(self.current_host_id)
if conn:
lines.append(f"conn.connected: {conn.connected}")
lines.append(f"conn.client: {conn.client}")
lines.append(f"conn.sftp: {conn.sftp}")
if conn.client:
try:
transport = conn.client.get_transport()
lines.append(f"transport active: {transport.is_active() if transport else 'None'}")
except Exception as e:
lines.append(f"transport error: {e}")
else:
lines.append("conn: None (not connected)")
lines.append(f"cmd_worker: {self.cmd_worker}")
if self.cmd_worker:
lines.append(f"cmd_worker.isRunning: {self.cmd_worker.isRunning()}")
lines.append("=== End Debug ===")
self._append_out("\n".join(lines))
self.statusBar().showMessage("诊断信息已输出", 3000)
# ============================================================
# AI / 关于