Coverage for src/lilbee/cli/tui/widgets/suggester.py: 100%

44 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-09-04 17:08 +0000

1"""Tab completion for the chat input via Textual's Suggester API.""" 

2 

3from __future__ import annotations 

4 

5from textual.suggester import Suggester 

6 

7from lilbee.cli.tui.command_registry import completion_names 

8from lilbee.cli.tui.widgets.autocomplete import ( 

9 _fetch_document_names, 

10 _model_options, 

11 _setting_options, 

12 _theme_options, 

13) 

14 

15_SLASH_COMMANDS = completion_names() 

16 

17 

18class SlashSuggester(Suggester): 

19 """Context-aware suggestions for the chat input. 

20 Suggests slash command names when input starts with '/'. 

21 Suggests argument values for commands that take them. 

22 """ 

23 

24 async def get_suggestion(self, value: str) -> str | None: 

25 if not value: 

26 return None 

27 

28 if value.startswith("/") and " " not in value: 

29 return self._suggest_command(value) 

30 

31 if " " in value: 

32 return self._suggest_argument(value) 

33 

34 return None 

35 

36 def _suggest_command(self, prefix: str) -> str | None: 

37 for cmd in _SLASH_COMMANDS: 

38 if cmd.startswith(prefix) and cmd != prefix: 

39 return cmd 

40 return None 

41 

42 def _suggest_argument(self, value: str) -> str | None: 

43 cmd, _, partial = value.partition(" ") 

44 cmd = cmd.lower() 

45 

46 if cmd == "/model": 

47 return self._suggest_from_list(value, partial, self._get_model_names()) 

48 if cmd == "/set": 

49 return self._suggest_from_list(value, partial, self._get_setting_names()) 

50 if cmd == "/delete": 

51 return self._suggest_from_list(value, partial, self._get_document_names()) 

52 if cmd == "/theme": 

53 return self._suggest_from_list(value, partial, self._get_theme_names()) 

54 return None 

55 

56 def _suggest_from_list(self, full: str, partial: str, options: list[str]) -> str | None: 

57 for opt in options: 

58 if opt.startswith(partial) and opt != partial: 

59 return full[: len(full) - len(partial)] + opt 

60 return None 

61 

62 # Option sources are shared with the completion overlay (autocomplete); 

63 # these stay as methods so callers and tests can override per-suggester. 

64 def _get_model_names(self) -> list[str]: 

65 return _model_options() 

66 

67 def _get_setting_names(self) -> list[str]: 

68 return _setting_options() 

69 

70 def _get_document_names(self) -> list[str]: 

71 return _fetch_document_names() 

72 

73 def _get_theme_names(self) -> list[str]: 

74 return _theme_options()