TermMosaic v1.0.0

v1.0.0 is the first release that makes a stability promise: the public API freezes here and Semantic Versioning applies in earnest — with one documented exception: widgets/widgettest is excluded from that promise, because it must evolve with the framework. Read the limitations before you rely on anything here.

Widgets widgets/split

Split

A container that divides its rectangle among focusable panes.

A container that divides its rectangle among focusable panes.

Package widgets/split. API reference: pkg.go.dev/widgets/split.

Split arranges panes along one axis.

Rendered output

Split rendered by cmd/capture through widgettest.Capture: colour at 3 widths (40, 80, 120 columns), and the exact plain-text cell grid at the widest of them.

MinSize() returns 3 × 1 cells. That is the whole widget including its own chrome, and it is the widget's assertion about itself — below it, Draw clips rather than blanks, and what to do about that is the application's decision.

40 columns × 11 rows

╭ modules ────────────╮ ╭ packages ────╮
│  module        size │ │   input      │
│  buffer       8.2 … │ │   layout     │
│  geometry     3.1 … │ │>  render    █│
│› headless     11.4… │ │   term      █│
│  layout       6.7 … │ │   virtual   █│
│  render       14.9… │ │   widgets/b…█│
│  virtual      5.0 … │ │   widgets/b…█│
│                     │ │   widgets/d… │
│                     │ │   widgets/f… │
╰─────────────────────╯ ╰──────────────╯

80 columns × 11 rows

╭ modules ────────────────────────────────────╮ ╭ packages ────────────────────╮
│  module           size state                │ │   input                      │
│  buffer         8.2 kB stable               │ │   layout                     │
│  geometry       3.1 kB stable               │ │>  render                    █│
│› headless      11.4 kB beta                 │ │   term                      █│
│  layout         6.7 kB stable               │ │   virtual                   █│
│  render        14.9 kB beta                 │ │   widgets/basic             █│
│  virtual        5.0 kB alpha                │ │   widgets/block             █│
│                                             │ │   widgets/data               │
│                                             │ │   widgets/form               │
╰─────────────────────────────────────────────╯ ╰──────────────────────────────╯

120 columns × 11 rows

╭ modules ────────────────────────────────────────────────────────────╮ ╭ packages ────────────────────────────────────╮
│  module           size state                                        │ │   input                                      │
│  buffer         8.2 kB stable                                       │ │   layout                                     │
│  geometry       3.1 kB stable                                       │ │>  render                                    █│
│› headless      11.4 kB beta                                         │ │   term                                      █│
│  layout         6.7 kB stable                                       │ │   virtual                                   █│
│  render        14.9 kB beta                                         │ │   widgets/basic                             █│
│  virtual        5.0 kB alpha                                        │ │   widgets/block                             █│
│                                                                     │ │   widgets/data                               │
│                                                                     │ │   widgets/form                               │
╰─────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────╯

Plain text — the exact cell grid, trimmed per line. This is the honest form: Braille and block-element glyphs can take a different advance width in a web font than a terminal gives them, which breaks the alignment they depend on.

# Split — A container that divides its rectangle among focusable panes.
# package: widgets/split
# constructor: split.New(d layout.Direction, panes ...termmosaic.Widget) *split.Split
# 120 columns x 11 rows, rendered through widgettest.Capture

╭ modules ────────────────────────────────────────────────────────────╮ ╭ packages ────────────────────────────────────╮
│  module           size state                                        │ │   input                                      │
│  buffer         8.2 kB stable                                       │ │   layout                                     │
│  geometry       3.1 kB stable                                       │ │>  render                                    █│
│› headless      11.4 kB beta                                         │ │   term                                      █│
│  layout         6.7 kB stable                                       │ │   virtual                                   █│
│  render        14.9 kB beta                                         │ │   widgets/basic                             █│
│  virtual        5.0 kB alpha                                        │ │   widgets/block                             █│
│                                                                     │ │   widgets/data                               │
│                                                                     │ │   widgets/form                               │
╰─────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────╯

What you are looking at. This is the cell grid the renderer produced, in a web page. It is not a screenshot of a terminal, and it cannot show interaction, resize or animation — a capture is one settled frame after N frames of settling. Braille and Block Elements can take a different advance width in your browser's font than in a terminal, so if a gauge or a sparkline above looks wrong, the plain-text form below it is the exact output and the colour one is the decoration. The colour frames are at 40, 80, 120 columns; the plain-text frame is at 120. More on this.

About this capture. Focus moves between panes with Tab and the focused pane is the one that takes keys. A still frame cannot show that; the marker in the second pane’s title is where the capture has to stop.

Package context

Package split provides Split, the pane composer: it solves one layout and hands each pane a rectangle.

It exists because a terminal application that wants two panes needs three things and none of them is interesting: solve a constraint list, hand the results out as rectangles, and let the user move the divider. The first is layout’s job and Split calls it rather than reimplementing it — ADR 0004 chose a closed, predictable constraint set precisely so that exactly one solver exists, and a second arithmetic pass inside a widget is how two solvers start to disagree about overflow.

A widget receives its rectangle through Bounds, and nothing in the framework hands it one: there is no Resize method to push down the tree (ADR 0007 rejects it), so a container must be able to SET bounds. Hence Bounded, which a pane implements by having a SetBounds method — block.Block, basic.Text and basic.Paragraph all do. A pane that does not implement it is still drawn, with whatever rectangle the application gave it; Split never invents one.

Constructing it

split.New(d layout.Direction, panes ...termmosaic.Widget) *split.Split

Split solves one constraint list and hands each pane a rectangle. It is the only widget that introduces an interface of its own — Bounded — for a pane that wants to say what it needs.

  • Direction — layout.Direction — DirectionRow or DirectionColumn. One axis only; nesting a Split inside a Split is how you get a grid.
  • Spacing — int, via SetSpacing(n) — cells between panes, removed from the pane rects rather than overlapped. It was an exported field until v0.5.0; assigning it left the cached solve stale and the panes kept their old sizes until the next resize. Read it back with Spacing().
  • Background — buffer.Style — the gap colour. Visible wherever Spacing is non-zero.

When not to use it

Do not use it for a grid. Split arranges panes along one axis. Two or three panes is the shape it solves; a grid is a Split inside a Split, and if you find yourself writing that, the constraint list is doing more work than the pane model is.

Do not use it as a border. It draws no frame. Each pane that needs one composes a Block; Split will not do it for you, and it deliberately does not know about Block.

Do not use it to get equal panes for free. Split calls the layout solver, and layout.Fill is order-insensitive — a deliberate divergence from tmux, pinned by a test. If you want fixed ratios, say so with layout.Ratio or layout.Percentage; do not rely on declaration order to break a tie.

Do not use it when one pane should be able to vanish. Panes are given at construction as a variadic. Hiding a pane means rebuilding the Split, which loses the pane’s state unless the application holds it — which is a fine design, but it is the application’s job, not the framework’s.

Do not expect it to solve a focus problem you have not described. Tab moves between focusable panes and the focused pane is the one that takes keys. That is the whole focus behaviour; a Split does not know which of your panes is a form and will not validate it.

Instead: layout.Solve directly for a fixed arrangement, Composition for the patterns that come up.