grid

package
v0.0.1 Latest Latest
Warning

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

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

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClusterWidth

func ClusterWidth(cluster string) int

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

func EncodeRow(cells []Cell, depth Depth) string

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.

func Rect

func Rect(x, y, w, h int) image.Rectangle

Rect builds a rectangle from a terminal-natural origin and size. The result is half-open: it covers columns [x, x+w) and rows [y, y+h).

Types

type Attr

type Attr uint8

Attr is a set of text attributes.

const (
	Bold Attr = 1 << iota
	Dim
	Italic
	Underline
	Reverse
	Strike
)

func (Attr) Has

func (a Attr) Has(want Attr) bool

Has reports whether every attribute in want is set.

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.

func (Cell) Blank

func (c Cell) Blank() bool

Blank reports whether the cell would print as empty space.

func (Cell) Width

func (c Cell) Width() int

Width is how many columns the cell occupies: 2 for the head of a wide cluster, 0 for the trailing half of one, 1 otherwise.

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 RGBColor

func RGBColor(r, g, b uint8) Color

RGBColor returns a colour that overrides the terminal default.

func (Color) Blend

func (c Color) Blend(over Color, opacity float64) Color

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.

func (Color) Default

func (c Color) Default() bool

Default reports whether the colour defers to the terminal.

func (Color) RGB

func (c Color) RGB() RGB

RGB returns the colour's components. They are meaningless when the colour is the terminal default.

type Cursor

type Cursor struct {
	Visible bool
	Pos     image.Point
}

Cursor is where the terminal's own cursor should end a frame.

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

func NewInline(w, h int) *Inline

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

func (i *Inline) Finish(w io.Writer) error

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

func (i *Inline) Flush(w io.Writer) error

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

func (i *Inline) Frame() View

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

func (i *Inline) Print(rows int, draw func(View))

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.

func (*Inline) Resize

func (i *Inline) Resize(w, h int)

Resize changes the width and the height the block may grow to.

func (*Inline) SetDepth

func (i *Inline) SetDepth(d Depth)

SetDepth says how much colour the terminal can show. It forces a full repaint, because every row the terminal is holding was encoded at the old depth.

func (*Inline) Size

func (i *Inline) Size() (w, h int)

Size returns the block's width and the height it may grow to.

type RGB

type RGB struct{ R, G, B uint8 }

RGB is a 24-bit colour.

func PaletteRGB

func PaletteRGB(index uint8) RGB

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) Index16

func (c RGB) Index16() uint8

Index16 is the nearest of the sixteen colours every terminal has.

func (RGB) Index256

func (c RGB) Index256() uint8

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

func NewScreen(w, h int) *Screen

NewScreen returns a screen of the given size whose first flush repaints everything.

func (*Screen) Flush

func (s *Screen) Flush(w io.Writer) error

Flush writes this frame to w, leaving the cursor wherever the frame placed it.

func (*Screen) Frame

func (s *Screen) Frame() View

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

func (s *Screen) Resize(w, h int)

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.

func (*Screen) SetDepth

func (s *Screen) SetDepth(d Depth)

SetDepth says how much colour the terminal can show. It forces a full repaint, because every cell the terminal is holding was encoded at the old depth.

func (*Screen) Size

func (s *Screen) Size() (w, h int)

Size returns the screen's width and height.

type Style

type Style struct {
	FG, BG Color
	Attr   Attr
}

Style is how a cell looks. The zero Style is the terminal's own appearance.

func (Style) Merge

func (s Style) Merge(over Style) Style

Merge lays over on top of s: whatever over states wins, whatever it leaves at its default is inherited. Attributes accumulate, because an overlay that adds emphasis should not silently drop the emphasis underneath it.

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

func NewSurface(w, h int) *Surface

NewSurface returns a blank surface of the given size.

func (*Surface) Bounds

func (s *Surface) Bounds() image.Rectangle

Bounds is the surface's own rectangle, with its origin at zero.

func (*Surface) CellAt

func (s *Surface) CellAt(x, y int) *Cell

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

func (s *Surface) CopyRows(src *Surface, srcTop, dstTop, n int)

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) Reset

func (s *Surface) Reset()

Reset blanks every cell.

func (*Surface) Resize

func (s *Surface) Resize(w, h int)

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.

func (*Surface) Row

func (s *Surface) Row(y int) []Cell

Row returns the cells of one row, or nil when y is outside the surface.

func (*Surface) Size

func (s *Surface) Size() (w, h int)

Size returns the surface's width and height.

func (*Surface) View

func (s *Surface) View() View

View returns a drawing view over the whole surface.

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) Bounds

func (v View) Bounds() image.Rectangle

Bounds is the view's own coordinate space, origin at zero.

func (View) CellAt

func (v View) CellAt(x, y int) *Cell

CellAt returns the cell at local (x, y), or nil when it is outside the clip.

func (View) Empty

func (v View) Empty() bool

Empty reports whether the view has nowhere to draw.

func (View) Fill

func (v View) Fill(r image.Rectangle, style Style)

Fill blanks every cell in r, in this view's coordinates, and gives it style.

func (v View) Link(x, y, w int, target string)

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

func (v View) PlaceCursor(x, y int)

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) Size

func (v View) Size() (w, h int)

Size returns the box the view was laid out into.

func (View) Sub

func (v View) Sub(r image.Rectangle) View

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

func (v View) Text(x, y int, s string, style Style) int

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.

func (View) Visible

func (v View) Visible() image.Rectangle

Visible is the part of the view that will actually reach the screen, in the view's own coordinates. It is empty for a view with nowhere to draw.

Jump to

Keyboard shortcuts

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