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
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.
Functions
@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