Coverage for src / lilbee / cli / tui / widgets / suggester.py: 100%
53 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
1"""Tab completion for the chat input via Textual's Suggester API."""
3from __future__ import annotations
5from textual.suggester import Suggester
7from lilbee.cli.settings_map import SETTINGS_MAP
8from lilbee.cli.tui.app import DARK_THEMES
9from lilbee.cli.tui.command_registry import completion_names
10from lilbee.services import get_services
12_SLASH_COMMANDS = completion_names()
15class SlashSuggester(Suggester):
16 """Context-aware suggestions for the chat input.
17 Suggests slash command names when input starts with '/'.
18 Suggests argument values for commands that take them.
19 """
21 async def get_suggestion(self, value: str) -> str | None:
22 if not value:
23 return None
25 if value.startswith("/") and " " not in value:
26 return self._suggest_command(value)
28 if " " in value:
29 return self._suggest_argument(value)
31 return None
33 def _suggest_command(self, prefix: str) -> str | None:
34 for cmd in _SLASH_COMMANDS:
35 if cmd.startswith(prefix) and cmd != prefix:
36 return cmd
37 return None
39 def _suggest_argument(self, value: str) -> str | None:
40 cmd, _, partial = value.partition(" ")
41 cmd = cmd.lower()
43 if cmd == "/model":
44 return self._suggest_from_list(value, partial, self._get_model_names())
45 if cmd == "/set":
46 return self._suggest_from_list(value, partial, self._get_setting_names())
47 if cmd == "/delete":
48 return self._suggest_from_list(value, partial, self._get_document_names())
49 if cmd == "/theme":
50 return self._suggest_from_list(value, partial, self._get_theme_names())
51 return None
53 def _suggest_from_list(self, full: str, partial: str, options: list[str]) -> str | None:
54 for opt in options:
55 if opt.startswith(partial) and opt != partial:
56 return full[: len(full) - len(partial)] + opt
57 return None
59 def _get_model_names(self) -> list[str]:
60 try:
61 from lilbee.models import list_installed_models
63 return list_installed_models()
64 except Exception:
65 return []
67 def _get_setting_names(self) -> list[str]:
68 return list(SETTINGS_MAP.keys())
70 def _get_document_names(self) -> list[str]:
71 try:
72 sources = get_services().store.get_sources()
73 return [s.get("filename", s.get("source", "")) for s in sources] # pragma: no cover
74 except Exception:
75 return []
77 def _get_theme_names(self) -> list[str]:
78 return list(DARK_THEMES)