Documentation
¶
Overview ¶
Package grid is the cell grid the whole terminal UI is drawn into: styled grapheme cells, a clipped drawing view over them, and the two ways a frame of them reaches a terminal.
Screen takes the terminal's whole screen and emits the smallest escape stream that turns one frame into the next. Inline draws a block in the terminal's own screen instead, printing finished output above it into the scrollback. They share the cells, the view and the encoding, and differ only in what a frame is allowed to assume about where it is.
It is the only layer that knows what a terminal is made of. Everything above it draws through View and never assembles an escape sequence.
Geometry is image.Rectangle and image.Point from the standard library rather than a private rectangle type. Terminal rectangles are ordinary half-open rectangles, and intersection, insetting and containment are already written and already correct there.
Index ¶
- func ClusterWidth(cluster string) int
- func EncodeRow(cells []Cell, depth Depth) string
- func Rect(x, y, w, h int) image.Rectangle
- type Attr
- type Cell
- type Color
- type Cursor
- type Depth
- type Drawer
- type Inline
- type RGB
- type Screen
- type Style
- type Surface
- func (s *Surface) Bounds() image.Rectangle
- func (s *Surface) CellAt(x, y int) *Cell
- func (s *Surface) CopyRows(src *Surface, srcTop, dstTop, n int)
- func (s *Surface) Reset()
- func (s *Surface) Resize(w, h int)
- func (s *Surface) Row(y int) []Cell
- func (s *Surface) Size() (w, h int)
- func (s *Surface) View() View
- type View
- func (v View) Bounds() image.Rectangle
- func (v View) CellAt(x, y int) *Cell
- func (v View) Empty() bool
- func (v View) Fill(r image.Rectangle, style Style)
- func (v View) Link(x, y, w int, target string)
- func (v View) PlaceCursor(x, y int)
- func (v View) Size() (w, h int)
- func (v View) Sub(r image.Rectangle) View
- func (v View) Text(x, y int, s string, style Style) int
- func (v View) Visible() image.Rectangle
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ClusterWidth ¶
ClusterWidth is how many columns one grapheme cluster occupies, clamped to what a cell can hold. A control character measures zero.
Everything that lays text out shares this function. Measuring text one way and drawing it another is the cause of every misaligned terminal UI, so there is one answer and one place it comes from.
func EncodeRow ¶
EncodeRow renders one row of cells as inline terminal text: style and hyperlink transitions and printable graphemes, and nothing that moves the cursor or erases anything.
It is how a finished transcript line is printed into the terminal's own scrollback, where the line must survive on its own with no screen to address. The result always closes an open hyperlink and returns to the default style, so rows can be concatenated safely.
Types ¶
type Cell ¶
type Cell struct {
Content string
Style Style
// Link is an OSC 8 hyperlink target. It is cell metadata rather than part of
// Style because a hyperlink has its own open/close protocol on the wire,
// while everything in Style is one SGR parameter list.
Link string
// contains filtered or unexported fields
}
Cell is one terminal cell.
The zero Cell is a blank single-width cell in the terminal's own style, so a freshly allocated or cleared surface is already valid.
Content is a whole grapheme cluster. A double-width cluster occupies two cells: the head carries the content, and the cell to its right is a trailing cell with no content of its own. Nothing outside this package can create half of such a pair.
type Color ¶
type Color struct {
// contains filtered or unexported fields
}
Color is a cell colour: either the terminal's own default, or a truecolor value. The zero Color is the default, which is what an unstyled cell wants.
func (Color) Blend ¶
Blend mixes c toward over by opacity, clamped to [0,1]. A blend involving the terminal default is over unchanged: there is no way to know what the default resolves to, and guessing would tint every theme differently.
type Depth ¶
type Depth uint8
Depth is how much colour a terminal is being asked to show.
A frame is always built in truecolor — a Color is either the terminal's default or a 24-bit value, and nothing above this package thinks about anything else. The depth is applied at the very last step, where a style becomes bytes, so a widget never has to know what it is drawing onto and a palette never has to be authored twice.
The zero value is Auto, which leaves the choice to whoever opened the terminal — this package cannot read an environment variable and has no business guessing. Everything here treats it as TrueColor, which is the bet the library made before this type existed; the difference is that it is now a bet a caller can lose gracefully instead of one they cannot opt out of.
const ( // Auto is the zero value: whatever the caller decides, and truecolor to // anything that has to draw before they have. Auto Depth = iota // TrueColor emits the 24-bit value unchanged. TrueColor // Depth256 maps each colour to the nearest entry of the xterm 256 palette. Depth256 // Depth16 maps each colour to the nearest of the eight ANSI colours and their // bright forms — the only colours a terminal is really obliged to have. Depth16 // NoColor drops colour entirely and keeps the attributes. It is what NO_COLOR // asks for, and what a terminal being logged to a file wants: bold and // underline still carry meaning in a transcript, and a colour does not. NoColor )
type Drawer ¶
type Drawer interface {
Draw(v View)
}
Drawer is anything that draws itself into a view.
It is one method and it lives here because a view is this package's, and the vocabulary a layer speaks should come from below it rather than from beside it. Everything further up that means "something drawable" says so by embedding this, so a widget, a printed message and a program's root are the same idea named three times rather than three ideas that happen to match.
type Inline ¶
type Inline struct {
// contains filtered or unexported fields
}
Inline draws an interface as a block in the terminal's own screen, with output that is finished printed above it.
It is the other way to put frames on a terminal, and the one that makes a program part of a session rather than a mode of it: what the interface has already said stays in the terminal's own scrollback, where the user can scroll back to it, select it, and see it still there after the program exits. A Screen takes a screen of its own and gives back a blank terminal; this keeps the transcript.
Why nothing here is addressed absolutely ¶
The block's position on the terminal is decided by whatever is above it, which this type does not own and cannot ask about. So every frame is written relative to where the last one left the cursor: back to the top of the block, down through its rows, and back to wherever the cursor belongs. Printing works the same way — the rows are written where the block's first row was, and the block is drawn below them, which is what pushes finished output up and into the scrollback.
The block is as tall as what was drawn: the rows up to the last one with anything on it, and never fewer than enough to hold the cursor. Nothing has to declare a height, and an interface that draws two rows occupies two rows.
What a resize costs ¶
A resize is the one thing this cannot get exactly right. The terminal may reflow what is above the block, and there is no way to ask where the block ended up, so the next frame repaints in full from where the cursor was left. That is exact when the terminal did not reflow and approximate when it did, which is the same bargain every inline interface makes.
func NewInline ¶
NewInline returns an inline block that may grow to h rows of w columns, whose first flush draws everything.
The height is a ceiling rather than a size: it is what the terminal can spare, and the block takes as much of it as the interface draws into.
func (*Inline) Finish ¶
Finish leaves the block on screen with the cursor below it, so whatever writes next — the shell's prompt, or this program's own output — starts on a line of its own instead of on top of the interface.
It is the counterpart of giving back the alternate screen, and the reason an inline program has to draw one last frame before it exits: the last thing it showed is the thing that stays.
func (*Inline) Flush ¶
Flush writes this frame to w, leaving the cursor wherever the frame placed it.
A flush that would change nothing writes nothing at all, for the same reason a Screen does: an idle interface should be silent on the wire and should leave the cursor's blink undisturbed.
func (*Inline) Frame ¶
Frame blanks the drawing surface and returns the view for this frame.
The view is as tall as the block may grow to, not as tall as the block is: how tall it is is decided by what this frame draws into it.
func (*Inline) Invalidate ¶
func (i *Inline) Invalidate()
Invalidate forgets what the terminal is showing, so the next flush rewrites the whole block.
func (*Inline) Print ¶
Print draws rows that become part of the terminal's own output, above the interface, and stay there.
The rows are drawn now, into a surface as wide as the block, and kept as the text they came to. They reach the terminal with the next flush, before the block, which is what puts them above it.
It takes a count rather than working one out because output can be taller than the terminal — a long answer printed into the scrollback is the ordinary case — and a caller that has laid its content out already knows how tall it is. Every row asked for is printed, so a blank row is a blank row and not slack.
type RGB ¶
type RGB struct{ R, G, B uint8 }
RGB is a 24-bit colour.
func PaletteRGB ¶
PaletteRGB is what the xterm 256-colour palette holds at an index.
The three regions are the sixteen ANSI colours, the 6×6×6 cube, and a 24-step grey ramp. Terminals may render the first sixteen however they like, so those values are what xterm uses and not a promise.
func (RGB) Index256 ¶
Index256 is the nearest entry of the xterm 256-colour palette.
Both the colour cube and the grey ramp are searched and the closer of the two wins. Searching only the cube would turn every near-grey into a muddy brown: the cube's greys are the six points where all three channels agree, and the ramp has twenty-four.
The first sixteen indices are left out of the search on purpose. A terminal is free to render those as anything at all — a theme's own palette, usually — so choosing one because its default value happened to be close is choosing a colour nobody can predict.
type Screen ¶
type Screen struct {
// contains filtered or unexported fields
}
Screen is the terminal's contents, double-buffered.
A frame is drawn into the back surface and flushed: the screen works out the smallest escape stream that turns what the terminal is showing into what was drawn, wraps it so the terminal applies it atomically, and swaps. Nothing above this type sequences escape codes, decides when to repaint, or tracks what the terminal already knows.
A flush that would change nothing writes nothing at all — not even the frame markers — because an idle UI should be silent on the wire and should leave the cursor's blink undisturbed.
func NewScreen ¶
NewScreen returns a screen of the given size whose first flush repaints everything.
func (*Screen) Flush ¶
Flush writes this frame to w, leaving the cursor wherever the frame placed it.
func (*Screen) Frame ¶
Frame blanks the drawing surface and returns the view for this frame.
Every frame draws everything it wants to be visible. Keeping content across frames is the diff's job, not the caller's, and a surface that carried yesterday's cells forward would make a missed redraw look like success.
func (*Screen) Invalidate ¶
func (s *Screen) Invalidate()
Invalidate forgets what the terminal is showing, so the next flush repaints in full. It is what to call after handing the terminal to another program.
func (*Screen) Resize ¶
Resize changes the screen's size. The next flush repaints everything: after a resize the terminal has reflowed its own contents, and nothing about what it is showing can be assumed.
type Surface ¶
type Surface struct {
// contains filtered or unexported fields
}
Surface is a rectangle of cells in row-major order. It is storage and geometry; drawing happens through the View it hands out, so no caller has to carry a clip rectangle alongside the buffer it is clipping.
func NewSurface ¶
NewSurface returns a blank surface of the given size.
func (*Surface) CellAt ¶
CellAt returns the cell at (x, y), or nil when the coordinates are outside the surface. The cell is addressable so a reader can inspect what was drawn; writing through it bypasses the wide-pair invariant and is a bug.
func (*Surface) CopyRows ¶
CopyRows copies n whole rows out of src, starting at srcTop, into s starting at dstTop. Rows that fall outside either surface are skipped, which is what lets a caller render an over-tall item into a scratch surface and lift the visible slice of it into place.
func (*Surface) Resize ¶
Resize changes the surface's size and blanks it. Content is not preserved: every resize is followed by a full redraw, so carrying stale cells across one would only make the first frame after it wrong in a subtler way.
type View ¶
type View struct {
// contains filtered or unexported fields
}
View is a clipped window onto a Surface, addressed in its own coordinates.
A view is how everything above this package draws. Handing a widget a view sized to its box means it cannot draw outside that box: coordinates are local, and anything landing beyond the clip is discarded rather than trusted.
The zero View draws nowhere and reports a size of zero, which is the right answer for a widget laid out into no space at all.
func (View) Link ¶
Link stamps target onto w columns starting at local (x, y), turning text that has already been written into a hyperlink. It is separate from View.Text because a link usually spans a run that was drawn in several pieces.
func (View) PlaceCursor ¶
PlaceCursor asks for the terminal's cursor to sit at local (x, y).
It is how the one widget that owns the cursor says so, without anyone in between having to carry the answer: the view already knows where it sits on the screen, so the widget speaks in its own coordinates and the translation is nobody's job.
A position outside what the view may draw on is ignored, for the same reason a glyph there would be: a widget scrolled off the screen does not get to move the cursor. A frame in which nobody places the cursor is a frame with no cursor, which is the right answer when nothing is being typed into.
func (View) Sub ¶
Sub returns a view onto r, expressed in this view's coordinates. Clipping only ever narrows: a widget cannot hand a child room it does not have itself.
func (View) Text ¶
Text writes s at local (x, y) and returns how many columns it advanced, including any it advanced outside the clip.
Text is grapheme-aware. A double-width cluster takes two columns and is never split: one that would straddle the right edge is dropped and its first column blanked, because half a glyph is worse than a gap. A zero-width cluster — a combining mark arriving on its own — joins the cell to its left instead of consuming a column of its own.