program

package
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package program runs a terminal interface.

It owns the terminal, the frame schedule, and the one goroutine that is allowed to touch the interface's state. It knows nothing about what the interface is for: what it drives is a Component, which draws itself and answers input, and everything a component needs from the program it asks for through a Runtime.

The concurrency model, in full

One goroutine draws and handles input. Anything that happens elsewhere — a request finishing, a file changing, a timer firing — reaches the interface through a Dispatcher obtained from Runtime.Dispatcher, and runs there. That is the whole of it, and it is why state reached only from that goroutine needs no internal lock.

The program parks when there is nothing to do. It wakes for input, for posted work, and for the terminal reporting progress — never on a clock that runs regardless. A component that wants a clock starts one with Runtime.Every, and an interface with nothing animating costs nothing.

The two places an interface can be

A program either takes a screen of its own, which it gives back on the way out, or draws in the terminal's own screen as a block with the session's output above it. The second is what Config.Inline asks for, and it is the difference between a program the user enters and leaves and one that is part of their session: what an inline interface has finished with is printed with InlineRuntime.Print and belongs to the terminal from then on — scrollable, selectable, and still there afterwards.

Index

Constants

View Source
const DefaultFrameInterval = 16 * time.Millisecond

DefaultFrameInterval is the shortest time between program redraws. A terminal cannot usefully show more, and a stream of updates would otherwise ask for a frame each.

View Source
const MaxCells = 1 << 18

MaxCells is the largest host-controlled program surface. A screen owns both a front and back cell store, so a bound belongs at the host edge before either is allocated. The limit admits terminals far larger than ordinary displays while keeping one resize from becoming an open-ended memory request.

Variables

View Source
var ErrFrameTimeout = errors.New("program: frame writer did not drain")

ErrFrameTimeout means a frame writer did not account for its pending frames before display ownership had to change. The program refuses the transition: a late frame would otherwise be written into the next owner's output.

View Source
var ErrInvalidFrameSequence = errors.New("program: invalid frame writer sequence")

ErrInvalidFrameSequence means a host accepted a non-empty frame without assigning it a usable position in its progress watermark. Continuing would allow a later frame to overtake output the presenter still owns, so publication stops instead.

View Source
var ErrInvalidSize = errors.New("program: invalid host size")

ErrInvalidSize means a host reported geometry that cannot safely back a program surface. Hosts are transport boundaries and their dimensions may come from an untrusted peer, so invalid input is an error rather than a grid allocation or panic.

View Source
var ErrStopped = errors.New("program: stopped")

ErrStopped means an ingress lost its interface owner before all accepted data could be applied. Pending data is deliberately released: cancellation ends the live interface and does not turn unconsumed input into published output.

Functions

func Run

func Run(ctx context.Context, cfg Config) (err error)

Run draws the interface until it is asked to stop, its input ends, or the terminal fails.

A cancelled context stops the program without being reported as a failure: being asked to stop is not one.

func ValidateSize added in v0.3.0

func ValidateSize(width, height int) error

ValidateSize reports whether width and height describe a safe non-empty program surface. Host adapters can call it before acquiring transport resources; Run applies it to both the opening size and every later resize.

Types

type BellHost added in v0.0.5

type BellHost interface{ Bell() }

BellHost rings the user-facing host's audible or visible bell.

type ByteBatch added in v0.1.0

type ByteBatch struct {
	Data  []byte
	Err   error
	Final bool
}

ByteBatch is one owner-side delivery from a ByteIngress.

Data is the ordered concatenation of bytes accepted since the previous delivery. The consumer owns it and may retain or change it after the callback returns. Final is true exactly once, after every accepted byte; Err is meaningful only then. A nil Err is successful completion.

type ByteIngress added in v0.1.0

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

ByteIngress carries a lossless ordered byte stream to an interface owner.

Write may block when limit bytes are waiting for the owner, applying backpressure at the producer rather than growing Dispatcher's general task queue. Adjacent writes are combined and at most one delivery task is pending at a time. The consumer is always called on the interface goroutine.

Close or CloseWithError completes the stream after all accepted bytes. When the program stops first, blocked writes return ErrStopped, pending bytes are released, and the consumer is not called from a background goroutine. A ByteIngress must be closed by its producer; its internal cancellation waiter then exits. The zero value is stopped.

func NewByteIngress added in v0.1.0

func NewByteIngress(dispatch Dispatcher, limit int, consume func(ByteBatch)) (*ByteIngress, error)

NewByteIngress makes a bounded byte ingress.

limit is the maximum number of bytes accepted but not yet taken by the interface owner. It must be positive. consume must be non-nil. A stopped or zero dispatcher is refused because it has no owner on which to invoke consume.

