layout

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package layout holds the pure, Model-independent scroll/viewport geometry the TUI views share: given a cursor, a window height and a per-row height function, it computes which row to anchor at the top and which logical item sits at a visual row. Keeping it dependency-free (no Model, no lipgloss) makes the variable-height scroll math unit-testable in isolation and is the first seam carved out of the large ui package.

Index

Constants

View Source
const RowMemoCap = 4096

RowMemoCap bounds each RowMemo so a long scroll through a huge table (millions of symbol/string rows) can't grow the cache for the whole session. When full the memo is flushed wholesale — cheaper than per-entry eviction, and the visible window repopulates in one render pass.

View Source
const TreeIndent = 2

TreeIndent is the per-depth indentation of a tree row.

Variables

This section is empty.

Functions

func ActiveSortHeaderLabel

func ActiveSortHeaderLabel(label string, width int, desc bool) string

ActiveSortHeaderLabel appends the direction triangle to an always-active header label (a single-column table with no per-column sort key), right-padded to width.

func AppendAddr

func AppendAddr(dst []byte, addr uint64, digits int) []byte

AppendAddr writes "0x" + addr, zero-padded to digits hex chars, into dst — what fmt.Sprintf("0x%0*x", digits, addr) produces, without fmt's boxing and its intermediate string. The hex and disasm views format an address for every visible row, on every frame.

func AppendRenderedRows

func AppendRenderedRows(lines *[]string, line string, w int, wrap bool, limit int) bool

AppendRenderedRows appends rendered rows until limit is reached.

func AppendRenderedRowsIndented

func AppendRenderedRowsIndented(lines *[]string, line string, w int, wrap bool, indent int, limit int) bool

AppendRenderedRowsIndented appends indented rendered rows until limit is reached.

func ApplySortHeaderClick

func ApplySortHeaderClick[T comparable](active *T, desc *bool, sort T) bool

ApplySortHeaderClick mirrors htop-style header sorting: choosing a different column sorts it ascending; clicking the active column reverses the direction. It returns whether the sort *field* changed (vs. only the direction).

func CarryWrapStyle

func CarryWrapStyle(rows []string)

CarryWrapStyle makes each wrapped row self-contained. A styled span (e.g. a coloured symbol or pointer annotation) split across a line break otherwise loses its colour: the cell renderer resets the pen at every line, so a continuation row that begins mid-span renders with the default colour. This re-emits the SGR active at each break — after any leading indent, so the hanging indent stays unstyled — and closes every row that ends mid-span.

func Clamp

func Clamp(v, lo, hi int) int

Clamp constrains v to the inclusive range [lo, hi].

func ContainsFold

func ContainsFold(s, substr string) bool

ContainsFold reports whether s contains substr, ASCII case-insensitively, without the allocation that strings.Contains(strings.ToLower(s), substr) costs per call. The list filters run this over every row on every keystroke, so the allocation matters on large tables (the Strings/Symbols views). substr is expected already-lowercased by the caller; non-ASCII bytes compare exactly, which is fine for the identifier/path/section text these filters match.

func ContainsFoldBytes

func ContainsFoldBytes(b []byte, substr string) bool

ContainsFoldBytes is ContainsFold over a byte slice (substr already lowercased), so the strings filter can scan zero-copy slices into the file image without allocating a string per entry.

func CtrlKeys

func CtrlKeys(keys ...string) string

CtrlKeys renders a Ctrl-chord list as "^t" / "^t/^f": each key gets a caret, joined with "/". Compact and identical on every platform.

func CycleStringList

func CycleStringList(on *bool, cur *string, list []string)

CycleStringList advances a facet filter through list: off → first → … → last → off. *on tracks whether the filter is active, *cur its current value.

func EachInternal

func EachInternal(nodes []*TreeNode, fn func(path string))

EachInternal calls fn for every internal node's path (used by "collapse all").

func FitANSIWidth

func FitANSIWidth(s string, w int) string

FitANSIWidth keeps a styled string intact when it fits within w visible columns, and falls back to a plain truncation when it doesn't — so a single over-long source line can't break the side-by-side layout while normal-width lines retain their syntax colours.

func HardWrapLongRows

func HardWrapLongRows(rows []string, w int) []string

HardWrapLongRows splits any row still wider than w columns.

func HitSortableHeader

func HitSortableHeader[T comparable](cols []SortableHeaderCol[T], x int) (T, bool)

HitSortableHeader returns the sort key of the column containing x, if any.

func IndentContinuationRows

func IndentContinuationRows(rows []string, w int, indent int) []string

IndentContinuationRows applies a hanging indent after the first row.

func LastOpenSGR

func LastOpenSGR(open, row string) string

