Documentation
¶
Overview ¶
Package termtable provides a DOM-like model for composing and rendering text tables on a terminal. It handles Unicode width (including emoji and CJK), ANSI escape sequences, word-wrapping, trimming, and column/row spanning with single-line Unicode box-drawing borders.
A table is composed of headers, body rows, and footers. Each row contains cells. Cells can span multiple columns (ColSpan) and multiple rows within the same section (RowSpan). Headers, body, and footers form separate sections in the grid; rowspans do not cross section boundaries.
Elements can be addressed two ways:
- Logical: row.Cell(i) returns the i-th declared cell in that row.
- Grid: table.CellAt(r, c) returns the cell covering the absolute grid coordinate (r, c); multiple (r, c) pairs map to the same cell when it spans.
Any element may be tagged with a unique ID via its With*ID option and looked up with table.GetElementByID.
Rendering proceeds in three passes: measurement, layout, and paint. The table is fully buffered; there is no streaming output.
Known limitations (Phase 1–3):
- Right-to-left / bidirectional text is not supported.
- Styling (colors, bold, alternate border styles) is reserved for a later phase; option hooks exist but have no effect yet.
- Terminal width resolves in this order: WithTargetWidth, WithTargetWidthPercent (as a fraction of the detected terminal width, COLUMNS, or 80), the COLUMNS environment variable, then the default rule — fill 90% of the attached screen with 80 as the floor. The resolved value is clamped to the attached terminal's width when one is detected, so output never exceeds the physical screen; writing to a pipe leaves the value uncapped.
Example ¶
Example demonstrates the minimal usage pattern: build a table, add headers and rows, print the result.
package main
import (
"fmt"
"github.com/fatih/color"
"github.com/carabiner-dev/termtable"
)
func main() {
// Suppress ANSI codes so the expected output below is stable
// regardless of where `go test` is run.
color.NoColor = true
t := termtable.NewTable(termtable.WithTargetWidth(30))
h := t.AddHeader()
h.AddCell(termtable.WithContent("Name"))
h.AddCell(termtable.WithContent("Count"))
r1 := t.AddRow()
r1.AddCell(termtable.WithContent("alpha"))
r1.AddCell(termtable.WithContent("1"))
r2 := t.AddRow()
r2.AddCell(termtable.WithContent("beta"))
r2.AddCell(termtable.WithContent("2"))
fmt.Print(t.String())
}
Output: ┌──────────────┬─────────────┐ │ Name │ Count │ ├──────────────┼─────────────┤ │ alpha │ 1 │ ├──────────────┼─────────────┤ │ beta │ 2 │ └──────────────┴─────────────┘
Example (BorderStyle) ¶
Example_borderStyle shows selecting an alternate border glyph set through the table's CSS-style configuration.
package main
import (
"fmt"
"github.com/fatih/color"
"github.com/carabiner-dev/termtable"
)
func main() {
color.NoColor = true
t := termtable.NewTable(
termtable.WithTargetWidth(30),
termtable.WithTableStyle("border-style: rounded"),
)
h := t.AddHeader()
h.AddCell(termtable.WithContent("Col A"))
h.AddCell(termtable.WithContent("Col B"))
r := t.AddRow()
r.AddCell(termtable.WithContent("one"))
r.AddCell(termtable.WithContent("two"))
fmt.Print(t.String())
}
Output: ╭──────────────┬─────────────╮ │ Col A │ Col B │ ├──────────────┼─────────────┤ │ one │ two │ ╰──────────────┴─────────────╯
Example (Columns) ¶
Example_columns shows configuring column widths and alignment with the CSS-style Column.Style helper.
package main
import (
"fmt"
"github.com/fatih/color"
"github.com/carabiner-dev/termtable"
)
func main() {
color.NoColor = true
t := termtable.NewTable(termtable.WithTargetWidth(40))
t.Column(0).Style("min-width: 10")
t.Column(1).Style("width: 6; text-align: center")
t.Column(2).Style("flex: 2")
h := t.AddHeader()
h.AddCell(termtable.WithContent("Check"))
h.AddCell(termtable.WithContent("Sts"))
h.AddCell(termtable.WithContent("Message"))
r := t.AddRow()
r.AddCell(termtable.WithContent("lookup"))
r.AddCell(termtable.WithContent("OK"))
r.AddCell(termtable.WithContent("all good"))
fmt.Print(t.String())
}
Output: ┌───────────────┬────────┬─────────────┐ │ Check │ Sts │ Message │ ├───────────────┼────────┼─────────────┤ │ lookup │ OK │ all good │ └───────────────┴────────┴─────────────┘
Index ¶
- Variables
- func DisplayWidth(s string) int
- func Layout(t *Table, m *measureResult) *layoutResult
- func Measure(t *Table) *measureResult
- func MinUnbreakableWidth(s string) int
- func NaturalLines(s string) [][]GraphemeRun
- func StripANSI(s string) string
- func Wrap(lines [][]GraphemeRun, width int, wrap, trim bool, maxHeight int, ...) []string
- type Alignment
- type BorderEdge
- type BorderSet
- type Cell
- type CellOption
- func WithAlign(a Alignment) CellOption
- func WithBackgroundColor(value string) CellOption
- func WithBold() CellOption
- func WithCellBorder(e BorderEdge) CellOption
- func WithCellBorderBottom(e BorderEdge) CellOption
- func WithCellBorderLeft(e BorderEdge) CellOption
- func WithCellBorderRight(e BorderEdge) CellOption
- func WithCellBorderTop(e BorderEdge) CellOption
- func WithCellID(id string) CellOption
- func WithCellStyle(css string) CellOption
- func WithColSpan(n int) CellOption
- func WithContent(s string) CellOption
- func WithItalic() CellOption
- func WithMaxLines(n int) CellOption
- func WithMultiLine() CellOption
- func WithReader(r io.Reader) CellOption
- func WithRowSpan(n int) CellOption
- func WithSingleLine() CellOption
- func WithStrikethrough() CellOption
- func WithTextColor(value string) CellOption
- func WithTrim(enable bool) CellOption
- func WithTrimPosition(pos TrimPosition) CellOption
- func WithUnderline() CellOption
- func WithVAlign(v VerticalAlignment) CellOption
- func WithWrap(enable bool) CellOption
- type Column
- func (c *Column) Align() Alignment
- func (c *Column) HasAlign() bool
- func (c *Column) ID() string
- func (c *Column) Index() int
- func (c *Column) Max() int
- func (c *Column) Min() int
- func (c *Column) SetAlign(a Alignment) *Column
- func (c *Column) SetID(id string) *Column
- func (c *Column) SetMax(n int) *Column
- func (c *Column) SetMaxPercent(p int) *Column
- func (c *Column) SetMin(n int) *Column
- func (c *Column) SetMinPercent(p int) *Column
- func (c *Column) SetVAlign(v VerticalAlignment) *Column
- func (c *Column) SetWeight(w float64) *Column
- func (c *Column) SetWidth(n int) *Column
- func (c *Column) SetWidthPercent(p int) *Column
- func (c *Column) Style(css string) *Column
- func (c *Column) Weight() float64
- func (c *Column) Width() int
- type ContentSourceReplacedEvent
- type CrossSectionSpanEvent
- type DuplicateIDEvent
- type Element
- type EmojiWidthMode
- type Footer
- func (f *Footer) AddCell(opts ...CellOption) *Cell
- func (f *Footer) AddCellWithError(opts ...CellOption) (*Cell, error)
- func (f *Footer) AttachCell(c *Cell) *Cell
- func (f *Footer) AttachCellWithError(c *Cell) (*Cell, error)
- func (f *Footer) Cell(i int) *Cell
- func (f *Footer) Cells() []*Cell
- func (f *Footer) ID() string
- type GraphemeRun
- type Header
- func (h *Header) AddCell(opts ...CellOption) *Cell
- func (h *Header) AddCellWithError(opts ...CellOption) (*Cell, error)
- func (h *Header) AttachCell(c *Cell) *Cell
- func (h *Header) AttachCellWithError(c *Cell) (*Cell, error)
- func (h *Header) Cell(i int) *Cell
- func (h *Header) Cells() []*Cell
- func (h *Header) ID() string
- type OverwriteEvent
- type Padding
- type ReaderErrorEvent
- type Row
- type RowOption
- type SpanOverflowEvent
- type Style
- type Table
- func (t *Table) AddFooter(opts ...RowOption) *Footer
- func (t *Table) AddHeader(opts ...RowOption) *Header
- func (t *Table) AddRow(opts ...RowOption) *Row
- func (t *Table) CellAt(r, c int) *Cell
- func (t *Table) Column(i int) *Column
- func (t *Table) Columns() []*Column
- func (t *Table) Footers() []*Footer
- func (t *Table) GetElementByID(id string) Element
- func (t *Table) Headers() []*Header
- func (t *Table) ID() string
- func (t *Table) InBounds(r, c int) bool
- func (t *Table) LastRenderError() error
- func (t *Table) NumColumns() int
- func (t *Table) NumRows() int
- func (t *Table) ResolvedTargetWidth() int
- func (t *Table) Rows() []*Row
- func (t *Table) String() string
- func (t *Table) Warnings() []Warning
- func (t *Table) WriteTo(w io.Writer) (int64, error)
- type TableOption
- func WithBorder(b BorderSet) TableOption
- func WithEmojiWidth(mode EmojiWidthMode) TableOption
- func WithMaxWidth(n int) TableOption
- func WithMaxWidthPercent(p int) TableOption
- func WithMinWidth(n int) TableOption
- func WithMinWidthPercent(p int) TableOption
- func WithSpanOverwrite(enable bool) TableOption
- func WithTableID(id string) TableOption
- func WithTablePadding(p Padding) TableOption
- func WithTableStyle(css string) TableOption
- func WithTargetWidth(w int) TableOption
- func WithTargetWidthPercent(p int) TableOption
- type TrimPosition
- type VerticalAlignment
- type Warning
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrSpanConflict is returned when a cell's span would overlap a grid // slot already occupied by another cell, and the table is not // configured with WithSpanOverwrite(true). Returned errors wrap this // sentinel with positional context. ErrSpanConflict = errors.New("cell span conflicts with an occupied grid slot") // ErrReaderAlreadyConsumed is a defensive guard returned if a cell's // reader has already been consumed when a render pass attempts to // resolve it again. In normal operation the cell buffers the reader's // content on first use so this error should not surface. ErrReaderAlreadyConsumed = errors.New("cell reader already consumed") // ErrTargetTooNarrow is returned during layout when the target // width cannot give every column at least one glyph of content // space after paying for borders and padding. When minimums // merely exceed the budget (a common case on narrow terminals), // the layout silently shrinks columns and clips content — that // is not an error. ErrTargetTooNarrow = errors.New("target width too narrow for one glyph per column") // ErrCrossSectionSpan is the sentinel wrapped by CrossSectionSpanEvent // for callers that want to use errors.Is on a warning's backing // error. No API returns this error directly — rowspan clamping // is reported as a warning, not a returned error. ErrCrossSectionSpan = errors.New("row span crosses section boundary") )
Functions ¶
func DisplayWidth ¶
DisplayWidth returns the number of terminal columns s would occupy when rendered, ignoring ANSI escape sequences. Grapheme clusters are counted by their East Asian Width: most characters are 1 column, CJK and emoji are 2, combining marks are 0.
This reports the Unicode-standard width. Table rendering may pick a wider "conservative" value to survive terminals without emoji ligature support — see EmojiWidthMode.
func Layout ¶
func Layout(t *Table, m *measureResult) *layoutResult
Layout performs Pass 2 of rendering: it takes per-column widths from Measure, combines them with user-supplied column configuration (width / min / max / weight), solves a width assignment that fits within the table's target width, then wraps every cell's content to those widths and computes per-row heights.
The solver algorithm:
- Effective bounds: effMin = max(contentMin, userMin); effMax = userMax (or unbounded). Columns with SetWidth are pinned so effMin = effMax = userWidth (clamped up to contentMin on conflict, producing a best-effort overflow rather than silently cropping content).
- Feasibility: when sum(effMin) exceeds the budget, shrink every column proportionally so each still gets at least one glyph of width; cells with unbreakable content wider than their slot clip it with an ellipsis via the wrap pass. ErrTargetTooNarrow only fires when the budget cannot even give every column a single glyph (available < nCols).
- Initialize colAssigned to effMin. Water-fill the remaining budget by column weights (default 1.0), capped at effMax per column. Rounding leftovers distribute one-per-column left-first until consumed.
- Multi-span constraint pass: for each multi-span cell, borrow width from outside-span slack until the cell's min fits across its columns. Donors must remain >= effMin; receivers must remain <= effMax.
- Wrap every cell to its content width and compute row heights, bumping the tail row of rowspan cells when their wrapped output exceeds the natural sum of covered rows.
Empty tables (zero columns) return an empty result with no error.
func Measure ¶
func Measure(t *Table) *measureResult
Measure performs Pass 1 of rendering: it walks every cell in the table, consumes any WithReader content (caching the bytes), and accumulates per-column minimum and desired display widths.
Single-span cells contribute directly to the column they occupy. Multi-span cells contribute a joint constraint that the layout solver balances across the cell's column range.
Reader failures are recorded in readerErrs but do not abort measurement; affected cells are treated as empty.
func MinUnbreakableWidth ¶
MinUnbreakableWidth returns the display width of the widest run of consecutive non-whitespace grapheme clusters in s (ANSI ignored). It is the smallest column width at which s can render without hard- breaking a word. Widths follow Unicode standard semantics; see DisplayWidth.
func NaturalLines ¶
func NaturalLines(s string) [][]GraphemeRun
NaturalLines splits s on '\n' (hard breaks) and returns each line as a sequence of grapheme runs with preserved ANSI escape prefixes. A trailing '\r' on each split line is removed so CRLF inputs behave the same as LF inputs.
An input of "" produces a single empty line. An input of "\n" produces two empty lines.
func StripANSI ¶
StripANSI returns s with all ANSI escape sequences removed. Visible text bytes are preserved byte-for-byte.
func Wrap ¶
func Wrap(lines [][]GraphemeRun, width int, wrap, trim bool, maxHeight int, trimPos TrimPosition) []string
Wrap transforms a slice of natural lines (e.g. as returned by NaturalLines) into a slice of rendered lines each at most width terminal columns wide. When wrap is true, content breaks at whitespace where possible and hard-breaks on runs longer than width. When wrap is false, each natural line yields one output line, optionally truncated with "…" if trim is true and the line exceeds width.
ANSI escape state is preserved across line boundaries: the cumulative escape bytes that were active at the start of each output line are re-emitted at its head, and every line that carries any escape bytes is terminated with a reset ("\x1b[0m"). This is deliberately redundant — a reduced-fidelity policy that keeps colored content readable without full SGR state tracking.
maxHeight > 0 caps the total number of output lines. Exceeding content is dropped; when trim is also true the last kept line is truncated to end in "…". maxHeight == 0 disables the cap.
Types ¶
type Alignment ¶
type Alignment uint8
Alignment controls horizontal placement of wrapped cell content within its allotted width.
const ( // AlignLeft is the default: content hugs the left edge, right padded // to fill the column. AlignLeft Alignment = iota // AlignCenter distributes padding on both sides; any odd remainder // goes to the right side. AlignCenter // AlignRight hugs the right edge, left padded to fill the column. AlignRight )
type BorderEdge ¶ added in v1.1.0
type BorderEdge uint8
BorderEdge is the per-edge directive used by row and cell styles. The zero value (BorderEdgeAuto) means "inherit from a parent or fall back to the table default".
const ( // BorderEdgeAuto is the unset value — the edge inherits from the // enclosing row, then the table default. BorderEdgeAuto BorderEdge = iota // BorderEdgeNone suppresses the edge entirely. If every adjacent // cell at a boundary resolves to None, the line (or column seam) // is omitted from output altogether. BorderEdgeNone // BorderEdgeHidden keeps the boundary's spacing but paints it as // whitespace. The line is still emitted; the glyph is a space. BorderEdgeHidden // BorderEdgeSolid draws the boundary using the table's BorderSet // glyphs (single/double/heavy/…). "Solid" here is the CSS name // for "draw it"; the actual stroke style is the BorderSet choice. BorderEdgeSolid )
type BorderSet ¶
type BorderSet struct {
// Horizontal is the glyph repeated along horizontal border runs.
Horizontal rune
// Vertical is the glyph repeated along vertical border runs.
Vertical rune
// Joins maps arm bitmasks to the glyph drawn at corners and
// junctions.
Joins [16]rune
}
BorderSet is the glyph set used to draw table borders. The Joins array is indexed by a four-bit arm mask with bit layout (least-significant first): N=1, E=2, S=4, W=8. Only the 11 valid entries (runs, corners, T-joins, crosses) are consulted during rendering; the remaining entries are left zero.
termtable ships several ready-made sets (SingleLine, DoubleLine, HeavyLine, RoundedLine, ASCIILine, NoBorder). Callers may also construct a BorderSet manually — see the ASCIILine source for the full shape.
func ASCIILine ¶
func ASCIILine() BorderSet
ASCIILine returns an ASCII-only BorderSet (- | +) suitable for environments that cannot render Unicode box-drawing characters — logs, legacy terminals, email clients. All T-joins, corners, and crosses render as '+'.
func DefaultSingleLine
deprecated
func DefaultSingleLine() BorderSet
DefaultSingleLine is an alias for SingleLine retained for compatibility with Phase 1 code. New callers should use SingleLine directly.
Deprecated: use SingleLine.
func DoubleLine ¶
func DoubleLine() BorderSet
DoubleLine returns the Unicode double-line BorderSet: ═ ║ ╔ ╗ ╚ ╝ ╠ ╣ ╦ ╩ ╬ . Useful for emphasis or to visually distinguish outer borders from any future inner styling.
func HeavyLine ¶
func HeavyLine() BorderSet
HeavyLine returns the Unicode heavy (bold) box-drawing BorderSet: ━ ┃ ┏ ┓ ┗ ┛ ┣ ┫ ┳ ┻ ╋ .
func NoBorder ¶
func NoBorder() BorderSet
NoBorder returns a BorderSet whose glyphs are all U+0020 (space). The resulting table has no visible dividers but preserves the horizontal and vertical spacing, producing an invisibly-gridded layout. Combine with WithTablePadding(Padding{}) to collapse padding as well.
func RoundedLine ¶
func RoundedLine() BorderSet
RoundedLine returns a BorderSet using single-line runs and joins but rounded outer corners (╭ ╮ ╰ ╯). Unicode has no rounded equivalents for T-joins or the full cross, so those remain the standard single-line glyphs.
func SingleLine ¶
func SingleLine() BorderSet
SingleLine returns the Unicode single-line box-drawing BorderSet: ─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ . This is the default used by NewTable when no WithBorder option is supplied.
type Cell ¶
type Cell struct {
// contains filtered or unexported fields
}
Cell is the fundamental content-bearing element. Cells belong to at most one row and occupy a rectangle in the table's grid defined by their anchor (GridRow, GridCol) and span (ColSpan, RowSpan).
func NewCell ¶
func NewCell(opts ...CellOption) *Cell
NewCell constructs a detached cell with the given options. A detached cell has no grid position until it is passed to a row via WithCell or Row.AttachCell.
func (*Cell) Align ¶
Align returns the cell's horizontal alignment. Reads the cell's style; returns AlignLeft when no alignment has been set at the cell level (column, row, and table cascade happens at render time).
func (*Cell) Content ¶
Content returns the cell's authored string content. If the cell was configured with WithReader and the reader has not yet been consumed, the empty string is returned.
func (*Cell) GridRow ¶
GridRow returns the absolute grid row of the cell's anchor across the whole table (headers first, then body, then footers). For detached cells (constructed with NewCell but never attached to a row), returns the section-local row since no table context exists.
type CellOption ¶
type CellOption func(*Cell)
CellOption configures a *Cell during NewCell, AddCell, or AttachCell.
func WithAlign ¶
func WithAlign(a Alignment) CellOption
WithAlign sets the cell's horizontal alignment. Default is AlignLeft. Stored on the cell's Style so it participates in the table → column → row → cell cascade; cells that never call WithAlign inherit from their row, then column, then table, defaulting to AlignLeft.
func WithBackgroundColor ¶
func WithBackgroundColor(value string) CellOption
WithBackgroundColor sets the cell's background color. Accepts the same value grammar as WithTextColor.
func WithCellBorder ¶ added in v1.1.0
func WithCellBorder(e BorderEdge) CellOption
WithCellBorder sets the border directive on all four edges of the cell. Equivalent to the CSS shorthand "border: <e>" in a cell style. See BorderEdge for the semantics.
func WithCellBorderBottom ¶ added in v1.1.0
func WithCellBorderBottom(e BorderEdge) CellOption
WithCellBorderBottom sets the border directive for the cell's bottom edge.
func WithCellBorderLeft ¶ added in v1.1.0
func WithCellBorderLeft(e BorderEdge) CellOption
WithCellBorderLeft sets the border directive for the cell's left edge.
func WithCellBorderRight ¶ added in v1.1.0
func WithCellBorderRight(e BorderEdge) CellOption
WithCellBorderRight sets the border directive for the cell's right edge.
func WithCellBorderTop ¶ added in v1.1.0
func WithCellBorderTop(e BorderEdge) CellOption
WithCellBorderTop sets the border directive for the cell's top edge.
func WithCellStyle ¶
func WithCellStyle(css string) CellOption
WithCellStyle sets style properties on the cell, cascaded over the row's and table's style. See WithTableStyle for the CSS grammar. Convenience options WithTextColor, WithBackgroundColor, WithBold, WithItalic, WithUnderline, and WithStrikethrough set individual properties and may be combined with WithCellStyle.
func WithColSpan ¶
func WithColSpan(n int) CellOption
WithColSpan sets the number of columns the cell occupies. Values of n <= 0 clamp to 1 (the default) so the option never produces an invalid span.
func WithContent ¶
func WithContent(s string) CellOption
WithContent sets the cell's textual content. Honors "\n" as a hard line break; combines with automatic wrapping when the cell is wider than its assigned column width. If a reader source was previously set on the cell via WithReader, it is discarded (a ContentSourceReplacedEvent warning is emitted when the cell is attached to a row).
func WithItalic ¶
func WithItalic() CellOption
WithItalic enables the italic text attribute on the cell.
func WithMaxLines ¶
func WithMaxLines(n int) CellOption
WithMaxLines caps the cell's wrapped content to at most n lines. A value of 0 means unbounded (the default). Equivalent to CSS line-clamp: N. When the limit fires and trim is enabled, the final kept line ends in an ellipsis.
func WithMultiLine ¶
func WithMultiLine() CellOption
WithMultiLine is a shorthand for WithWrap(true). Useful when a row or column has forced single-line mode and a particular cell needs to opt back into wrapping.
func WithReader ¶
func WithReader(r io.Reader) CellOption
WithReader sets the cell's content source to an io.Reader consumed lazily on the first render pass. If a string source was previously set on the cell via WithContent, it is discarded (a ContentSourceReplacedEvent warning is emitted when the cell is attached to a row).
func WithRowSpan ¶
func WithRowSpan(n int) CellOption
WithRowSpan sets the number of rows the cell occupies within its section. Values of n <= 0 clamp to 1 (the default). Rowspans that would extend past the last row of the section are clamped by the renderer and a CrossSectionSpanEvent is emitted.
func WithSingleLine ¶
func WithSingleLine() CellOption
WithSingleLine is a shorthand for WithWrap(false). Long content renders on one line; if trim is enabled (the default) it is truncated with an ellipsis.
func WithStrikethrough ¶
func WithStrikethrough() CellOption
WithStrikethrough enables the line-through text attribute on the cell. (Not every terminal renders this; supported by most modern emulators.)
func WithTextColor ¶
func WithTextColor(value string) CellOption
WithTextColor sets the cell's foreground color. Accepts named colors, a hex string, or rgb(r,g,b). Unrecognized values are ignored.
func WithTrim ¶
func WithTrim(enable bool) CellOption
WithTrim toggles ellipsis-based trimming when a cell's content must be cut (either because single-line content overflows the column, or because a line-clamp limit was exceeded). Default is true. Equivalent to CSS text-overflow: ellipsis (trim=true) or clip (trim=false).
func WithTrimPosition ¶
func WithTrimPosition(pos TrimPosition) CellOption
WithTrimPosition controls where the ellipsis (or clip) lands when a cell's content must be truncated to fit. Default is TrimEnd — the content's prefix is kept and the marker sits at the right edge. TrimStart keeps the suffix (marker on the left), TrimMiddle keeps both ends. Equivalent to termtable's CSS extension text-overflow-position: end | start | middle.
This only affects horizontal single-line truncation (wrap=false, or the last line of a line-clamped multi-line cell when it doesn't fit its column width). Vertical dropping under line-clamp always happens from the end.
func WithUnderline ¶
func WithUnderline() CellOption
WithUnderline enables the underline text attribute on the cell.
func WithVAlign ¶
func WithVAlign(v VerticalAlignment) CellOption
WithVAlign sets the cell's vertical alignment within its row (which may be taller than the cell's own wrapped content when a neighbour wrapped to more lines). Default is VAlignTop. Like WithAlign, the value cascades via Style — cells without an explicit vertical alignment inherit row, column, and table defaults in that order.
func WithWrap ¶
func WithWrap(enable bool) CellOption
WithWrap toggles automatic word-wrapping on whitespace. Default is true (multi-line). Equivalent to setting CSS white-space: normal (wrap=true) or nowrap (wrap=false). Participates in the Style cascade — a row or column setting the same property forces every inheriting cell.
type Column ¶
type Column struct {
// contains filtered or unexported fields
}
Column is a virtual element representing a grid column. Columns are created automatically as cells populate new column positions; they may also be retrieved explicitly via Table.Column. Configuration is imperative through Set* methods:
t.Column(1).SetMax(8).SetAlign(termtable.AlignCenter)
Methods return the receiver so calls may be chained. Unset fields are inherited from content measurements or from the layout solver's defaults (weight = 1, alignment = AlignLeft).
func (*Column) Align ¶
Align returns the column's alignment override. Check HasAlign to distinguish an unset value from an explicit AlignLeft.
func (*Column) HasAlign ¶
HasAlign reports whether the column has an alignment override in force, either from Column.SetAlign or from Column.Style with text-align.
func (*Column) SetAlign ¶
SetAlign sets the default horizontal alignment for cells in this column. Cells with their own WithAlign override this; cells without an explicit alignment, and rows without a row-level text-align, inherit from the column.
func (*Column) SetID ¶
SetID assigns the column an ID that can be resolved via Table.GetElementByID. Passing an empty string unsets a previously assigned ID. If id collides with an element already using it, a DuplicateIDEvent is recorded on Table.Warnings, the old ID mapping is preserved, and the column's ID is cleared.
func (*Column) SetMax ¶
SetMax caps the column's content width at n. The solver honors the cap even when content would naturally prefer more space; content wraps to fit. A value of n <= 0 clears the cap. Also clears any percent form previously set via SetMaxPercent.
func (*Column) SetMaxPercent ¶
SetMaxPercent caps the column's content width at p percent of the table's target width. Mutually exclusive with SetMax — last setter wins.
func (*Column) SetMin ¶
SetMin sets a lower bound on the column's content width. The effective minimum is max(contentMinimum, userMinimum), so this never shrinks the column below what the content genuinely requires. A value of n <= 0 clears the override. Also clears any percent form previously set via SetMinPercent.
func (*Column) SetMinPercent ¶
SetMinPercent sets the column's min-width floor as p percent of the table's target width. Mutually exclusive with SetMin — last setter wins.
func (*Column) SetVAlign ¶
func (c *Column) SetVAlign(v VerticalAlignment) *Column
SetVAlign sets the default vertical alignment for cells in this column. Cells with their own WithVAlign override this; otherwise the column's value participates in the table → column → row → cell cascade.
func (*Column) SetWeight ¶
SetWeight sets the column's share of leftover width after minimums and explicit widths are satisfied. Columns with larger weights receive proportionally more of the remainder. Default is 1.0 (equal share). A weight of 0 prevents the column from absorbing any leftover — useful for pinning a column close to its minimum while others grow.
func (*Column) SetWidth ¶
SetWidth pins the column to exactly n display columns of content (not counting padding or borders). Overrides SetMin and SetMax for layout purposes. A value of n <= 0 clears the explicit width so the solver returns to applying min/max/weight instead. Also clears any percent form previously set via SetWidthPercent.
func (*Column) SetWidthPercent ¶
SetWidthPercent pins the column to p percent of the table's target width. Mutually exclusive with SetWidth — last setter wins. p must be in (0, 100]; out-of-range values clear the percent form.
func (*Column) Style ¶
Style parses a CSS-like declaration block and applies it to the column. Sizing properties route to the imperative Set* methods (so Column.Style and Column.SetWidth are interchangeable); style properties populate a column-level Style that cascades to every cell in the column.
Supported sizing properties:
width: N pins the column to exactly N content columns min-width: N lower bound on content width max-width: N upper bound on content width flex: N weight for distributing leftover budget text-align: L|C|R default alignment for cells in the column
Style properties are the same set accepted by WithTableStyle (color, background, font-weight, font-style, text-decoration). border-color at column level is ignored — border glyphs are table-wide and configured via WithTableStyle.
Unrecognized properties and unparseable values are silently ignored.
type ContentSourceReplacedEvent ¶
ContentSourceReplacedEvent is recorded when a cell is configured with both WithContent and WithReader. The later option wins; the earlier source is discarded. FinalSource is "content" or "reader" depending on which option was applied last.
func (ContentSourceReplacedEvent) String ¶
func (e ContentSourceReplacedEvent) String() string
type CrossSectionSpanEvent ¶
type CrossSectionSpanEvent struct {
CellID string
DeclaredSpan int
EffectiveSpan int
Section string
}
CrossSectionSpanEvent is recorded when a rowSpan declared on a cell reaches beyond the last row of its section (headers, body, or footers). Rendering clamps the effective rowspan to the section boundary; authored rowSpan on the Cell is preserved as-is.
func (CrossSectionSpanEvent) String ¶
func (e CrossSectionSpanEvent) String() string
type DuplicateIDEvent ¶
type DuplicateIDEvent struct {
ID string
Kind string // "cell", "row", "header", "footer", "column", "table"
}
DuplicateIDEvent is recorded when an element tries to register an ID already claimed by a different element. The first element keeps its registration; the second element's ID is cleared so Table.GetElementByID cannot return ambiguous results.
func (DuplicateIDEvent) String ¶
func (e DuplicateIDEvent) String() string
type Element ¶
type Element interface {
// contains filtered or unexported methods
}
Element is the sealed interface implemented by every addressable object in a table: *Table, *Header, *Footer, *Row, *Cell, *Column. Callers resolve to a concrete type with a type switch.
type EmojiWidthMode ¶
type EmojiWidthMode uint8
EmojiWidthMode controls how termtable counts display columns for grapheme clusters whose rendering varies across terminals — most commonly ZWJ emoji families like 👨👩👧, regional-indicator flag pairs like 🇯🇵, and skin-tone modifier sequences like 👋🏽.
The Unicode standard considers each of these a single grapheme cluster and defines a "collapsed" display width (typically 2). In practice the rendering width depends on whether the user's font has a glyph for the joined form. When the font doesn't, terminals fall back to rendering each constituent codepoint as its own glyph — which can double, triple, or sextuple the visual width and misalign the whole table.
const ( // EmojiWidthAuto (the zero value) resolves to EmojiWidthGrapheme // when termtable detects a terminal known to render composite // emoji correctly, and EmojiWidthConservative everywhere else. // The TERMTABLE_EMOJI_WIDTH environment variable overrides the // detection. EmojiWidthAuto EmojiWidthMode = iota // EmojiWidthConservative counts every visible codepoint in a // grapheme cluster at its standalone width, skipping zero-width // composers (ZWJ, variation selectors, combining marks). The // result is an upper bound on the rendered width — tables stay // aligned even on terminals that don't implement emoji // ligatures. EmojiWidthConservative // EmojiWidthGrapheme uses the Unicode-standard cluster width // reported by uniseg. Produces tight layouts on modern // terminals; may misalign rows on ones that fall back to // rendering composite emoji piecewise. EmojiWidthGrapheme )
func (EmojiWidthMode) String ¶
func (m EmojiWidthMode) String() string
type Footer ¶
type Footer struct {
// contains filtered or unexported fields
}
Footer is a footer row. Footers are rendered below body rows.
func (*Footer) AddCell ¶
func (f *Footer) AddCell(opts ...CellOption) *Cell
AddCell constructs a cell from the given options and attaches it to the footer row. See Row.AddCell for the panic / error contract.
func (*Footer) AddCellWithError ¶
func (f *Footer) AddCellWithError(opts ...CellOption) (*Cell, error)
AddCellWithError is the error-returning counterpart to AddCell.
func (*Footer) AttachCell ¶
AttachCell attaches a previously constructed cell to the footer row.
func (*Footer) AttachCellWithError ¶
AttachCellWithError is the error-returning counterpart to AttachCell.
type GraphemeRun ¶
GraphemeRun carries a single grapheme cluster's text, its display width, and any ANSI escape bytes that immediately preceded it in the source (so a wrap-aware renderer can re-emit them). Cluster text never contains ANSI escapes and never contains '\n' — NaturalLines splits hard breaks before segmentation.
type Header ¶
type Header struct {
// contains filtered or unexported fields
}
Header is a header row. Headers are rendered above body rows and may carry styling or layout differences once styling support lands.
func (*Header) AddCell ¶
func (h *Header) AddCell(opts ...CellOption) *Cell
AddCell constructs a cell from the given options and attaches it to the header row. See Row.AddCell for the panic / error contract.
func (*Header) AddCellWithError ¶
func (h *Header) AddCellWithError(opts ...CellOption) (*Cell, error)
AddCellWithError is the error-returning counterpart to AddCell.
func (*Header) AttachCell ¶
AttachCell attaches a previously constructed cell to the header row.
func (*Header) AttachCellWithError ¶
AttachCellWithError is the error-returning counterpart to AttachCell.
type OverwriteEvent ¶
type OverwriteEvent struct {
// DroppedID is set when an existing cell was entirely covered by the
// new cell and removed from its row.
DroppedID string
// TruncatedID is set when an existing cell's span was reduced to
// avoid the new cell. NewColSpan / NewRowSpan describe the resulting
// span.
TruncatedID string
NewColSpan int
NewRowSpan int
// At is the grid anchor of the overwriting cell.
At [2]int
}
OverwriteEvent is recorded when WithSpanOverwrite(true) causes a later cell's span to drop or truncate an earlier cell.
func (OverwriteEvent) String ¶
func (e OverwriteEvent) String() string
type Padding ¶
type Padding struct {
Left, Right, Top, Bottom int
}
Padding controls the empty space reserved inside a cell around its content. Values are measured in terminal columns (left/right) and rows (top/bottom).
func DefaultPadding ¶
func DefaultPadding() Padding
DefaultPadding is the padding applied table-wide when WithTablePadding is not supplied: one column of left/right breathing room, no vertical padding.
type ReaderErrorEvent ¶
ReaderErrorEvent is recorded when Measure's lazy reader consumption fails. The affected cell renders as empty; the error is preserved for inspection via Table.Warnings.
func (ReaderErrorEvent) String ¶
func (e ReaderErrorEvent) String() string
type Row ¶
type Row struct {
// contains filtered or unexported fields
}
Row is a row in the table body.
func (*Row) AddCell ¶
func (r *Row) AddCell(opts ...CellOption) *Cell
AddCell constructs a cell from the given options and attaches it to the row. Under the default table configuration (WithSpanOverwrite(true)) this call cannot fail — span conflicts absorb existing cells and are recorded as OverwriteEvent warnings on Table.Warnings. Under WithSpanOverwrite(false), a conflict panics; use AddCellWithError for explicit error handling instead.
func (*Row) AddCellWithError ¶
func (r *Row) AddCellWithError(opts ...CellOption) (*Cell, error)
AddCellWithError is the error-returning counterpart to AddCell. Useful when the table runs in strict mode (WithSpanOverwrite(false)) and callers want to recover from span conflicts without a panic.
func (*Row) AttachCell ¶
AttachCell attaches a previously constructed cell to the row. Cells already belonging to another row are migrated — their previous attachment is cleaned up before the new one is applied. The panic / WithError contract mirrors AddCell.
func (*Row) AttachCellWithError ¶
AttachCellWithError is the error-returning counterpart to AttachCell.
func (*Row) Cell ¶
Cell returns the i-th cell declared in the row (logical coordinate). Returns nil if i is out of range.
type RowOption ¶
type RowOption func(*rowBody)
RowOption configures a row being added via AddRow, AddHeader, or AddFooter. Internally it operates on the shared rowBody; users never interact with rowBody directly.
func WithCell ¶
WithCell queues a previously constructed cell for adoption into the row. Multiple WithCell options may be supplied; they are attached in the order given after the row itself has been inserted.
func WithRowBorder ¶ added in v1.1.0
func WithRowBorder(e BorderEdge) RowOption
WithRowBorder sets the border directive for both the top and the bottom edges of a row. Equivalent to the CSS shorthand "border: <e>" on the row's style. See BorderEdge for the semantics.
func WithRowBorderBottom ¶ added in v1.1.0
func WithRowBorderBottom(e BorderEdge) RowOption
WithRowBorderBottom sets the border directive for the row's bottom edge. Use this to underline the header row in a borderless table.
func WithRowBorderTop ¶ added in v1.1.0
func WithRowBorderTop(e BorderEdge) RowOption
WithRowBorderTop sets the border directive for the row's top edge.
func WithRowStyle ¶
WithRowStyle sets a style that applies to every cell in this row unless the cell overrides the corresponding properties. See WithTableStyle for the supported CSS property grammar.
type SpanOverflowEvent ¶
SpanOverflowEvent is recorded when a column-span cell cannot fit within the column budget its span covers, even after layout borrow/repay. Rendering continues but the cell overflows its allotted width.
func (SpanOverflowEvent) String ¶
func (e SpanOverflowEvent) String() string
type Style ¶
type Style struct {
// contains filtered or unexported fields
}
Style collects the visual formatting attributes that can be applied to a Table, row, or Cell. Each field is optional; unset fields are inherited from an enclosing element via style merge. Display-width math is unaffected by styling — attributes are emitted as ANSI escape sequences that contribute zero terminal columns.
When cell content already contains its own ANSI escape sequences and a background is also set via this Style, the content's inner reset sequence will drop the outer background. Use one or the other to avoid artifacts.
type Table ¶
type Table struct {
// contains filtered or unexported fields
}
Table is the root element. It owns the column list, the header / body / footer row collections, the ID registry, and the occupancy grids used for span tracking.
func NewTable ¶
func NewTable(opts ...TableOption) *Table
NewTable constructs an empty table configured with the given options.
func (*Table) AddFooter ¶
AddFooter appends a new footer row and returns it. See AddHeader for the panic contract.
func (*Table) AddHeader ¶
AddHeader appends a new header row and returns it. Under the default table configuration this call cannot fail. If strict mode is enabled via WithSpanOverwrite(false) and a pre-built cell supplied via WithCell produces a span conflict, AddHeader panics — use AttachCellWithError on the returned row for explicit error handling instead.
func (*Table) AddRow ¶
AddRow appends a new body row and returns it. See AddHeader for the panic contract.
func (*Table) CellAt ¶
CellAt returns the cell covering absolute grid coordinate (r, c). r is interpreted as: rows [0, len(Headers)) index into headers; rows [len(Headers), len(Headers)+len(Rows)) index into the body; the remainder index into footers. Returns nil if (r, c) is out of bounds or the slot is unoccupied.
func (*Table) Column ¶
Column returns the virtual Column element at index i, creating it (and any earlier missing columns) on demand.
func (*Table) GetElementByID ¶
GetElementByID looks up any named element in the table: the table itself, a column, a header/body/footer row, or a cell. Returns nil if no element with that ID is registered.
func (*Table) InBounds ¶
InBounds reports whether (r, c) is a valid grid coordinate within the table's current dimensions.
func (*Table) LastRenderError ¶
LastRenderError returns the error from the most recent call to String, or nil if the last call succeeded. The value is overwritten on every String call (so calling String a second time after a successful render will clear a previous error). WriteTo callers do not need this accessor — they receive the error directly.
func (*Table) NumColumns ¶
NumColumns returns the number of columns currently present in the table. Columns grow as cells populate new positions.
func (*Table) NumRows ¶
NumRows returns the total number of rows across all sections (headers + body + footers).
func (*Table) ResolvedTargetWidth ¶
ResolvedTargetWidth returns the target width the table will use for layout. The resolver follows CSS-style semantics:
- width (WithTargetWidth / WithTargetWidthPercent / "width: …" in CSS / COLUMNS env) pins the table to an absolute or relative size.
- min-width (WithMinWidth / "min-width: …") is the floor.
- max-width (WithMaxWidth / "max-width: …") is the ceiling.
- When no explicit width is set, the default is min-width:80 and max-width:90 % — the table lives at at least 80 columns and grows to fill up to 90 % of the attached screen as content demands.
Whatever value the cascade produces is clamped to the attached terminal's width when one is detected (stdout or stderr, via golang.org/x/term), so output never exceeds the physical screen.
ResolvedTargetWidth is called without a measurement, so its "natural" baseline is the table's max-width. Layout uses the same resolver internally but with the content-derived natural width, giving the table exactly enough columns to hold its content (clamped to the bounds).
func (*Table) String ¶
String renders the table to a string. Layout errors (e.g., a target width too narrow to fit content minimums) do not prevent best-effort output — the renderer falls back to the minimum column widths and produces a possibly-overflowing table. Inspect Table.Warnings() to see non-fatal events collected during rendering; use WriteTo for access to the underlying error.
func (*Table) Warnings ¶
Warnings returns the concatenation of authoring-time events (span overwrites, ID reassignments) and the events produced by the most recent render pass (span overflow, cross-section spans, reader errors). Calling String or WriteTo multiple times does not duplicate render-time events — each render overwrites them.
func (*Table) WriteTo ¶
WriteTo renders the table to w. Returns the number of bytes written and either a write error from w or a layout error (e.g., ErrTargetTooNarrow) — write errors take precedence when both occur.
When the attached stdout or stderr is a terminal, every output line is clipped to that terminal's width (with an ellipsis marking the cut). Writes to pipes, files, or other non-interactive sinks pass through unclipped.
type TableOption ¶
type TableOption func(*Table)
TableOption configures a *Table.
func WithBorder ¶
func WithBorder(b BorderSet) TableOption
WithBorder replaces the table's border glyph set. Defaults to DefaultSingleLine.
func WithEmojiWidth ¶
func WithEmojiWidth(mode EmojiWidthMode) TableOption
WithEmojiWidth pins the emoji-width counting mode for the table. The default (EmojiWidthAuto) picks EmojiWidthConservative unless termtable detects a terminal known to render composite emoji correctly, in which case it picks EmojiWidthGrapheme. The TERMTABLE_EMOJI_WIDTH environment variable overrides the detection; an explicit non-auto value here overrides both.
See EmojiWidthMode for the semantics.
func WithMaxWidth ¶
func WithMaxWidth(n int) TableOption
WithMaxWidth sets the CSS-style max-width ceiling for the table. When no explicit WithTargetWidth is supplied, the layout size still never exceeds n columns. Non-positive n is ignored. Mutually exclusive with WithMaxWidthPercent — last setter wins.
func WithMaxWidthPercent ¶
func WithMaxWidthPercent(p int) TableOption
WithMaxWidthPercent sets the max-width ceiling as a percentage of the attached screen width. Mutually exclusive with WithMaxWidth.
func WithMinWidth ¶
func WithMinWidth(n int) TableOption
WithMinWidth sets the CSS-style min-width floor for the table. When no explicit WithTargetWidth is supplied, the layout size still never drops below n columns. Non-positive n is ignored. Mutually exclusive with WithMinWidthPercent — last setter wins.
func WithMinWidthPercent ¶
func WithMinWidthPercent(p int) TableOption
WithMinWidthPercent sets the min-width floor as a percentage of the attached screen width. Mutually exclusive with WithMinWidth.
func WithSpanOverwrite ¶
func WithSpanOverwrite(enable bool) TableOption
WithSpanOverwrite controls span-conflict behavior. The default (true) lets later cells overwrite earlier spans: fully-covered cells are dropped and partially overlapped cells are truncated, with an OverwriteEvent recorded on Table.Warnings for each. This means AddCell and AttachCell never fail for layout reasons under default settings — conflicts are absorbed.
Passing false switches to strict mode: a colliding span is an author error. AddCell / AttachCell panic with a wrapped ErrSpanConflict, and the new cell is not placed. Callers who want explicit error handling instead of panics can use the AddCellWithError / AttachCellWithError methods on the row.
func WithTableID ¶
func WithTableID(id string) TableOption
WithTableID assigns a unique ID to the table itself, retrievable via Table.GetElementByID.
func WithTablePadding ¶
func WithTablePadding(p Padding) TableOption
WithTablePadding overrides the table-wide cell padding. Padding is uniform across every cell — configuring it per-cell would let columns misalign. Default is DefaultPadding() (one column of horizontal padding, no vertical).
func WithTableStyle ¶
func WithTableStyle(css string) TableOption
WithTableStyle sets table-wide style defaults via a CSS-like declaration block, e.g.
WithTableStyle("color: white; background: blue; border-style: double; border-color: cyan")
Supported properties:
- color, background (background-color), border-color: color values as named colors ("red", "bright-cyan"), hex ("#rrggbb"), or rgb(r,g,b).
- font-weight: bold | normal
- font-style: italic | normal
- text-decoration: underline | line-through | none
- border-style: single | double | heavy | rounded | ascii — selects the BorderSet used for the table, equivalent to calling WithBorder with the corresponding constructor (SingleLine, DoubleLine, HeavyLine, RoundedLine, ASCIILine).
- border-style: hidden — uses space glyphs for every boundary, so borders preserve spacing but render invisibly. Equivalent to WithBorder(NoBorder()).
- border-style: none — sets the table's default edge directive to "no border". Unlike "hidden", boundaries with no cell opting in to a visible border are dropped entirely from output. Combine with per-row or per-cell "border-bottom: solid" etc. to selectively re-enable a single edge.
- border / border-top / border-right / border-bottom / border-left: per-edge directive accepting "none", "hidden", or "solid". Works on table (default for all rows/cells), row, and cell styles.
- width: N | N% — target layout width, equivalent to WithTargetWidth / WithTargetWidthPercent. The last width declaration on the table wins.
Unknown properties and unrecognized values are silently ignored.
func WithTargetWidth ¶
func WithTargetWidth(w int) TableOption
WithTargetWidth pins the layout target width to w terminal columns. When unset, the table reads the COLUMNS environment variable, then falls back to the default rule — fill 90% of the attached screen with 80 as the floor. In every case the resolved value is clamped to the attached terminal's width so output never overflows the screen; pipes and other non-interactive sinks leave the value uncapped.
Mutually exclusive with WithTargetWidthPercent — last setter wins.
func WithTargetWidthPercent ¶
func WithTargetWidthPercent(p int) TableOption
WithTargetWidthPercent pins the layout target width to p percent of the terminal width. The percentage base is the attached terminal when one is detected, else the COLUMNS environment variable, else the 80-column default. Non-positive p is ignored.
The resulting width is always clamped to the attached terminal so output never overflows the screen — percentages above 100 therefore behave the same as 100 on a TTY, but can produce genuinely wider tables when writing to a pipe.
Mutually exclusive with WithTargetWidth — last setter wins.
type TrimPosition ¶
type TrimPosition uint8
TrimPosition controls where an ellipsis (or clip) lands when a cell's content needs to be truncated horizontally. It is only consulted when content is actually being truncated — cells that fit leave their content unchanged regardless of this setting.
const ( // TrimEnd (the default) keeps the content's prefix and places // the truncation marker at the right — e.g. "www.exampl…". TrimEnd TrimPosition = iota // TrimStart keeps the content's suffix and places the marker // at the left — e.g. "…/page.html". TrimStart // TrimMiddle keeps both ends and places the marker between — // e.g. "www.exam…/page.html". TrimMiddle )
func (TrimPosition) String ¶
func (p TrimPosition) String() string
type VerticalAlignment ¶
type VerticalAlignment uint8
VerticalAlignment controls where a cell's wrapped content sits within a row (or rowspan block) that is taller than the content itself — a common situation when one cell wraps to multiple lines and its neighbours have only one.
const ( // VAlignTop is the default: content hugs the top of the cell, // any extra vertical space sits below. VAlignTop VerticalAlignment = iota // VAlignMiddle distributes extra space evenly above and below // the content; any odd remainder goes to the bottom. VAlignMiddle // VAlignBottom pushes content to the bottom; any extra vertical // space sits above. VAlignBottom )
func (VerticalAlignment) String ¶
func (v VerticalAlignment) String() string