oc_chat
Loading...
Searching...
No Matches
oc_ui.terminal.input Namespace Reference

Classes

class  TurnStats
class  PromptDriver

Functions

 _cache_usage_fns ()
str build_prompt_prefix (ChatSession session)
str _getch_raw ()
str read_single_key (str prompt="", *, str valid="", fallback=None)
str _build_bottom_toolbar (session, TurnStats|None turn_stats)

Variables

str _PROMPT_COLOR_PT = "fg:#ff87af"
str _PROMPT_COLOR_ANSI = "\033[38;5;211m"
str _ANSI_RESET = "\033[0m"

Detailed Description

Input layer for the OC Chat REPL (Phase 2 PR 2.A + 2.B).

Purpose
-------
:class:`PromptDriver` is the single entry point for reading the next
user line. At construction it tries to import ``prompt_toolkit``; if
that succeeds (and ``OC_CHAT_LEGACY_PROMPT`` is not set), it builds a
:class:`prompt_toolkit.PromptSession` with file-backed history,
context-aware tab completion (see
:class:`~oc_ui.terminal.completers.OCChatCompleter`), an
``Alt+Enter`` keybinding that inserts a literal newline, a
``bottom_toolbar`` showing live session stats, an ``rprompt`` showing
token count, and a ``Ctrl+P`` binding that opens a fuzzy command
palette. Otherwise it falls back to plain :func:`input` — same
prompt format, no behaviour change.

The driver runs **between turns only**. Rich's ``Live`` region owns
the terminal during streaming; ``prompt_toolkit`` owns it during
input. They never overlap, so the two libraries coexist without
fighting over the screen.

Responsibilities
----------------
- :class:`PromptDriver` — wraps prompt-toolkit (or legacy
  :func:`input`) and exposes a single ``read_line`` call.
- :func:`build_prompt_prefix` — formats the ``[<model>] You: `` string used by
  both paths. The session NAME is deliberately not in the typing line; it is
  shown on the bottom status toolbar instead (:func:`_build_bottom_toolbar`).
  Both surfaces report whichever session ``read_user_input`` is handed, so the
  caller must pass the CURRENT session — ``ctx.session``, never a loop-local
  bound at startup, which a slash command may since have replaced (plan 152 D7).
- :class:`TurnStats` — thread-safe shared state used to communicate
  token counts from the streaming thread to the rprompt.

Diagnostics
-----------
Domain: OC_UI.TERMINAL
Levels:
    L2 — lifecycle events
    L3 — semantic details
    L4 — deep tracing

Contracts
---------
- ``PromptDriver.read_line`` is blocking; the caller is responsible
  for backgrounding it if needed.
- **Chat prompts are MULTI-LINE (multiline-prompt fix, 2026-07-18):**
  when ``read_user_input`` is called without a ``prompt_text`` override
  (the main chat prompt), Enter inserts a newline and submission is
  **Ctrl+Enter** (arrives as ``c-j`` on Windows consoles — incl.
  Windows Terminal / VS Code via ConPTY), with ``Ctrl+J`` and
  ``Alt+Enter`` as the portable equivalents on terminals that cannot
  distinguish Ctrl+Enter from Enter. A multi-line paste therefore lands
  in the buffer as ONE prompt — nothing auto-submits. Up/Down move the
  cursor within a multi-line buffer and navigate history from the
  first/last line (prompt_toolkit ``auto_up``/``auto_down`` defaults).
- **Menu/startup prompts stay single-line:** any call passing
  ``prompt_text=`` keeps the classic Enter-submits behaviour, so
  numbered menus answer on a single keypress+Enter as before.
- ``TurnStats`` mutations are all guarded by an internal lock; reads
  return a consistent snapshot.
- The legacy ``input()`` fallback path remains SINGLE-line (Python's
  ``input`` cannot compose multi-line); it is otherwise functionally
  equivalent.
