"""Sessions panel widget for the TUI."""
from __future__ import annotations
from textual.app import ComposeResult
from textual.containers import Vertical
from textual.widgets import DataTable, Static
from textual.widgets.data_table import RowKey
from clients.terminal.tui.events import SessionListUpdated, SessionSelected, SessionInfo
class SessionsPanel(Vertical):
"""Right-side panel showing server sessions and allowing quick switching."""
DEFAULT_CSS = """
SessionsPanel {
border: solid $tui-panel;
background: $tui-panel;
color: $tui-text-muted;
padding: 0;
height: 1fr;
width: 1fr;
}
SessionsPanel .title {
text-style: bold;
color: $tui-primary;
padding: 1 1 0 1;
height: auto;
}
SessionsPanel DataTable {
height: 1fr;
border: none;
background: $tui-panel;
color: $tui-text-muted;
}
SessionsPanel DataTable > .datatable--header {
color: $tui-text-dim;
text-style: bold;
background: $tui-surface;
}
SessionsPanel DataTable > .datatable--row {
background: $tui-panel;
}
SessionsPanel DataTable > .datatable--row-sessions-panel-cursor {
background: $tui-selection;
color: $tui-background;
}
SessionsPanel DataTable > .datatable--row-sessions-panel-cursor .datatable--cursor {
color: $tui-background;
}
SessionsPanel .empty {
color: $tui-text-dim;
text-align: center;
padding: 1;
}
"""
def __init__(self) -> None:
super().__init__()
self._title = Static("Sessions", classes="title")
self._table: DataTable | None = None
self._sessions: list[SessionInfo] = []
self._current_id: str | None = None
def compose(self) -> ComposeResult:
yield self._title
yield DataTable(id="sessions-table")
def on_mount(self) -> None:
self._table = self.query_one("#sessions-table", DataTable)
self._table.cursor_type = "row"
self._table.show_header = True
self._table.zebra_stripes = True
self._table.add_columns("", "ID", "Profile", "Preview")
self._populate_table()
def on_session_list_updated(self, event: SessionListUpdated) -> None:
self._sessions = event.sessions
self._current_id = event.current_id
self._populate_table()
def _populate_table(self) -> None:
if self._table is None:
return
self._table.clear()
if not self._sessions:
self._table.add_row("", "—", "", "No sessions")
return
for session in self._sessions:
marker = "●" if session.id == self._current_id else ""
short_id = "-".join(session.id.split("-")[:2])
profile = self._truncate(session.profile_id, 12)
preview = self._truncate(session.title, 24)
self._table.add_row(marker, short_id, profile, preview, key=session.id)
if self._current_id:
try:
self._table.move_cursor(row=RowKey(self._current_id))
except Exception:
pass
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
session_id = str(event.row_key.value) if event.row_key else ""
if session_id:
self.post_message(SessionSelected(session_id))
def on_key(self, event) -> None:
if self._table is None:
return
if hasattr(event, "key") and event.key in ("enter", "return"):
cursor = self._table.cursor_coordinate
if cursor:
row_key = self._table.get_row_at(cursor.row)
session_id = str(row_key) if row_key else ""
if session_id:
self.post_message(SessionSelected(session_id))
event.stop()
event.prevent_default()
@staticmethod
def _truncate(text: str, max_len: int) -> str:
if len(text) <= max_len:
return text
return text[: max_len - 1] + "…"