From 1db483f7eca72e35bd361d87de52d350fd9dfbed Mon Sep 17 00:00:00 2001 From: Joshua Reusch Date: Wed, 2 Oct 2024 21:55:28 +0200 Subject: [PATCH] Size, Piece custom types --- src/string_width.gleam | 59 ++++++++++++++++++------------------ test/despair_test.gleam | 2 +- test/string_width_test.gleam | 14 ++++----- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/string_width.gleam b/src/string_width.gleam index e1471a1..d12b1be 100644 --- a/src/string_width.gleam +++ b/src/string_width.gleam @@ -130,6 +130,12 @@ pub fn at_tab_offset(options: Options, tab_offset: Int) -> Options { // -- MEASURING STRINGS -------------------------------------------------------- +/// A custom type making it easy to differentiate the width (columns) and +/// height (rows). +pub type Size { + Size(rows: Int, columns: 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. @@ -150,8 +156,8 @@ pub fn at_tab_offset(options: Options, tab_offset: Int) -> Options { /// // --> 5 /// ``` pub fn line(str: String) -> Int { - let #(_, cols) = dimensions_with(str, default_options) - cols + let Size(columns:, rows: _) = dimensions_with(str, default_options) + columns } /// Like `line`, but use custom options. @@ -167,8 +173,8 @@ pub fn line(str: String) -> Int { /// // --> 2 /// ``` pub fn line_with(str: String, options: Options) -> Int { - let #(_, cols) = dimensions_with(str, options) - cols + let Size(columns:, rows: _) = dimensions_with(str, options) + columns } /// The required number of rows and columns to display the given text. @@ -185,12 +191,12 @@ pub fn line_with(str: String, options: Options) -> Int { /// dimensions("hello,\n안녕하세요") /// // --> #(2, 10) /// ``` -pub fn dimensions(str: String) -> #(Int, Int) { +pub fn dimensions(str: String) -> Size { dimensions_with(str, default_options) } /// Like `dimensions`, but use custom options. -pub fn dimensions_with(str: String, options: Options) -> #(Int, Int) { +pub fn dimensions_with(str: String, options: Options) -> Size { let #(str, ranges, range_width) = prepare_measure(options, str) let fun = fn(state, chr, width) { @@ -218,9 +224,10 @@ pub fn dimensions_with(str: String, options: Options) -> #(Int, Int) { let #(rows, cols_max, cols_curr) = fold_parts(str, 0, ranges, #(0, 0, options.tab_offset), on_chars, on_range) + let columns = int.max(cols_max, cols_curr) - options.tab_offset case cols_curr > 0 { - True -> #(rows + 1, int.max(cols_max, cols_curr) - options.tab_offset) - False -> #(rows, cols_max - options.tab_offset) + True -> Size(rows: rows + 1, columns:) + False -> Size(rows:, columns:) } } @@ -230,7 +237,7 @@ fn tab(options: Options, col: Int) -> Int { } // magic hook that allows us to not be stupid with FFI and still get -// _most_ of the performance (we are now like 50% slower). +// _most_ of the performance (we are now like 50% slower, or back to old speed). @external(javascript, "./string_width_ffi.mjs", "prepare_measure") fn prepare_measure( options: Options, @@ -249,18 +256,7 @@ fn prepare_measure( // line to short? align left/right/center/stretch // line to long? truncate/wrap // break at? shys/whitespace/unicode word boudnaries/grahpeme boundaries? this is hard -// widows when wrapping -// margins -// - -// if one component is followed by another zero-width component treat them as one? -// maybe even in fold? -// mode_ext -// mode_wcwidth - -// mixed: pass grapheme clusters but measure codepoints -// individual_codepoints (default see above) / for grapheme clusters, pass width at the first cp -// passing codepoints is only useful for measure? I don't think a user ever wants that +// justify vs text align // fold over words? (handle shy hyphens/soft breaks etc) // I do not want to implement full unicode word segmentation here though @@ -282,6 +278,11 @@ pub fn is_ansi_component(str: String, options: Options) -> Bool { } } +/// 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) +} + /// A higher-level `fold` that keeps track of the position inside of the string. /// /// Handles tabs and newlines, and always passes full grapheme clusters, @@ -291,7 +292,7 @@ pub fn is_ansi_component(str: String, options: Options) -> Bool { pub fn fold( over str: String, from state: state, - with fun: fn(state, String, Int, Int, Int) -> state, + with fun: fn(state, Piece) -> state, ) -> state { fold_with(str, default_options, state, fun) } @@ -306,17 +307,17 @@ pub fn fold_with( over str: String, using options: Options, from state: state, - with fun: fn(state, String, Int, Int, Int) -> state, + with fun: fn(state, Piece) -> state, ) -> state { - let fun = fn(state, chr, width) { - let #(state, row, col) = state + let fun = fn(state, piece, width) { + let #(state, row, column) = state - let state = fun(state, chr, width, row, col) + let state = fun(state, Piece(piece:, row:, column:, width:)) - case chr { + case piece { "\n" -> #(state, row + 1, options.tab_offset) - "\t" -> #(state, row, tab(options, col)) - _ -> #(state, row, col + width) + "\t" -> #(state, row, tab(options, column)) + _ -> #(state, row, column + width) } } diff --git a/test/despair_test.gleam b/test/despair_test.gleam index 44d3140..a10f52e 100644 --- a/test/despair_test.gleam +++ b/test/despair_test.gleam @@ -4,7 +4,7 @@ import string_width fn sw(str: String) { let options = string_width.new() - |> string_width.mode_2027 + |> string_width.mode_2027_ext string_width.line_with(str, options) } diff --git a/test/string_width_test.gleam b/test/string_width_test.gleam index 689c029..9747c94 100644 --- a/test/string_width_test.gleam +++ b/test/string_width_test.gleam @@ -1,6 +1,6 @@ import gleeunit import gleeunit/should -import string_width.{dimensions, line as sw} +import string_width.{Size, dimensions, line as sw} pub fn main() { gleeunit.main() @@ -41,16 +41,16 @@ pub fn docs_test() { swg("👩‍👩‍👦‍👦") |> should.equal(2) sw("\u{1B}[31mhello\u{1B}[39m") |> should.equal(5) - dimensions("안녕하세요") |> should.equal(#(1, 10)) + dimensions("안녕하세요") |> should.equal(Size(1, 10)) - dimensions("hello,\n안녕하세요") |> should.equal(#(2, 10)) + dimensions("hello,\n안녕하세요") |> should.equal(Size(2, 10)) } pub fn dimensions_test() { - dimensions("") |> should.equal(#(0, 0)) - dimensions("\n") |> should.equal(#(1, 0)) - dimensions("\nhello") |> should.equal(#(2, 5)) - dimensions("hello,\n안녕하세요\n") |> should.equal(#(2, 10)) + dimensions("") |> should.equal(Size(0, 0)) + dimensions("\n") |> should.equal(Size(1, 0)) + dimensions("\nhello") |> should.equal(Size(2, 5)) + dimensions("hello,\n안녕하세요\n") |> should.equal(Size(2, 10)) } pub fn pedantic_test() { -- knotmirror 0.0.1