screen

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: GPL-3.0 Imports: 4 Imported by: 0

Documentation

Overview

Package screen is a terminal emulator: it consumes an escape stream and reconstructs the grid a reader would be looking at.

It exists so a test can assert on the PICTURE rather than the bytes. The difference is not cosmetic: a substring check proves a sequence was emitted and says nothing about where the text landed, and every bug this codebase has actually had in terminal rendering was of the second kind - a cursor left in the wrong place, a repaint that ate the transcript, a downward move the receiving end ignored.

It lives in its own package rather than beside the code it checks because two other packages need it: internal/cache, whose pinned band has to line up with the coordinates a mouse click resolves against, and eventually a recorder that replays a session for documentation.

Index

Constants

This section is empty.

Variables

View Source
var DarkTheme = Theme{
	Background: "#14161b",
	Foreground: "#d5d1ca",
	Black:      "#14161b",
	Red:        "#e2695f",
	Green:      "#79bd76",
	Yellow:     "#d6b158",
	Blue:       "#6ba3c4",
	Magenta:    "#b98bc4",
	Cyan:       "#57a8a8",
	White:      "#d5d1ca",
}

DarkTheme is the default: a modern terminal profile rather than the sixteen ANSI primaries, which are harsh at small sizes on a page.

View Source
var LightTheme = Theme{
	Background: "#fbfaf7",
	Foreground: "#22242a",
	Black:      "#22242a",
	Red:        "#b03028",
	Green:      "#3f7a3c",
	Yellow:     "#8a6a15",
	Blue:       "#2f6a8c",
	Magenta:    "#7b4b8a",
	Cyan:       "#2b6f6f",
	White:      "#22242a",
}

LightTheme renders on a page that is not dark. Kept deliberately close to DarkTheme in hue so a docs page can carry both without the two reading as different products.

View Source
var ThemeVariants = []ThemeVariant{
	{"", DarkTheme},
	{"-light", LightTheme},
}

ThemeVariants is every palette a committed picture ships in. An SVG referenced by <img> is its own document and cannot read the page's theme, so the page picks the file rather than the picture adapting itself. The unsuffixed name stays the dark one, which is what every existing reference already points at.

Functions

func Animate

func Animate(frames []*Screen, holds []float64, opts SVGOptions) (string, error)

Animate renders a sequence of frames as one self-contained animated SVG.

No JavaScript and no player: each frame is a group whose opacity is driven by SMIL, which every browser that renders SVG at all supports. That matters for a docs site with a strict content policy, where a script-driven player would simply not run.

Frames must share a size; the first one sets it. A frame is shown for its own hold time, and the whole sequence loops - a terminal recording that stops on the last frame reads as a page that failed to load.

func VariantPath

func VariantPath(path, suffix string) string

VariantPath names the file one variant of a picture is written to, given the unsuffixed name and the variant's suffix.

Types

type SVGOptions

type SVGOptions struct {
	FontFamily string
	FontSize   int
	CellWidth  int // advance width of one character cell
	LineHeight int
	Baseline   int // distance from a row's top to its text baseline
	Pad        int
	Radius     int
	Theme      Theme
}

SVGOptions is the geometry and palette one rendering uses. The zero value is usable: every field falls back to a default sized for a readable docs asset.

type Screen

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

Screen is a terminal: a grid of cells, a cursor, and a scroll region.

It implements exactly the vocabulary magus emits and nothing else, so it is a complete model of what magus does to a terminal rather than a partial model of terminals in general. An unrecognized sequence is dropped rather than guessed at.

That rule cuts both ways, and it has already bitten once: a sequence this does not know is SILENTLY IGNORED, so adding one to the production code without adding it here makes this quietly model a different terminal than the one users have. Anything new in the emitted vocabulary belongs here in the same change.

The scroll-region behavior is the part that carries its weight: a newline on the last row of the scroll region scrolls only rows [top, bottom], leaving the reserved zone below untouched. That single rule is what the whole Region design rests on, and emulating it is what lets a test prove the zone survives output rather than assuming it.

func New

func New(width, height int) *Screen

