string_width
Types
Options to change the default behaviour of the functions in this library. If you are unsure what to do here, passing an empty array is almost always the right call!
pub type Option {
HandleGraphemeClusters
CountAnsiEscapeCodes
AmbiguousAsWide
}
Constructors
-
HandleGraphemeClusters
Most terminal emulators do not handle grapheme clusters well and will instead show their decomposition. To make sure a given string always fits even on those terminals, the functions in this package will copy this behaviour as well.
If you pass this option, the returned numbers will more accurately represent the width of a string on a website or in an editor.
NOTE: Grapheme support in terminals is highly experimental and subject to ongoing discussion! When working on CLIs or TUIs, you do not want to use this option most of the time.
See also Grapheme Clusters and Terminal Emulators for a better explanation on how terminals behave.
-
CountAnsiEscapeCodes
Do not ignore ansi escape sequences, and count them as regular characters.
You can pass this option as an optimisation if you are sure that your string doesn’t contain any ansi escape codes.
-
AmbiguousAsWide
Some characters are marked by Unicode as “ambiguous”, meaning they may occupy 1 or 2 cells, depending on the context, current language, selected font, surrounding text, and more.
Unicode recommends treating these characters as narrow by default, but you can change this behaviour using this option.
Functions
pub fn codepoint(chr: UtfCodepoint, options: List(Option)) -> Int
Estimate the required width of a single unicode code point.
If you encounter a mismatch that is consistent across multiple terminal emulators, please open an issue or ping me on Discord!
pub fn dimensions(
str: String,
options: List(Option),
) -> #(Int, Int)
The required number of rows and columns to display the given text.
The number of rows is equal to the number of lines, while the number of columns represents the maximum line width.
Examples
dimensions("안녕하세요", [])
// --> #(1, 10)
dimensions("hello,\n안녕하세요", [])
// --> #(2, 10)
pub fn fold(
over string: String,
using options: List(Option),
from state: a,
with fun: fn(a, String, Int) -> a,
) -> a
Iterate over the measured components of a string. Components are either
graphemes or codepoints, depending on the HandleGraphemeClusters
option,
or other undivisible sequences, like ANSI escape codes.
This is a lower-level utility compared to the others in this package. It does not by itself keep track of any additional state, and does not for example handle newlines or tabs. Instead, you can use fold to implement all kinds of higher-level layout primitives.
Concatenating all components is guaranteed to produce the original string.
Examples
// A slower string_width.line implementation that doesn't handle tabs
fold("hello", [], from: 0, with: fn(so_far, _chr, width) { width + so_far })
// --> 5
// truncate a string after 50 characters, but keep all ansi sequences.
use #(total, acc), chr, width <- fold(input, [], from: #(0, ""))
case total >= 50 {
True -> case chr {
"\u{1b}" <> _ -> #(total, acc <> chr)
_ -> #(total, acc)
}
False -> #(total + width, acc <> chr)
}
pub fn grapheme_cluster(
grapheme: String,
options: List(Option),
) -> Int
Estimate the required width for a single grapheme cluster.
Note: Grapheme cluster handling in terminals is highly experimental and subject to ongoing discussions, and very inconsistent across different terminal emulators. The exact width is context-dependent, so this value may may exactly match what you might expect!
pub fn int_codepoint(cp: Int, options: List(Option)) -> Int
pub fn line(str: String, options: List(Option)) -> Int
Get the number of columns required to print a line in a terminal.
Line breaks are ignored. If the given string contains newlines, the result is undefined.
Examples
line("äöüè", [])
// --> 4
line("안녕하세요", [])
// --> 10
line("👩👩👦👦", [])
// --> 8
line("👩👩👦👦", [HandleGraphemeClusters])
// --> 2
line("\u{1B}[31mhello\u{1B}[39m", [])
// --> 5