Documentation
¶
Overview ¶
Package term owns the terminal itself: taking it over, giving it back, reading what it sends, and writing frames to it without blocking whoever drew them.
It is the only package in the TUI that touches the operating system. Everything above it works in cells, events and frames, and can be tested without a terminal at all — which is the point of putting the parts that need one here and nowhere else.
Index ¶
Constants ¶
const DrainGrace = 250 * time.Millisecond
DrainGrace is how long to wait for queued frames to reach the terminal before abandoning them. A terminal that has stopped accepting bytes must not be able to hold up an exit, and no amount of waiting makes one start accepting them again.
It is what Writer.Close waits, and the right answer for anyone else with a reason to wait for the terminal to catch up.
Variables ¶
var ErrClosed = errors.New("term: writer closed")
ErrClosed marks a frame that was handed over after the writer began shutting down, or that was still queued when its grace period ran out. Such a frame is abandoned rather than written.
var ErrNotTerminal = errors.New("term: not a terminal")
ErrNotTerminal is reported by Open when the process is not attached to a terminal — piped, redirected, or running under something that gave it no tty.
Functions ¶
func DetectDepth ¶
DetectDepth works out how much colour this terminal can show, from the environment it was started in.
This is the one place the library detects rather than asks. Everything else a terminal might not support is requested and ignored if unimplemented, which costs nothing when the guess is wrong. Colour is not like that: a truecolor sequence sent to a terminal that cannot read it does not degrade, it prints wrong, and there is no request that fails safely.
What it reads, in the order it reads it:
- NO_COLOR, set to anything at all, means no colour. That is the whole of the convention, including the part where an empty value still counts.
- COLORTERM naming truecolor or 24-bit means truecolor. Terminals that mean it say so here.
- TERM of "dumb", or no TERM at all, means no colour.
- TERM mentioning 256 means the 256-colour palette.
- Anything else is truecolor.
That last line is a decision worth stating. Plenty of terminals handle 24-bit colour and describe themselves as plain "xterm", so treating an unrecognised TERM as sixteen colours would make the common case worse to fix the rare one. A caller who knows better says so, and [program.Config] carries that through.
func DetectGraphics ¶
DetectGraphics works out whether this terminal can show inline images.
It is the same bargain as DetectDepth and the same reason for living here: graphics.DetectIn is a function of an environment, and this is the package allowed to have one.
Types ¶
type Options ¶
type Options struct {
// AltScreen draws on a screen of its own, leaving the user's scrollback as it
// was. Without it, frames are drawn in place among whatever else is on screen.
AltScreen bool
// Mouse asks for mouse reporting, including movement and not only clicks.
Mouse bool
// Focus asks to be told when the terminal window gains or loses focus, which is
// what lets a UI stop animating while nobody is looking.
Focus bool
// Keyboard asks for the Kitty keyboard protocol: unambiguous key codes, key
// releases and repeats, and the text a key produced. Terminals that do not
// implement it ignore the request.
Keyboard bool
}
Options says which of the terminal's optional behaviours a session wants.
The zero value asks for none of them, which is a legitimate choice for a session that only wants raw keys, and is why each is named for what turning it on gets you rather than for turning it off.
type Terminal ¶
type Terminal struct {
// contains filtered or unexported fields
}
Terminal is a terminal taken over for a session.
It is the whole boundary: raw mode, the modes it turned on, the goroutines reading input, and the writer frames go through. Terminal.Close gives all of it back, in the order that leaves the terminal as it was found, and is safe to call more than once — including from a deferred call on a path that already failed.
func Open ¶
Open takes over the terminal on standard input and output.
It reports ErrNotTerminal when there is no terminal to take over, which is the case a caller has to handle rather than force: a program whose output is being piped wants to write text, not frames.
func (*Terminal) Close ¶
Close gives the terminal back.
The order is the reverse of taking it over, and every step runs even if an earlier one failed: a terminal left in raw mode is unusable, so a failure to write the restore sequences must not be a reason to skip leaving raw mode.
func (*Terminal) Events ¶
Events is the terminal's input, closed when the input ends or the session does.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer writes frames to the terminal from a goroutine of its own.
The reason it exists is that a terminal write can block for a long time — a remote session, a suspended emulator, a scrolled-back pager — and a UI loop that waits for one stops reading input, which is what an unresponsive terminal program actually is.
Progress is reported as a watermark rather than as a stream of results: the only question anyone asks is how far the terminal has got, and a counter answers it without a queue to keep in order or a consumer to keep up. Writer.Progress wakes the loop when the watermark moves, and Writer.Written says where it is.
Concurrency: Writer.Queue, Writer.Drain and Writer.Close belong to the goroutine that owns the terminal. The writer's own goroutine is the only other participant, and the counters it publishes are read safely from anywhere.
func (*Writer) Close ¶
Close drains what it can, stops the goroutine and reports whether anything had to be abandoned. It is idempotent.
A write already inside the terminal cannot be interrupted. When the grace period ends with one outstanding, Close returns without waiting for the goroutine: it finishes on its own, discarding what is left rather than writing it.
func (*Writer) Drain ¶
Drain waits until every frame queued so far has been written or failed, or until the timeout passes, and reports whether everything was accounted for.
It is what to call before handing the terminal to another program, so that program does not find half a frame in front of it. A failed frame counts as drained: a broken terminal must not be able to wedge a shutdown.
It takes nothing from Writer.Progress. Waiting here must not cost the loop a wake-up it is owed, which is what the broadcast channel is for.
func (*Writer) Err ¶
Err is the first write failure, or nil.
A terminal that has failed a write does not recover, and a UI that cannot reach its terminal has nothing left to do, so this is a reason to exit rather than something to retry.
func (*Writer) Progress ¶
func (w *Writer) Progress() <-chan struct{}
Progress wakes its receiver when the write watermark has moved. It carries no value: the watermark is read from Writer.Written, which is always current.
func (*Writer) Queue ¶
Queue takes ownership of a frame and returns the sequence number reserved for it. The sequence is reserved before the goroutine can see the frame, so Writer.Queued already accounts for it when Queue returns.
Queue returns without waiting unless the terminal has fallen further behind than the channel can hold. A frame handed over after Writer.Close is failed rather than written, which is not an error to the caller: shutting down while a frame was in flight is ordinary.