"""Inline slash-command hints shown above the input box.
A non-interactive ``Static`` that renders the commands matching the current
input while the user is typing a ``/``-command. It is purely visual — it never
takes focus and does not intercept keys. ``Tab`` completion and ``Enter``
execution are handled by :class:`InputBox` / ``_PromptInput``.
Visibility rule: hints appear only while the input starts with ``/`` and
contains no whitespace yet (i.e. the command name is still being typed). The
moment a space appears the command name is complete and the hints hide.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from rich.text import Text
from textual.widgets import Static
if TYPE_CHECKING:
from clients.terminal.tui.commands.base import BaseCommand
# Cap the list so a one-character prefix does not push the whole input frame up
# by half a screen.
_MAX_HINTS = 8
class CommandHints(Static):
"""Render matching slash commands for the current input."""
DEFAULT_CSS = """
CommandHints {
display: none;
height: auto;
max-height: 10;
width: 100%;
padding: 0 1;
background: $tui-surface;
color: $tui-text;
border-top: solid $tui-border;
}
"""
def __init__(self) -> None:
super().__init__("")
self._matches: list[BaseCommand] = []
def update_for(self, text: str) -> None:
"""Recompute hints for ``text`` and show/hide the widget."""
# Show only while typing the command name: starts with '/' and no space.
if not text.startswith("/") or any(ch.isspace() for ch in text):
self._matches = []
self.display = False
return
from clients.terminal.tui.commands.registry import get_registry
self._matches = get_registry().match(text[1:])
if not self._matches:
self.display = False
return
self.update(self._build_content())
self.display = True
def first_match(self) -> BaseCommand | None:
"""Top match (exact-name-first), used for ``Tab`` completion."""
return self._matches[0] if self._matches else None
def _build_content(self) -> Text:
lines: list[Text] = []
for cmd in self._matches[:_MAX_HINTS]:
meta = cmd.meta
aliases = f" ({', '.join(meta.aliases)})" if meta.aliases else ""
keybind = f" [{meta.keybind}]" if meta.keybind else ""
lines.append(
Text.assemble(
Text(f"/{meta.name}{aliases}", style="bold"),
Text(keybind, style="dim"),
Text(f" — {meta.description}", style="dim"),
)
)
return Text("\n").join(lines)