ui

package
v1.101.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package ui contains the lean TUI presentation layer: terminal rendering, input widgets, reusable views, and plain view-models.

This package may use the shared TUI rendering vocabulary, but it must not directly import docker-agent runtime or driver packages. Runtime events and app commands are translated by the parent leantui controller package.

Index

Constants

View Source
const (
	PromptText   = "❯ "
	PromptWidth  = 2
	Continuation = "  "
)
View Source
const ContextBarWidth = 10

ContextBarWidth is the cell width of the context-usage gauge.

View Source
const MaxToolOutputLines = 12

Variables

This section is empty.

Functions

func ComposeLine

func ComposeLine(left, right string, width int) string

ComposeLine right-aligns right within width, truncating left if necessary.

func DisplayWidth

func DisplayWidth(s string) int

DisplayWidth returns the rendered cell width of s, ignoring ANSI escape sequences.

func EnsureToolDefinition

func EnsureToolDefinition(toolCall tools.ToolCall, toolDef tools.Tool) tools.Tool

EnsureToolDefinition fills a missing tool definition name from the call.

func FormatTokens

func FormatTokens(n int64) string

FormatTokens formats a token count for compact status display.

func KittyImageSequence

func KittyImageSequence(pngData []byte, cols, rows int) string

KittyImageSequence encodes PNG data as a kitty graphics escape sequence.

func PadRight

func PadRight(s string, w int) string

PadRight pads s with spaces up to w cells. It never Truncates.

func RenderAssistantLines

func RenderAssistantLines(text string, width int) []string

RenderAssistantLines renders an assistant message as markdown. Each returned line is guaranteed to fit within width so the differential renderer's row accounting stays correct.

func RenderBar

func RenderBar(pct float64) string

RenderBar renders the context usage gauge.

func RenderContext

func RenderContext(d StatusModel) string

RenderContext renders the context and cost portion of the status.

func RenderInlineImage

func RenderInlineImage(img InlineImage, width int) []string

RenderInlineImage renders an inline image label and kitty image sequence.

func RenderNoticeLines

func RenderNoticeLines(prefix, text string, width int, style lipgloss.Style) []string

func RenderPendingUserLines

func RenderPendingUserLines(text string, width int) []string

func RenderReasoningLines

func RenderReasoningLines(text string, width int) []string

RenderReasoningLines renders agent reasoning as dimmed italic text.

func RenderStatus

func RenderStatus(d StatusModel, width int) []string

RenderStatus builds the two-line footer:

<working dir>  ⎇ <branch>                          <agent>
<context bar> <pct> · <tokens> · <cost>  <model> · <effort>

func RenderTool

func RenderTool(t ToolView, width int) []string

RenderTool renders a tool call with the same renderer registry used by the full TUI. This keeps built-in tools and registered custom renderers visually consistent between the normal and lean interfaces.

func RenderToolOutput

func RenderToolOutput(output string, width int) []string

RenderToolOutput renders plain streamed shell output.

func RenderToolWithState

func RenderToolWithState(t *ToolView, width, frame int, sessionState service.SessionStateReader) []string

RenderToolWithState renders a tool call using session state.

func RenderUserLines

func RenderUserLines(text string, width int) []string

RenderUserLines renders a submitted user message as committed scrollback, echoing it with the same prompt marker used by the input box.

func RenderUserLinesWith

func RenderUserLinesWith(text string, width int, promptStyle, textStyle lipgloss.Style) []string

func RuneWidth

func RuneWidth(r rune) int

func StAccent

func StAccent() lipgloss.Style

func StBold

func StBold() lipgloss.Style

func StError

func StError() lipgloss.Style

func StMuted

func StMuted() lipgloss.Style

func StPlaceholder

func StPlaceholder() lipgloss.Style

func StPrimary

func StPrimary() lipgloss.Style

func StReasoning

func StReasoning() lipgloss.Style

func StSecondary

func StSecondary() lipgloss.Style

func StSuccess

func StSuccess() lipgloss.Style

func StToolBox

func StToolBox(width int) lipgloss.Style

func StWarning

func StWarning() lipgloss.Style

func ToolViewID

func ToolViewID(toolCall tools.ToolCall) string

ToolViewID returns a stable id for a tool call view.

func Truncate

func Truncate(s string, w int) string

