""" Registry Tab — displays loaded plugins and their status. Subscribes to registry.plugin.* events to show live plugin state. Allows loading/unloading plugins interactively. """ from __future__ import annotations from typing import Any, Optional from textual.app import ComposeResult from textual.binding import Binding from textual.widget import Widget from textual.widgets import DataTable, Footer, Label, Static class RegistryTab(Widget): """ Plugin registry viewer. Shows all discovered plugins with their version, status, module path, and load timestamp. Pressing Enter on a row toggles load/unload. """ DEFAULT_CSS = """ RegistryTab { layout: vertical; height: 1fr; padding: 1 2; } RegistryTab .tab-title { color: $primary; text-style: bold; height: 1; margin-bottom: 1; } RegistryTab DataTable { height: 1fr; border: round $primary; } RegistryTab .hint { height: 1; color: $text-muted; margin-top: 1; } """ BINDINGS = [ Binding("r", "refresh_registry", "Refresh"), Binding("enter", "toggle_plugin", "Toggle"), ] COLUMNS = [ ("Name", 14), ("Version", 9), ("Status", 11), ("Tags", 22), ("Loaded At", 22), ("Module", 32), ("Error", 30), ] def compose(self) -> ComposeResult: yield Static("// REGISTRY — Plugin Manifest", classes="tab-title") yield DataTable(id="registry-table", zebra_stripes=True, cursor_type="row") yield Static( "Press [bold]r[/bold] to refresh | [bold]Enter[/bold] to toggle load", classes="hint", ) def on_mount(self) -> None: table = self.query_one("#registry-table", DataTable) for col_label, width in self.COLUMNS: table.add_column(col_label, width=width) app = self.app if hasattr(app, "bus"): app.bus.subscribe("registry.plugin.*", self._on_registry_event) self._refresh_table() def _on_registry_event(self, topic: str, data: Any) -> None: self.call_from_thread(self._refresh_table) def _refresh_table(self) -> None: table = self.query_one("#registry-table", DataTable) table.clear() app = self.app if not hasattr(app, "registry"): return for record in sorted(app.registry.all_records(), key=lambda r: r.name): loaded_at = record.loaded_at.strftime("%Y-%m-%d %H:%M:%S") if record.loaded_at else "—" table.add_row( record.name, record.version, record.status.name, ", ".join(record.tags) or "—", loaded_at, record.module_path, record.error or "—", ) def action_refresh_registry(self) -> None: self._refresh_table() def action_toggle_plugin(self) -> None: table = self.query_one("#registry-table", DataTable) row_key = table.cursor_row if row_key is None: return # Get plugin name from the focused row try: cell_value = table.get_cell_at((row_key, 0)) except Exception: return plugin_name = str(cell_value) app = self.app if not hasattr(app, "registry"): return record = app.registry.get(plugin_name) if record is None: return from core.registry import PluginStatus if record.status == PluginStatus.ACTIVE: app.registry.unload(plugin_name) else: app.registry.load(plugin_name) self._refresh_table()