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.
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.
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.
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.
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 RenderHUD(data HUDData, width int) string
- func RenderToolSummary(tool, args, result string, isError bool, width int) string
- type App
- 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) IsOpen() bool
- func (o *Overlay) Mode() overlayMode
- func (o *Overlay) Models() []modelOption
- func (o *Overlay) MoveDown()
- func (o *Overlay) MoveUp()
- func (o *Overlay) OpenModels(activeID string)
- func (o *Overlay) OpenSessions(rows []sessionRow)
- func (o *Overlay) OpenTape()
- func (o *Overlay) SelectedModelID() string
- func (o *Overlay) SelectedSessionID() string
- func (o *Overlay) SessionsRows() []sessionRow
- 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) 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) StartReasoning()
- func (s *Scrollback) SwapSelectionEnds()
- func (s *Scrollback) ToggleAllReasoning()
- func (s *Scrollback) ToggleLastReasoning()
- type Theme
Constants ¶
const ( IconCheck = "✓" IconToolPending = "●" IconToolOk = "✓" IconToolErr = "×" IconArrowRight = "→" IconRadioOn = "◉" IconRadioOff = "○" BarThick = "▌" // 工具卡片左竖边条 BarThin = "│" SectionRule = "─" ScrollbarThumb = "┃" ScrollbarTrack = "│" IconSkill = "▲" IconModel = "◇" )
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.
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.
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
// 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
// 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
}
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.
func NewOverlay ¶
func NewOverlay() *Overlay
NewOverlay returns an idle Overlay (mode = chat, nothing open).
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 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 picker.
func (*Overlay) OpenModels ¶
OpenModels switches to the /models picker. Cursor lands on the row that matches activeID, falling back to 0 if no match.
func (*Overlay) OpenSessions ¶
func (o *Overlay) OpenSessions(rows []sessionRow)
OpenSessions switches to the /sessions picker with the supplied rows.
func (*Overlay) OpenTape ¶
func (o *Overlay) OpenTape()
OpenTape switches to the /tape view with the cursor at the top.
func (*Overlay) SelectedModelID ¶
SelectedModelID returns the model id under the cursor, or "" when the cursor is out of range.
func (*Overlay) SelectedSessionID ¶
SelectedSessionID returns the session id under the cursor, or "" when the cursor is out of range.
func (*Overlay) SessionsRows ¶
func (o *Overlay) SessionsRows() []sessionRow
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) 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) 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
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.
BrandDeep color.Color // #1D4ED8
BrandLight color.Color // #7DD3FC
CardBar lipgloss.Style // left sidebar bar (foreground BrandDeep)
CardHeader lipgloss.Style
CardBody lipgloss.Style
}
Theme holds the lipgloss styles used across the TUI. We ship a dark theme as default and a light alt. Theme is selected at startup from config; mid-session switching is a v0.2 feature.
func LightTheme ¶
func LightTheme() Theme
LightTheme returns the light alt. Same semantics, inverted contrast.