Truncate shortens s to at most w cells, appending an ellipsis when it had to cut anything.

func WrapANSI

func WrapANSI(s string, w int) []string

WrapANSI hard-wraps s to width w, keeping ANSI styling intact and returning one string per physical row. Existing newlines in s start new rows.

Types

type Autocomplete

type Autocomplete struct {
	Active bool
	// contains filtered or unexported fields
}

Autocomplete drives the slash-command popup. It is active whenever the input is a partial command: a single token starting with "/" and no spaces yet.

func NewAutocomplete

func NewAutocomplete() *Autocomplete

func (*Autocomplete) Current

func (a *Autocomplete) Current() (Command, bool)

func (*Autocomplete) Dismiss

func (a *Autocomplete) Dismiss()

func (*Autocomplete) MoveDown

func (a *Autocomplete) MoveDown()

func (*Autocomplete) MoveUp

func (a *Autocomplete) MoveUp()

func (*Autocomplete) Render

func (a *Autocomplete) Render(width int) []string

Render returns the popup rows (top to bottom) for the given width.

func (*Autocomplete) SetCommands

func (a *Autocomplete) SetCommands(cmds []Command)

func (*Autocomplete) Sync

func (a *Autocomplete) Sync(input string) bool

Sync recomputes the popup state from the current editor text. It returns true while the popup is showing.

type Command

type Command struct {
	Name string
	Desc string
	Kind CommandKind
}

func FilterCommands

func FilterCommands(all []Command, prefix string) []Command

FilterCommands returns the commands whose name has the given prefix, built-in commands first, then agent commands, each group alphabetically sorted.

type CommandKind

type CommandKind int

CommandKind distinguishes built-in lean-TUI commands (handled locally) from agent-provided commands and skills (resolved and sent to the agent).

const (
	CmdBuiltin CommandKind = iota
	CmdAgent
)

type ConfirmModel

type ConfirmModel struct {
	Tool string
	View ToolView
}

ConfirmModel holds a pending tool-approval prompt.

func (*ConfirmModel) Render

func (c *ConfirmModel) Render(width int) []string

type Editor

type Editor struct {
	// contains filtered or unexported fields
}

Editor is a multi-line text input. The buffer is a flat rune slice with the cursor expressed as an index into it; newlines are stored literally so the same structure handles single-line prompts and pasted multi-line text. All visual wrapping is derived on demand from a given content width.

func NewEditor

func NewEditor(placeholder string) *Editor

func (*Editor) Backspace

func (e *Editor) Backspace()

func (*Editor) DeleteForward

func (e *Editor) DeleteForward()

func (*Editor) DeleteToLineEnd

func (e *Editor) DeleteToLineEnd()

func (*Editor) DeleteToLineStart

func (e *Editor) DeleteToLineStart()

func (*Editor) DeleteWordBack

func (e *Editor) DeleteWordBack()

func (*Editor) Down

func (e *Editor) Down(termWidth int) bool

func (*Editor) HistoryNext

func (e *Editor) HistoryNext()

func (*Editor) HistoryPrev

func (e *Editor) HistoryPrev()

func (*Editor) Insert

func (e *Editor) Insert(runes []rune)

func (*Editor) InsertNewline

func (e *Editor) InsertNewline()

func (*Editor) IsEmpty

func (e *Editor) IsEmpty() bool

func (*Editor) Layout

func (e *Editor) Layout(termWidth int) (lines []string, curRow, curCol int)

Layout renders the editor for the given terminal width, returning one styled string per physical row along with the hardware cursor position (row within the returned slice, column in terminal cells).

func (*Editor) MoveLeft

func (e *Editor) MoveLeft()

func (*Editor) MoveLineEnd

func (e *Editor) MoveLineEnd()

func (*Editor) MoveLineStart

func (e *Editor) MoveLineStart()

func (*Editor) MoveRight

func (e *Editor) MoveRight()

func (*Editor) MoveWordLeft

func (e *Editor) MoveWordLeft()

func (*Editor) MoveWordRight

func (e *Editor) MoveWordRight()

func (*Editor) RememberHistory

func (e *Editor) RememberHistory(s string)

RememberHistory records a submitted entry and resets the history cursor.

func (*Editor) Reset

func (e *Editor) Reset()

