feat: ajouter les Snippets de commandes

Bibliotheque de commandes organisee par categories (Linux/Docker/Proxmox
fournis en exemples au premier lancement, librement modifiables), avec
recherche.

- app/snippets.py : Snippet/SnippetStore, meme pattern que les autres stores
  (JSON atomique, schema_version).
- SnippetManagerDialog : creer/modifier/supprimer/rechercher, regroupe par
  categorie.
- Integration terminal : le dernier commit du depot a delibrement fait du
  clic droit un raccourci de collage (style terminal Unix), sans menu
  contextuel. Plutot que d'ecraser ce choix recent, les snippets passent par
  un bouton dedie "📋 Snippets" dans la barre de l'onglet (SessionTabWidget et
  LocalTerminalTabWidget), avec un menu Categorie -> Snippet -> Action
  (Inserer dans le terminal / Executer / Executer avec Multi-Send).
  "Inserer" n'envoie jamais Entree (le texte est ecrit, a l'utilisateur de
  valider) ; seul "Executer" l'ajoute.
- BroadcastManager.broadcast_now() : diffusion explicite et ponctuelle vers
  tous les terminaux, independante de l'etat du bouton Multi (utilisee par
  "Executer avec Multi-Send", qui est une action volontaire et pas une frappe
  clavier a relayer).
- La construction du menu (_build_snippets_menu) est separee de son
  affichage (_show_snippets_menu) : testable sans declencher l'appel
  bloquant menu.exec().

tests/test_snippets.py : stockage (CRUD, recherche, categories, persistance)
et integration terminal (inserer/executer/multi-send, construction et
declenchement du menu), avec LocalTerminalConnection.start() neutralisee.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Florian
2026-09-05 02:55:10 +02:00
parent a3c72ade0b
commit 0a07ddcb62
8 changed files with 545 additions and 10 deletions

View File

@@ -19,8 +19,10 @@ from app.terminal_widget import TerminalWidget
from app.dialogs.key_dialog import KeyManagerDialog
from app.dialogs.known_hosts_dialog import KnownHostsDialog
from app.dialogs.workspace_dialog import WorkspaceDialog
from app.dialogs.snippet_dialog import SnippetManagerDialog
from app.theme import THEME_LABELS, apply_theme, current_theme, save_theme
from app.workspace import WorkspaceStore
from app.snippets import SnippetStore
from app.controllers.broadcast_manager import BroadcastManager
from app.controllers.session_controller import SessionController
from app.controllers.connection_controller import ConnectionController
@@ -40,10 +42,12 @@ class MainWindow(QMainWindow):
self.session_store = SessionStore()
self.key_manager = KeyManager()
self.workspace_store = WorkspaceStore()
self.snippet_store = SnippetStore()
self.broadcast = BroadcastManager(self._iter_tabs, self)
self.connection_controller = ConnectionController(
self.session_store, self.key_manager, self.broadcast, parent_widget=self,
self.session_store, self.key_manager, self.broadcast,
snippet_store=self.snippet_store, parent_widget=self,
)
self.session_controller = SessionController(
self.session_store, self.key_manager, self.broadcast.button, parent_widget=self,
@@ -166,6 +170,11 @@ class MainWindow(QMainWindow):
manage_workspaces_action.triggered.connect(self.open_workspace_manager)
workspaces_menu.addAction(manage_workspaces_action)
snippets_menu = self.menuBar().addMenu("&Snippets")
manage_snippets_action = QAction("Gestionnaire de snippets...", self)
manage_snippets_action.triggered.connect(self.open_snippet_manager)
snippets_menu.addAction(manage_snippets_action)
help_menu = self.menuBar().addMenu("&Aide")
about_action = QAction("A propos", self)
about_action.triggered.connect(self.show_about)
@@ -219,6 +228,10 @@ class MainWindow(QMainWindow):
dialog = WorkspaceDialog(self.workspace_manager, parent=self)
dialog.exec()
def open_snippet_manager(self) -> None:
dialog = SnippetManagerDialog(self.snippet_store, parent=self)
dialog.exec()
def show_about(self) -> None:
QMessageBox.information(
self, "A propos",