Coverage for src / lilbee / cli / tui / widgets / arg_hint.py: 100%
40 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
1"""One-line status row that mirrors the active slash command's signature."""
3from __future__ import annotations
5from pathlib import Path
6from typing import ClassVar
8from textual.content import Content
9from textual.widgets import Static
11from lilbee.cli.tui.command_registry import COMMANDS
13_CSS_FILE = Path(__file__).parent / "arg_hint.tcss"
15_REGISTRY = {cmd.name: cmd for cmd in COMMANDS}
16for _cmd in COMMANDS:
17 for _alias in _cmd.aliases:
18 _REGISTRY[_alias] = _cmd
21class ArgHintLine(Static):
22 """Reactive hint row that mirrors the active slash command's signature."""
24 DEFAULT_CSS: ClassVar[str] = _CSS_FILE.read_text(encoding="utf-8")
26 def __init__(self, *, id: str | None = None) -> None:
27 super().__init__("", id=id)
28 self.display = False
30 def update_for_input(self, text: str) -> None:
31 """Render the appropriate hint for the current chat input contents."""
32 rendered = _hint_for(text)
33 if rendered is None:
34 self._clear()
35 return
36 self.update(rendered)
37 self.display = True
39 def _clear(self) -> None:
40 self.update("")
41 self.display = False
44def _hint_for(text: str) -> Content | None:
45 """Build the hint content for *text*, or ``None`` when nothing should show."""
46 if not text.startswith("/"):
47 return None
48 head = text.split(" ", 1)[0].lower()
49 cmd = _REGISTRY.get(head)
50 if cmd is None:
51 return None
52 # No trailing space yet means the user is still composing the command
53 # name itself; do not crowd them with the hint until they advance.
54 if " " not in text:
55 return None
57 name_part = Content.styled(f" {cmd.name}", "$success")
58 args_part = Content.styled(f" {cmd.args_hint}", "$text-muted") if cmd.args_hint else Content("")
59 sep = Content.styled(" · ", "$text-muted")
60 help_part = Content.styled(cmd.help_text, "$text-muted")
61 return Content.assemble(name_part, args_part, sep, help_part)