feat: command snippets + UI test coverage for new features
- core/snippets.py: SnippetManager with 20 prebuilt sysadmin commands, CRUD + reorder, persisted to ~/.sshclient/snippets.json - ui/snippet_dialog.py: SnippetDialog (add/edit) + SnippetManagerDialog (table view with add/edit/delete/move up/down) - ui/terminal_panel.py: snippet combo box in toolbar, send_command() method to inject commands into interactive shell, snippet manager button - README.md: updated feature table, usage section, project structure - test_ui.py: added [2/4] checks for sparkline/snippets/theme, added [4/4] theme toggle verification
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
命令片段管理对话框
|
||||
"""
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
|
||||
QPushButton, QLabel, QLineEdit, QTextEdit, QMessageBox, QHeaderView,
|
||||
QAbstractItemView, QSplitter, QWidget, QFormLayout,
|
||||
)
|
||||
|
||||
from core.snippets import SnippetManager
|
||||
|
||||
|
||||
class SnippetDialog(QDialog):
|
||||
"""单个片段的编辑/新增对话框"""
|
||||
|
||||
def __init__(self, parent=None, name: str = "", cmd: str = ""):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("编辑片段" if name else "新增片段")
|
||||
self.setMinimumWidth(460)
|
||||
v = QVBoxLayout(self)
|
||||
form = QFormLayout()
|
||||
self.name_edit = QLineEdit(name)
|
||||
self.name_edit.setPlaceholderText("如:查看端口占用")
|
||||
form.addRow("名称:", self.name_edit)
|
||||
self.cmd_edit = QTextEdit(cmd)
|
||||
self.cmd_edit.setPlaceholderText("如:ss -tlnp | grep :80")
|
||||
self.cmd_edit.setMaximumHeight(80)
|
||||
form.addRow("命令:", self.cmd_edit)
|
||||
v.addLayout(form)
|
||||
btns = QHBoxLayout()
|
||||
btns.addStretch(1)
|
||||
ok = QPushButton("保存")
|
||||
ok.clicked.connect(self._on_ok)
|
||||
cancel = QPushButton("取消")
|
||||
cancel.clicked.connect(self.reject)
|
||||
btns.addWidget(ok)
|
||||
btns.addWidget(cancel)
|
||||
v.addLayout(btns)
|
||||
|
||||
def _on_ok(self):
|
||||
if not self.name_edit.text().strip():
|
||||
QMessageBox.warning(self, "提示", "请输入名称")
|
||||
return
|
||||
if not self.cmd_edit.toPlainText().strip():
|
||||
QMessageBox.warning(self, "提示", "请输入命令")
|
||||
return
|
||||
self.accept()
|
||||
|
||||
def get_value(self) -> dict:
|
||||
return {
|
||||
"name": self.name_edit.text().strip(),
|
||||
"cmd": self.cmd_edit.toPlainText().strip(),
|
||||
}
|
||||
|
||||
|
||||
class SnippetManagerDialog(QDialog):
|
||||
"""片段管理主对话框:表格 + 增删改 + 上下移"""
|
||||
|
||||
def __init__(self, mgr: SnippetManager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.mgr = mgr
|
||||
self.setWindowTitle("命令片段管理")
|
||||
self.setMinimumSize(520, 420)
|
||||
self._build_ui()
|
||||
self._refresh_table()
|
||||
|
||||
def _build_ui(self):
|
||||
v = QVBoxLayout(self)
|
||||
# 工具栏
|
||||
toolbar = QHBoxLayout()
|
||||
toolbar.addWidget(QLabel("📋 命令片段列表"))
|
||||
toolbar.addStretch(1)
|
||||
self.btn_add = QPushButton("➕ 新增")
|
||||
self.btn_add.clicked.connect(self._add)
|
||||
self.btn_edit = QPushButton("✏ 编辑")
|
||||
self.btn_edit.clicked.connect(self._edit)
|
||||
self.btn_del = QPushButton("🗑 删除")
|
||||
self.btn_del.clicked.connect(self._delete)
|
||||
self.btn_up = QPushButton("⬆ 上移")
|
||||
self.btn_up.clicked.connect(lambda: self._move(-1))
|
||||
self.btn_down = QPushButton("⬇ 下移")
|
||||
self.btn_down.clicked.connect(lambda: self._move(1))
|
||||
for b in (self.btn_add, self.btn_edit, self.btn_del, self.btn_up, self.btn_down):
|
||||
toolbar.addWidget(b)
|
||||
v.addLayout(toolbar)
|
||||
# 表格
|
||||
self.table = QTableWidget(0, 2)
|
||||
self.table.setHorizontalHeaderLabels(["名称", "命令"])
|
||||
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
||||
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
self.table.doubleClicked.connect(self._edit)
|
||||
v.addWidget(self.table)
|
||||
# 关闭按钮
|
||||
btns = QHBoxLayout()
|
||||
btns.addStretch(1)
|
||||
close = QPushButton("关闭")
|
||||
close.clicked.connect(self.accept)
|
||||
btns.addWidget(close)
|
||||
v.addLayout(btns)
|
||||
|
||||
def _refresh_table(self):
|
||||
snippets = self.mgr.list_all()
|
||||
self.table.setRowCount(len(snippets))
|
||||
for i, s in enumerate(snippets):
|
||||
self.table.setItem(i, 0, QTableWidgetItem(s["name"]))
|
||||
cmd_display = s["cmd"][:60] + ("..." if len(s["cmd"]) > 60 else "")
|
||||
self.table.setItem(i, 1, QTableWidgetItem(cmd_display))
|
||||
self.table.item(i, 0).setToolTip(s["cmd"])
|
||||
|
||||
def _add(self):
|
||||
dlg = SnippetDialog(self)
|
||||
if dlg.exec_() == dlg.Accepted:
|
||||
v = dlg.get_value()
|
||||
self.mgr.add(v["name"], v["cmd"])
|
||||
self._refresh_table()
|
||||
|
||||
def _edit(self):
|
||||
row = self.table.currentRow()
|
||||
if row < 0:
|
||||
return
|
||||
s = self.mgr.list_all()[row]
|
||||
dlg = SnippetDialog(self, s["name"], s["cmd"])
|
||||
if dlg.exec_() == dlg.Accepted:
|
||||
v = dlg.get_value()
|
||||
self.mgr.update(row, v["name"], v["cmd"])
|
||||
self._refresh_table()
|
||||
|
||||
def _delete(self):
|
||||
row = self.table.currentRow()
|
||||
if row < 0:
|
||||
return
|
||||
s = self.mgr.list_all()[row]
|
||||
if QMessageBox.question(
|
||||
self, "确认删除", f"删除片段「{s['name']}」?",
|
||||
QMessageBox.Yes | QMessageBox.No
|
||||
) == QMessageBox.Yes:
|
||||
self.mgr.remove(row)
|
||||
self._refresh_table()
|
||||
|
||||
def _move(self, direction):
|
||||
row = self.table.currentRow()
|
||||
if row < 0:
|
||||
return
|
||||
self.mgr.move(row, direction)
|
||||
new_row = row + direction
|
||||
self._refresh_table()
|
||||
if 0 <= new_row < self.table.rowCount():
|
||||
self.table.selectRow(new_row)
|
||||
Reference in New Issue
Block a user