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>
57 lines
2.3 KiB
Python
57 lines
2.3 KiB
Python
"""Envoi simultane (« Multi-Send ") : une frappe faite dans un onglet terminal part
|
|
aussi vers tous les autres onglets terminal ouverts, quand le mode est actif."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Callable, Iterable
|
|
|
|
from PySide6.QtCore import QObject
|
|
from PySide6.QtWidgets import QToolButton, QWidget
|
|
|
|
|
|
class BroadcastManager(QObject):
|
|
"""Ne connait pas le QTabWidget : `tabs_provider` lui fournit les onglets courants,
|
|
ce qui la garde independante de ConnectionController."""
|
|
|
|
def __init__(self, tabs_provider: Callable[[], Iterable[QWidget]], parent=None):
|
|
super().__init__(parent)
|
|
self._tabs_provider = tabs_provider
|
|
|
|
self.button = QToolButton()
|
|
self.button.setText("⇉ Multi")
|
|
self.button.setCheckable(True)
|
|
self.button.setToolTip(
|
|
"Envoi simultane : ce que vous tapez part vers tous les onglets terminal ouverts"
|
|
)
|
|
|
|
@property
|
|
def enabled(self) -> bool:
|
|
return self.button.isChecked()
|
|
|
|
def register(self, tab) -> None:
|
|
"""A appeler pour chaque nouvel onglet terminal (SSH ou local) susceptible
|
|
d'emettre et de recevoir des donnees diffusees."""
|
|
tab.terminal.data_to_send.connect(lambda data, source=tab: self._broadcast(data, source))
|
|
|
|
def _broadcast(self, data: bytes, source) -> None:
|
|
if not self.enabled:
|
|
return
|
|
self._send_to_all(data, exclude=source)
|
|
|
|
def broadcast_now(self, data: bytes) -> None:
|
|
"""Diffuse immediatement vers tous les terminaux, independamment de l'etat
|
|
du bouton Multi : declenchement explicite et ponctuel (ex : un snippet
|
|
execute avec « Executer avec Multi-Send »), pas une frappe clavier."""
|
|
self._send_to_all(data, exclude=None)
|
|
|
|
def _send_to_all(self, data: bytes, exclude) -> None:
|
|
# import tardif : evite un cycle (app.ui.tabs importe des dialogues, pas ce module)
|
|
from app.ui.tabs import BaseTerminalTab
|
|
|
|
for tab in self._tabs_provider():
|
|
if tab is exclude or not isinstance(tab, BaseTerminalTab):
|
|
continue
|
|
if tab.connection:
|
|
# on ecrit directement dans la connexion : passer par le terminal
|
|
# relancerait la diffusion et boucherait a l'infini
|
|
tab.connection.send(data)
|