textwrap

package
v0.0.0-...-ec0414e Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

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

func StringWidth(s string) int

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

func Truncate(s string, width int, ellipsis string) string

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 NewCache

func NewCache() *Cache

NewCache returns an empty cache.

func (*Cache) Forget

func (c *Cache) Forget(block uint64)

Forget drops all entries for the given block (e.g. when it is removed).

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 CacheKey

type CacheKey struct {
	Block uint64
	Width int
	Gen   uint64
}

CacheKey identifies one block's wrap at one width and content generation.

type Line

type Line []Span

Line is a logical (unwrapped) line of styled spans.

func TruncateLine

func TruncateLine(line Line, width int, ellipsis string) Line

TruncateLine is Truncate for a styled Line. It preserves span styles across the cut and assigns the ellipsis the style of the last visible grapheme. The returned line never aliases the input when truncation occurs.

type Span

type Span struct {
	Text  string
	Style cellbuf.Style
}

Span is a run of text in one style.

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.

Jump to

Keyboard shortcuts

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