app

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package app is the interactive session shell: one event loop composing the input reader, the composer editor, and the screen painter into a running terminal application. The shell owns mechanics, not policy: it decodes keystrokes into editor actions, keeps prompt history, coalesces repaints into frames, and hands every session-level intent (a submitted prompt, Escape, a key nothing claimed) to the caller through hooks. What a prompt means, what streams back, and how output is styled belong to the host.

Concurrency follows one rule: all mutable state sits behind a single mutex, hooks are invoked with the mutex released, and every mutator only records state and requests a frame. Painting happens solely on the frame scheduler's goroutine, so the painter, which is not concurrency safe, has exactly one caller for the shell's whole life.

The shell is pure with respect to the terminal: it reads decoded events from any io.Reader and writes frames to any io.Writer, takes its size by value, and takes time through the injected clock. Raw mode, terminal modes, and resize watching are the term package's job, wired by the caller.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

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

App is the running shell. Create one with New, drive it with Run, and feed it from any goroutine through Append, SetLive, SetStatus, and Resize.

func New

func New(cfg Config) *App

New builds the shell and starts its frame scheduler. Run must be called exactly once to drive input and to release the scheduler on the way out.

func (*App) Append

func (a *App) Append(lines ...string)

Append commits finalized lines to the terminal's scrollback above the live region on the next frame. The lines are styled by the caller and become ordinary terminal output: never repainted, wrapped by the terminal itself, selectable and searchable like any other program's output.

func (*App) Draft

func (a *App) Draft() string

Draft returns the composer's current content.

func (*App) PasteImage

func (a *App) PasteImage(att editor.Attachment)

PasteImage inserts an image chip at the cursor and repaints. It is how the host lands a clipboard image in the composer (Ctrl+V, or the /paste fallback): the chip carries the image out of line, and the bytes surface on the next submit through Attachments. Empty data is ignored.

func (*App) Quit

func (a *App) Quit()

Quit stops Run from any goroutine. Safe to call more than once.

func (*App) Resize

func (a *App) Resize(width, height int)

Resize records a new terminal size; the next frame re-renders every component at the new width and repaints from scratch, since the terminal rewrapped the old rows and the painter's diff base is stale.

func (*App) Run

func (a *App) Run() error

Run paints the first frame, then consumes input events until Quit is called or Input reaches EOF. On the way out it stops the scheduler, flushes any pending finalized lines, leaves the last frame in the terminal's scrollback with the cursor on a fresh line below it, and returns the first write error the painter hit, if any.

func (*App) SetCapture

func (a *App) SetCapture(fn func(input.Event) bool)

SetCapture installs a modal input handler that takes first claim on every decoded event, ahead of the completion menu and the composer, so an overlay (an approval prompt) can own the keyboard while it is up. The handler reports whether it consumed the event; an unconsumed event falls through to the normal editor path. Passing nil clears the modal and returns input to the composer. It is safe to call from any goroutine.

func (*App) SetDraft

func (a *App) SetDraft(text string)

SetDraft replaces the composer's content, cursor at the end.

func (*App) SetLive

func (a *App) SetLive(c screen.Component)

SetLive installs the component rendered above the status line and the composer: the in-flight output being streamed, repainted in place each frame. Nil clears it.

func (*App) SetStatus

func (a *App) SetStatus(line string)

SetStatus sets the one-row status line between the live output and the composer. Empty hides it.

func (*App) Suspend

func (a *App) Suspend(run func())

Suspend hands the terminal to run for its duration: painting stops, the live region is cleared so run's output starts below the scrollback, and the input reader is parked off the input stream so run's process can own it exclusively. After run returns, the shell reclaims the input and repaints from scratch. The caller owns the terminal's modes: leave raw mode and the shell's terminal modes inside run before starting a child process, and restore them before returning. Suspend must be called from a hook (the event loop goroutine); before Run, or while already suspended, it does nothing.

func (*App) Width

func (a *App) Width() int

Width returns the terminal's current width in cells. A host that renders its own content to fit the terminal (wrapping markdown, laying out a transcript) reads it here so its output tracks resizes, which arrive through Resize on the event loop goroutine while the host renders on its own.

type CommandCandidate

type CommandCandidate struct {
	Show  string
	Apply string
}

CommandCandidate is one slash-command completion: Show is the menu label, Apply is the whole composer line the shell sets when the candidate is chosen.