func (*ByteIngress) Close added in v0.1.0

func (i *ByteIngress) Close() error

Close completes the stream successfully after all accepted bytes are delivered.

func (*ByteIngress) CloseWithError added in v0.1.0

func (i *ByteIngress) CloseWithError(err error) error

CloseWithError completes the stream with err after all accepted bytes are delivered. The first close wins. io.EOF is normalized to successful completion.

func (*ByteIngress) Done added in v0.1.0

func (i *ByteIngress) Done() <-chan struct{}

Done closes after the final batch is consumed or the interface owner stops.

func (*ByteIngress) Write added in v0.1.0

func (i *ByteIngress) Write(p []byte) (n int, err error)

Write accepts p in order, blocking while limit bytes are pending. It copies p; the caller may reuse p after Write returns. A partial count is accompanied by an error.

type Clipboard added in v0.0.5

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

Clipboard is the clipboard associated with the user-facing host. Its zero value refuses writes and ignores reads.

func (Clipboard) Copy added in v0.0.5

func (c Clipboard) Copy(text string) bool

Copy puts text on the host clipboard when supported.

func (Clipboard) Paste added in v0.0.5

func (c Clipboard) Paste()

Paste requests clipboard contents. A successful answer arrives as input.Paste.

type Component

type Component interface {
	Draw(view grid.View)
	Handle(event input.Event) bool
}

Component is an interface a program can run: it draws itself into the space it is given, and says whether it wants an event.

It is handed a view that is already positioned and clipped, so its coordinates are its own. An event it does not consume is dropped by the program — a component is the root of its own tree and there is nobody above it to pass one on to.

type Config

type Config struct {
	// Root builds the component to run on a screen of its own. It is given the runtime
	// first, so the component can hold it from the moment it exists. Returning nil is
	// an error.
	Root func(*Runtime) Component

	// Inline builds the component to run as a block in the terminal's own screen,
	// with output that is finished printed above it. Its component is given an
	// [InlineRuntime], which is a [Runtime] that can also print.
	// Returning nil is an error.
	Inline func(*InlineRuntime) Component

	// Terminal says which of the terminal's optional behaviours to ask for. Ignored
	// when Host is set.
	//
	// AltScreen is the program's to decide rather than the caller's, because where
	// frames go is the rendering model and not an input capability: it follows from
	// which of Root and Inline was set. Asking for it alongside Inline is a
	// contradiction and is reported as one.
	Terminal term.Options

	// Color says how much colour the terminal can show. The zero value, [grid.Auto],
	// asks [term.DetectDepth] — which is the one thing in this library that reads
	// its environment rather than making a request and letting it be ignored,
	// because a truecolor sequence a terminal cannot read prints wrong rather than
	// degrading.
	//
	// Setting it is how a program that already knows — from its own configuration,
	// or because it is writing to something that is not a terminal at all — takes
	// that decision back.
	Color grid.Depth

	// Host overrides where input comes from and frames go. Nil opens the real terminal
	// and gives it back on the way out.
	Host Host

	// FrameInterval is the shortest time between redraws. Zero uses
	// [DefaultFrameInterval]; a negative duration is invalid.
	FrameInterval time.Duration
}

Config is what a program needs to run.

Exactly one of Root and Inline says what to run, and which one it is decides where the interface is drawn: Root takes a screen of its own, Inline draws in the terminal's own screen and prints finished output into its scrollback.

func (Config) Validate added in v0.3.0

func (c Config) Validate() error

Validate reports contradictions in c without opening a terminal or invoking a component builder. Transport adapters call it before acquiring their own session resources; Run calls it as well, so there is one definition of a runnable configuration.

type CopyHost added in v0.0.5

type CopyHost interface{ Copy(text string) bool }

CopyHost writes to the clipboard associated with the user-facing host.

type DirectoryHost added in v0.0.5

type DirectoryHost interface {
	ReportDirectory(path string) error
}

DirectoryHost tells a terminal how to resolve relative paths in program output.

type Dispatcher added in v0.0.5

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

Dispatcher is a copyable, concurrency-safe handle into a running program. Its zero value drops work. It deliberately exposes no owner-only operation.

func (Dispatcher) Done added in v0.1.0

func (d Dispatcher) Done() <-chan struct{}

Done is closed when the program can no longer accept or apply work. The zero Dispatcher's channel is already closed. A background producer selects on Done to stop work that has no remaining owner.

func (Dispatcher) Post added in v0.0.5

func (d Dispatcher) Post(fn func())

Post runs fn on the interface goroutine and requests a frame afterwards. A nil function requests only the frame. Calls never wait, preserve FIFO acceptance order, and are dropped after the program stops or on a zero Dispatcher.

