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
- func ActiveSortHeaderLabel(label string, width int, desc bool) string
- func AppendAddr(dst []byte, addr uint64, digits int) []byte
- func AppendRenderedRows(lines *[]string, line string, w int, wrap bool, limit int) bool
- func AppendRenderedRowsIndented(lines *[]string, line string, w int, wrap bool, indent int, limit int) bool
- func ApplySortHeaderClick[T comparable](active *T, desc *bool, sort T) bool
- func CarryWrapStyle(rows []string)
- func Clamp(v, lo, hi int) int
- func ContainsFold(s, substr string) bool
- func ContainsFoldBytes(b []byte, substr string) bool
- func CtrlKeys(keys ...string) string
- func CycleStringList(on *bool, cur *string, list []string)
- func EachInternal(nodes []*TreeNode, fn func(path string))
- func FitANSIWidth(s string, w int) string
- func HardWrapLongRows(rows []string, w int) []string
- func HitSortableHeader[T comparable](cols []SortableHeaderCol[T], x int) (T, bool)
- func IndentContinuationRows(rows []string, w int, indent int) []string
- func LastOpenSGR(open, row string) string
- func MaxViewportTop(n, visible int, rowHeight func(int) int) int
- func NavKey(cur *int, n, page int, key string) bool
- func Overlay(bg, fg string, x, y int) string
- func PadBody(s string, w, h int) string
- func PadBodyRows(lines []string, w, h int) string
- func PadRight(s string, w int) string
- func PadVisual(s string, w int) string
- func PageStep(from, n, visibleLines int, rowHeight func(int) int) int
- func RenderLineRowsIndented(line string, w int, wrap bool, indent int) []string
- func RenderStyle(s string, w int, st lipgloss.Style) string
- func ReverseInts(s []int)
- func SegPath(s string) int
- func SortDirectionLabel(desc bool) string
- func SortHeaderLabel[T comparable](label string, width int, sort, active T, desc bool) string
- func StylePrefix(st lipgloss.Style) string
- func TrailingSortHeaderLabel[T comparable](label string, sort, active T, desc bool) string
- func TreeCollapseOne(rows []TreeRow, cur *int, collapsed map[string]bool) bool
- func TreeExpandOne(rows []TreeRow, cur *int, collapsed map[string]bool) bool
- func TreeToggleSubtree(rows []TreeRow, cur int, collapsed map[string]bool) bool
- func TruncateANSI(s string, w int) string
- func TruncateMiddle(s string, n int) string
- func ViewportTop(top, n, visible int, rowHeight func(int) int) int
- func VisualItemAtRow(top, n, row int, rowHeight func(int) int) (int, bool)
- func VisualTop(cur, top, n, visible int, rowHeight func(int) int) int
- func WrapRows(s string, w int, cutset string) []string
- type Painter
- type RowMemo
- type SegFunc
- type SortableHeaderCol
- type TreeNode
- type TreeRow
Constants ¶
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.
const TreeIndent = 2
TreeIndent is the per-depth indentation of a tree row.
Variables ¶
This section is empty.
Functions ¶
func ActiveSortHeaderLabel ¶
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 ¶
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 ¶
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 ContainsFold ¶
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 ¶
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 ¶
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 ¶
CycleStringList advances a facet filter through list: off → first → … → last → off. *on tracks whether the filter is active, *cur its current value.
func EachInternal ¶
EachInternal calls fn for every internal node's path (used by "collapse all").
func FitANSIWidth ¶
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 ¶
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 ¶
IndentContinuationRows applies a hanging indent after the first row.
func LastOpenSGR ¶
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 ¶
MaxViewportTop returns the latest top row that can fill the viewport.
func NavKey ¶
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 ¶
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 PadBodyRows ¶
PadBodyRows clamps and pads pre-split rows to exactly w by h cells.
func PadRight ¶
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 ¶
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 ¶
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 ¶
RenderLineRowsIndented renders a logical line with optional hanging indent.
func RenderStyle ¶
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 SortDirectionLabel ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
TruncateANSI naively truncates while keeping the trailing SGR reset.
func TruncateMiddle ¶
TruncateMiddle keeps both ends of a string visible within n columns.
func ViewportTop ¶
ViewportTop clamps a detached viewport top for variable-height rows.
func VisualItemAtRow ¶
VisualItemAtRow maps a visual row offset to a logical item index.
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 ¶
NewPainter captures st's SGR sequence. Build it when the theme is built, not when a frame is drawn.
func (Painter) Fill ¶
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 ¶
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.
type SegFunc ¶
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 ¶
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.