Documentation
¶
Overview ¶
Package tui holds the terminal-presentation rules Draugr applies everywhere it writes for a person: when color is allowed, what the colors mean, and how to link to more detail.
It exists because those rules were being re-derived per command. The console report, the log handler and the install prompt each had their own copy of the "is this a terminal" check, and two separate ANSI palettes had drifted apart. Output that looks assembled by different people is a real cost for a tool whose terminal *is* the product.
The rules, in one place:
- color only when writing to an interactive terminal, and never when NO_COLOR is set (https://no-color.org)
- a fixed, semantic palette, so callers ask for "critical" rather than for red
- anything that degrades (color, hyperlinks) degrades to plain text, so piped output and CI logs stay readable
Index ¶
- func ColorEnabled(w io.Writer) bool
- func Columns(w io.Writer) int
- func FullColor() bool
- func IsTerminal(v any) bool
- func Pad(s string, width int) string
- func Truncate(s string, width int) string
- type Cell
- type Painter
- func (p Painter) Append(buf []byte, style Style, s string) []byte
- func (p Painter) BandChips(counts [4]int) string
- func (p Painter) Chip(style Style, text string) string
- func (p Painter) Enabled() bool
- func (p Painter) Link(url, text string) string
- func (p Painter) Paint(style Style, s string) string
- type Style
- type Table
- func (t *Table) Fit(width int) *Table
- func (t *Table) Indent(prefix string) *Table
- func (t *Table) Render(w io.Writer)
- func (t *Table) Row(cells ...Cell) *Table
- func (t *Table) RowWithNote(note string, cells ...Cell) *Table
- func (t *Table) RowWithNotes(notes []string, cells ...Cell) *Table
- func (t *Table) StyledNotes() *Table
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ColorEnabled ¶
ColorEnabled reports whether colored output is appropriate for w: it must be an interactive terminal, and NO_COLOR must be unset.
func Columns ¶ added in v0.116.0
Columns reports how many character cells wide w is, or 0 when that cannot be answered.
A caller that draws in place needs this, because a terminal wraps a line it cannot fit and the wrapped line occupies two rows. Anything that then moves the cursor back over what it drew moves too few rows and erases whatever is above, which is the reader's own scrollback rather than anything this program wrote.
COLUMNS wins over the ioctl so a test, and a reader debugging a layout, can say what the width is without a terminal of that size.
func FullColor ¶ added in v0.119.0
func FullColor() bool
FullColor reports whether the terminal says it can show twenty-four-bit color.
COLORTERM is the only signal there is: TERM describes a terminal type from a database that mostly predates the capability, and probing means writing a color and reading back what the terminal made of it, which a program writing to a pipe cannot do. An absent variable is read as "no", so the sixteen-color rendering is what an unknown terminal gets.
func IsTerminal ¶
IsTerminal reports whether v (an *os.File, in practice stdin/stdout/stderr) is a character device. Accepts any value so it can answer for a reader (is the user there to be prompted?) as well as a writer (should this be colored?).
func Pad ¶
Pad left-aligns s to width, measured on the unstyled text. Padding must be computed before color is applied, or escape codes inflate the apparent length and columns stop lining up.
func Truncate ¶ added in v0.116.0
Truncate shortens s to width character cells, measuring the text a reader sees rather than the bytes. Zero or negative width returns s unchanged, for a caller that could not find out.
Escape sequences are carried through and cost nothing, which is what makes this different from slicing a string: a styled line is mostly bytes the terminal never displays, so cutting by length removes visible text long before the edge and can cut a sequence in half, leaving the rest of the screen wearing a color nothing turns off. A line that is cut ends with a reset for the same reason, and an escape sitting exactly on the cut is kept rather than dropped, so a hyperlink whose text just fits is closed by its own terminator.
Types ¶
type Cell ¶
type Cell struct {
Text string
Style Style
// URL turns the cell into a hyperlink where the terminal supports it. It costs no width,
// which is what makes it usable in a table that's already wide.
URL string
// Note is a second part of the same cell, set after Text in its own style and separated by a
// space. It is measured, so the column still lines up.
//
// For a qualifier that belongs to a value rather than beside it. A column of two-character
// values under an eight-character heading wastes six columns on every row, and a qualifier
// given a column of its own pays for the heading twice.
Note string
NoteStyle Style
}
Cell is one value in a table row: the text, how it should read, and optionally where it points. The zero value is a plain, unlinked cell, so a caller only names what differs.
type Painter ¶
type Painter struct {
// contains filtered or unexported fields
}
Painter renders styled text, or plain text when color isn't appropriate for the destination. The zero value is a valid plain-text painter, so a caller that forgets to construct one degrades safely instead of emitting escape codes into a file.
func Colored ¶
func Colored() Painter
Colored returns a Painter for a caller that has already decided, such as one whose color setting comes from configuration rather than from inspecting the writer.
func For ¶
For returns a Painter suited to w: color only for an interactive terminal with NO_COLOR unset.
func FullColorPainter ¶ added in v0.119.0
func FullColorPainter() Painter
FullColorPainter is Colored for a caller that has also decided the terminal can show the project's own colors. Used by the tests that pin what those look like.
func Plain ¶
func Plain() Painter
Plain returns a Painter that never colors, for tests and for building strings whose destination isn't known yet.
func (Painter) Append ¶
Append is Paint for a caller building a byte buffer, the log handler writes a line per record, and going through strings would allocate on every one.
func (Painter) BandChips ¶ added in v0.121.0
BandChips renders the four counts as filled labels.
Filled, because that is how a band is drawn everywhere else and it does something a colored word cannot: the count and the band read as one object rather than two words that happen to be adjacent. A band with nothing in it stays on the row and says zero, so the shape of the ramp is learnable from any run.
func (Painter) Chip ¶ added in v0.119.0
Chip renders text as a filled label in the role's color, the shape a band wears everywhere else Draugr shows one.
It degrades twice rather than once. A terminal with full color gets the color itself; a sixteen-color terminal gets the role reversed, which fills the same area in whatever red or yellow that terminal calls the role; and a destination with no color at all, a pipe or a CI log, gets the bare text, because a chip with no fill is a word with two extra spaces around it.
func (Painter) Enabled ¶
Enabled reports whether this painter emits color, so callers can skip work that only matters when colored.
func (Painter) Link ¶
Link renders text as an OSC 8 terminal hyperlink to url. Terminals that support it show the text and follow the link on click; everywhere else, an older terminal, a pipe, a CI log. The escape codes are absent and the text stands alone. It therefore costs no width, which is what makes it usable in a table that is already wide.
A caller with nowhere to link should pass an empty url and get the text back.
type Style ¶
type Style string
Style is a semantic role, not a color: callers say what a thing *is* and the palette decides how it looks, so the same concept renders identically in every command.
const ( StyleNone Style = "" StyleCritical Style = "1;31" // bold red StyleHigh Style = "31" StyleMedium Style = "33" StyleLow Style = "2" StyleFail Style = "1;31" StylePass Style = "32" StyleAccent Style = "33" // draws the eye without implying severity StyleInfo Style = "36" // a band that is neither urgent nor negligible StyleFixed Style = "1;32" // the release that ends a finding StyleMuted Style = "2" // supporting detail: headers, labels, units StyleStrong Style = "1" // the part of a line to read first, at no cost in color )
The palette. Severity styles mirror the report bands; the rest are roles, not colors.
The values are the sixteen-color codes every terminal has had since the 1980s, and they are what a caller gets unless the terminal says it can do better. See exact, which carries the same roles in the project's own colors.
func PriorityStyle ¶ added in v0.121.0
PriorityStyle is the color a band wears, everywhere the product draws one.
P3 had no color of its own once and was drawn in whatever the terminal's text color is, which is also what an unranked row and a heading look like. Three of four bands being distinguishable is not a ramp.
type Table ¶
type Table struct {
// contains filtered or unexported fields
}
Table renders aligned columns for a person reading a terminal.
It exists because alignment and color interact badly: any padding computed after styling counts escape bytes as visible width and the columns drift apart. text/tabwriter has the same flaw, its Escape mechanism hides the bytes from parsing but still measures them, so every command that wanted color was going to hand-roll its own width arithmetic. Table measures the plain text, pads, and only then paints.
func NewTable ¶
NewTable starts a table written with p. Headers may be omitted for a table whose columns need no labeling; when given, they're dimmed so they frame the data without competing.
func (*Table) Fit ¶ added in v0.119.0
Fit bounds the table's width, trimming the last column's text to what is left after the others.
The last column because it is the only one nothing follows, and because a table is bounded by a terminal rather than by a design: the columns before it are identifiers and locations, which are useless shortened, and the last one is prose, which is readable shortened and still readable gone. A width of zero leaves the table as wide as its content, which is the right answer when the destination is a file or a pipe and there is no width to respect.
func (*Table) Render ¶
Render writes the table. Columns are sized to their widest plain-text value, and the final column is never padded. Nothing follows it, and trailing spaces are noise in a diff or a copied-out log.
func (*Table) Row ¶
Row appends a row. Rows may be shorter than the header, missing cells render empty.
func (*Table) RowWithNote ¶
RowWithNote appends a row followed by a dimmed continuation line, aligned under the second column. Use it when a row's own columns can't carry the explanation.
func (*Table) RowWithNotes ¶ added in v0.56.0
RowWithNotes appends a row followed by several dimmed continuation lines, each aligned under the second column. Empty strings are dropped, so a caller can pass a line that may or may not have anything to say without guarding at the call site.
func (*Table) StyledNotes ¶ added in v0.119.0
StyledNotes tells the table its notes arrive painted, so it sets them as given rather than dimming them whole.