Never waiting is the whole point and it is also the whole cost: the queue has no upper bound, so a producer posting faster than the interface goroutine can drain grows it without limit. Nothing here can fix that, because the only two things a queue can do when it is full are block the caller and lose work, and this edge exists to do neither. Coalescing therefore belongs to the caller, at the source: post the state a burst arrived at rather than one call per item, or keep the state somewhere the interface goroutine reads and post nothing but a request for a frame — which is what a nil function is for, and why several of them collapse into one. Runtime.Every is the same discipline applied to a clock.

type Environment added in v0.0.5

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

Environment is the stable set of terminal facts learned before a program runs. Its zero value reports that nothing was learned.

func (Environment) Ground added in v0.0.5

func (e Environment) Ground() grid.Ground

Ground reports the host's foreground and background colours when known.

func (Environment) Keyboard added in v0.0.5

func (e Environment) Keyboard() (input.KeyboardFlags, bool)

Keyboard reports negotiated keyboard protocol features.

func (Environment) Wheel added in v0.0.5

func (e Environment) Wheel() input.Wheel

Wheel reports how host wheel events should be scaled.

type EventSource added in v0.1.0

type EventSource interface {
	Events() <-chan input.Event
	Err() error
}

EventSource is one ordered input stream and its terminal result.

Events closes after the last event. Once it has closed, Err reports why the stream ended: nil means a clean end of input, while a non-nil error is the transport failure that ended it. Err must not report io.EOF as a failure.

The two methods form one lifecycle. Keeping the result on the source avoids a race between an event channel closing and a separate error channel becoming readable, and follows the same iteration-then-error shape as a scanner.

type FrameWriter added in v0.0.5

type FrameWriter interface {
	// Queue takes ownership of frame. The caller does not read or change the slice
	// after the call; an asynchronous implementation may retain it without copying.
	// Every accepted call returns a non-zero sequence strictly greater than earlier
	// sequences from the same writer. Written reports a watermark in that sequence
	// space.
	Queue(frame []byte) uint64
	Progress() <-chan struct{}
	Written() uint64
	Err() error
	Drain(timeout time.Duration) error
}

FrameWriter is the part of a frame queue the program needs.

It is defined by the consumer rather than exposing term.Writer through Host. Implementations must preserve queue order, report progress as a watermark and be safe for concurrent use. Progress returns one stable channel for the writer's lifetime; closing it means the writer has permanently stopped and Err must report the cause. Drain returns nil only after every frame accepted before the call has either been written or accounted for. term.Writer is the standard implementation.

type GroundHost added in v0.0.5

type GroundHost interface{ Ground() grid.Ground }

GroundHost supplies the colours discovered before a program starts.

type HandoverHost added in v0.0.5

type HandoverHost interface {
	Hand(run func() error) error
}

HandoverHost temporarily gives exclusive ownership of its display to run.

type Host

type Host interface {
	// Input is the ordered input stream and the reason it eventually ends.
	Input() EventSource
	// Writer is where frames go. The interface is defined here, where it is used;
	// a host is not coupled to the terminal package's concrete writer.
	Writer() FrameWriter
	// Size is the terminal's size in cells. The result must satisfy [ValidateSize].
	Size() (w, h int, err error)
}

Host is where a program's input comes from and its frames go.

A program opens the real terminal unless it is given one of these. Being able to supply it is what lets an interface be driven and inspected in a test, with no terminal in sight.

Everything beyond transport is optional. Each independently useful operation is represented by a small consumer interface such as GroundHost, CopyHost or NotifyHost, so implementing one never silently depends on implementing its neighbours. ImageHost is the exception because its transport and geometry form one protocol. Absent capabilities receive harmless defaults.

type ImageHost added in v0.0.5

type ImageHost interface {
	Graphics() graphics.Protocol
	CellSize() (image.Point, bool)
	Transmit(png []byte) (graphics.Image, error)
}

ImageHost transmits images and reports the protocol geometry needed to place them. These methods form one capability: a handle from Transmit cannot be placed without the protocol and cell geometry that interpret it.

type Images added in v0.0.5

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

Images is the host's image transport. Its zero value reports no protocol and refuses transmission.

func (Images) CellSize added in v0.0.5

func (i Images) CellSize() (image.Point, bool)

CellSize reports one terminal cell's pixel size when known.

func (Images) Protocol added in v0.0.5

func (i Images) Protocol() graphics.Protocol

Protocol reports the host's richest image protocol.

func (Images) Transmit added in v0.0.5

func (i Images) Transmit(png []byte) (graphics.Image, error)

Transmit sends one PNG and returns the image handle used by frames.

type InlineRuntime added in v0.0.5

