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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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 /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
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 /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
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) /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
pub fn dimensions(str: String) -> Size { let #(rows, columns, _) = do_measure(str, default_options) Size(rows:, columns:) } /// Like `dimensions`, but use custom options. +/// +///
+/// +/// Back to top ↑ +/// +///
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" /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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 /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
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~" /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
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" /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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" /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
pub fn align( str: String, to max_width: Int, @@ -794,6 +933,12 @@ pub fn align( } /// Like `align`, bu use custom options for measure. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
pub fn fold_with( over str: String, using options: Options, @@ -1022,6 +1185,12 @@ pub fn fold_with( /// False -> #(total + width, acc <> chr) /// } /// ``` +/// +///
+/// +/// Back to top ↑ +/// +///
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. +/// +///
+/// +/// Back to top ↑ +/// +///
pub fn fold_raw_pieces( over string: String, using options: Options,