string_width
Measure
line, line_with, dimensions, dimensions_with
Layout
limit, limit_with, position, position_with, align, align_with, tabs_to_spaces, tabs_to_spaces_with
Options Builder
new, ambiguous_as_wide, count_ansi_codes, mode_2027, mode_2027_ext, mode_wcwidth, at_tab_offset, with_tab_width
Advanced
fold, fold_with, fold_raw, is_ansi_component
Types
Control the text or box alignment on the horizontal axis.
pub type Alignment {
Left
Center
Right
}
Constructors
-
Left
-
Center
-
Right
Options to change the default behaviour of the functions in this library. If you are unsure what to do here, the defaults should work great!
pub opaque type Options
The measured pieces of a string, passed to you by fold
and fold_with
pub type Piece {
Piece(piece: String, row: Int, column: Int, width: Int)
}
Constructors
-
Piece(piece: String, row: Int, column: Int, width: Int)
Control the box alignment on the vertical axis.
pub type Placement {
Top
Middle
Bottom
}
Constructors
-
Top
-
Middle
-
Bottom
Functions
pub fn align(
str: String,
to max_width: Int,
align alignment: Alignment,
with space: String,
) -> String
Align each line horizontally, using a spacer character or string as a filler.
Each line of the return value will be at least max_width
wide.
If a line is already longer than the max width, it will not be changed. If the space strings width does not evenly divide the missing amount of columns, the extra spacer will overflow the max width.
Examples
align("hello", to: 10, align: Left, with: " ")
// --> "hello "
align("12345", to: 8, align: Right, with: "0")
// --> "00012345"
align(" Welcome ", to: 20, align: Center, with: "==")
// --> "====== Welcome ======" // (len = 21)
pub fn align_with(
str: String,
to max_width: Int,
align alignment: Alignment,
using options: Options,
with space: String,
) -> String
Like align
, bu use custom options for measure.
pub fn ambiguous_as_wide(options: Options) -> Options
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.
pub fn at_tab_offset(
options: Options,
tab_offset: Int,
) -> Options
Define an offset for tab stop calculations. (Default: 0)
If the text you print doesn’t start at the first column, but is instead indented somehow, you can set this option to this number to make sure tab stops are correctly calculated.
pub fn count_ansi_codes(options: Options) -> Options
Do not ignore ansi escape sequences, and count them as regular characters.
You can enable this option as an optimisation if you are sure that your string doesn’t contain any ansi escape codes.
pub fn dimensions(str: String) -> Size
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("안녕하세요")
// --> Size(rows: 1, columns: 10)
dimensions("hello,\n안녕하세요")
// --> Size(rows: 2, columns: 10)
pub fn dimensions_with(str: String, options: Options) -> Size
Like dimensions
, but use custom options.
pub fn fold(
over str: String,
from state: a,
with fun: fn(a, Piece) -> a,
) -> a
A higher-level fold
that keeps track of the position inside of the string.
Handles tabs and newlines, and always passes full grapheme clusters, regardless of options. Concatenating the graphemes produces the original string.
Intended to be used as a basis for custom layout algorithms.
pub fn fold_raw(
over string: String,
using options: Options,
from state: a,
with fun: fn(a, String, Int) -> a,
) -> a
A low-level fold that iterate over the measured components of a string.
Components are either graphemes or codepoints depending on the mode
option,
or other undivisible sequences, like ANSI escape codes.
This function does not by itself keep track of any additional state, and
doesn’t for example handle newlines or tabs like fold
would.
If you know that your algorithm is safe and won’t break up graphemes (or
maybe this is acceptable), you can use this fold variant instead as a
performance optimisation.
Concatenating all components is guaranteed to produce the original string.
Examples
// A slower string_width.line implementation that doesn't handle tabs
fold_raw("hello", new(), 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_raw(input, new(), from: #(0, ""))
case total >= 50 {
True -> case chr {
"\u{1b}" <> _ -> #(total, acc <> chr)
_ -> #(total, acc)
}
False -> #(total + width, acc <> chr)
}
pub fn fold_with(
over str: String,
using options: Options,
from state: a,
with fun: fn(a, Piece) -> a,
) -> a
A higher-level fold
that keeps track of the position inside of the string
Handles tabs and newlines, and always passes full grapheme clusters, regardless of options. Concatenating the graphemes produces the original string.
Intended to be used as a basis for custom layout algorithms.
pub fn is_ansi_component(str: String, options: Options) -> Bool
Returns true if a given component string recieved in fold
is an ANSI
escape sequence.
pub fn limit(
str: String,
to max_size: Size,
ellipsis ellipsis: String,
) -> String
Limit the dimensions of a string, either by wrapping on white space characters, or by truncating the last line and appending an ellipsis.
The lines of the resulting string are left aligned and are at most columns
wide. If a single word is longer than the maximum allowed line width, the
word is broken into 2 pieces at the grapheme level. If the string gets
truncated, this function will keep collecting ANSI sequences to make sure
all formatting is preserved.
A commonly used ellipsis character is "…"
, also known as
U+2026 HORIZONTAL ELLIPSIS.
Examples
limit("Hello World", Size(rows: 1, columns: 10), ellipsis: "...")
// --> "Hello W..."
limit("Hello World", Size(rows: 2, columns: 5), ellipsis: "...")
// --> "Hello\nWorld"
pub fn limit_with(
str: String,
to max_size: Size,
using options: Options,
ellipsis ellipsis: String,
) -> String
Like limit
, but also customise the options used for measuring.
pub fn line(str: String) -> Int
Get the number of columns required to print a line in a terminal.
If multiple lines are given, the length of the longest line is returned.
Examples
line("äöüè")
// --> 4
line("안녕하세요")
// --> 10
line("👩👩👦👦")
// --> 8
line("\u{1B}[31mhello\u{1B}[39m")
// --> 5
pub fn line_with(str: String, options: Options) -> Int
Like line
, but use custom options.
Example
let options =
new()
|> mode
line_with("👩👩👦👦", options)
// --> 2
pub fn mode_2027(options: Options) -> Options
Measure grapheme clusters instead of individual code points.
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 enable 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.
pub fn mode_2027_ext(options: Options) -> Options
Measure grapheme clusters instead of individual code points, and always treat grapheme clusters with more than one non-modifier character as wide.
This is a custom extension to Unicode Core. Many single grapheme clusters would currently still render as wide glyphs in many contexts, since font shaping would not be able to collapse them into a single narrow “ligature”.
This mode is an additional heuristic that tries to handle more such cases.
See mode_2027
for more explanation on the implications.
pub fn mode_wcwidth(options: Options) -> Options
The default mode and the mode suitable for most terminals.
Measures individual code points, similar to the libc wcwidth
function
that almost all of these terminals use.
Note that except for fold_raw
, this library will still always process
grapheme clusters as a unit.
pub fn position(
str: String,
in bounding_box: Size,
align alignment: Alignment,
place placement: Placement,
with space: String,
) -> String
Position the string area inside a bigger box without changing text alignment. The box will be filled with the space character.
If a line is already bigger than the maximum width, it will not be changed. If there are more lines than the maximum amount of rows, the extra lines will still be kept. If the space strings’ width does not evenly divide the missing amount of columns, the extra spacer will overflow the max width.
Examples
position("X", in: Size(3, 3), align: Center, place: Middle, with: "o")
// --> "ooo\noXo\nooo"
pub fn position_with(
str: String,
in bounding_box: Size,
horizontal alignment: Alignment,
vertical placement: Placement,
using options: Options,
with space: String,
) -> String
Like position
, but use custom options to measure each line.
pub fn tabs_to_spaces(str: String) -> String
Replace all tab characters found in the string with the amount of spaces that this tab would have had otherwise.
This makes it safe to prepend to a line, without changing the spacing produced by tabs anymore.
Examples
tabs_to_spaces("Hello\tWorld")
// --> "Hello World" // 3 spaces
pub fn tabs_to_spaces_with(
str: String,
using options: Options,
) -> String
Like tabs_to_spaces
, but customise the opttions as well.
NB: You can use at_tab_offset
and with_tab_width
to change the measure of
tab characters inside the string!
let options = new() |> with_tab_width(4)
tabs_to_spaces_with("hi\tcutie~", options)
// --> "hi cutie~"
pub fn with_tab_width(
options: Options,
tab_width: Int,
) -> Options
Change the number of columns between tab stops. (Default: 8)
Whenever a tab character \t
is encountered, the current column number will
be rounded up to the next multiple of this number.