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)))