diff --git a/src/string_width.gleam b/src/string_width.gleam
index b0c8c38..e12de2d 100644
--- a/src/string_width.gleam
+++ b/src/string_width.gleam
@@ -1,14 +1,17 @@
+//// The `with` variants of function additionally work with custom options.
+////
//// #### Measure
-//// [line](#line), [line_with](#line_with),
-//// [dimensions](#dimensions), [dimensions_with](#dimensions_with),
+//// [line](#line)[[_with](#line_with)]
+//// [dimensions](#dimensions)[[_with](#dimensions_with)],
//// [get_terminal_size](#get_terminal_size)
////
//// #### Layout
-//// [limit](#limit), [limit_with](#limit_with),
-//// [position](#position), [position_with](#position_with),
-//// [align](#align), [align_with](#align_with),
-//// [tabs_to_spaces](#tabs_to_spaces), [tabs_to_spaces_with](#tabs_to_spaces_with)
-////
+//// [limit](#limit)[[_with](#limit_with)],
+//// [position](#position)[[_with](#position_with)],
+//// [align](#align)[[_with](#align_with)],
+//// [tabs_to_spaces](#tabs_to_spaces)[[_with](#tabs_to_spaces_with)],
+//// [inline_styles](#inline_styles)
+////
//// #### Options Builder
//// [new](#new),
//// [ambiguous_as_wide](#ambiguous_as_wide), [count_ansi_codes](#count_ansi_codes),
@@ -16,7 +19,8 @@
//// [at_tab_offset](#at_tab_offset), [with_tab_width](#with_tab_width)
////
//// #### Advanced
-//// [fold](#fold), [fold_with](#fold_with), [fold_raw](#fold_raw), [fold_raw_pieces](#fold_raw_pieces),
+//// [fold](#fold)[[_with](#fold_with)],
+//// [fold_raw](#fold_raw), [fold_raw_pieces](#fold_raw_pieces),
//// [is_ansi_component](#is_ansi_component)
////
@@ -40,9 +44,20 @@ import string_width/internal/tables
// https://github.com/muesli/reflow/
// https://github.com/charmbracelet/x/
-// TODO: in limit, tabs are counted as their original width, not the width where they will be placed
-
-// v4:
+// v3.2.0:
+// x collect/reset SGR codes like reflow does
+// - make align trim lines?
+// - padding layout
+// - join/columns layout
+// - drop_left/drop_right
+// x change the module header to not list _with functions separately
+// x add "Back to top" links to all functions
+// - explore skipping multiple bytes at a time like OTP json (8 bytes check if interesting -> continue)
+// - propose a @inline attribute
+// - try poslen -> split for binary states (align, limit)
+// - cprof, eprof, eflamb`e
+
+// v4.0.0:
// - fix labeled arguments in position_with
const max_safe_integer = 0x1fffffffffffff
@@ -76,6 +91,12 @@ const default_options = Options(
)
/// Start building up new options.
+///
+///
pub fn new() -> Options {
default_options
}
@@ -96,6 +117,12 @@ pub fn new() -> Options {
///
/// See also [Grapheme Clusters and Terminal Emulators](https://mitchellh.com/writing/grapheme-clusters-in-terminals)
/// for a better explanation on how terminals behave.
+///
+///
pub fn mode_2027(options: Options) -> Options {
Options(..options, mode: Mode2027)
}
@@ -110,6 +137,12 @@ pub fn mode_2027(options: Options) -> Options {
/// 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_2027_ext(options: Options) -> Options {
Options(..options, mode: Mode2027Ext)
}
@@ -121,6 +154,12 @@ pub fn mode_2027_ext(options: Options) -> Options {
///
/// Note that except for `fold_raw`, this library will still always process
/// grapheme clusters as a unit.
+///
+///
pub fn mode_wcwidth(options: Options) -> Options {
Options(..options, mode: ModeWcwidth)
}
@@ -131,6 +170,12 @@ pub fn mode_wcwidth(options: Options) -> Options {
///
/// Unicode recommends treating these characters as narrow by default,
/// but you can change this behaviour using this option.
+///
+///
pub fn ambiguous_as_wide(options: Options) -> Options {
Options(..options, ambiguous_as_wide: True)
}
@@ -139,6 +184,12 @@ pub fn ambiguous_as_wide(options: Options) -> Options {
///
/// You can enable this option as an optimisation if you are sure that your
/// string doesn't contain any ansi escape codes.
+///
+///
pub fn count_ansi_codes(options: Options) -> Options {
Options(..options, count_ansi_codes: True)
}
@@ -147,6 +198,12 @@ pub fn count_ansi_codes(options: Options) -> Options {
///
/// Whenever a tab character `\t` is encountered, the current column number will
/// be rounded up to the next multiple of this number.
+///
+///
pub fn with_tab_width(options: Options, tab_width: Int) -> Options {
Options(..options, tab_width:)
}
@@ -156,6 +213,12 @@ pub fn with_tab_width(options: Options, tab_width: Int) -> Options {
/// 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 at_tab_offset(options: Options, tab_offset: Int) -> Options {
Options(..options, tab_offset:)
}
@@ -172,6 +235,12 @@ pub type Size {
///
/// Checks the built-in methods for the target, as well as the `LINES` and
/// `COLUMNS` environment variables.
+///
+///
pub fn get_terminal_size() -> Result(Size, Nil) {
case do_get_terminal_size() {
Ok(#(rows, columns)) -> Ok(Size(rows:, columns:))
@@ -202,6 +271,12 @@ fn do_get_terminal_size() -> Result(#(Int, Int), Nil)
/// line("\u{1B}[31mhello\u{1B}[39m")
/// // --> 5
/// ```
+///
+///
pub fn line(str: String) -> Int {
let #(_, columns, _) = do_measure(str, default_options)
columns
@@ -219,6 +294,12 @@ pub fn line(str: String) -> Int {
/// line_with("👩👩👦👦", options)
/// // --> 2
/// ```
+///
+///
pub fn line_with(str: String, options: Options) -> Int {
let #(_, columns, _) = do_measure(str, options)
columns
@@ -238,12 +319,24 @@ pub fn line_with(str: String, options: Options) -> Int {
/// dimensions("hello,\n안녕하세요")
/// // --> Size(rows: 2, columns: 10)
/// ```
+///
+///
pub fn dimensions(str: String) -> Size {
let #(rows, columns, _) = do_measure(str, default_options)
Size(rows:, columns:)
}
/// Like `dimensions`, but use custom options.
+///
+///
pub fn dimensions_with(str: String, options: Options) -> Size {
let #(rows, columns, _) = do_measure(str, options)
Size(rows:, columns:)
@@ -338,6 +431,12 @@ fn prepare_measure(
/// limit("Hello World", Size(rows: 2, columns: 10), ellipsis: "...")
/// // --> "Hello\nWorld"
/// ```
+///
+///
pub fn limit(
str: String,
to max_size: Size,
@@ -387,6 +486,12 @@ fn limit_state_overflow(
}
/// Like `limit`, but also customise the options used for measuring.
+///
+///
pub fn limit_with(
str: String,
to max_size: Size,
@@ -622,6 +727,12 @@ pub fn limit_with(
/// tabs_to_spaces("Hello\tWorld")
/// // --> "Hello World" // 3 spaces
/// ```
+///
+///
pub fn tabs_to_spaces(str: String) -> String {
tabs_to_spaces_with(str, default_options)
}
@@ -636,6 +747,12 @@ pub fn tabs_to_spaces(str: String) -> String {
/// tabs_to_spaces_with("hi\tcutie~", options)
/// // --> "hi cutie~"
/// ```
+///
+///
pub fn tabs_to_spaces_with(str: String, using options: Options) -> String {
use acc, piece <- fold_raw_pieces(str, options, from: "")
case piece.piece {
@@ -672,7 +789,17 @@ pub type Placement {
/// ```gleam
/// position("X", in: Size(3, 3), align: Center, place: Middle, with: "o")
/// // --> "ooo\noXo\nooo"
+///
+/// // text still left-aligned, but moved to the right by 1 space
+/// position("Trans\nrights\nare\nhuman\nrights", Size(0, 7), Right, Top, " ")
+/// // --> " Trans \n rights\n are \n human \n rights"
/// ```
+///
+///
pub fn position(
str: String,
in bounding_box: Size,
@@ -684,6 +811,12 @@ pub fn position(
}
/// Like `position`, but use custom options to measure each line.
+///
+///
pub fn position_with(
str: String,
in bounding_box: Size,
@@ -784,6 +917,12 @@ pub fn position_with(
/// align("Trans\nrights\nare\nhuman\nrights", to: 7, align: Right, with: " ")
/// // --> " Trans\n rights\n are\n human\n rights"
/// ```
+///
+///
pub fn align(
str: String,
to max_width: Int,
@@ -794,6 +933,12 @@ pub fn align(
}
/// Like `align`, bu use custom options for measure.
+///
+///
pub fn align_with(
str: String,
to max_width: Int,
@@ -805,14 +950,14 @@ pub fn align_with(
use line, line_width <- do_align(str, options)
let missing = div_up(max_width - line_width, space_width)
- case alignment {
- Left -> line <> string.repeat(space, missing)
- Right -> string.repeat(space, missing) <> line
- Center -> {
- let left = missing / 2
- let right = missing - left
- string.repeat(space, left) <> line <> string.repeat(space, right)
- }
+ case alignment {
+ Left -> line <> string.repeat(space, missing)
+ Right -> string.repeat(space, missing) <> line
+ Center -> {
+ let left = missing / 2
+ let right = missing - left
+ string.repeat(space, left) <> line <> string.repeat(space, right)
+ }
}
}
@@ -923,6 +1068,12 @@ pub fn inline_styles(str: String) -> String {
/// Returns true if a given component string recieved in `fold` is an ANSI
/// escape sequence.
+///
+///
pub fn is_ansi_component(str: String, options: Options) -> Bool {
case options.count_ansi_codes {
True -> False
@@ -945,6 +1096,12 @@ pub type Piece {
/// regardless of options. Concatenating the graphemes produces the original string.
///
/// Intended to be used as a basis for custom layout algorithms.
+///
+///
pub fn fold(
over str: String,
from state: state,
@@ -959,6 +1116,12 @@ pub fn fold(
/// regardless of options. Concatenating the graphemes produces the original string.
///
/// Intended to be used as a basis for custom layout algorithms.
+///
+///
pub fn fold_with(
over str: String,
using options: Options,
@@ -1022,6 +1185,12 @@ pub fn fold_with(
/// False -> #(total + width, acc <> chr)
/// }
/// ```
+///
+///
pub fn fold_raw(
over string: String,
using options: Options,
@@ -1049,6 +1218,12 @@ pub fn fold_raw(
/// A version of `fold_raw` that does keep track of some state for you to
/// handle tabs, and count the current row/column, passing `Piece` values to
/// you instead.
+///
+///
pub fn fold_raw_pieces(
over string: String,
using options: Options,