Something went wrong. Try again.
Measure the width of text in the terminal and build simple layouts!
Something went wrong. Try again.
11 kB · 348 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349import gleam/listimport gleeunitimport gleeunit/shouldimport string_width.{Piece, Size, dimensions, line as sw}
pub fn main() { let _ = echo string_width.get_terminal_size() as "Terminal size" gleeunit.main()}
fn swg(str: String) { let options = string_width.new() |> string_width.mode_2027_ext string_width.line_with(str, options)}
fn sww(str: String) { let options = string_width.new() |> string_width.ambiguous_as_wide string_width.line_with(str, options)}
fn swc(str: String) { let options = string_width.new() |> string_width.count_ansi_codes string_width.line_with(str, options)}
pub fn main_test() { sw("abcde") |> should.equal(5) sw("古池や") |> should.equal(6) sw("あいうabc") |> should.equal(9) sw("±") |> should.equal(1) sw("ノード.js") |> should.equal(9) sw("你好") |> should.equal(4) sw("안녕하세요") |> should.equal(10)}
pub fn docs_test() { sw("äöüè") |> should.equal(4) sw("안녕하세요") |> should.equal(10) sw("123|") sw("🏳️⚧️") |> should.equal(2) swg("🏳️⚧️") |> should.equal(2) sw("👩👩👦👦") |> should.equal(8) swg("👩👩👦👦") |> should.equal(2) sw("\u{1B}[31mhello\u{1B}[39m") |> should.equal(5)
dimensions("안녕하세요") |> should.equal(Size(1, 10))
dimensions("hello,\n안녕하세요") |> should.equal(Size(2, 10))}
pub fn dimensions_test() { 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 dimensions_with_tab_offset_test() { let options = string_width.new() |> string_width.at_tab_offset(3)
string_width.dimensions_with("", options) |> should.equal(Size(0, 0))}
pub fn pedantic_test() { // these are the test cases I had for v3. // I wanna document what other programs do, even if I think they are buggy. // sindre, fstw; vscode; gnome-terminal, kitty, contour, foot (off), foot (on) // 123456789 sindre fstw | vscode gnome-terminal kitty foot(0) foot(1) contour
// let sw = fn(str, opts) { sw(io.debug(str), opts) }
// 🏳️⚧️ 2 2 | 2 2 4 3 2 2 sw("🏳️⚧️") |> should.equal(2) swg("🏳️⚧️") |> should.equal(2)
// 👩👩👧👦 2 2 | 2 8 8 8 2 2 sw("👩👩👦👦") |> should.equal(8) swg("👩👩👦👦") |> should.equal(2)
// র্য 1 2 | 1 2 2 2 2 1 sw("র্য") |> should.equal(2) // despair1
// র্য 1 3 | 2 2 2 2 2 2 sw("র্য") |> should.equal(2) // despair2
// अनुच्छेद 4 5 | 5 5 5 5 5 5 sw("अनुच्छेद") |> should.equal(5) // despair3
// ශ්ර 2 2 | 2.5 2 2 2 2 2 sw("ශ්ර") |> should.equal(2) swg("ශ්ර") |> should.equal(2) // ශ්ර 2 3 | 1.5 2 2 2 2 1 sw("ශ්ර") |> should.equal(2) swg("ශ්ර") |> should.equal(2)
// क्षि 1 2 | 2 3 2 3 2 1 sw("क्षि") |> should.equal(3) // despair4
// ↔️ 2 2 | 1 1 2 1 1 1 sw("↔️") |> should.equal(1) swg("↔️") |> should.equal(2) // VS 16 forces double-width
// 葛󠄀 2 2 | 2 2 2 2 2 2 sw("葛󠄀") |> should.equal(2) swg("葛󠄀") |> should.equal(2)
// Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞ 6 6 | 6 6 6 6 6 6 sw("Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞") |> should.equal(6) swg("Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞") |> should.equal(6)
// バ| :L 1 2 | 2 2 2 2 1 sw("バ") |> should.equal(2) // despair5}
pub fn ambigous_test() { sw("⛣") |> should.equal(1) sww("⛣") |> should.equal(2) sw("★") |> should.equal(1) sww("★") |> should.equal(2) sw("“") |> should.equal(1) sww("“") |> should.equal(2) sw("あいう★") |> should.equal(7) sww("あいう★") |> should.equal(8) sw("★あいう") |> should.equal(7) sww("★あいう") |> should.equal(8)}
pub fn v5v7_test() { // these are examples in https://github.com/sindresorhus/string-width/issues/56 sw("◼") |> should.equal(1) sw("⚠") |> should.equal(1) sw("✔") |> should.equal(1) sw("♥") |> should.equal(1) sw("♀") |> should.equal(1) sw("♂") |> should.equal(1) sw("\u{21a9}") |> should.equal(1) sw("\u{2194}") |> should.equal(1) sw("\u{2197}") |> should.equal(1) sw("✔") |> should.equal(1) sw("\u{2709}") |> should.equal(1) sw("\u{26a0}") |> should.equal(1)}
pub fn dakuten_test() { // https://github.com/sindresorhus/string-width/issues/55 sw("バ") |> should.equal(2) sw("パ") |> should.equal(2)}
pub fn ansi_test() { sw("\u{1B}[31m\u{1B}[39m") |> should.equal(0) swc("\u{1B}[31m\u{1B}[39m") |> should.equal(8) sw("\u{1B}]8;;https://github.com\u{0007}Click\u{1B}]8;;\u{0007}") |> should.equal(5)}
pub fn default_emoji_presentation_test() { // ⌚ sw("\u{231A}") |> should.equal(2)}
pub fn default_text_presentation_emoji_test() { // ↔️ default text presentation rendered as emoji sw:2 sw("\u{2194}") |> should.equal(1) // I do not know currently how to handle this properly. // sw("\u{2194}\u{FE0F}") |> should.equal(2)}
pub fn emoji_modifier_base_test() { // 👩 sw("\u{1F469}") |> should.equal(2)}
pub fn emoji_with_modifier_test() { sw("\u{1F469}\u{1F3FF}") |> should.equal(4)}
pub fn variation_selector_test() { sw("\u{845B}\u{E0100}") |> should.equal(2) sw("A\u{FE0F}") |> should.equal(1) sw("\u{FE0F}") |> should.equal(0)}
pub fn thai_test() { sw("_\u{0E34}") |> should.equal(1) sw("ปฏัก") |> should.equal(3)}
pub fn control_characters_test() { sw("\u{0}") |> should.equal(0) sw("\u{1f}") |> should.equal(0) sw("\u{7f}") |> should.equal(0) sw("\u{86}") |> should.equal(0) sw("\u{9f}") |> should.equal(0) sw("\u{1b}") |> should.equal(0)}
pub fn combining_characters_test() { sw("x\u{0300}") |> should.equal(1) sw("\u{0300}\u{0301}") |> should.equal(0) sw("e\u{0301}e") |> should.equal(2) sw("x\u{036f}") |> should.equal(1) sw("\u{036f}\u{036f}") |> should.equal(0)}
pub fn zwj_test() { sw("👶") |> should.equal(2) sw("👶🏽") |> should.equal(4) sw("👩👩👦👦") |> should.equal(8) sw("👨❤️💋👨") |> should.equal(7)}
pub fn zwj_cluster_test() { swg("👶") |> should.equal(2) swg("👶🏽") |> should.equal(2) swg("👩👩👦👦") |> should.equal(2) swg("👨❤️💋👨") |> should.equal(2) swg("\u{1F469}\u{1F3FF}") |> should.equal(2) swg("👩🎓") |> should.equal(2)}
pub fn zero_width_test() { sw("\u{200b}") |> should.equal(0) sw("x\u{200b}x") |> should.equal(2) sw("\u{200c}") |> should.equal(0) sw("x\u{200c}x") |> should.equal(2) sw("\u{200d}") |> should.equal(0) sw("x\u{200d}x") |> should.equal(2) sw("\u{feff}") |> should.equal(0) sw("x\u{feff}x") |> should.equal(2)}
pub fn variation_selectors_test() { // regional indicator symbol A with variation selector sw("\u{1f1e6}\u{fe0f}") |> should.equal(1) sw("A\u{fe0f}") |> should.equal(1) sw("\u{fe0f}") |> should.equal(0)}
pub fn edge_cases_test() { sw("") |> should.equal(0) sw("\u{200b}\u{200b}") |> should.equal(0) sw("x\u{200b}x\u{200b}") |> should.equal(2) sw("x\u{0300}x\u{0300}") |> should.equal(2) // 😀 with variation selector sw("😀\u{fe0f}") |> should.equal(2) sw("👩🎓") |> should.equal(4) // combining diacritical marks extended sw("x\u{1ab0}x\u{1ab0}") |> should.equal(2) // combining diacritical marks supplement sw("x\u{1dc0}x\u{1dc0}") |> should.equal(2) // combining diacritical marks for symbols sw("x\u{20d0}x\u{20d0}") |> should.equal(2) // combining half marks sw("x\u{fe20}x\u{fe20}") |> should.equal(2)}
pub fn ignores_default_ignorable_test() { // word joiner sw("\u{2060}") |> should.equal(0) // function application sw("\u{2061}") |> should.equal(0) // invisible times sw("\u{2062}") |> should.equal(0) // invisible separator sw("\u{2063}") |> should.equal(0) // invisible plus sw("\u{2064}") |> should.equal(0) // zero-width no-break space sw("\u{feff}") |> should.equal(0) sw("x\u{2060}x") |> should.equal(2) sw("x\u{2061}x") |> should.equal(2) sw("x\u{2062}x") |> should.equal(2) sw("x\u{2063}x") |> should.equal(2) sw("x\u{2064}x") |> should.equal(2) sw("x\u{feff}x") |> should.equal(2)}
pub fn fold_identity_test() { let reducer = fn(acc, chr, _) { acc <> chr } let do_test = fn(input, options) { string_width.fold_raw(input, options, "", reducer) |> should.equal(input) }
let default = string_width.new() let clusters = default |> string_width.mode_2027
do_test("äöüè", default) do_test("안녕하세요", default) do_test("🏳️⚧️", default) do_test("🏳️⚧️", clusters) do_test("👩👩👦👦", default) do_test("wobble\u{1B}[31mhello\u{1B}[39mwibble", default)}
pub fn tab_test() { sw("\t") |> should.equal(8) sw("asdf\t") |> should.equal(8) sw("1234567\t") |> should.equal(8) sw("12345678\t") |> should.equal(16) sw("wibble\twobble") |> should.equal(14)}
pub fn tab_offset_test() { let sw = fn(str, offset) { let options = string_width.new() |> string_width.at_tab_offset(offset)
string_width.line_with(str, options) }
sw("\t", 3) |> should.equal(5) sw("12345\t678", 3) |> should.equal(16)}
pub fn tab_fold_test() { string_width.fold("hello\tworld", [], list.prepend) |> list.reverse |> should.equal([ Piece("h", 0, 0, 1), Piece("e", 0, 1, 1), Piece("l", 0, 2, 1), Piece("l", 0, 3, 1), Piece("o", 0, 4, 1), Piece("\t", 0, 5, 3), Piece("w", 0, 8, 1), Piece("o", 0, 9, 1), Piece("r", 0, 10, 1), Piece("l", 0, 11, 1), Piece("d", 0, 12, 1), ])}