Measure the width of text in the terminal and build simple layouts!
0

Configure Feed

Select the types of activity you want to include in your feed.

fix ansi code matching

+39 -42
+15 -32
src/string_width/internal/ansi.gleam
··· 1 1 import gleam/list 2 - import gleam/string 3 2 4 3 type State { 5 4 Text ··· 28 27 @external(javascript, "../../string_width_ffi.mjs", "match_ansi") 29 28 pub fn match(str: String) -> List(#(Int, Int)) { 30 29 let #(_, _, matches) = { 31 - use #(pos, state, matches), cp <- fold_codepoints(str, #(0, Text, [])) 30 + use #(pos, state, matches), byte <- fold_bytes(str, #(0, Text, [])) 32 31 33 32 case state { 34 33 Text -> 35 - case cp { 34 + case byte { 36 35 // ESC 37 36 0x1b -> #(pos + 1, Escape(pos), matches) 38 - // C1 CSI 39 - 0x9b -> #(pos + 1, CSI(pos), matches) 40 - // C1 OSC 41 - 0x9d -> #(pos + 1, OSC(pos), matches) 42 37 _ -> #(pos + 1, state, matches) 43 38 } 44 39 45 40 Escape(start) -> 46 - case cp { 41 + case byte { 47 42 // '[' 48 43 0x5b -> #(pos + 1, CSI(start), matches) 49 44 // ']' 50 45 0x5d -> #(pos + 1, OSC(start), matches) 51 46 // a..z single character end 52 - _ if cp >= 0x61 && cp <= 0x7a -> { 47 + _ if byte >= 0x61 && byte <= 0x7a -> { 53 48 #(pos + 1, Text, [#(start, pos - start + 1), ..matches]) 54 49 } 55 50 // A..Z single character end 56 - _ if cp >= 0x41 && cp <= 0x5a -> { 51 + _ if byte >= 0x41 && byte <= 0x5a -> { 57 52 #(pos + 1, Text, [#(start, pos - start + 1), ..matches]) 58 53 } 59 54 // ignore (use OSC state) ··· 61 56 } 62 57 63 58 CSI(start) -> 64 - case cp { 59 + case byte { 65 60 // csi parameter or intermediate 66 - _ if cp >= 0x20 && cp <= 0x3f -> #(pos + 1, state, matches) 61 + _ if byte >= 0x20 && byte <= 0x3f -> #(pos + 1, state, matches) 67 62 // csi final range 68 - _ if cp >= 0x40 && cp <= 0x7e -> { 63 + _ if byte >= 0x40 && byte <= 0x7e -> { 69 64 #(pos + 1, Text, [#(start, pos - start + 1), ..matches]) 70 65 } 71 66 // invalid sequence ··· 73 68 } 74 69 75 70 OSC(start) -> 76 - case cp { 71 + case byte { 77 72 // BEL, ST 78 - 0x07 | 0x9c -> #(pos + 1, Text, [#(start, pos - start + 1), ..matches]) 73 + 0x07 -> #(pos + 1, Text, [#(start, pos - start + 1), ..matches]) 79 74 // ESC 80 75 0x1b -> #(pos + 1, OSCEsc(start), matches) 81 76 _ -> #(pos + 1, state, matches) 82 77 } 83 78 84 79 OSCEsc(start) -> 85 - case cp { 80 + case byte { 86 81 // \\, BEL, ST 87 - 0x5c | 0x07 | 0x9c -> { 82 + 0x5c | 0x07 -> { 88 83 #(pos + 1, Text, [#(start, pos - start + 1), ..matches]) 89 84 } 90 85 _ -> #(pos + 1, OSC(start), matches) ··· 95 90 list.reverse(matches) 96 91 } 97 92 98 - // Duplicates from string_width, but making them public does weird things to 99 - // codegen as far as I can remember and we want to be SURE we can go fast. 100 - 101 - // also, this makes it quite self-contained if i ever want to publish this. 102 - 103 - @external(erlang, "string_width_ffi", "fold_codepoints") 104 - @external(javascript, "../../string_width_ffi.mjs", "fold_codepoints") 105 - fn fold_codepoints( 106 - str: String, 107 - state: state, 108 - fun: fn(state, Int) -> state, 109 - ) -> state { 110 - use state, cp <- list.fold(string.to_utf_codepoints(str), state) 111 - fun(state, string.utf_codepoint_to_int(cp)) 112 - } 93 + @external(erlang, "string_width_ffi", "fold_bytes") 94 + @external(javascript, "../../string_width_ffi.mjs", "fold_bytes") 95 + fn fold_bytes(str: String, state: state, fun: fn(state, Int) -> state) -> state 113 96 114 97 @external(erlang, "erlang", "split_binary") 115 98 @external(javascript, "../../string_width_ffi.mjs", "unsafe_split")
+7 -7
src/string_width_ffi.erl
··· 1 1 -module(string_width_ffi). 2 2 -export([ 3 + fold_bytes/3, 3 4 fold_codepoints/3, 4 5 utf_codepoint_to_string/1, 5 6 get_terminal_size/0 6 7 ]). 7 8 8 - fold_codepoints(Binary, State, Fun) -> 9 - case Binary of 10 - <<Cp/utf8, Rest/binary>> -> fold_codepoints(Rest, Fun(State, Cp), Fun); 11 - _ -> State 12 - end. 9 + fold_bytes(<<Byte/integer, Rest/binary>>, State, Fun) -> fold_bytes(Rest, Fun(State, Byte), Fun); 10 + fold_bytes(_Binary, State, _Fun) -> State. 11 + 12 + fold_codepoints(<<Cp/utf8, Rest/binary>>, State, Fun) -> fold_codepoints(Rest, Fun(State, Cp), Fun); 13 + fold_codepoints(_Binary, State, _Fun) -> State. 13 14 14 - utf_codepoint_to_string(Cp) -> 15 - <<Cp/utf8>>. 15 + utf_codepoint_to_string(Cp) -> <<Cp/utf8>>. 16 16 17 17 get_terminal_size() -> 18 18 case {io:rows(), io:columns()} of
+8 -1
src/string_width_ffi.mjs
··· 1 1 import { toList, Ok, Error } from './gleam.mjs' 2 2 3 - const ANSI_RE = /(?:\x1b[a-zA-Z])|(?:(?:\x1b\[|\x9b)[\x20-\x3f]*?[\x40-\x7e])|(?:[\x1b\x0d].*?(?:\x07|\x1b\\|\x9c))/mg 3 + const ANSI_RE = /\x1b(?:[a-zA-Z]|(?:\[[\x20-\x3f]*?[\x40-\x7e])|(?:.*?(?:\x07|\x1b\\)))/mg 4 4 const ASCII_RE = /[\x20-\x7e]+(?=[\x20-\x7e]|$)/g; 5 5 const SEGMENTER = new Intl.Segmenter(); 6 6 ··· 70 70 71 71 export function unsafe_split(str, at) { 72 72 return [str.slice(0, at), str.slice(at)] 73 + } 74 + 75 + export function fold_bytes(str, state, fun) { 76 + for (let i = 0; i < str.length; ++i) { 77 + state = fun(state, str.charCodeAt(i)) 78 + } 79 + return state 73 80 } 74 81 75 82 export function fold_codepoints(str, state, fun) {
+9 -2
test/ansi_test.gleam
··· 43 43 } 44 44 45 45 pub fn match_link_test() { 46 - use st <- list.each(["\u{07}", "\u{1b}\u{5c}", "\u{9c}"]) 47 - let l = string.length(st) 46 + use st <- list.each(["\u{07}", "\u{1b}\u{5c}"]) 47 + let l = string.byte_size(st) 48 48 ansi.match( 49 49 "\u{001B}]8;k=v;https://example-a.com/?a_b=1&c=2#tit%20le" 50 50 <> st ··· 94 94 ansi.strip("wibble\u{001B}[00;38;5;244mwobble") 95 95 |> should.equal("wibblewobble") 96 96 } 97 + 98 + pub fn emoji_test() { 99 + ansi.strip( 100 + "πŸ‘©β€πŸ‘©β€πŸ‘¦β€πŸ‘¦\u{1b}[00;38;5;244mhello\u{1b}[mπŸ‘©β€πŸ‘©β€πŸ‘¦β€πŸ‘¦", 101 + ) 102 + |> should.equal("πŸ‘©β€πŸ‘©β€πŸ‘¦β€πŸ‘¦helloπŸ‘©β€πŸ‘©β€πŸ‘¦β€πŸ‘¦") 103 + }