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 ¶
- Variables
- func Animate(frames []*Screen, holds []float64, opts SVGOptions) (string, error)
- func VariantPath(path, suffix string) string
- type SVGOptions
- type Screen
- func (s *Screen) Crop(rows int) *Screen
- func (s *Screen) Cursor() (row, col int)
- func (*Screen) Fd() uintptr
- func (s *Screen) FindRow(substr string) int
- func (s *Screen) LastUsedRow() int
- func (s *Screen) Row(n int) string
- func (s *Screen) Rows() []string
- func (s *Screen) SVG(opts SVGOptions) string
- func (s *Screen) ScrollRegion() (top, bottom int)
- func (s *Screen) Scrolled() int
- func (s *Screen) Snapshot() *Screen
- func (s *Screen) String() string
- func (s *Screen) StyleAt(row, col int) string
- func (s *Screen) Write(p []byte) (int, error)
- type Theme
- type ThemeVariant
Constants ¶
This section is empty.
Variables ¶
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.
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.
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 ¶
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 ¶
New returns a Screen of the given size, cursor at the top left and the whole grid scrollable.
func (*Screen) Crop ¶
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) Fd ¶
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 ¶
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 ¶
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) 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 ¶
ScrollRegion reports the rows the terminal will scroll, 1-based inclusive. Rows outside it are pinned.
func (*Screen) Scrolled ¶
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 ¶
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 ¶
String renders the whole screen, for a failure message that shows what the user would have been looking at.
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 ¶
ThemeVariant pairs a palette with the filename suffix a picture rendered in it carries.