- ``OC_CHAT_LEGACY_PROMPT`` is honoured at construction; toggling it
  mid-session has no effect.

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_ui.terminal.statusline (StatusBarState, render_status_line and token formatting for the bottom toolbar and rprompt)
- oc_ui.terminal.turn_layout (TURN_INDENT for the prompt prefix)
- oc_chat.core.cache_usage (lazy; cache hit ratio and prompt cache hit/miss split for turn stats)
- oc_chat.core.commands (lazy; COMMANDS for the Ctrl+P command palette)
- oc_chat.core.persistence (lazy; list_sessions for the Ctrl+P command palette)
- oc_chat.core.session (lazy; ChatSession type hint for the prompt prefix and read_user_input)
- oc_ui.terminal.completers (lazy; OCChatCompleter for tab completion)
- prompt_toolkit (lazy; PromptSession for the interactive prompt)
- prompt_toolkit.completion (lazy; FuzzyCompleter and WordCompleter for the Ctrl+P palette)
- prompt_toolkit.filters (lazy; Condition gating the multi-line chat key bindings)
- prompt_toolkit.formatted_text (lazy; FormattedText for the coloured chat prompt)
- prompt_toolkit.history (lazy; FileHistory for persistent input history)
- prompt_toolkit.key_binding (lazy; KeyBindings for the newline, submit, cancel and palette keys)

Function Documentation

◆ _build_bottom_toolbar()

str oc_ui.terminal.input._build_bottom_toolbar ( session,
TurnStats | None turn_stats )
protected
Build the bottom-toolbar string shown at the prompt (idle regime, D3-a).

Renders through the shared :func:`~oc_ui.terminal.statusline.render_status_line`
formatter — the ONLY bar-string producer; :class:`RichPresenter` renders
the same formatter for the streaming/dispatch regimes. Format::

    model: deepseek-v4-pro │ name: 141421 │ this turn: 1.2K · 3 iters · 2 tools · 75% cache · saved ~900 tok · 12.4s │ session: 430K · 40 tools · 68% cache · 18m 32s │ effort: high   Ctrl+D exit · Ctrl+P palette

Definition at line 459 of file input.py.

◆ _cache_usage_fns()

oc_ui.terminal.input._cache_usage_fns ( )
protected

Definition at line 114 of file input.py.

Here is the caller graph for this function:

◆ _getch_raw()

str oc_ui.terminal.input._getch_raw ( )
protected
Block for one keypress and return it (no echo, no Enter needed).

Windows multi-byte function keys (arrows, F-keys) are consumed and reported
as the sentinel ``"\\x00"`` so the caller's valid-key filter ignores them.

Definition at line 377 of file input.py.

Here is the caller graph for this function:

◆ build_prompt_prefix()

str oc_ui.terminal.input.build_prompt_prefix ( ChatSession session)
Return the input prompt prefix used by the REPL.

Format:  ``\n   [<model>] You: ``

The session name is intentionally omitted from the typing line — it added
nothing there (and was truncated). The full session name is shown on the
bottom status toolbar instead (see :func:`_build_bottom_toolbar`).

Plan 218 T3: the line carries the same three-space turn indent every
other turn-scoped line does. The prompt echo sits between the previous
rule and the turn rule that the Enter keypress generates, so indenting
it is what makes the question read as part of the turn it opens —
the operator's "it would be even better if the You: line fell within
the turn boundary". The driver gains ONLY this indent (P4).

Definition at line 351 of file input.py.

Here is the caller graph for this function:

◆ read_single_key()

str oc_ui.terminal.input.read_single_key ( str prompt = "",
* ,
str valid = "",
fallback = None )
Read a single keypress from an interactive TTY (no Enter required).

Returns the pressed character lower-cased, or ``""`` for Enter/Return — a
caller treats ``""`` as "accept the default / first option". When *valid* is
non-empty, keys outside it (and not Enter) are ignored and the read repeats.
``Ctrl+C`` raises :class:`KeyboardInterrupt`; ``Ctrl+D`` raises
:class:`EOFError` — the caller's existing handlers decide what that means.

On a non-interactive stdin there is no raw single-key mode, so this degrades
to one line read: *fallback* (e.g. a ``prompt_driver.read_user_input``
closure) when given, else :func:`input`. The first character of the entered
line is used (empty line -> ``""``).

Definition at line 403 of file input.py.

Here is the call graph for this function:

Variable Documentation

◆ _ANSI_RESET

str oc_ui.terminal.input._ANSI_RESET = "\033[0m"
protected

Definition at line 346 of file input.py.

◆ _PROMPT_COLOR_ANSI

str oc_ui.terminal.input._PROMPT_COLOR_ANSI = "\033[38;5;211m"
protected

Definition at line 345 of file input.py.

◆ _PROMPT_COLOR_PT

str oc_ui.terminal.input._PROMPT_COLOR_PT = "fg:#ff87af"
protected

Definition at line 344 of file input.py.