type InlineRuntime struct{ *Runtime }

InlineRuntime is a Runtime that can publish completed output into terminal scrollback. It is only constructed for Config.Inline, and its zero value is inert.

func (*InlineRuntime) Append added in v0.0.5

func (r *InlineRuntime) Append(draw func(grid.View) bool)

Append continues the last published row until draw reports completion.

func (*InlineRuntime) Print added in v0.0.5

func (r *InlineRuntime) Print(p Printable)

Print publishes a measured drawable above an inline interface.

func (*InlineRuntime) PrintRows added in v0.0.5

func (r *InlineRuntime) PrintRows(rows int, draw func(grid.View))

PrintRows publishes a caller-sized drawing above an inline interface.

type KeyboardHost added in v0.0.5

type KeyboardHost interface {
	Keyboard() (input.KeyboardFlags, bool)
}

KeyboardHost supplies keyboard protocol features negotiated with the host.

type NotifyHost added in v0.0.5

type NotifyHost interface{ Notify(text string) }

NotifyHost sends a notification through the user-facing host.

type PasteHost added in v0.0.5

type PasteHost interface{ Paste() }

PasteHost requests text from the clipboard associated with the user-facing host. Answers arrive asynchronously through EventSource.Events as an input.Paste.

type Printable

type Printable interface {
	Draw(view grid.View)
	layout.Measurer
}

Printable is something that can say how tall it is at a width and then draw itself into that space.

The interface is defined here where printing consumes it. Any higher-level value with the same drawing and measuring behaviour satisfies it without an adapter.

type Runtime added in v0.0.5

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

Runtime is the program resource owned by the interface goroutine.

It is concrete rather than a provider-defined interface: consumers that need only a subset declare that interface where they use it. Background work receives only Runtime.Dispatcher, preserving ownership in the type system. Host features are grouped into the concrete Environment, Clipboard, Session and Images values rather than flattened into one capability catalogue. The zero value is inert; it is safe to embed in an object that has not been attached to a program.

func (*Runtime) Clipboard added in v0.0.5

func (r *Runtime) Clipboard() Clipboard

Clipboard returns the runtime's clipboard capability.

func (*Runtime) Dispatcher added in v0.0.5

func (r *Runtime) Dispatcher() Dispatcher

Dispatcher returns the concurrency-safe handle for background work.

func (*Runtime) Environment added in v0.0.5

func (r *Runtime) Environment() Environment

Environment returns the host facts available to this runtime.

func (*Runtime) Every added in v0.0.5

func (r *Runtime) Every(d time.Duration, fn func()) (stop func())

Every schedules coalesced ticks on the interface goroutine.

func (*Runtime) Images added in v0.0.5

func (r *Runtime) Images() Images

Images returns the runtime's image capability.

func (*Runtime) Quit added in v0.0.5

func (r *Runtime) Quit()

Quit asks the program to stop.

func (*Runtime) Refresh added in v0.0.5

func (r *Runtime) Refresh()

Refresh requests a frame without changing component state.

func (*Runtime) Session added in v0.0.5

func (r *Runtime) Session() Session

Session returns the terminal-session capability owned by this runtime.

type Session added in v0.0.5

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

Session controls the live terminal session around rendered frames. It groups operations that must remain ordered with the interface owner's state. Its zero value performs harmless notification no-ops and can hand control to a callback, but cannot suspend a process.

It holds the runtime rather than the resolved services the other capabilities hold, because two of its methods need the owner itself and not what the host can answer: see Session.Hand.

func (Session) Bell added in v0.0.5

func (s Session) Bell()

Bell asks the host for the user's attention.

func (Session) Hand added in v0.0.5

func (s Session) Hand(run func() error) error

Hand gives exclusive display ownership to run and repaints after it returns. If pending frames cannot drain, it returns ErrFrameTimeout without calling run.

func (Session) Notify added in v0.0.5

func (s Session) Notify(text string)

Notify asks the host to display a desktop notification.

func (Session) ReportDirectory added in v0.0.5

func (s Session) ReportDirectory(path string) error

ReportDirectory tells the host which directory relative links belong to.

func (Session) SetTitle added in v0.0.5

func (s Session) SetTitle(title string)

SetTitle names the host window when supported.

func (Session) Suspend added in v0.0.5

func (s Session) Suspend() error

Suspend restores the terminal and stops the process until it is continued.

type TitleHost added in v0.0.5

type TitleHost interface{ SetTitle(title string) }

TitleHost names the user-facing host window.

type WheelHost added in v0.0.5

type WheelHost interface{ Wheel() input.Wheel }

WheelHost supplies the host's wheel-event scale.

Jump to

Keyboard shortcuts

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