LastOpenSGR returns the SGR sequence still in effect at the end of row, given the sequence already open when the row began. A reset ("\x1b[0m" / "\x1b[m") clears it; any other SGR replaces it. Styles in this UI are emitted as one complete SGR per span (lipgloss does this), so tracking the last sequence is sufficient.

func MaxViewportTop

func MaxViewportTop(n, visible int, rowHeight func(int) int) int

MaxViewportTop returns the latest top row that can fill the viewport.

func NavKey(cur *int, n, page int, key string) bool

NavKey applies a standard list-navigation key (up/down/k/j, pgup/pgdown, home/end/G) to a cursor over n items, paging by page rows. It returns true when it consumed the key (so the caller can stop), and always leaves the cursor in [0, n-1] — or 0 for an empty list. `[`/`]` page up/down here too: only the list views route through NavKey (disasm/hex/source-open handle those keys themselves), so paging with them is free in exactly the list views.

func Overlay

func Overlay(bg, fg string, x, y int) string

Overlay places fg over bg at column x, row y. Both are pre-rendered strings. It is ANSI- and width-aware: the background to the left and right of the modal keeps its colours and lines up correctly even when those lines contain styled or multi-byte content (e.g. the disasm source-pane border).

func PadBody

func PadBody(s string, w, h int) string

PadBody clamps and pads a rendered body to exactly w by h cells.

func PadBodyRows

func PadBodyRows(lines []string, w, h int) string

PadBodyRows clamps and pads pre-split rows to exactly w by h cells.

func PadRight

func PadRight(s string, w int) string

PadRight pads s to exactly w visible columns, truncating when it's longer so an over-wide line (e.g. a long demangled symbol) can't wrap and shove the layout down behind the status line.

func PadVisual

func PadVisual(s string, w int) string

PadVisual right-pads s to a minimum display width of w columns (ANSI- and width-aware), leaving a string that's already wider untouched. This is the cell-accurate equivalent of fmt's "%-*s", which counts runes/bytes rather than terminal cells and so misaligns columns containing wide or styled text.

func PageStep

func PageStep(from, n, visibleLines int, rowHeight func(int) int) int

PageStep returns how many list items make up one screen "page": the number of items whose stacked rendered heights fill visibleLines, starting at item from (the current top of the viewport), clamped to at least 1. rowHeight gives each item's line count (1 for single-line rows). Paging by this many items advances the view by about one screen instead of overshooting when rows carry chrome (header/filter lines reduce visibleLines) or wrap onto multiple lines.

func RenderLineRowsIndented

func RenderLineRowsIndented(line string, w int, wrap bool, indent int) []string

RenderLineRowsIndented renders a logical line with optional hanging indent.

func RenderStyle

func RenderStyle(s string, w int, st lipgloss.Style) string

RenderStyle is Fill with the painter built on the spot, for callers that do not hold one. It skips NewPainter's probe render: Fill never consults `simple`, so paying for it here would make this path cost two lipgloss renders instead of the one it always cost.

func ReverseInts

func ReverseInts(s []int)

ReverseInts reverses s in place — the cheap path for flipping a list already in its natural ascending order to descending.

func SegPath

func SegPath(s string) int

SegPath splits on "/" (filesystem paths and library install paths).

func SortDirectionLabel

func SortDirectionLabel(desc bool) string

SortDirectionLabel is the human-readable name of a sort direction.

func SortHeaderLabel

func SortHeaderLabel[T comparable](label string, width int, sort, active T, desc bool) string

SortHeaderLabel returns label with a direction triangle appended when it is the active sort column, right-padded to width; otherwise the plain label.

func StylePrefix

func StylePrefix(st lipgloss.Style) string

StylePrefix returns the leading SGR sequence a lipgloss style emits (its "open" codes), or "" when the style adds no styling.

It costs a full lipgloss render, so anything on a render path should build a Painter once instead of calling this per frame.

func TrailingSortHeaderLabel

func TrailingSortHeaderLabel[T comparable](label string, sort, active T, desc bool) string

TrailingSortHeaderLabel is SortHeaderLabel for a trailing column that isn't padded to a fixed width (the triangle follows immediately).

func TreeCollapseOne

func TreeCollapseOne(rows []TreeRow, cur *int, collapsed map[string]bool) bool

TreeCollapseOne collapses the node at *cur, or — when it is a leaf or already collapsed — the nearest ancestor group above it (moving the cursor onto it).

func TreeExpandOne

func TreeExpandOne(rows []TreeRow, cur *int, collapsed map[string]bool) bool

TreeExpandOne expands the collapsed node at *cur (one level) and moves the cursor onto the first item of the now-revealed branch. Returns whether anything changed (the caller then rebuilds the flattened rows).

func TreeToggleSubtree

