Documentation
¶
Overview ¶
Package textwrap implements Unicode-aware word wrapping with continuation ("joiner") tracking, plus a (width, generation)-keyed wrap cache — the Go counterpart of xai-grok-pager's word_wrap_lines_with_joiners and WrapCache.
Width accounting uses grapheme clusters: CJK and emoji clusters are 2 columns, combining marks 0, and a wide cluster is never split across the wrap boundary. Continuation lines carry Joined == true so selection/copy can rejoin logical lines without injecting newlines.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func StringWidth ¶
StringWidth returns the display width of s in columns (grapheme-aware). Note that control characters (including '\t') measure 0 here; Wrap separately accounts a '\t' as a width-1 space for break purposes.
func Truncate ¶
Truncate cuts s to at most width columns, appending ellipsis when cut. s is returned unchanged when it already fits. Cuts happen at grapheme boundaries, so the result may fall short of width when a wide cluster straddles the cut point. width <= 0 returns ""; if the ellipsis alone is wider than width, a fitting prefix of the ellipsis is returned.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache memoizes wraps per (block, width, generation). Stale generations and widths for the same block are evicted on insert, so memory stays bounded by the number of live blocks: at most one (width, gen) entry is kept per block, since a block is typically rendered at a single width.
Cache is not safe for concurrent use; it is intended to be owned by a single goroutine (the TUI event loop), so it takes no mutex.
func (*Cache) Get ¶
func (c *Cache) Get(key CacheKey, build func() []WrappedLine) []WrappedLine
Get returns the cached wrap for key, computing and storing it via build on a miss. Inserting evicts any entry for the same block with a different width or generation.
type WrappedLine ¶
type WrappedLine struct {
Spans Line
Joined bool // true when this row continues the previous logical line
}
WrappedLine is one physical row produced by wrapping.
func Wrap ¶
func Wrap(line Line, width int) []WrappedLine
Wrap breaks one logical line into physical rows of at most width columns. Wrapping prefers word boundaries (spaces) and falls back to hard breaks at grapheme boundaries for words longer than width. Styles are preserved across breaks. width <= 0 returns a single row with the original spans.
Details:
- A "word" is a maximal run of non-space clusters. Break opportunities are the ASCII space and '\t' (the tab is treated as a space of width 1 for simplicity).
- The space run at a wrap point is consumed: it appears neither at the end of the broken row nor at the start of the continuation row. Spaces inside a row are preserved verbatim. Trailing spaces of the logical line keep whatever fits on the final row; the overflow is consumed.
- A wide (2-column) cluster is never split: if it would start at the last column, the row is left one column short and the cluster wraps to the next row. At width 1 a wide cluster cannot fit anywhere, so it occupies its own row and overflows by one column.
- The first row has Joined == false; every continuation row has Joined == true. An empty line yields one empty row.
func WrapAll ¶
func WrapAll(lines []Line, width int) []WrappedLine
WrapAll wraps a slice of logical lines in order. Each logical line starts a fresh row with Joined == false.