New returns a Screen of the given size, cursor at the top left and the whole grid scrollable.

func (*Screen) Crop

func (s *Screen) Crop(rows int) *Screen

Crop returns a copy holding only the first rows rows.

Cropping rather than recording at the smaller size on purpose: the size drives how the tools being recorded lay their output out and how many rows magus reserves, so shrinking the terminal changes the session. This changes only the picture of it. A rows outside 1..height is clamped.

func (*Screen) Cursor

func (s *Screen) Cursor() (row, col int)

Cursor reports where the cursor is, in 1-based terminal coordinates.

func (*Screen) Fd

func (*Screen) Fd() uintptr

Fd and Write make a Screen usable directly as a writer wherever production code expects a terminal, so a test drives the real code path rather than a transcript of it.

func (*Screen) FindRow

func (s *Screen) FindRow(substr string) int

FindRow returns the 1-based row whose text contains substr, or 0.

It is how a test asks "where did this actually end up" without hard-coding the arithmetic it is trying to check.

func (*Screen) LastUsedRow

func (s *Screen) LastUsedRow() int

LastUsedRow returns the 1-based row of the last row with any text on it, or 0 when the screen is blank.

What it is for: a recording is taken at a terminal size the SESSION needs, and the rows a reserved band occupied are blank again once the band is released. Rendering those to a picture spends a sixth of the image on nothing.

func (*Screen) Row

func (s *Screen) Row(n int) string

Row returns one 1-based row's text.

func (*Screen) Rows

func (s *Screen) Rows() []string

Rows renders the grid as right-trimmed text, one string per terminal row.

func (*Screen) SVG

func (s *Screen) SVG(opts SVGOptions) string

SVG renders what the terminal currently shows as a standalone SVG picture.

SVG rather than a raster format for three reasons that all matter to a repository. It is TEXT, so a rendered terminal diffs like source and a change to the output shows up in review as the lines that changed rather than as an opaque blob. It needs no player, so a docs page embeds it with an <img> or inline and nothing has to load. And it scales, so the same asset is legible in a sidebar thumbnail and at full width.

Deterministic by construction: no timestamps, no randomness, no map iteration. The same grid renders byte-for-byte the same picture, which is what lets a generated asset be committed and gated for drift.

func (*Screen) ScrollRegion

func (s *Screen) ScrollRegion() (top, bottom int)

ScrollRegion reports the rows the terminal will scroll, 1-based inclusive. Rows outside it are pinned.

func (*Screen) Scrolled

func (s *Screen) Scrolled() int

Scrolled reports how many lines have been pushed off the top of the scroll region, so a test can assert a repaint did not disturb the transcript.

func (*Screen) Snapshot

func (s *Screen) Snapshot() *Screen

Snapshot returns an independent copy of the screen as it is right now.

This is how a frame is captured for a recording: the terminal keeps being written to, and a sequence of pointers to one live Screen would all show its final state. The copy carries the CELLS, styles included - which the obvious alternative of re-rendering String() into a fresh screen does not, because String is plain text and drops every color.

Cursor position and scroll region are copied too, so a snapshot is a complete terminal rather than a picture of one.

func (*Screen) String

func (s *Screen) String() string

String renders the whole screen, for a failure message that shows what the user would have been looking at.

func (*Screen) StyleAt

func (s *Screen) StyleAt(row, col int) string

StyleAt reports the SGR parameters in force at a cell, so a test can assert that a warning is yellow without matching escape bytes.

func (*Screen) Write

func (s *Screen) Write(p []byte) (int, error)

type Theme

type Theme struct {
	Background string
	Foreground string
	Red        string
	Green      string
	Yellow     string
	Blue       string
	Magenta    string
	Cyan       string
	White      string
	Black      string
}

Theme maps the attributes magus emits to colors. Only the eight SGR foregrounds it actually uses are named; anything else renders as the default, which is the same restraint the emulator keeps about sequences it does not know.

type ThemeVariant

type ThemeVariant struct {
	Suffix string
	Theme  Theme
}

ThemeVariant pairs a palette with the filename suffix a picture rendered in it carries.

Jump to

Keyboard shortcuts

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