Display
本頁內容尚未翻譯。
CrystalCode.Display is the TUI host. Spectre.Console supplies markup,
color, panels, grids, rules, and padding as an offline rasterizer.
Process startup sets console output to UTF-8 without a BOM, binds
Spectre’s profile to the matching UTF-8 writer, and turns its Unicode
output on, then restores the previous output encoding when the process
exits. Changing the code page replaces Console.Out, so the profile is
rebound to the new writer; otherwise Spectre would keep encoding with the
old OEM code page. Console input encoding is left alone. Files, prompts,
and provider bodies are already UTF-8. The progress spinner is braille, so
a Windows OEM code page would replace each frame with ? and garble CJK
text.
AnsiConsole.Live is not the session shell: it fights the composer.
Widgets are rasterized into frame rows. A live user, thinking, tool, or
error card keeps rows that are already wrapped. New stream text reflows
only the open tail, and scrolling or typing reuses those rows. The shell enters the alternate
screen when the terminal is a TTY and paints a retained frame: transcript
viewport, optional overlay, optional pinned todos, optional progress row,
status bar, and multiline composer. Unchanged
rows are left in place; a width or height change clears the buffer. The
frame uses one terminal-size snapshot, and the composer window is projected
again if overlays or pinned rows leave fewer rows than it requested. Composer
tabs display as four spaces while the submitted prompt retains tab characters;
other painted rows expand tabs by the same width. One column remains free for
the cursor at the right edge. The executable maps engine events onto that frame
through SessionProjection and SessionRenderer; it does
not paint rows itself. Entering the alternate screen sets the window
title to Crystal Code when the terminal allows it, and restores the
previous title on exit. If setup fails after the alternate buffer is
entered, the shell leaves that buffer, turns off bracketed paste and
mouse reporting when those were enabled, and restores the title when
it was changed. Windows VT input mode is restored on screen disposal,
with a process-exit retry if the first restoration fails. When the terminal
drops below the usable minimum
(ShellLayout.MinUsableWidth x ShellLayout.MinUsableHeight, 80x24),
the frame is replaced by a centered resize notice sized to the real
terminal and input is ignored, including keys typed while the window is
too small. The session resumes automatically once the window is large
enough. /stats also replaces the frame: the status bar, composer,
queue, todos, and progress row are not painted, and keys other than
Esc, q, and scrolling are ignored. The page is a centered column of
Overview, Tokens, and Tools panels. Tool rows show a share bar, count,
and percent. Esc, q, or Ctrl+C restores the session frame and leaves
the composer draft in place. ShellLayout.MinWidth/MinHeight (16x8) remain a math floor
for layout only.
Text that came from a model, a tool, or a file never reaches the terminal as
control bytes. TerminalText.Sanitize removes C0 and C1 controls, DEL, and
escape introducers from streamed and committed text. Tool bodies still collapse
a carriage return the way a terminal overwrites the current line. One-line
chrome (SanitizeLine for todos, activity, and progress) flattens carriage
returns, line breaks, and tabs to spaces and does not use that overwrite.
PaintLine.Fit strips controls again from every row after tab expansion, so
no producer can leak an escape sequence into a frame. Todo content, activity
and progress text, and widget segments go through the same path. Approval
cards are different: the
operator must see what is being approved, so control and bidirectional
characters in commands, diffs, rationale, and tool summaries are shown as
visible \xNN and \uNNNN escapes through TerminalText.Reveal instead of
being dropped.
Width policy: TextWidth.Measure must never be narrower than Spectre’s
Segment.CellCount(). WideRanges is generated from Spectre’s own widths,
and a test compares every code point. Each row is rasterized by FrameRow
at a very wide virtual width and cropped at a cell boundary, so Spectre can
never re-wrap a row and emit a line feed into the frame. Known limits:
variation-selector emoji sequences are measured as one cell, and ambiguous
width characters assume a narrow terminal.
ScreenPainter wraps every paint in DEC 2026 synchronized output
(?2026h and ?2026l), which terminals that lack the mode ignore.
Autowrap is disabled while rows are written. Before it is enabled again
the cursor is parked at the origin, so a full-width row cannot leave the
pending-wrap flag set and shift ASCII onto the composer. Unchanged rows,
including the composer, stay in place. On Windows the console echo flag
is off so the host does not print mouse and mode reports into the input
line. A finished report whose escape was already consumed is dropped, not
pasted. An unfinished tail is still text, so a paste such as [12;40 is
kept.
WidgetPaint keeps foreground, background, bold, dim, italic, underline,
strikethrough, and invert when it rasterizes Spectre widgets. Links and
blinking are dropped on purpose.
ScrollAnchor keeps the transcript on the row the operator is reading. That
position is one committed row, named by entry index and line. A live tail
uses the entry index it will occupy once committed. New rows at the end move
the distance from the bottom so the anchored row stays put. At the bottom,
where no row is anchored, the view follows new output. A scroll that leaves
the bottom is measured from the latest row count. A width change keeps the
distance and re-clamps it, then takes a new anchor. When a block above the
anchor changes height, the same row is found again.
Terminal.Gui is referenced from CrystalCode.Display with a floating
version and is not used. Do not call Application.Init or mix a second
console writer with the self-owned loop.
The status bar shows the selected context budget as Context 272K beside
the thinking gear. The custom status-line field context-window shows the
same value and is included in its default field list.
The status bar shows approval, thinking (when the selected model
supports it: Think Off, or Think plus the resolved gear when
thinking is on), Tier <token> and Speed <token> when those slots
are set, the non-default prompt set as Prompt <name>, model,
workspace, the latest request’s context percent (CTX), cumulative
token counts (IN / OUT), and, when the bar has room, a cumulative
Title Case total (783k Total). During a turn the status bar updates CTX, IN, and OUT after each
model round and each tool batch: provider usage as it arrives, then a local
transcript estimate after tools until the next round reports. It also
shows tool count (Tool / Tools), and
elapsed time. When the session has todos, a pinned Todos bar sits
above the progress row and status bar. The existing adaptive status bar is the
default. Operators may independently enable an ordered custom status line
through customStatusLine, statusLine, or /statusline. Custom token fields
name their scope explicitly as context, request, or session usage. Disabling
the custom line returns to the existing adaptive rendering path.
A disk plugin may register extra field names. Those names stay off the
adaptive bar, the progress row, /status, and /stats. They appear on the
custom line only when the operator lists them. The engine publishes the
latest plain text as StatusFieldsChanged, in the order the changes were
taken. The terminal stores that text and paints it on the next input poll.
The host does not poll the plugin and does not run a status timer. Empty
text occupies no cell. A narrow
custom line still drops custom slots from the right.
Each row is a checkbox colored
by status: pending chrome, in progress accent, completed ok, cancelled
muted. The bar shows the first four items and a +N more line when
the list is longer; /todos (alias /todo) prints the full untruncated
list in the transcript. The transcript Result card uses the same colors for
- [ ] / - [~] / - [x] / - [-] lines so they are not painted as
diff removals. Named chrome labels are Title Case. Short status
abbreviations are uppercase. Mode is Plan or Work on the
composer prompt and is not repeated on the status bar. While a turn
runs, a progress row sits directly above the status bar
(Waiting For Model, Thinking, Writing, Running Command,
Awaiting Approval, Reviewing, Waiting For Answer,
Compacting, Retrying In Ns (Attempt K)). The retry caption counts
remaining wait down. The same row shows
Loading Tools while operator tool
sets are discovered at session start and on /cd or /space, after the frame is
up, so a slow assembly load does not leave a blank terminal. The
caption is prefixed with a one-cell spinner
that advances while the turn is live, plus the current activity
elapsed time (5s, 2m18s). When estimatedTokens is on, Thinking
and Writing also show a live estimate (~1.2k Tokens) after elapsed
time. Elapsed resets when the progress
caption changes. It is independent of the
status-bar activity bullet (• Bash). The row is omitted when
idle. Assistant text is
rendered as markdown while it streams and after it commits (headings,
lists, fenced code with a dim code background, inline code and bold, and
GFM tables). A table is aligned by display columns, wrapped inside a
column, or stacked when the terminal cannot give every column its minimum.
A table that cannot be parsed or laid out is painted as its original lines.
User, thinking, tool, and result blocks are rounded panels. A live turn
writes a Tool card when the model round closes with tool calls, before
those calls execute, using the same one-line summary as session replay.
Tool names in chrome and cards are Title Case; stream name chunks are
coalesced per tool call so a repeated snapshot does not become
ReadReadRead and sequential calls never concatenate. Success
result panels honor verboseResults (config.json, default on, omitted
when on). It is the parent of verboseTools and verboseCommands.
While it is off, every success result is omitted, including read, search,
skill, edit, write, bash, bash_start, bash_status, bash_logs,
and bash_stop. The Tool card stays unless verboseCalls is off.
That switch (default on, omitted when on) hides every Tool card,
including read, search, edit, write, bash, background bash, and
external tools. It does not hide success or failure results, and
verboseResults does not hide Tool cards. Failure results stay. The child
switches stay stored and do not draw a result until the parent is on
again; Ctrl+G does not expand a command result in that state because the
panel is omitted, not collapsed. A missing verboseResults field keeps
the parent on. While the parent is on, tool verbose off omits
read/search/skill result panels and still shows edit and write
results, and command verbose off collapses bash and the four background
bash tools to a hidden-line hint plus the last output line. crystal run
does not read verboseResults. Policy and grant auto-pass cards honor verboseApprovals
(default on). Review allow cards honor verboseReview (default on). The
switches are independent: turning approvals off does not hide a review allow
card, and turning review off does not hide a policy or grant card. Cards
already in the transcript follow their own switch, and turning that switch
back on shows them again. A saved verboseApprovals value of false no longer
hides review allow cards. The ask overlay still appears. Ctrl+O toggles tool verbose and
Ctrl+G toggles command verbose when the composer is empty; /verbose
shows or changes the same settings (tools, commands, results,
calls, approvals, review, thinking, on, off). results is
the parent of tools and commands. calls hides Tool cards only.
Thinking panels honor verboseThinking
(default on). When it is off, live and committed Thinking cards are
omitted, including cards restored with a session; turning it back on
shows the stored text again. Inline output, used when the alternate
screen is unavailable, applies the same switches to restored thinking,
tool results (including the verboseResults parent), policy and grant
approval text, and review allow text.
The progress row, the thinking gear, and export stay.
crystal run --show-thinking remains a separate one-shot
flag and does not read this setting.
Approval is a Spectre panel with a two-column Title Case field grid
(Status, Reason, Risk, Authority, Outcome). Question panels show
header tabs, described choices, selection state, custom input, and a final
answer review when confirmation is required. Plain Up/Down stays with question
selection. When the question panel is taller than its slot, PageUp/PageDown,
the wheel, and Ctrl+Up/Down scroll that panel. Moving the highlighted choice,
or the cursor in a custom answer, scrolls the panel to keep that row visible.
A manual scroll stays until that row changes. A scroll that would move past
either end, and any scroll while the panel already fits, scrolls the
transcript. Custom answers edit inside the overlay with an independent buffer:
Enter saves and Escape cancels editing without dismissing the question. The
paused main composer keeps its draft and hides its cursor. Ask and auto-pass
cards for edit and write show a capped + / - preview
of old_string / new_string or contents. Overlay keys share the
session frame loop, so transcript scroll and resize still work while a
prompt is up. Ask overlays use the same card; Y / S / A / N map
Once / Session / Always / Deny for ordinary workspace calls. Calls that cannot
reuse grants offer only Y / N (Once / Deny) and ignore Session/Always keys.
The follow-up queue is a Queued panel
above the composer.
Composer keys: Enter submits when idle and queues while a turn is
running. Queued text stays above the composer and is sent when the
current tool batch or turn ends. Empty Enter while a follow-up is
queued interrupts the turn, or compaction if that is still running, so
the queue is sent now. Empty Enter with nothing queued does not stop
the turn. Backspace deletes one character on every
platform. Windows Ctrl+Backspace deletes a word. On Unix, ReadKey tags
plain Backspace as Control; that is still one character. Ctrl+W or
Alt/Option+Backspace deletes a word. Ctrl+C at idle clears the composer.
Two Ctrl+C presses on an empty composer exit. Ctrl+J or \+Enter inserts a
newline. Tab toggles Plan/Work or completes a / command. Shift+Tab also toggles Plan/Work. Chrome labels are Plan, Work, Review, Default, Edit, Audit, and Full.
When the slash picker is open, plain Up/Down moves its selection without
resetting it and scrolls the visible candidate window to keep that selection
on screen. Tab accepts the selected completion. Enter accepts a partial
selection first; when the composer already contains that complete command or
argument, Enter submits normally.
Status abbreviations are CTX, IN, and OUT. The status
bar includes a queued count while follow-ups wait. Auto-pass prints a
panel with Status, Reason, Risk, Authority, and, for review, Outcome
plus rationale. Policy and grant panels follow verboseApprovals. Review
allow panels follow verboseReview. Reasoning streams into the
transcript while verboseThinking is on. Built-in slash verbs live in SlashCatalog and include
aliases (/new is /clear, /continue is /resume,
/q and /exit are /quit, /think is /thinking, /summarize is
/compact, /todo is /todos, /prompts is /promptset,
/verbose toggles tool, command, result, call, approval, review, or thinking detail). /export
writes markdown or json for the current conversation; /prompts export
writes built-in prompt templates. Export roots default to
~/.crystal/exports/ (exportDirectory omitted or home) and are
configurable through exportDirectory in
config.json. A slash picker appears while the prompt
is a command prefix. After a verb that takes an argument
(/thinking, /approval, /model, /tokens, /verbose), Tab also completes the argument.
/model completes current-provider models, then a provider name, then
that provider’s models. /approval model completes on, off, and the
stored approval provider’s catalog, and /approval thinking completes the
selected approval model’s gear. /export completes the format, optional system flag,
and the system flag after an explicit path. /prompts export offers directory
examples while Enter on the optional argument boundary still submits with the
default directory. Ctrl+O and Ctrl+G toggle verbose tool and command output when the composer is
empty. Plain Up/Down move through the composer’s visual rows, including
wrapped rows. At the first or last row they navigate submitted prompt
history; Up on an empty prompt recalls the latest entry, and Down past
the newest entry restores the unsent draft and its cursor. The slash
picker owns plain Up/Down while open, but it stays closed while the
composer is showing a recalled entry, including one that starts with /.
Editing that entry opens the picker again. Submitted text-only history is
stored by workspace in ~/.crystal/prompt-history.jsonl with an
owner-only file mode where supported. Image-bearing prompts remain in
memory only and are forgotten when the session changes, since their
markers do not carry image bytes or a stable cross-session attachment.
PageUp/PageDown are the primary transcript scroll controls;
Ctrl+Up/Down also scroll when the terminal passes those keys through.
The alternate screen enables bracketed paste (2004) and mouse reporting
(1000 with SGR encoding 1006) and turns alternate scroll (1007) off.
While that screen is up, the shell repeats
those modes about once a second. On Windows it also puts VT input back and
turns quick edit and native mouse input off, then restores the console
input mode captured at entry when the screen closes. The next refresh puts
the wheel back if the console host cleared it. A wheel report scrolls the transcript and is
drained without waiting; other mouse reports are swallowed. The wheel is
never converted to Up/Down, so plain Up/Down stay with the composer, prompt
history, or the active selection. Terminal text selection needs Shift
(Option or Fn on some macOS terminals) while mouse reporting is on, and the
help list says so. Escape clears the composer. Pasted text is normalized to
LF and tabs, and control, invisible, and bidirectional formatting characters
are dropped. Escape is held only
when no further bytes are available or the sequence is still incomplete.
KeyBurst collects one ReadKey drain. InputDecoder turns that burst
into InputKey, InputPaste, or InputWheel. Platform differences stay
in the decoder; the composer, scroll policy, and overlays consume events
only. Linux and macOS usually deliver parsed ConsoleKey values. Windows
VT input leaves Key empty, so Tab, Enter, letters, and CSI arrive as
KeyChar. The shell restores the prior Windows console input mode on exit.
macOS Option-as-Meta is ESC plus a letter or Backspace and
becomes Alt; a native Alt modifier on a parsed key is kept. A CR+LF drain
is one Enter, not paste. Paste is the text between CSI 200~ and 201~.
A printable burst with an internal newline is still paste when those
markers are absent, so it is inserted and does not submit. A burst whose
only newline is one trailing CR or CRLF stays keys, and so does a burst
that contains Backspace or Delete. Escape sequences that are not a
bracketed-paste wrap are not treated as paste. The host does not parse VT. The frame polls terminal
size and repaints when the window is resized. Redirected output stays
sequential.