func (*Editor) SetText

func (e *Editor) SetText(s string)

func (*Editor) Text

func (e *Editor) Text() string

func (*Editor) Up

func (e *Editor) Up(termWidth int) bool

Up moves the cursor one visual row up, preserving the column. It reports false when the cursor is already on the first row, letting the caller fall back to history navigation.

type InlineImage

type InlineImage struct {
	Name    string
	MIME    string
	PNGData []byte
	Width   int
	Height  int
}

InlineImage is a PNG image prepared for inline kitty-protocol rendering.

type InputParser

type InputParser struct {
	// contains filtered or unexported fields
}

InputParser turns raw terminal bytes into key events. It is stateful only to reassemble bracketed-paste payloads, which may span several reads.

func (*InputParser) Feed

func (p *InputParser) Feed(b []byte) []Key

type Key

type Key struct {
	Typ   KeyType
	Runes []rune
}

Key is a single decoded keyboard event. For KeyRune and KeyPaste the decoded characters are carried in runes; every other key type carries no payload.

type KeyType

type KeyType int
const (
	KeyNone KeyType = iota
	KeyRune
	KeyPaste
	KeyEnter
	KeyAltEnter // insert a literal newline (multi-line input)
	KeyTab
	KeyShiftTab
	KeyBackspace
	KeyDelete
	KeyUp
	KeyDown
	KeyLeft
	KeyRight
	KeyWordLeft
	KeyWordRight
	KeyHome
	KeyEnd
	KeyEsc
	KeyCtrlC
	KeyCtrlD
	KeyCtrlU // delete to start of line
	KeyCtrlK // delete to end of line
	KeyCtrlW // delete word backwards
	KeyCtrlL // redraw
)

type PendingUserKind

type PendingUserKind int
const (
	PendingUserSteer PendingUserKind = iota
	PendingUserFollowUp
)

type PendingUserMessage

type PendingUserMessage struct {
	Display string
	Content string
	Kind    PendingUserKind
}

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer draws the whole conversation as a single, growing array of lines and keeps it in sync with the terminal using minimal, differential updates. It writes to the normal screen buffer (never the alternate screen): content that scrolls off the top becomes immutable terminal scrollback, exactly like a regular program's output, while the bottom of the array (the input box and status footer) is rewritten in place.

The model mirrors how Claude Code / pi render: each frame the controller produces the full set of lines; the renderer diffs them against the previous frame and rewrites only the changed rows within the visible viewport, letting the terminal scroll naturally when content is appended past the bottom.

func NewRenderer

func NewRenderer(w *bufio.Writer, width, height int) *Renderer

func (*Renderer) EraseBelow

func (r *Renderer) EraseBelow(line int)

EraseBelow drops everything from buffer row `line` downward (the interactive chrome), leaving the cursor on that now-blank row so the shell prompt returns directly beneath the conversation. Used on exit.

func (*Renderer) Frame

func (r *Renderer) Frame(newLines []string, cursorLine, cursorCol int)

Frame reconciles the screen with newLines and places the hardware cursor at (cursorLine, cursorCol), where cursorLine is an index into newLines.

func (*Renderer) Repaint

func (r *Renderer) Repaint()

Repaint forces the next frame to clear the screen and redraw from scratch.

func (*Renderer) SetSize

func (r *Renderer) SetSize(width, height int)

SetSize records a new terminal size and forces a clean repaint on the next frame, since wrapping and the viewport both change with the dimensions.

func (*Renderer) ViewportTop

func (r *Renderer) ViewportTop() int

type Screen

type Screen struct {
	Transcript   *Transcript
	Editor       *Editor
	Autocomplete *Autocomplete
	Status       StatusModel
	Confirm      *ConfirmModel
}

Screen aggregates the lean TUI presentation models and lays out a full frame.

func NewScreen

func NewScreen(workingDir, branch, editorPlaceholder string) *Screen

func (*Screen) Frame

func (s *Screen) Frame(width, _, spinnerFrame int, busy bool, sessionState service.SessionStateReader, pendingUsers []PendingUserMessage) (lines []string, cursorLine, cursorCol int)

Frame produces the full terminal frame and cursor position.

type StatusModel

