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

1"""One-line status row that mirrors the active slash command's signature.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from typing import ClassVar 

7 

8from textual.content import Content 

9from textual.widgets import Static 

10 

11from lilbee.cli.tui.command_registry import COMMANDS 

12 

13_CSS_FILE = Path(__file__).parent / "arg_hint.tcss" 

14 

15_REGISTRY = {cmd.name: cmd for cmd in COMMANDS} 

16for _cmd in COMMANDS: 

17 for _alias in _cmd.aliases: 

18 _REGISTRY[_alias] = _cmd 

19 

20 

21class ArgHintLine(Static): 

22 """Reactive hint row that mirrors the active slash command's signature.""" 

23 

24 DEFAULT_CSS: ClassVar[str] = _CSS_FILE.read_text(encoding="utf-8") 

25 

26 def __init__(self, *, id: str | None = None) -> None: 

27 super().__init__("", id=id) 

28 self.display = False 

29 

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 

38 

39 def _clear(self) -> None: 

40 self.update("") 

41 self.display = False 

42 

43 

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 

56 

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)