func TreeToggleSubtree(rows []TreeRow, cur int, collapsed map[string]bool) bool

TreeToggleSubtree expands or collapses the whole subtree under the node at cur: collapse-all-below when it is currently expanded, expand-all-below when not.

func TruncateANSI

func TruncateANSI(s string, w int) string

TruncateANSI naively truncates while keeping the trailing SGR reset.

func TruncateMiddle

func TruncateMiddle(s string, n int) string

TruncateMiddle keeps both ends of a string visible within n columns.

func ViewportTop

func ViewportTop(top, n, visible int, rowHeight func(int) int) int

ViewportTop clamps a detached viewport top for variable-height rows.

func VisualItemAtRow

func VisualItemAtRow(top, n, row int, rowHeight func(int) int) (int, bool)

VisualItemAtRow maps a visual row offset to a logical item index.

func VisualTop

func VisualTop(cur, top, n, visible int, rowHeight func(int) int) int

VisualTop returns the nearest top that keeps cur visible.

func WrapRows

func WrapRows(s string, w int, cutset string) []string

WrapRows splits s into width-limited rows using ansi.Wrap.

Types

type Painter

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

A Painter applies a style's colour, holding the style's SGR sequence rather than re-deriving it.

Deriving it means rendering a sample string through lipgloss, which serialises the colours into ANSI and allocates — and the callers here (the table header, the panel background behind every frame, an address on every disasm row) run on every redraw. The zero Painter adds nothing and passes text through unchanged.

func NewPainter

func NewPainter(st lipgloss.Style) Painter

NewPainter captures st's SGR sequence. Build it when the theme is built, not when a frame is drawn.

func (Painter) Fill

func (p Painter) Fill(s string, w int) string

Fill applies the painter's colour across every line of s, padding each to w columns and re-arming the colour after the per-span resets inside it, so the fill survives the styles nested in the line. A painter that adds nothing returns s unchanged.

func (Painter) Text

func (p Painter) Text(s string) string

Text applies the painter's colour to a single run of *plain* text — byte for byte what a lipgloss render of the same style produces, but without the render for the styles that allow it.

Only for text that carries no styling of its own: an embedded reset would end the colour early, where lipgloss would re-arm it. Use Fill for a line that has styled spans inside it.

type RowMemo

type RowMemo[K comparable, V any] map[K]V

RowMemo is a lazily-allocated, bounded memo cache for rendered rows (or their heights), keyed by K. It centralises the "nil-check → lookup → build → store" pattern the list/disasm renderers would otherwise repeat by hand. The zero value (nil map) is ready to use.

func (*RowMemo[K, V]) Get

func (m *RowMemo[K, V]) Get(key K, build func() V) V

Get returns the cached value for key, building and caching it on a miss.

type SegFunc

type SegFunc func(s string) int

SegFunc returns the byte length of the first path segment of s (including its trailing separator), or -1 when s has no separator (so s is a leaf remainder).

type SortableHeaderCol

type SortableHeaderCol[T comparable] struct {
	Start, End int
	Sort       T
}

SortableHeaderCol is the clickable x-range [start,end) of one sortable column, tagged with the sort key it selects.

type TreeNode

type TreeNode struct {
	Label    string // segment shown for this node; internal nodes keep the trailing separator
	Path     string // internal nodes only: full path from the root, the collapse-state key. Left empty for leaves (never read for them — collapse keys off the item index), which avoids a prefix+label concatenation per leaf (tens of MB on a 100k+-symbol tree).
	Leaf     int    // item index for a leaf, -1 for an internal node
	Count    int    // number of leaf descendants (for the collapsed "(n)" hint)
	Children []*TreeNode
}

TreeNode is one node of a name tree. Internal (group) nodes have leaf == -1 and children; leaves carry the index of the underlying item (symbol/file/lib).

func BuildScopedTree

func BuildScopedTree(idxs []int, label func(int) string) []*TreeNode

BuildScopedTree groups symbols into a two-pass name tree: first by scope/word boundaries (segScoped), then folding whatever that leaves as singletons by a shared "_" prefix (segUnder). idxs must already be sorted by label.

func BuildTree

func BuildTree(idxs []int, label func(int) string, seg SegFunc) []*TreeNode

BuildTree groups idxs (already sorted by label) into a name tree using seg to pick segment boundaries.

type TreeRow

type TreeRow struct {
	Node  *TreeNode
	Depth int
}

TreeRow is one flattened, currently-visible row: a node and its depth.

func FlattenTree

func FlattenTree(nodes []*TreeNode, collapsed map[string]bool, depth int, out []TreeRow) []TreeRow

FlattenTree appends the visible rows of nodes to out: every node, plus the children of expanded internal nodes (collapsed[path] hides descendants).

Jump to

Keyboard shortcuts

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