type StatusModel struct {
	WorkingDir string
	Branch     string

	Agent    string
	Model    string
	Thinking string

	ContextLength int64
	ContextLimit  int64
	Tokens        int64 // input + output tokens used so far
	Cost          float64
	CostKnown     bool
}

StatusModel is the snapshot of run state shown in the footer.

type Terminal

type Terminal struct {
	// contains filtered or unexported fields
}

Terminal owns the raw-mode TTY: it switches the input file descriptor into raw mode, exposes a cancelable reader for keyboard input, a buffered writer for output, and the current window size. It deliberately never enters the alternate screen so the conversation is written to (and scrolls in) the normal terminal buffer.

func NewTerminal

func NewTerminal(in, out *os.File) (*Terminal, error)

func (*Terminal) Reader

func (t *Terminal) Reader() cancelreader.CancelReader

func (*Terminal) Resized

func (t *Terminal) Resized() (w, h int, ok bool)

Resized blocks until the terminal is resized, then refreshes the cached dimensions and reports the new size. It returns ok=false once the resize channel is closed during shutdown.

func (*Terminal) Restore

func (t *Terminal) Restore()

Restore tears the terminal back down: it disables bracketed paste, cancels the reader, restores the saved terminal state and stops watching for resizes.

func (*Terminal) Size

func (t *Terminal) Size() (w, h int)

func (*Terminal) Writer

func (t *Terminal) Writer() *bufio.Writer

type ToolResult

type ToolResult struct {
	Response       string
	Result         *tools.ToolCallResult
	AgentName      string
	ToolDefinition tools.Tool
	Images         []InlineImage
}

ToolResult is the runtime-free data needed to finish a tool call view.

type ToolTracker

type ToolTracker struct {
	// contains filtered or unexported fields
}

ToolTracker holds the render state of in-flight tool calls, keyed by id and kept in call order so the conversation shows them as they arrive.

func NewToolTracker

func NewToolTracker() *ToolTracker

func (*ToolTracker) ByIDLen

func (t *ToolTracker) ByIDLen() int

ByIDLen reports the number of tracked tool-call ids.

func (*ToolTracker) Empty

func (t *ToolTracker) Empty() bool

Empty reports whether there are no tracked tool calls.

func (*ToolTracker) Finish

func (t *ToolTracker) Finish(id string, result ToolResult) *ToolView

Finish marks a tool call complete and returns an immutable snapshot. It returns nil when there is nothing to render.

func (*ToolTracker) ForEach

func (t *ToolTracker) ForEach(fn func(*ToolView))

ForEach visits the tracked tools in call order, skipping nil entries.

func (*ToolTracker) Get

func (t *ToolTracker) Get(id string) *ToolView

Get returns a tracked tool call by id.

func (*ToolTracker) Len

func (t *ToolTracker) Len() int

Len reports the number of tracked tool calls.

func (*ToolTracker) Remove

func (t *ToolTracker) Remove(id string)

Remove deletes a tracked tool call by id.

func (*ToolTracker) Reset

func (t *ToolTracker) Reset()

Reset clears all tracked tool calls.

func (*ToolTracker) Upsert

func (t *ToolTracker) Upsert(agentName string, toolCall tools.ToolCall, toolDef tools.Tool, status tuitypes.ToolStatus)

Upsert creates or updates a tracked tool call. Argument fragments streamed while the call is still pending are concatenated.

type ToolView

type ToolView struct {
	// contains filtered or unexported fields
}

ToolView is the render state of a single tool call. It deliberately stores the same TUI message shape used by the full-screen TUI so the lean renderer can delegate the visual representation to pkg/tui/components/tool.

func NewToolView

func NewToolView(agentName string, toolCall tools.ToolCall, toolDef tools.Tool, status tuitypes.ToolStatus) *ToolView

NewToolView creates a tool call render model.

func (*ToolView) Message

func (t *ToolView) Message() *tuitypes.Message

func (*ToolView) SetImages

func (t *ToolView) SetImages(images []InlineImage)

type Transcript

type Transcript struct {
	// contains filtered or unexported fields
}

Transcript owns everything that scrolls: the finalized conversation blocks, the in-progress streamed block, and the in-flight tool calls. Committed blocks are immutable scrollback; the pending block and tool calls are the live region that changes each frame until they finalize into blocks.

func NewTranscript

func NewTranscript() *Transcript

