6Context-aware tab-completion for the OC Chat REPL (Phase 2 PR 2.A).
10The :class:`OCChatCompleter` understands the *shape* of partial input
11and picks completions from the appropriate live registry — never
12hardcoded. It is plugged into ``prompt_toolkit.PromptSession`` by
13:class:`~oc_ui.terminal.input.PromptDriver`.
15This module imports ``prompt_toolkit`` at top-level — only modules
16that need completion functionality should import it, and they should
17only do so after the optional dependency has been verified to be
18available. The :class:`PromptDriver` handles that gating; tests use
21Completion sources (in order of input shape):
23================================== =====================================
25================================== =====================================
26``/<partial>`` ``oc_chat.core.commands.COMMANDS``
27``/model <p>`` / ``/use <p>`` ``ctx.model_manager.known_models()``
28``/session load <p>`` ``list_sessions()`` names
29``/session switch <p>`` ``list_sessions()`` names
30``/session save <p>`` ``list_sessions()`` names + free
31``/session new <p>`` free (no completion)
32``/context <p>`` / ``/read <p>`` paths via ``PathCompleter``
33``/ui reasoning <p>`` ``(\"show\", \"dim\", \"hide\")``
34``/effort <p>`` ``REASONING_EFFORT_LEVELS``
35``/help``, free text, etc. no completions
36================================== =====================================
40- :class:`OCChatCompleter` — top-level completer that routes the
41 prompt-toolkit ``Document`` / ``CompleteEvent`` to one of the
42 per-shape sub-completers.
43- Per-verb completion logic for ``/model``, ``/session``, ``/read``,
44 ``/context``, ``/ui``, ``/effort``.
56- ``get_completions`` is a generator and must not block.
57- All registry reads happen at call time so completions stay fresh
58 as the user adds models / sessions during a REPL run.
59- Unknown verbs yield no completions rather than raising.
63- oc_diagnostics (structured debug logging via _dbg)
64- prompt_toolkit.completion (interactive REPL input with completion and history)
65- prompt_toolkit.document (interactive REPL input with completion and history)
66- oc_chat.core.commands (lazy; COMMANDS, COMMAND_HELP and UI_REASONING_VALUES for verb and choice completion)
67- oc_chat.core.config (lazy; REASONING_EFFORT_LEVELS for /effort completion)
68- oc_chat.core.persistence (lazy; list_sessions for session name completion)
70from __future__
import annotations
72from typing
import Iterable
74from oc_diagnostics
import _dbg
76from prompt_toolkit.completion
import (
77 CompleteEvent, Completer, Completion, PathCompleter,
79from prompt_toolkit.document
import Document
83 """Tab completion driven by the live :class:`Context`."""
96 complete_event: CompleteEvent) -> Iterable[Completion]:
97 func =
"get_completions"
98 text = document.text_before_cursor
99 _dbg(
"OC_UI.TERMINAL", func, f
"text={text!r}", 4)
105 if not text.startswith(
"/"):
109 head, _, tail = text.partition(
" ")
124 word_pt = document.get_word_before_cursor(WORD=
False)
126 if verb
in (
"/model",
"/use"):
129 elif verb ==
"/effort":
131 elif verb ==
"/context" or verb ==
"/read":
133 elif verb ==
"/session":
143 """Return the last whitespace-delimited token in *text*.
145 Used instead of ``get_word_before_cursor(WORD=False)`` for values
146 that may contain hyphens (model names, session names).
148 return text.split()[-1]
if text.split()
else ""
159 except Exception
as e:
161 _dbg(
"OC_UI.TERMINAL", func, f
"VALIDATION_ERROR: {type(e).__name__}: {e}", 3)
167 return sorted(COMMANDS.keys())
172 return list(self.
_ctx.model_manager.known_models())
173 except Exception
as e:
175 _dbg(
"OC_UI.TERMINAL", func, f
"VALIDATION_ERROR: {type(e).__name__}: {e}", 3)
179 func =
"_effort_values"
182 return tuple(REASONING_EFFORT_LEVELS)
183 except ImportError
as e:
185 _dbg(
"OC_UI.TERMINAL", func, f
"VALIDATION_ERROR: {type(e).__name__}: {e}", 3)
186 return (
"low",
"medium",
"high")
189 """Plan 147 D5: lazily import the canonical tuple (same pattern as
190 :meth:`_effort_values` / :meth:`_verbs`) instead of hand-duplicating
191 it — a deferred import avoids the module-load-time cycle a top-level
192 ``from oc_chat.core.commands import ...`` would risk."""
193 func =
"_reasoning_values"
196 return tuple(UI_REASONING_VALUES)
197 except ImportError
as e:
199 _dbg(
"OC_UI.TERMINAL", func, f
"VALIDATION_ERROR: {type(e).__name__}: {e}", 3)
200 return (
"show",
"dim",
"hide")
205 for verb
in self.
_verbs():
206 if verb.startswith(partial):
207 yield Completion(verb, start_position=-len(partial),
214 line = COMMAND_HELP.get(verb,
"")
217 return line.split(
" - ", 1)[1].strip()
218 except (ImportError, KeyError)
as e:
220 _dbg(
"OC_UI.TERMINAL", func, f
"VALIDATION_ERROR: {type(e).__name__}: {e}", 3)
225 if name.startswith(word):
226 yield Completion(name, start_position=-len(word))
229 choices: Iterable[str]) -> Iterable[Completion]:
230 for choice
in choices:
231 if choice.startswith(word.lower()):
232 yield Completion(choice, start_position=-len(word))
235 document: Document, complete_event,
236 ) -> Iterable[Completion]:
238 tokens = text.split()
243 sub = tokens[1].lower()
if len(tokens) >= 2
else ""
244 if sub
not in _SESSION_SUBS_THAT_TAKE_NAMES:
257 if name.startswith(name_word):
258 yield Completion(name, start_position=-len(name_word))
261 tokens = text.split()
265 sub = tokens[1].lower()
if len(tokens) >= 2
else ""
266 if sub ==
"reasoning":
270 ) -> Iterable[Completion]:
274 text = document.text_before_cursor
276 first_space = text.find(
" ")
279 arg = text[first_space + 1:]
280 sub_doc = Document(text=arg, cursor_position=len(arg))
288 "new",
"list",
"current",
"save",
"load",
"export",
"switch",
"clear",
290_SESSION_SUBS_THAT_TAKE_NAMES = (
"load",
"switch",
"save")
292_UI_SUBS = (
"reasoning",)
Iterable[Completion] _complete_choices(self, str word, Iterable[str] choices)
Iterable[Completion] _complete_ui(self, str text, str word)
None _refresh_caches(self)
Iterable[Completion] _delegate_path_completion(self, Document document, complete_event)
Iterable[Completion] _complete_session(self, str text, str word, Document document, complete_event)
Iterable[Completion] _complete_model(self, str word)
list[str] _sessions(self)
str _verb_help(self, str verb)
tuple[str,...] _effort_values(self)
list[str]|None _sessions_cache
tuple[str,...] _reasoning_values(self)
Iterable[Completion] get_completions(self, Document document, CompleteEvent complete_event)
str _last_token(str text)
Iterable[Completion] _complete_verb(self, str partial)