type CommandCompleter

type CommandCompleter interface {
	Suggest(line string) []CommandCandidate
}

CommandCompleter is the host side of slash-command completion. Suggest returns the candidates for the current composer line, best first and at most a menu's worth, or nil when nothing applies (the line is not a command, or it is already complete). Each candidate carries the text shown in the menu and the whole line to set when it is chosen. It runs on the event loop with no lock held, so it must be fast.

type Completer

type Completer interface {
	Complete(query string) []string
	Accepted(item string)
}

Completer is the host side of @-completion. Complete returns the candidates for a query, best first, at most a menu's worth; the query may be empty (the trigger was just typed, ask for the unfiltered universe). Accepted reports the candidate the user chose, so the host can rank recent picks higher.

type Config

type Config struct {
	// Input is the terminal's raw byte stream (stdin in raw mode). The shell
	// stops when it reaches EOF or when Quit is called; closing Input is the
	// caller's responsibility and is what unblocks a pending read.
	Input io.Reader
	// Output receives frames (stdout). Writes are single buffered frames
	// wrapped in synchronized output.
	Output io.Writer
	// Timing is the shell's source of time (frame pacing, the escape-key
	// delay). Nil means the system clock.
	Timing clock.Timing
	// Width and Height are the terminal's size in cells at startup; resizes
	// arrive later through Resize. Zero values default to 80 by 24.
	Width, Height int
	// Theme styles the shell's own chrome (the prompt gutter, the status
	// line, the placeholder). Nil means the default theme.
	Theme *theme.Theme
	// FrameInterval is the minimum time between repaints. Zero means about
	// sixty frames per second.
	FrameInterval time.Duration
	// EscDelay is how long a lone Escape byte may dangle before it resolves
	// as the Escape key rather than the start of a sequence. Zero means 50ms.
	EscDelay time.Duration
	// Placeholder is the hint shown in the empty composer.
	Placeholder string
	// Keymap is the composer's key bindings. Nil means the default map;
	// build a custom one with editor.LoadKeymap.
	Keymap editor.Keymap
	// AltScreen selects the alternate-screen renderer instead of the default
	// inline one. The inline renderer commits the transcript to the terminal's
	// own scrollback and is the right choice almost everywhere; the alternate
	// screen is the fallback for emulators where inline scroll-region insertion
	// is unsafe (Zellij-class multiplexers). The caller enters and leaves the
	// alternate screen around Run (through the term package) when this is set.
	AltScreen bool

	// OnSubmit receives each submitted prompt: the prompt text and the images
	// attached to it (nil when there are none), in the order their chips
	// appeared. It runs on the event loop goroutine with no locks held; long
	// work belongs on the host's own goroutine, which reports back through
	// Append, SetLive, and SetStatus.
	OnSubmit func(text string, images []editor.Attachment)
	// OnEsc fires when Escape is pressed. The host owns what Escape means:
	// interrupt the in-flight turn, dismiss a panel, nothing.
	OnEsc func()
	// OnKey receives keys neither the editor nor a named hook claimed.
	// Return true to consume the key; unconsumed keys fall through to the
	// shell's defaults (Ctrl+C).
	OnKey func(k input.Key) bool
	// Completer supplies @-completion candidates. The shell owns the popup
	// (tracking the token at the cursor, navigation, accept, dismiss); the
	// host owns the universe being completed over. Nil disables completion.
	// Both methods run on the event loop goroutine with no locks held, so
	// Complete must be fast; slow indexing belongs behind a host-built cache.
	Completer Completer
	// Commands supplies completion for a slash-command line. A slashed line has no
	// trigger token the way an @-mention does, so command and argument completion look
	// at the whole composer line instead: given that line, Suggest returns the
	// candidates to replace it with. Nil disables slash-command completion.
	Commands CommandCompleter
	// Marker maps the composer's current content to its first-row gutter
	// marker, so the host can surface an input mode the content selects
	// ("! " while the prompt is a shell command). Empty selects the default
	// prompt marker; a non-empty marker is fitted to the gutter's width. It
	// runs on the paint goroutine under the shell's lock, so it must be a
	// fast pure function of its argument. Nil always uses the default.
	Marker func(content string) string
}

Config wires the shell to its terminal and its host. Input and Output are required; every other field has a working default.

Jump to

Keyboard shortcuts

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