text.bend
bend-kit-tty@0.1.0.0 · 46 lines · 0 definitions
1# Pure terminal text: ANSI SGR styling and Unicode 17 display width.
2import Base
3import bend-kit-unicode@0.1.0.0/unicode.bend as Unicode
4import bend-kit-tty@0.1.0.0/width.bend as W
5
6# Wrap text in one numeric SGR code and a reset when enabled; otherwise return it unchanged.
7def style(enabled: Bool, code: U32, text: String) -> String:
8 match enabled:
9 case True{}:
10 String.concat(["\u{1B}[", U32.show(code), "m", text, "\u{1B}[0m"])
11 case False{}:
12 text
13
14def vs16(s: String) -> Bool:
15 match s:
16 case SNil{}:
17 False{}
18 case SCon{Chr{+c}, t}:
19 U32.is_eq(c, 65039) || vs16(t)
20
21# A grapheme cluster takes its first code point's columns (W.w); VS16 makes a narrow base an emoji, 2 wide.
22def cluster(g: String) -> U32:
23 match g:
24 case SNil{}:
25 0
26 case SCon{Chr{+c}, t}:
27 +w = W.w(c)
28 Bool.pick(U32, U32.is_eq(w, 1) && vs16(t), 2, w)
29
30def width.go(gs: List<&2, String>, +acc: U32) -> U32:
31 match gs:
32 case Nil{}:
33 acc
34 case Con{g, t}:
35 width.go(t, (acc + cluster(g) : U32))
36
37# Display columns: East Asian Wide and Fullwidth clusters are 2, marks and controls 0, Ambiguous 1.
38# Escape sequences are not stripped: style()'s output is wider than what the terminal shows.
39def width(text: String) -> U32:
40 width.go(Unicode.graphemes(text), 0)
41
42# Append spaces until text occupies at least cols display columns.
43def pad_right(+text: String, +cols: U32) -> String:
44 +w = width(text)
45 +n = Bool.pick(U32, U32.is_lt(w, cols), (cols - w : U32), 0)
46 String.append(text, String.repeat(" ", U32.to_nat(n)))