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
@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"]
@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"
@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…"
@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"
@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("[31mhello[0m", 2)
"[31mhe[0m"
@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("[31mred[0m")
3
@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"]