NewTranscript creates an empty transcript.

func (*Transcript) AddBlock

func (t *Transcript) AddBlock(render func(width int) []string)

AddBlock appends a finalized, lazily-rendered block to the conversation.

func (*Transcript) AppendAssistant

func (t *Transcript) AppendAssistant(content string)

AppendAssistant appends streamed assistant text.

func (*Transcript) AppendReasoning

func (t *Transcript) AppendReasoning(content string)

AppendReasoning appends streamed reasoning text.

func (*Transcript) BlockCount

func (t *Transcript) BlockCount() int

BlockCount reports the number of committed transcript blocks.

func (*Transcript) BlockLines

func (t *Transcript) BlockLines(index, width int) []string

BlockLines renders a committed block by index.

func (*Transcript) Clear

func (t *Transcript) Clear()

Clear drops all transcript content and active tool state.

func (*Transcript) ClearActive

func (t *Transcript) ClearActive()

ClearActive drops the live region (the streamed block and any in-flight tool calls) while keeping the committed scrollback intact. Used when starting a new session.

func (*Transcript) FinishTool

func (t *Transcript) FinishTool(id string, result ToolResult, sessionState service.SessionStateReader)

FinishTool commits a completed tool call as an immutable block.

func (*Transcript) FlushPending

func (t *Transcript) FlushPending()

FlushPending finalizes the in-progress streamed block into the conversation.

func (*Transcript) Lines

func (t *Transcript) Lines(width, spinnerFrame int, busy bool, sessionState service.SessionStateReader, pendingUsers []PendingUserMessage) []string

Lines renders everything that scrolls: finalized blocks, the in-progress streamed block, running tool calls, and user messages waiting to be accepted by the runtime. A blank line separates each entry. The spinner is shown only while busy with nothing yet streaming.

func (*Transcript) RemoveTool

func (t *Transcript) RemoveTool(id string)

RemoveTool removes an in-flight tool call by id.

func (*Transcript) Tool

func (t *Transcript) Tool(id string) *ToolView

Tool returns an in-flight tool call by id.

func (*Transcript) ToolByIDCount

func (t *Transcript) ToolByIDCount() int

ToolByIDCount reports the number of tracked tool-call ids.

func (*Transcript) ToolCount

func (t *Transcript) ToolCount() int

ToolCount reports the number of in-flight tool calls.

func (*Transcript) UpsertTool

func (t *Transcript) UpsertTool(agentName string, toolCall tools.ToolCall, toolDef tools.Tool, status tuitypes.ToolStatus)

UpsertTool creates or updates an in-flight tool call.

type UsageSnapshot

type UsageSnapshot struct {
	ContextLength int64
	ContextLimit  int64
	Tokens        int64
	Cost          float64
}

UsageSnapshot is the per-session token and cost usage summarized in the status footer.

type UsageTracker

type UsageTracker struct {
	// contains filtered or unexported fields
}

UsageTracker aggregates per-session token usage so the footer can show the active session's context window alongside the total cost of the whole run (the root session plus any nested agent sessions). It keeps a stack of in-flight sessions so the "active" session is whichever stream is on top.

func NewUsageTracker

func NewUsageTracker() *UsageTracker

func (*UsageTracker) Active

func (u *UsageTracker) Active() (UsageSnapshot, bool)

Active returns the usage of the session whose context the footer should show: the top of the active stack, else the root, else the most recent, else the sole recorded session.

func (*UsageTracker) Empty

func (u *UsageTracker) Empty() bool

func (*UsageTracker) Record

func (u *UsageTracker) Record(sessionID string, snapshot UsageSnapshot)

Record stores usage for a session, adopting the first session seen as the root when no stream has started yet.

func (*UsageTracker) Reset

func (u *UsageTracker) Reset()

func (*UsageTracker) RootSessionID

func (u *UsageTracker) RootSessionID() string

func (*UsageTracker) StreamStarted

func (u *UsageTracker) StreamStarted(sessionID string)

StreamStarted pushes a newly-started session onto the active stack, adopting the first one as the root session.

func (*UsageTracker) StreamStopped

func (u *UsageTracker) StreamStopped()

StreamStopped pops the most recently-started session off the active stack.

func (*UsageTracker) TotalCost

func (u *UsageTracker) TotalCost() float64

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL