Documentation
¶
Overview ¶
chrome.go renders the live activity band between the scrollback and the status line, and owns the redraw-ticker lifecycle that drives it. The band is always reserved one row (in App.layout) so its appearance and disappearance never reflow the viewport.
The Chrome module encapsulates: spinner phase + caption (thinking / writing / tool), the "new content below" hint, and the boolean tickActive flag that keeps the App from scheduling redundant coalescing ticks.
completions.go is the inline popup that backs the `/` and `@` menus. It is a small, self-contained list component the input owns: not a full-screen overlay but a bordered card spliced into View()'s parts slice directly above the input box.
The component holds the full candidate set for the active trigger and a fuzzy-filtered index window; ranking reuses fuzzyMatch (fuzzy.go) so the menu and the filterable pickers share one matcher. Rows are rendered with the raised panel surface so they read as a card consistent with the beautify ladder, the cursor row gets the selection band, the fuzzy-matched runes in the label are bolded, and the detail column is dimmed.
fileindex.go backs the `@` file-mention menu. It is the file source for the completions popup: a depth-limited walk of the working directory producing repo-relative paths, skipping the usual noise dirs (.git, node_modules, vendor) and any dot-directory, capped so a giant tree can never blow up memory or the render path.
The walk is intentionally off the UI hot path: App kicks it off in a tea.Cmd (eagerly at startup, see Init) and the result is delivered as a fileIndexMsg that App caches. Until that message lands the popup shows a single "(indexing files…)" placeholder row. Ranking is left to fuzzyMatch over the relative path (the same matcher the `/` menu and pickers share), so no path-aware scoring lives here.
fuzzy.go is a dependency-free tiered subsequence matcher used to rank completion candidates (the `/` and `@` menus) and filterable pickers. It mirrors crush's tierExactName/tierPrefixName/tierPathSegment/tierFallback ordering: the tier is the dominant component of the score (lower is better), and within a tier earlier+denser matches on shorter candidates win.
history.go is a readline-style prompt-history ring plus pure file helpers for cross-session recall (G2). The ring mirrors crush's historyPrev/historyNext semantics: ↑ walks toward older entries, ↓ walks back toward newer and finally restores the live draft. Consecutive duplicates and empty strings are never stored, and the ring is capped to the newest `max` entries.
The file helpers (loadPromptHistory/appendPromptHistory) are intentionally pure — they touch no shared mutable state, so they are safe to call from a tea.Cmd off the UI goroutine (§10: the writer must not race Update).
logo.go renders the startup wordmark in the crush style: per-letter half-block letterforms joined horizontally, flanked by diagonal ╱ fields, with a meta row (brand name + version) above and a deep→light brand gradient applied per row. Mirrors the composition of crush's internal/ui/logo but with our own DeepSeek Ocean glyphs and colors.
markdown.go renders assistant text through Glamour (markdown-aware, ANSI-styled) and reasoning / tool output through word-aware wrap.
A single Glamour renderer is cached per (style, width) tuple — the renderer constructor parses a non-trivial style JSON, so reusing across refreshes keeps the per-keystroke render cost negligible.
Package tui implements the Bubble Tea TUI for deepseekcode.
Architecture in one paragraph: a single tea.Program owns the UI state. The agent runs in a goroutine; a separate consumer goroutine reads agent.Events() and wraps each event into a single agentEventMsg that is dispatched to the tea.Program. Update folds that one message type — type-switching on the inner Event — into the App model. Permission asks travel the same channel and carry their reply chan, so the agent goroutine blocks waiting on the user without any out-of-band wiring.
Components: items.go owns rendered chat-item types; scrollback.go owns the chat buffer + stream cursors + visual selection; app.go owns the model + Update; status.go renders the status line; theme.go owns Lip Gloss styles; keymap.go owns keybindings.
pager.go implements a fullscreen overlay for viewing tool results that would otherwise dominate the chat scrollback. The pager owns its own viewport so j/k/G/gg navigate the long output independently of the chat scroll position.
Activated by `p` in Normal mode (opens the last tool result) and by future callers like /paste, /diff. Dismissed by `q` or esc, which returns the user to Insert mode with input refocused.
permission.go owns the modal permission card: pending ask state, 2×2 button grid, key resolution, and rendering. App composes one PermissionFlow instead of carrying pendingPerm + permCursor as flat fields plus a 130-line renderPermissionPrompt method.
placeholder.go drives the dynamic textarea placeholder. The hint reflects state: a "Working…" caption while a run is active, otherwise one of a small rotation of friendly "Ready" hints. The rotation is deterministic — indexed by a turn counter, never a clock or random source — so golden/snapshot tests never flap.
question.go owns the modal question card: pending ask state, per-question option list, single/multi select, and rendering. Mirrors permission.go's Open/Resolve/Render pattern.
queue_paste.go owns two P2 input affordances:
- G11 prompt queueing: a prompt submitted while a run is active is appended to a.queued instead of starting a second run; the active run's Done path drains the next queued prompt and submits it. The queued count is surfaced subtly via a toast on enqueue and on the status line.
- G12 large-paste collapse: a bracketed paste over pasteCollapseLines lines is held in full in a.pasteStore and the DISPLAY collapses to a one-line "[pasted N lines]" chip. The chip is expanded back to the full text at submit time, so the model always sees the real paste while the input box stays a single readable line.
scrollback.go owns the chat history: items, in-progress stream cursors, the rendered line cache, and visual-mode selection.
Why a dedicated module. The bug-prone pattern this replaces was "raw pointers into the items slice" (streamText / streamThink), which dangled after slice realloc and needed a backward-walking rebind heuristic that itself had a cross-turn bleed bug. Indices stay valid because the items slice only ever appends: nothing reorders, nothing removes.
Selection invariant. visual-mode selection is captured at the rendered-line index space; if the underlying items mutate during a selection (e.g. streaming tokens extend the buffer), the seq number drifts and the selection auto-invalidates on the next Render. Keeps the indexing model honest without needing to re-map selections across content changes.
scrollbar.go overlays a one-cell vertical scrollbar onto the right edge of the rendered viewport body. The thumb is brand-light and the track rides the border token so the bar is clearly visible against the painted canvas while still reading as chrome rather than content. The thumb/track glyphs are the shared ScrollbarThumb / ScrollbarTrack symbols.
visual.go implements a Vim-style line-range selection mode for the scrollback. Activated by `v` in Normal mode.
Why we need this: alt-screen mode (tea.WithAltScreen) means the terminal shows only the currently rendered frame — no scrollback history above it. Terminal-native shift-drag selection therefore can't reach content that the user has scrolled past, which is exactly the case for long agent outputs that span multiple screens.
Visual mode solves that by tracking a line-space cursor that:
- moves with j/k, ^U/^D, gg/G inside the in-app viewport
- scrolls the viewport when it crosses the visible window
- paints a reverse-video highlight on the [anchor, cursor] range
- yanks the highlighted text (ANSI-stripped) on `y`
Selection survives content updates because indices live in "rendered line" space — newly appended lines extend the buffer but don't shift existing indices.
welcome.go renders the startup banner. Width-adaptive: a terminal narrower than the letterform wordmark falls back to a single-line greeting so nothing wraps mid-glyph. The wide banner is composed by logo.go (letterforms + diagonal fields + brand gradient).
yank.go implements clipboard copy via OSC 52 escape sequences.
OSC 52 ("Set Clipboard") is honored by iTerm2, kitty, alacritty, WezTerm, and recent tmux/screen. It is the only portable way to write the system clipboard from inside a TUI without taking a hard dependency on platform-specific paste daemons (xclip, pbcopy, wsl). On terminals that don't support it the sequence is silently ignored — no crash, no visible noise.
We deliberately write to stderr rather than stdout. Bubble Tea's renderer owns stdout and would otherwise risk interleaving the escape mid-frame. stderr connects to the same tty in interactive sessions, so the terminal sees the sequence either way.
Index ¶
- Constants
- func ApplyBoldForegroundGrad(base lipgloss.Style, input string, color1, color2 color.Color) string
- func ApplyForegroundGrad(base lipgloss.Style, input string, color1, color2 color.Color) string
- func ForegroundGrad(base lipgloss.Style, input string, bold bool, color1, color2 color.Color) []string
- func Highlight(t Theme, source, lang string) string
- func LeftBarGlyph() string
- func RenderHUD(data HUDData, width int) string
- func RenderToolSummary(tool, args, result string, isError bool, width int) string
- type App
- type BadgeKind
- type Chrome
- func (c *Chrome) Active() bool
- func (c *Chrome) AdvanceFrame()
- func (c *Chrome) BeginThinking()
- func (c *Chrome) BeginTool(name string)
- func (c *Chrome) BeginWriting()
- func (c *Chrome) EndToolBatch()
- func (c *Chrome) MarkTickStarted()
- func (c *Chrome) MarkTickStopped()
- func (c *Chrome) Render(t Theme, showNewBelow bool) string
- func (c *Chrome) Reset()
- func (c *Chrome) SetFirstTokenTimeout(d time.Duration)
- func (c *Chrome) TickActive() bool
- func (c *Chrome) ToolReady()
- func (c *Chrome) UpdateTokens(n int)
- type Config
- type HUDData
- type Overlay
- func (o *Overlay) Close()
- func (o *Overlay) Cursor() int
- func (o *Overlay) FilterBackspace()
- func (o *Overlay) FilterClear() bool
- func (o *Overlay) FilterCursor() int
- func (o *Overlay) FilterString() string
- func (o *Overlay) FilterType(r rune)
- func (o *Overlay) Filterable() bool
- func (o *Overlay) HelpTab() int
- func (o *Overlay) IsOpen() bool
- func (o *Overlay) Mode() overlayMode
- func (o *Overlay) Models() []modelOption
- func (o *Overlay) MoveDown()
- func (o *Overlay) MoveUp()
- func (o *Overlay) NextHelpTab()
- func (o *Overlay) OpenHelp()
- func (o *Overlay) OpenModels(activeID string)
- func (o *Overlay) OpenPalette(actions []paletteAction)
- func (o *Overlay) OpenSessions(rows []sessionRow)
- func (o *Overlay) OpenTape()
- func (o *Overlay) OpenThemes(activeID string)
- func (o *Overlay) Palette() []paletteAction
- func (o *Overlay) PrevHelpTab()
- func (o *Overlay) SelectedAction() (paletteAction, bool)
- func (o *Overlay) SelectedModelID() string
- func (o *Overlay) SelectedSessionID() string
- func (o *Overlay) SelectedThemeID() string
- func (o *Overlay) SessionsRows() []sessionRow
- func (o *Overlay) SetHelpTab(i int)
- func (o *Overlay) Themes() []themeOption
- func (o *Overlay) VisibleRows() []int
- type PanelTier
- type PermissionFlow
- func (p *PermissionFlow) Active() bool
- func (p *PermissionFlow) MoveDown()
- func (p *PermissionFlow) MoveLeft()
- func (p *PermissionFlow) MoveRight()
- func (p *PermissionFlow) MoveUp()
- func (p *PermissionFlow) Open(ev agent.EventPermissionAsk)
- func (p *PermissionFlow) Render(t Theme, width int) string
- func (p *PermissionFlow) Resolve(key string) (resp agent.PermissionResponse, reply chan<- agent.PermissionResponse, ...)
- func (p *PermissionFlow) ShiftTab()
- func (p *PermissionFlow) Tab()
- func (p *PermissionFlow) Tool() string
- type QuestionFlow
- func (q *QuestionFlow) Active() bool
- func (q *QuestionFlow) Cancel() chan<- tools.QuestionResponse
- func (q *QuestionFlow) MoveDelta(delta int)
- func (q *QuestionFlow) Next() (done bool, resp tools.QuestionResponse, reply chan<- tools.QuestionResponse)
- func (q *QuestionFlow) Open(ev agent.EventQuestionAsk)
- func (q *QuestionFlow) Render(t Theme, width int) string
- func (q *QuestionFlow) Toggle()
- type RenderCache
- type Scrollback
- func (s *Scrollback) AppendError(text string)
- func (s *Scrollback) AppendHookFired(hookName, event, decision, reason string, dur time.Duration)
- func (s *Scrollback) AppendInfo(text string)
- func (s *Scrollback) AppendReasoning(delta string) int
- func (s *Scrollback) AppendRepair(kind, tool, message string)
- func (s *Scrollback) AppendStepFinish(stopReason string, usage llm.Usage, model string)
- func (s *Scrollback) AppendText(delta string) (created bool, tokens int)
- func (s *Scrollback) AppendToolCall(callID, tool, args string)
- func (s *Scrollback) AppendToolResult(callID string, result tools.Result, dur time.Duration)
- func (s *Scrollback) AppendUser(text string)
- func (s *Scrollback) AppendWelcome()
- func (s *Scrollback) BeginSelection(line int)
- func (s *Scrollback) CancelSelection()
- func (s *Scrollback) Clear()
- func (s *Scrollback) EndReasoning()
- func (s *Scrollback) EndSelection() string
- func (s *Scrollback) EndStreams()
- func (s *Scrollback) ExpandLastResult() bool
- func (s *Scrollback) ExtendSelection(line int)
- func (s *Scrollback) FullLines() []string
- func (s *Scrollback) InvalidateRenderCache()
- func (s *Scrollback) Items() []chatItem
- func (s *Scrollback) LastAssistantText() string
- func (s *Scrollback) LastToolResult() (tool, content string, ok bool)
- func (s *Scrollback) Len() int
- func (s *Scrollback) Render(t Theme, width int) string
- func (s *Scrollback) SelectionActive() bool
- func (s *Scrollback) SelectionCursor() int
- func (s *Scrollback) SelectionRange() (lo, hi int, active bool)
- func (s *Scrollback) Seq() uint64
- func (s *Scrollback) SetModel(model string)
- func (s *Scrollback) StartReasoning()
- func (s *Scrollback) SwapSelectionEnds()
- func (s *Scrollback) ToggleAllReasoning()
- func (s *Scrollback) ToggleLastReasoning()
- type Theme
- func (t Theme) Badge(kind BadgeKind) lipgloss.Style
- func (t Theme) Gutter() string
- func (t Theme) IsLight() bool
- func (t Theme) LeftBar(c color.Color) lipgloss.Style
- func (t Theme) Panel(tier PanelTier) lipgloss.Style
- func (t Theme) Transparent() bool
- func (t Theme) Truecolor() bool
- func (t Theme) WithTransparent(v bool) Theme
- func (t Theme) WithTruecolor(v bool) Theme
Constants ¶
const ( IconCheck = "✓" IconToolPending = "●" IconToolOk = "✓" IconToolErr = "×" IconArrowRight = "→" IconRadioOn = "◉" IconRadioOff = "○" BarThick = "▌" // 工具卡片左竖边条 BarThin = "│" SectionRule = "─" ScrollbarThumb = "┃" ScrollbarTrack = "│" IconSkill = "▲" IconModel = "◇" IconFoldClosed = "▸" // collapsed reasoning fold glyph IconFoldOpen = "▾" // expanded reasoning fold glyph )
const Gutter = " "
Gutter is the single 2-cell left gutter prepended to EVERY chat item. Components reference Theme.Gutter (a method returning this constant) so the indent is defined in exactly one place.
Variables ¶
This section is empty.
Functions ¶
func ApplyBoldForegroundGrad ¶
ApplyBoldForegroundGrad renders a given string with a bold horizontal gradient foreground. Returns "" for empty input.
func ApplyForegroundGrad ¶
ApplyForegroundGrad renders a given string with a horizontal gradient foreground. Returns "" for empty input.
func ForegroundGrad ¶
func ForegroundGrad(base lipgloss.Style, input string, bold bool, color1, color2 color.Color) []string
ForegroundGrad returns a slice of strings representing the input string rendered with a horizontal gradient foreground from color1 to color2. Each string in the returned slice corresponds to a grapheme cluster in the input string.
func Highlight ¶
Highlight uses chroma to syntax-highlight source by lang, returning an ANSI-colored string. Background is locked to the Ocean card body color. Returns source unchanged if lang is empty or unrecognized.
func LeftBarGlyph ¶ added in v0.3.0
func LeftBarGlyph() string
LeftBarGlyph returns the thick bar glyph the LeftBar style is meant to render. Exposed so components don't re-declare the rune.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is the root Bubble Tea Model.
One App per running TUI process. The Agent runs in a goroutine owned by the App; a separate pumpEvents goroutine consumes agent.Events() and forwards each event as an agentEventMsg via App.send (the tea.Program.Send func, captured in Run).
func New ¶
New constructs an App. The returned App is a tea.Model; pass it to tea.NewProgram and call .Run().
func (*App) Init ¶
Init satisfies tea.Model. The first scrollback entry is an ASCII welcome banner (whale mascot + DEEPSEEKCODE wordmark); startup notices like resume confirmations follow.
func (*App) Run ¶
Run starts the Bubble Tea program. Blocks until quit.
Mouse cell-motion is enabled so we receive click + drag + release events. We use them to drive a TUI-managed selection (handleMouse): click-drag highlights lines, dragging past the top/bottom edge auto-scrolls the viewport so the selection can span the entire scrollback, and releasing the button auto-yanks the selected text to the clipboard via OSC 52. Mouse wheel still scrolls the viewport (bubbles/viewport handles that).
Why not native terminal drag-select: alt-screen mode means the terminal has no scrollback above our rendered frame, so native drag tops out at the visible window. TUI-managed selection is the only way to extend across content the user has scrolled past.
If the user wants native terminal behavior (mouse drag-select + terminal's own scrollback), `P` in Normal mode (or /export) opens the full session in $PAGER (default `less -R`) which owns the TTY completely while running.
func (*App) View ¶
View renders the whole UI.
Note: we do NOT paint a full-screen background. An outer lipgloss Background cannot reliably fill behind glamour / viewport output — those emit ANSI resets (\x1b[0m) that snap the background to the terminal default mid-line, leaving ragged black gaps (see ADR-0002). Backgrounds live only on explicit panels / badges / diff bands, which render their own full-width per-line fills and so never bleed; the screen sits on the terminal's own background.
type BadgeKind ¶ added in v0.3.0
type BadgeKind int
BadgeKind selects the fill color of a filled chip rendered by Theme.Badge.
const ( // BadgeOk is a success chip (ok color). BadgeOk BadgeKind = iota // BadgeErr is an error chip (err color). BadgeErr // BadgeWarn is a warning chip (warn color). BadgeWarn // BadgeInfo is an informational chip (brandLight color). BadgeInfo // BadgeBrand is a primary/brand chip (brandDeep color). BadgeBrand )
type Chrome ¶
type Chrome struct {
// contains filtered or unexported fields
}
Chrome owns the live activity band — spinner, phase caption, and the tick-active flag that the App uses to avoid scheduling redundant redraw timers.
func (*Chrome) AdvanceFrame ¶
func (c *Chrome) AdvanceFrame()
AdvanceFrame moves the spinner one tick forward.
func (*Chrome) BeginThinking ¶
func (c *Chrome) BeginThinking()
BeginThinking transitions the band to the "thinking…" caption.
func (*Chrome) BeginTool ¶
BeginTool transitions to the tool caption. It is called once per tool call; a turn's parallel tool calls each call it. The first call of a fresh batch (phase was not already tool) resets the batch counters and the elapsed clock; subsequent calls in the same batch only bump the started count so the caption can render "N ready" against the batch size.
func (*Chrome) BeginWriting ¶
func (c *Chrome) BeginWriting()
BeginWriting transitions to the "writing…" caption.
func (*Chrome) EndToolBatch ¶
func (c *Chrome) EndToolBatch()
EndToolBatch marks the current tool batch closed at a step boundary (called on EventStepFinish, once per step). The counters are NOT zeroed here — that is deferred to the next BeginTool so the completed "N ready" stays visible through the inter-step gap rather than flashing "0 ready".
func (*Chrome) MarkTickStarted ¶
func (c *Chrome) MarkTickStarted()
MarkTickStarted / MarkTickStopped flip the tick-scheduled flag. Idempotent — handlers call MarkTickStarted defensively.
func (*Chrome) MarkTickStopped ¶
func (c *Chrome) MarkTickStopped()
MarkTickStopped is called when the redraw tick concludes its follow-up scheduling — agent idle AND chrome idle.
func (*Chrome) Render ¶
Render returns the one-line band content. When idle and the user has scrolled away from the bottom, it surfaces the "new content below" indicator instead. Returns "" when there's nothing to say — the reserved row renders as blank.
func (*Chrome) Reset ¶
func (c *Chrome) Reset()
Reset returns the band to idle — called on agent done.
func (*Chrome) SetFirstTokenTimeout ¶
SetFirstTokenTimeout records the agent's real first-token timeout so the cold-start caption can quote it. Called once at App construction.
func (*Chrome) TickActive ¶
TickActive reports whether a redraw ticker is currently scheduled.
func (*Chrome) ToolReady ¶
func (c *Chrome) ToolReady()
ToolReady records that one tool call in the current batch has produced a result. Clamped to the started count. No-op outside a tool batch.
func (*Chrome) UpdateTokens ¶
UpdateTokens refreshes the rolling token estimate shown in the caption.
type Config ¶
type Config struct {
Agent *agent.Agent
Model string
Thinking bool
Theme string
Cwd string
// TransparentBackground (ui.transparent_background) disables ALL background
// fills — the bg-tier panels (tool results, diffs, reasoning) degrade to
// left-bars / separators with no opaque fills. The full-screen canvas is no
// longer painted regardless (it bled through glamour/viewport resets; see
// ADR-0002), so this flag is now a "fully flat / fg-only" toggle.
TransparentBackground bool
// Optional persistence integration. Provide all three or none.
SessionID string
UndoFn func(n int) (int, error)
ListSessions func() ([]session.Session, error)
SetModelFn func(model string) error
SetThemeFn func(theme string) error
// StartupNotices are shown as info chat items at TUI start. Used for
// resume confirmations, warnings about degraded persistence, etc. —
// anything the caller would have written to stderr in CLI mode.
StartupNotices []string
// CompactionCount initialises the status-line compaction counter from
// a resumed session's history. Populate from sess.CompactionCount.
CompactionCount int
// Commands are user-defined slash commands loaded from .deepseek/command/*.md.
Commands map[string]commands.Command
// HistoryPath is the per-project prompt-history ring file (G2). The caller
// (cmd/dsc) owns the path construction so the TUI stays filesystem-light.
// Empty disables persistence: history lives only for the session and no
// file is read or written.
HistoryPath string
// HistorySeed folds extra recall entries (oldest → newest) into the ring on
// top of whatever HistoryPath loads — e.g. a resumed session's prior user
// prompts. Deduped against the file by the ring's push rules.
HistorySeed []string
// Language overrides the error message language. "" uses auto-detect.
Language string
// LSPReady reports whether at least one LSP server is attached.
LSPReady bool
}
Config bundles construction params for New.
type HUDData ¶
type HUDData struct {
Model string
Effort string
ContextTokens int
ContextLimit int
CacheHitRatio float64
InputHitTokens int
InputMissTokens int
SavedCNY float64
OutputTokens int
ReasoningTokens int
StepCNY float64
SessionCNY float64
ActiveAgent string
RunningJobs int
}
HUDData holds the data for rendering the status HUD.
type Overlay ¶
type Overlay struct {
// contains filtered or unexported fields
}
Overlay owns the modal-picker state — which picker is up, where the cursor sits, and the picker-specific data rows. App composes one of these instead of carrying overlay/overlayCursor/models/ sessionsRows as four flat fields.
The filterable pickers (/models, /sessions) and the command palette share a single filterableList (G5/G6): it holds the live filter string and the fuzzy-ranked subset of visible row indices, so the cursor moves through the narrowed set rather than the raw rows. modeTape keeps the old direct-cursor model (no filter); modeHelp keeps a plain scroll offset.
func NewOverlay ¶
func NewOverlay() *Overlay
NewOverlay returns an idle Overlay (mode = chat, nothing open).
func (*Overlay) FilterBackspace ¶ added in v0.3.0
func (o *Overlay) FilterBackspace()
func (*Overlay) FilterClear ¶ added in v0.3.0
func (*Overlay) FilterCursor ¶ added in v0.3.0
func (*Overlay) FilterString ¶ added in v0.3.0
func (*Overlay) FilterType ¶ added in v0.3.0
FilterType / FilterBackspace / FilterClear drive the shared filter for the filterable overlays. FilterClear reports whether there was a filter to clear (so the caller can implement "esc clears, then closes").
func (*Overlay) Filterable ¶ added in v0.3.0
Filterable reports whether the active overlay routes typing into the shared filter (the pickers and the palette) rather than treating letters as nav keys. modeTape and modeHelp are not filterable.
func (*Overlay) HelpTab ¶ added in v0.3.2
HelpTab returns the active help tab index (0..helpTabCount-1).
func (*Overlay) Mode ¶
func (o *Overlay) Mode() overlayMode
Mode returns the active overlay mode (modeChat when none is open).
func (*Overlay) Models ¶
func (o *Overlay) Models() []modelOption
Models / SessionsRows / Palette return the picker-specific row slices for rendering. Returned slices are read-only.
func (*Overlay) MoveDown ¶
func (o *Overlay) MoveDown()
MoveDown / MoveUp advance the cursor inside the active overlay. For the filterable pickers they walk the visible (narrowed) set; modeTape and modeHelp move the raw cursor / scroll offset.
func (*Overlay) NextHelpTab ¶ added in v0.3.2
func (o *Overlay) NextHelpTab()
NextHelpTab / PrevHelpTab cycle the active help tab with wrap-around (Next from the last tab -> first; Prev from the first -> last) and reset the scroll offset (cursor) to 0.
func (*Overlay) OpenHelp ¶ added in v0.3.0
func (o *Overlay) OpenHelp()
OpenHelp switches to the help overlay (G7), scrolled to the top.
func (*Overlay) OpenModels ¶
OpenModels switches to the /models picker. The filterableList is seeded with one label per model (short name + note); the cursor lands on the row that matches activeID, falling back to 0 if no match. Once the user filters, the cursor tracks the narrowed set.
func (*Overlay) OpenPalette ¶ added in v0.3.0
func (o *Overlay) OpenPalette(actions []paletteAction)
OpenPalette switches to the command palette (G5) over the supplied action list. The filter starts empty so every action is visible.
func (*Overlay) OpenSessions ¶
func (o *Overlay) OpenSessions(rows []sessionRow)
OpenSessions switches to the /sessions picker with the supplied rows. The filter matches against the short id + summary of each row.
func (*Overlay) OpenTape ¶
func (o *Overlay) OpenTape()
OpenTape switches to the /tape view with the cursor at the top.
func (*Overlay) OpenThemes ¶ added in v0.3.2
OpenThemes switches to the /theme picker. The filterableList is seeded with "Label Desc" per row; the cursor lands on the row that matches activeID, falling back to 0 if no match.
func (*Overlay) PrevHelpTab ¶ added in v0.3.2
func (o *Overlay) PrevHelpTab()
func (*Overlay) SelectedAction ¶ added in v0.3.0
SelectedAction returns the palette action under the cursor and true, or the zero action and false when nothing matches the filter.
func (*Overlay) SelectedModelID ¶
SelectedModelID returns the model id under the cursor (mapped through the filter), or "" when nothing matches.
func (*Overlay) SelectedSessionID ¶
SelectedSessionID returns the session id under the cursor (mapped through the filter), or "" when nothing matches.
func (*Overlay) SelectedThemeID ¶ added in v0.3.2
SelectedThemeID returns the theme id under the cursor (mapped through the filter), or "" when nothing matches.
func (*Overlay) SessionsRows ¶
func (o *Overlay) SessionsRows() []sessionRow
func (*Overlay) SetHelpTab ¶ added in v0.3.2
SetHelpTab sets the active help tab, clamped to [0, helpTabCount-1], and resets the scroll offset (cursor) to 0. A no-op-safe clamp: negative -> 0, >= helpTabCount -> helpTabCount-1.
func (*Overlay) Themes ¶ added in v0.3.2
func (o *Overlay) Themes() []themeOption
Themes returns the picker rows (read-only).
func (*Overlay) VisibleRows ¶ added in v0.3.0
type PanelTier ¶ added in v0.3.0
type PanelTier int
PanelTier selects which background surface Theme.Panel fills. The tiers climb from the painted canvas up through inset wells, panels, and raised modals. Panel() degrades every tier to no-background when fills are disabled (transparent mode or non-truecolor terminal).
type PermissionFlow ¶
type PermissionFlow struct {
// contains filtered or unexported fields
}
PermissionFlow encapsulates the modal permission card. Mutations only through methods.
func NewPermissionFlow ¶
func NewPermissionFlow() *PermissionFlow
NewPermissionFlow returns an inactive flow (no pending ask).
func (*PermissionFlow) Active ¶
func (p *PermissionFlow) Active() bool
Active reports whether a permission ask is pending user input.
func (*PermissionFlow) MoveDown ¶
func (p *PermissionFlow) MoveDown()
func (*PermissionFlow) MoveLeft ¶
func (p *PermissionFlow) MoveLeft()
func (*PermissionFlow) MoveRight ¶
func (p *PermissionFlow) MoveRight()
func (*PermissionFlow) Open ¶
func (p *PermissionFlow) Open(ev agent.EventPermissionAsk)
Open stores the ask and resets the cursor to the safe default (allow once). Caller is responsible for switching App.mode to modePermission and re-laying out the screen.
func (*PermissionFlow) Render ¶
func (p *PermissionFlow) Render(t Theme, width int) string
Render returns the modal permission card. Returns "" when there's nothing pending.
func (*PermissionFlow) Resolve ¶
func (p *PermissionFlow) Resolve(key string) (resp agent.PermissionResponse, reply chan<- agent.PermissionResponse, check permissions.Check, ok bool)
Resolve maps a key to a button decision. Returns (resp, reply, check, ok=true) when the key resolved a button; ok=false means the key wasn't a button (caller should swallow). On ok=true the flow is cleared (Active becomes false) — the caller is responsible for sending resp on reply, restoring mode, and re-layouting.
func (*PermissionFlow) ShiftTab ¶
func (p *PermissionFlow) ShiftTab()
func (*PermissionFlow) Tab ¶
func (p *PermissionFlow) Tab()
func (*PermissionFlow) Tool ¶
func (p *PermissionFlow) Tool() string
Tool returns the tool name behind the pending ask, or "" when no ask is pending.
type QuestionFlow ¶
type QuestionFlow struct {
// contains filtered or unexported fields
}
QuestionFlow encapsulates the modal question card.
func NewQuestionFlow ¶
func NewQuestionFlow() *QuestionFlow
NewQuestionFlow returns an inactive flow.
func (*QuestionFlow) Active ¶
func (q *QuestionFlow) Active() bool
Active reports whether a question ask is pending.
func (*QuestionFlow) Cancel ¶
func (q *QuestionFlow) Cancel() chan<- tools.QuestionResponse
Cancel clears the pending flow and returns the reply channel so the caller can send an empty response to unblock the agent.
func (*QuestionFlow) MoveDelta ¶
func (q *QuestionFlow) MoveDelta(delta int)
MoveDelta moves the option cursor up (negative) or down (positive).
func (*QuestionFlow) Next ¶
func (q *QuestionFlow) Next() (done bool, resp tools.QuestionResponse, reply chan<- tools.QuestionResponse)
Next advances to the next question or finishes. Returns done=true when all questions are answered, along with the full response and the reply channel.
func (*QuestionFlow) Open ¶
func (q *QuestionFlow) Open(ev agent.EventQuestionAsk)
Open stores the ask and resets state. Caller switches to modeQuestion.
func (*QuestionFlow) Render ¶
func (q *QuestionFlow) Render(t Theme, width int) string
Render returns the modal question card.
func (*QuestionFlow) Toggle ¶
func (q *QuestionFlow) Toggle()
Toggle flips the selection of the current option (multi-select).
type RenderCache ¶
type RenderCache struct {
// contains filtered or unexported fields
}
func NewRenderCache ¶
func NewRenderCache(max int) *RenderCache
func (*RenderCache) Put ¶
func (c *RenderCache) Put(key, value string)
type Scrollback ¶
type Scrollback struct {
// contains filtered or unexported fields
}
Scrollback is the deep module that owns the chat scrollback. Mutations only through methods. Indices into items are stable — callers may take them away and reuse them later.
func NewScrollback ¶
func NewScrollback() *Scrollback
NewScrollback returns an empty Scrollback with no in-progress streams and no selection.
func (*Scrollback) AppendError ¶
func (s *Scrollback) AppendError(text string)
AppendError adds an error line and closes any in-progress stream.
func (*Scrollback) AppendHookFired ¶
func (s *Scrollback) AppendHookFired(hookName, event, decision, reason string, dur time.Duration)
AppendHookFired adds a hook-execution line. Deny decisions are rendered in red; other decisions are informational.
func (*Scrollback) AppendInfo ¶
func (s *Scrollback) AppendInfo(text string)
AppendInfo adds an out-of-band notice. Closes streams: an info line interleaved with streaming would visually split the stream.
func (*Scrollback) AppendReasoning ¶
func (s *Scrollback) AppendReasoning(delta string) int
AppendReasoning extends the in-progress reasoning block. Defends against deltas without a prior Start by lazily starting one. Returns the rough token estimate (chars/4) for chrome counters.
func (*Scrollback) AppendRepair ¶
func (s *Scrollback) AppendRepair(kind, tool, message string)
AppendRepair adds a repair receipt line. Kind is one of args_completed, args_invalid, recovered, suppressed, schema_complex.
func (*Scrollback) AppendStepFinish ¶
func (s *Scrollback) AppendStepFinish(stopReason string, usage llm.Usage, model string)
AppendStepFinish closes the step's footer line with usage and stop-reason. Ends any in-progress stream.
func (*Scrollback) AppendText ¶
func (s *Scrollback) AppendText(delta string) (created bool, tokens int)
AppendText extends the in-progress assistant-text block; creates one if none is in progress. Returns (createdNewBlock, runningTokens). Callers (chrome) flip to "writing" phase when createdNewBlock is true.
func (*Scrollback) AppendToolCall ¶
func (s *Scrollback) AppendToolCall(callID, tool, args string)
AppendToolCall records a tool invocation and closes any in-progress text stream — tool calls partition assistant text into segments.
func (*Scrollback) AppendToolResult ¶
AppendToolResult records the matching result for callID. Walks items backward to recover the tool name and args from the prior toolCall.
func (*Scrollback) AppendUser ¶
func (s *Scrollback) AppendUser(text string)
AppendUser closes any in-progress stream (turn boundary) and adds the user prompt line.
func (*Scrollback) AppendWelcome ¶
func (s *Scrollback) AppendWelcome()
AppendWelcome adds the startup banner. Does not touch streams (no stream is in progress at startup).
func (*Scrollback) BeginSelection ¶
func (s *Scrollback) BeginSelection(line int)
BeginSelection starts a visual-mode selection anchored at line. Captures the current seq; subsequent index-space mutations invalidate the selection on the next Render.
func (*Scrollback) CancelSelection ¶
func (s *Scrollback) CancelSelection()
CancelSelection drops any active selection without yanking.
func (*Scrollback) Clear ¶
func (s *Scrollback) Clear()
Clear empties the scrollback and resets stream cursors + selection.
func (*Scrollback) EndReasoning ¶
func (s *Scrollback) EndReasoning()
EndReasoning closes the in-progress reasoning block. No-op if none.
func (*Scrollback) EndSelection ¶
func (s *Scrollback) EndSelection() string
EndSelection returns the selected text (ANSI-stripped) and clears the selection. Returns "" if nothing was selected.
func (*Scrollback) EndStreams ¶
func (s *Scrollback) EndStreams()
EndStreams closes any in-progress stream cursors. Idempotent — safe to call at turn boundaries (agent done) and from every one-shot appender as a defensive reset.
func (*Scrollback) ExpandLastResult ¶
func (s *Scrollback) ExpandLastResult() bool
ExpandLastResult expands the most recent collapsed (and currently truncated) tool result. Returns true if it found one.
func (*Scrollback) ExtendSelection ¶
func (s *Scrollback) ExtendSelection(line int)
ExtendSelection moves the selection cursor to line. No-op if no selection is active.
func (*Scrollback) FullLines ¶
func (s *Scrollback) FullLines() []string
FullLines returns the line split from the last Render call. Returns nil if Render hasn't been called yet. Used by mouse/visual cursor math to clamp into a valid index range.
func (*Scrollback) InvalidateRenderCache ¶ added in v0.3.2
func (s *Scrollback) InvalidateRenderCache()
InvalidateRenderCache resets the per-item render cache AND the package-level diffCache WITHOUT dropping items or resetting seq (unlike Clear()). Used by theme switching so cached renders in the old theme are evicted.
func (*Scrollback) Items ¶
func (s *Scrollback) Items() []chatItem
Items returns a read-only snapshot of the items slice. The slice header may be retained for the caller's read but must not be mutated; mutations during streaming would race.
func (*Scrollback) LastAssistantText ¶
func (s *Scrollback) LastAssistantText() string
LastAssistantText returns the text of the most recent assistant text item, or "" when none.
func (*Scrollback) LastToolResult ¶
func (s *Scrollback) LastToolResult() (tool, content string, ok bool)
LastToolResult returns the (tool, content) of the most recent non-empty tool result, or ("","",false) when none exists. Used by the pager overlay.
func (*Scrollback) Render ¶
func (s *Scrollback) Render(t Theme, width int) string
Render returns the ANSI-styled scrollback at the given width. The underlying line-split is cached on (width, seq); selection highlight is re-applied on every call since cursor moves don't bump seq.
Side-effect: if the active selection's seq has drifted (the items changed underneath it), the selection clears here. See file header.
func (*Scrollback) SelectionActive ¶
func (s *Scrollback) SelectionActive() bool
SelectionActive reports whether a selection is in progress.
func (*Scrollback) SelectionCursor ¶
func (s *Scrollback) SelectionCursor() int
SelectionCursor returns the current cursor line, or -1 if no selection is active. Used to drive viewport auto-scroll.
func (*Scrollback) SelectionRange ¶
func (s *Scrollback) SelectionRange() (lo, hi int, active bool)
SelectionRange returns (lo, hi, active). lo ≤ hi.
func (*Scrollback) Seq ¶
func (s *Scrollback) Seq() uint64
Seq returns the mutation counter. Bumped on any state change that affects rendering. Useful for tests and for dirty-detection.
func (*Scrollback) SetModel ¶ added in v0.3.0
func (s *Scrollback) SetModel(model string)
SetModel records the active main-loop model so subsequently appended tool items carry it (for the per-model tool-bar accent). Stamped at append time, so a mid-session /models switch only colors tool cards produced afterward.
func (*Scrollback) StartReasoning ¶
func (s *Scrollback) StartReasoning()
StartReasoning marks the start of a reasoning block. The next AppendReasoning calls extend that block until EndReasoning.
func (*Scrollback) SwapSelectionEnds ¶
func (s *Scrollback) SwapSelectionEnds()
SwapSelectionEnds swaps anchor and cursor, mirroring Vim's `o`.
func (*Scrollback) ToggleAllReasoning ¶
func (s *Scrollback) ToggleAllReasoning()
ToggleAllReasoning flips every reasoning block to a single target state — collapse if any are open, else expand.
func (*Scrollback) ToggleLastReasoning ¶
func (s *Scrollback) ToggleLastReasoning()
ToggleLastReasoning expands/collapses the most recent reasoning block. No-op if none.
type Theme ¶
type Theme struct {
Name string
// --- Raw token colors, exported for call sites (never hex inline). ---
BrandDeep color.Color
BrandLight color.Color
AccentFlash color.Color
AccentPro color.Color
// Selection pairing — the focused-row band color + its text (see selBg).
SelBg color.Color
SelFg color.Color
BgBase color.Color
BgWell color.Color
BgSurface color.Color
BgRaised color.Color
FgBase color.Color
FgMuted color.Color
FgSubtle color.Color
FgFaint color.Color
BorderColor color.Color
OkColor color.Color
ErrColor color.Color
WarnColor color.Color
OnAccent color.Color
// Diff band colors (hard-coded per mode — see diffBands).
DiffAddFg color.Color
DiffAddBg color.Color
DiffDelFg color.Color
DiffDelBg color.Color
// --- Legacy semantic styles (re-derived from tokens). ---
UserPrompt lipgloss.Style
AssistantText lipgloss.Style
Reasoning lipgloss.Style
ReasoningFold lipgloss.Style
ToolCall lipgloss.Style
ToolOk lipgloss.Style
ToolErr lipgloss.Style
ToolBody lipgloss.Style
HookInfo lipgloss.Style
HookDeny lipgloss.Style
Repair lipgloss.Style
Status lipgloss.Style
StatusModel lipgloss.Style
StatusGood lipgloss.Style
StatusBad lipgloss.Style
Info lipgloss.Style
Error lipgloss.Style
Hint lipgloss.Style
InputBorder lipgloss.Style
InputBorderDim lipgloss.Style // Normal mode: dim border around textarea
PermPrompt lipgloss.Style
PermFocus lipgloss.Style // focused permission-card button (inverted)
PermButton lipgloss.Style // unfocused permission-card button
// Ocean visual identity — brand gradient endpoints + card styles.
CardBar lipgloss.Style // left sidebar bar (foreground BrandDeep)
CardHeader lipgloss.Style
CardBody lipgloss.Style
// contains filtered or unexported fields
}
Theme holds the lipgloss styles used across the TUI, composed from a raw palette. We ship a dark theme as default and a light alt. Theme is selected at startup from config.
The layout is two-layer (crush-style): the unexported palette holds raw tokens; the exported fields below are semantic styles/colors derived from them. New components should prefer the token colors + the Panel/Badge/ LeftBar/Gutter helpers over the legacy named styles.
func AuroraTheme ¶ added in v0.3.2
func AuroraTheme() Theme
AuroraTheme returns the aurora theme — cool teal & green lean.
func LightTheme ¶
func LightTheme() Theme
LightTheme returns the light alt. Same semantics, inverted contrast.
func MidnightTheme ¶ added in v0.3.2
func MidnightTheme() Theme
MidnightTheme returns the midnight theme — azure on near-black, max contrast.
func NebulaTheme ¶ added in v0.3.2
func NebulaTheme() Theme
NebulaTheme returns the nebula theme — indigo & violet lean.
func PickTheme ¶
PickTheme returns the configured theme by name; defaults to dark on unknown names. truecolor is detected from the environment so themes built here degrade fills automatically on limited terminals; callers may still override via WithTransparent (config opt-out).
func (Theme) Badge ¶ added in v0.3.0
Badge returns a filled chip style: background = the semantic color for the kind, foreground = onAccent, Padding(0,1), Bold. Unlike Panel, a badge is a deliberate accent and stays filled even in transparent / degraded modes (it's a small chip, not a surface) so status semantics survive.
func (Theme) Gutter ¶ added in v0.3.0
Gutter returns the 2-cell left gutter string prepended to every chat item.
func (Theme) LeftBar ¶ added in v0.3.0
LeftBar returns the role-bar style: the thick bar glyph rendered in the given color. Callers render the glyph via this style (e.g. theme.LeftBar(theme.BrandDeep).Render(tui.LeftBarGlyph())).
func (Theme) Panel ¶ added in v0.3.0
Panel returns a lipgloss.Style backgrounded with the given tier's surface color. It returns NO background (a plain foreground-only style) when the Theme is in transparent mode OR the terminal is not truecolor — callers then degrade to left-bars / separators rather than opaque fills.
func (Theme) Transparent ¶ added in v0.3.0
Transparent reports whether canvas painting / bg fills are disabled.
func (Theme) Truecolor ¶ added in v0.3.0
Truecolor reports whether the terminal can render 24-bit color.
func (Theme) WithTransparent ¶ added in v0.3.0
WithTransparent returns a copy of the theme with the transparent flag set. Used to thread the ui.transparent_background config opt-out into the theme.
func (Theme) WithTruecolor ¶ added in v0.3.0
WithTruecolor returns a copy of the theme with the truecolor flag set. Used to thread color-profile detection into the theme.
Source Files
¶
- app.go
- chrome.go
- commands_registry.go
- completions.go
- diffview.go
- fileindex.go
- filterable.go
- fuzzy.go
- grad.go
- highlight.go
- history.go
- items.go
- keymap.go
- logo.go
- markdown.go
- messages.go
- mode.go
- overlay.go
- pager.go
- permission.go
- placeholder.go
- question.go
- queue_paste.go
- render_cache.go
- scrollback.go
- scrollbar.go
- status.go
- status_hud.go
- symbols.go
- theme.go
- toast.go
- tool_renderers.go
- visual.go
- welcome.go
- yank.go