Cringe.Measure (cringe v0.4.0)

Copy Markdown View Source

Terminal-cell measurement and clipping helpers.

These functions operate on terminal cell width rather than byte size or grapheme count. They account for common wide emoji/CJK graphemes, combining marks, variation selectors, zero-width joiner sequences, and ANSI SGR styling.

Summary

Functions

Splits text into chunks no wider than width terminal cells.

Drops text until at least count terminal cells are removed.

Fits text to an exact terminal-cell width.

Pads text with spaces until it reaches a terminal-cell width.

Takes text up to a terminal-cell width.

Returns the visible terminal-cell width of a string.

Wraps text to a terminal-cell width.

Functions

chunks(text, width)

@spec chunks(String.t(), pos_integer()) :: [String.t()]

Splits text into chunks no wider than width terminal cells.

iex> Cringe.Measure.chunks("a🚀b東c", 3)
["a🚀", "b東", "c"]

drop(text, count)

@spec drop(String.t(), non_neg_integer()) :: String.t()

Drops text until at least count terminal cells are removed.

ANSI styling is stripped from the result.

iex> Cringe.Measure.drop("ab🚀cd", 4)
"cd"

iex> Cringe.Measure.drop("ab🚀cd", 3)
"cd"

fit(text, width, opts \\ [])

@spec fit(String.t(), non_neg_integer(), keyword()) :: String.t()

Fits text to an exact terminal-cell width.

Text shorter than the target width is padded. Text longer than the target width is clipped. Pass ellipsis?: true to reserve one cell for when clipping.

iex> Cringe.Measure.fit("🚀", 4)
"🚀  "

iex> Cringe.Measure.fit("ab🚀cd", 4)
"ab🚀"

iex> Cringe.Measure.fit("ab🚀cd", 4, ellipsis?: true)
"ab…"

pad(text, width)

@spec pad(String.t(), non_neg_integer()) :: String.t()

Pads text with spaces until it reaches a terminal-cell width.

iex> Cringe.Measure.pad("🚀", 4)
"🚀  "

iex> Cringe.Measure.pad("hello", 2)
"hello"

take(text, width)

@spec take(String.t(), non_neg_integer()) :: String.t()

Takes text up to a terminal-cell width.

The result never splits a grapheme. ANSI SGR sequences are preserved, and an ANSI reset is appended when truncation leaves styling active.

iex> Cringe.Measure.take("ab🚀cd", 4)
"ab🚀"

iex> Cringe.Measure.take("ab🚀cd", 3)
"ab"

iex> Cringe.Measure.take("hello", 2)
"he"

width(text)

@spec width(String.t()) :: non_neg_integer()

Returns the visible terminal-cell width of a string.

iex> Cringe.Measure.width("abc")
3

iex> Cringe.Measure.width("🚀")
2

iex> Cringe.Measure.width("é")
1

iex> Cringe.Measure.width("red")
3

wrap(text, width)

@spec wrap(String.t(), pos_integer()) :: [String.t()]

Wraps text to a terminal-cell width.

Existing newlines are preserved as line boundaries. Lines containing spaces are word-wrapped; long words are split with chunks/2.

iex> Cringe.Measure.wrap("hello world", 5)
["hello", "world"]

iex> Cringe.Measure.wrap("a🚀b東c", 3)
["a🚀", "b東", "c"]