cape.tui.tuiapp: Textual app for the CAPE terminal UI¶
This module contains the OpenCode-style terminal user interface to
CAPE, built on the optional textual package. It provides, in one
persistent app with a scroll log and a pinned command composer:
- In-process CAPE commands
Commands starting with
cape,pycart,pyfun, etc. run in-process usingcape.cfdx.cli.main()in a worker thread, with their output streamed into the scroll log.
- External commands
Any other command runs as a subprocess in its own process group; its combined output is streamed into the scroll log.
- Tab completion
Pressing TAB in the editor completes the current word using the same
cape.ui.promptutils.CfdxCompleterlogic ascape.ui, plus TUI meta-commands. Multiple matches appear in a selectable box immediately above the editor.
- Ctrl-C / ESC interrupts
Pressing Ctrl-C (or ESC) while a command is running interrupts it. External commands receive
SIGINTin their process group; in-process CAPE commands get aKeyboardInterruptinjected into their worker thread. With nothing running, Ctrl-C clears the editor.
- Prompt bridge
Interactive prompts raised by commands (via
cape.promptutils.prompt_color()) mount as clickable widgets in the stream. Answering with Ctrl-C aborts the command.
- TUI meta-commands
Commands starting with
:are handled by the TUI itself::help[<cmd>],:history[<n>],:!N,:status,:cd<dir> (plaincdalso works),:pwd,:clear, and:exit/:quit.
- OpenCode-style chrome
Dark command composer, context and command discovery below the prompt, a live spinner while a command runs, and backgrounded command bubbles in the scroll log. Ctrl-P opens the command picker; clicking a blue command chevron folds or expands its output.
- Text selection
Dragging the mouse over log or editor text highlights it, and releasing copies the selection to the system clipboard (OSC 52), like OpenCode.
History (loaded from and saved to the CAPE TUI history file) is recalled with the up/down arrow keys.
- class cape.tui.tuiapp.CapeTuiApp(cls: type, *a, histfile: str | None = None, **kw)¶
OpenCode-style CAPE terminal user interface
A scroll log plus a pinned command composer. Commands run in worker threads: CAPE CLI commands in-process with redirected
sys.stdout/sys.stderr, other commands as subprocesses in their own process group. Interactive prompts mount clickable widgets above the editor while the worker waits.- Attributes:
- _log:
textual.widgets.RichLog Scroll log of commands and output
- _input:
ComposerTextArea Command editor
- _status:
textual.widgets.Static One-line status bar below the composer
- _prompt_widget:
None|textual.widget.Widget Currently mounted interactive prompt, if any
- _completer:
None|CapeTuiCompleter Tab-completer for the editor
- _proc:
None|subprocess.Popen Currently running external command, if any
- _worker:
None|threading.Thread Currently running command’s worker thread, if any
- _running_cmd:
None|str Text of the command currently running, if any
- _histfile:
None|str Name of the command history file
- _json_files:
tuple[str] Successfully loaded JSON files, least to most recently used
- _last_json_file:
None|str Most recently used JSON file
- _last_json_display_file:
None|str Most recent JSON file relative to its controller’s root
- _history:
list[str] Session command history (oldest first)
- _hist_ix:
None|int Index being browsed during history recall, if any
- _hist_draft:
str Editor text saved when history browsing started
- _stats:
dict Session statistics (
commands,failures, etc.)- _t0_cmd:
float Start time of the running command
- _t0_session:
float Start time of the session
- _spin_ix:
int Current frame of the busy spinner
- _spin_timer:
None|textual.timer.Timer Timer advancing the busy spinner
- _log:
- BINDINGS = [Binding(key='ctrl+d', action='quit', description='Quit', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='ctrl+c', action='interrupt', description='Interrupt', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='escape', action='interrupt', description='Interrupt', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='tab', action='tab_complete', description='Complete', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='up', action='history_prev', description='Previous command', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='down', action='history_next', description='Next command', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='enter', action='submit_input', description='Submit', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='shift+enter', action='insert_newline', description='New line', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None), Binding(key='ctrl+p', action='command_palette', description='Commands', show=False, key_display=None, priority=True, tooltip='', id=None, system=False, group=None)]¶
The default key bindings.
- CSS = '\n Screen > .screen--selection {\n background: #264f78;\n color: #ffffff;\n }\n App, #body, RichLog {\n background: #0d0d0d;\n color: #d6d6d6;\n }\n #body {\n height: 100%;\n }\n RichLog {\n height: 1fr;\n padding: 1 2 0 2;\n scrollbar-color: #343434;\n scrollbar-color-hover: #565f89;\n }\n #suggestions {\n display: none;\n width: 64;\n max-width: 90%;\n height: auto;\n max-height: 9;\n margin: 0 2;\n padding: 0 1;\n background: #202020;\n border-left: solid #7aa2f7;\n color: #d6d6d6;\n }\n #suggestions > .option-list--option-highlighted {\n background: #343a48;\n }\n #composer {\n height: auto;\n min-height: 4;\n max-height: 6;\n margin: 0 2;\n padding: 1 1 0 1;\n background: #202020;\n border-left: solid #7aa2f7;\n }\n #composer.busy {\n border-left: solid #ff9e64;\n }\n #prompt-input {\n height: 1;\n min-height: 1;\n max-height: 3;\n border: none;\n background: transparent;\n color: #eeeeee;\n padding: 0;\n scrollbar-size-vertical: 0;\n scrollbar-size-horizontal: 0;\n }\n #composer-hint {\n height: 1;\n color: #858585;\n }\n #status-bar {\n height: 2;\n margin: 0 2;\n padding: 0 0 1 0;\n background: #0d0d0d;\n color: #888888;\n }\n '¶
Inline CSS, useful for quick scripts. This is loaded after CSS_PATH, and therefore takes priority in the event of a specificity clash.
- action_command_palette() None¶
Open a picker for built-in TUI commands (Ctrl-P).
- action_insert_newline() None¶
Insert a literal newline without submitting the editor.
- action_interrupt() None¶
Interrupt the running command, or clear the editor
External commands run in their own process group and are sent
SIGINT; in-process CAPE commands get aKeyboardInterruptinjected into the worker thread. With nothing running, the editor is cleared.
- action_submit_input() None¶
Accept a completion or submit the current editor contents.
- action_tab_complete() None¶
Complete the word left of the cursor (TAB action)
Completions come from
CapeTuiCompleter. A unique match is inserted directly; multiple matches open a picker directly above the command editor.
- check_action(action: str, parameters: Tuple[object, ...]) bool | None¶
Check whether an action is enabled.
Implement this method to add logic for [dynamic actions](/guide/actions#dynamic-actions) / bindings.
- Args:
action: The name of an action. parameters: A tuple of any action parameters.
- Returns:
- True if the action is enabled+visible,
False if the action is disabled+hidden, None if the action is disabled+visible (grayed out in footer)
- compose() Iterable[Widget]¶
Yield child widgets for a container.
This method should be implemented in a subclass.
- save_history() None¶
Write the (trimmed) session history back to the file
- class cape.tui.tuiapp.CapeTuiCompleter(cls, app: CapeTuiApp)¶
CAPE tab-completer that reads the line from the TUI editor
This overrides
CfdxCompleter.get_line_buffer()to source the command line from the app’sComposerTextArea(text before the cursor) instead ofreadline, and adds suggestions for the TUI:meta-commands.- Attributes:
- app:
CapeTuiApp App whose editor is being completed
- app:
- genr8_suggestions(text: str) list[str]¶
Generate suggestions, including TUI meta-commands
- Call:
>>> suggestions = comp.genr8_suggestions(text)
- Inputs:
- comp:
CapeTuiCompleter TUI autocompleter
- text:
str Current text of current word
- comp:
- Outputs:
- get_line_buffer() str¶
Get the editor text before the cursor
- Call:
>>> line = comp.get_line_buffer()
- Inputs:
- comp:
CapeTuiCompleter TUI autocompleter
- comp:
- Outputs:
- line:
str Command input text, truncated at the cursor
- line:
- class cape.tui.tuiapp.CommandLog(**kwargs)¶
Scroll log whose command markers fold their associated output.
Text selection is implemented here because
RichLogrenders its lines to cached strips: by itself it neither highlights the selection nor can it extract the selected text for copying.- can_focus = True¶
Widget may receive focus.
- can_focus_children = True¶
Widget’s children may receive focus.
- clear()¶
Clear the text log.
- Returns:
The RichLog instance.
- end_group() None¶
Route subsequent writes to the top-level transcript.
- get_selection(selection)¶
Get the text under the selection
- Call:
>>> text, ending = app.get_selection(selection)
- Inputs:
selection: selection information
- Outputs:
- text:
str Text extracted from selection
- ending:
"\n" Ending
- text:
- render_line(y: int)¶
Render a line of content.
- Args:
y: Y Coordinate of line.
- Returns:
A rendered line.
- start_section(name: str, header: Text, folded: Text, collapsed: bool = False) None¶
Start a separately foldable output section.
- write(content, *args, **kwargs)¶
Writre a string or Rich renderable to the bottom of the log
- Call:
>>> content = app.write(content, *args, **kwargs)
- Inputs:
- content:
str| renderable Rich renderable (or a string) to write
- width: {
None} |int Minimum width;
None->RichLog.min-width- expand:
True|False Permit expanding of content to the width of content region; ignored if width specified
- shrink:
True|False Permit shrinking of content to the width of content region; ignored if width specified
- scroll_end:
None|True|False Enable automatic croll to end
- content:
- Outputs:
- content:
RichLog The RichLog intsance
- content:
- class cape.tui.tuiapp.CommandPalette(name: str | None = None, id: str | None = None, classes: str | None = None)¶
Small picker for the TUI’s built-in commands.
- BINDINGS = [Binding(key='escape', action='dismiss_palette', description='Close', show=False, key_display=None, priority=False, tooltip='', id=None, system=False, group=None), Binding(key='ctrl+p', action='dismiss_palette', description='Close', show=False, key_display=None, priority=False, tooltip='', id=None, system=False, group=None)]¶
A list of key bindings.
- CSS = '\n CommandPalette {\n align: center middle;\n background: #000000 65%;\n }\n #command-list {\n width: 60;\n max-width: 90%;\n height: auto;\n max-height: 70%;\n background: #202020;\n border-left: solid #7aa2f7;\n padding: 1 2;\n }\n #command-list > .option-list--option-highlighted {\n background: #343a48;\n }\n '¶
Inline CSS, useful for quick scripts. Rules here take priority over CSS_PATH.
- Note:
This CSS applies to the whole app.
- can_focus = False¶
Widget may receive focus.
- can_focus_children = True¶
Widget’s children may receive focus.
- compose() Iterable[Widget]¶
Called by Textual to create child widgets
This method is called when a widget is mounted or by setting recompose=True when calling
textual.widget.Widget.refresh().Note that you don’t typically need to explicitly call this method.
Example:
def compose(self) -> ComposeResult: yield Header() yield Label("Press the button below:") yield Button() yield Footer()
- class cape.tui.tuiapp.ComposerTextArea(**kwargs)¶
One-to-three-line soft-wrapping editor for the TUI composer.
- can_focus = True¶
Widget may receive focus.
- can_focus_children = True¶
Widget’s children may receive focus.
- insert_newline() None¶
Replace the current selection with an explicit newline.
- class cape.tui.tuiapp.LogWriter(app: CapeTuiApp)¶
Redirect writes (e.g.
sys.stdout) into a RichLogLines are buffered; complete lines are posted to the app’s scroll log from the writing thread via
call_from_thread. ANSI color codes are converted to RichTextstyling.- Attributes:
- app:
CapeTuiApp App whose log receives written lines
- _buf:
str Buffer holding an incomplete line
- app:
- flush() None¶
Flush write buffers, if applicable.
This is not implemented for read-only and non-blocking streams.