r2ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +79 -0
- data/lib/r2ui/app.rb +311 -34
- data/lib/r2ui/canvas.rb +135 -14
- data/lib/r2ui/cli/builder.rb +72 -0
- data/lib/r2ui/cli/context.rb +45 -0
- data/lib/r2ui/cli/definition.rb +174 -0
- data/lib/r2ui/cli/ext/ask.rb +133 -0
- data/lib/r2ui/cli/ext/box.rb +112 -0
- data/lib/r2ui/cli/ext/choose.rb +162 -0
- data/lib/r2ui/cli/ext/choose_many.rb +241 -0
- data/lib/r2ui/cli/ext/color_option.rb +64 -0
- data/lib/r2ui/cli/ext/completion.rb +520 -0
- data/lib/r2ui/cli/ext/config.rb +179 -0
- data/lib/r2ui/cli/ext/confirm.rb +110 -0
- data/lib/r2ui/cli/ext/dashboard.rb +105 -0
- data/lib/r2ui/cli/ext/diff.rb +195 -0
- data/lib/r2ui/cli/ext/editor.rb +94 -0
- data/lib/r2ui/cli/ext/env.rb +108 -0
- data/lib/r2ui/cli/ext/examples.rb +62 -0
- data/lib/r2ui/cli/ext/filter.rb +269 -0
- data/lib/r2ui/cli/ext/format.rb +104 -0
- data/lib/r2ui/cli/ext/list.rb +117 -0
- data/lib/r2ui/cli/ext/log.rb +89 -0
- data/lib/r2ui/cli/ext/pager.rb +97 -0
- data/lib/r2ui/cli/ext/pairs.rb +62 -0
- data/lib/r2ui/cli/ext/parallel.rb +274 -0
- data/lib/r2ui/cli/ext/password.rb +158 -0
- data/lib/r2ui/cli/ext/progress.rb +330 -0
- data/lib/r2ui/cli/ext/reference.rb +129 -0
- data/lib/r2ui/cli/ext/spinner.rb +165 -0
- data/lib/r2ui/cli/ext/summary.rb +63 -0
- data/lib/r2ui/cli/ext/table.rb +141 -0
- data/lib/r2ui/cli/ext/tasks.rb +216 -0
- data/lib/r2ui/cli/ext/trace.rb +64 -0
- data/lib/r2ui/cli/ext/tree.rb +114 -0
- data/lib/r2ui/cli/ext/version.rb +67 -0
- data/lib/r2ui/cli/extension.rb +129 -0
- data/lib/r2ui/cli/help.rb +115 -0
- data/lib/r2ui/cli/helpers.rb +20 -0
- data/lib/r2ui/cli/live.rb +150 -0
- data/lib/r2ui/cli/parser.rb +165 -0
- data/lib/r2ui/cli/prompt.rb +133 -0
- data/lib/r2ui/cli/runner.rb +86 -0
- data/lib/r2ui/cli/shell.rb +185 -0
- data/lib/r2ui/cli/testing.rb +49 -0
- data/lib/r2ui/cli.rb +129 -0
- data/lib/r2ui/compat/bubbletea/LICENSE-bubbletea.txt +21 -0
- data/lib/r2ui/compat/bubbletea/ansi.rb +424 -0
- data/lib/r2ui/compat/bubbletea/commands.rb +143 -0
- data/lib/r2ui/compat/bubbletea/input.rb +269 -0
- data/lib/r2ui/compat/bubbletea/input_reader.rb +66 -0
- data/lib/r2ui/compat/bubbletea/messages.rb +251 -0
- data/lib/r2ui/compat/bubbletea/model.rb +65 -0
- data/lib/r2ui/compat/bubbletea/native.rb +254 -0
- data/lib/r2ui/compat/bubbletea/renderer.rb +192 -0
- data/lib/r2ui/compat/bubbletea/runner.rb +425 -0
- data/lib/r2ui/compat/bubbletea/terminal.rb +230 -0
- data/lib/r2ui/compat/bubbletea/version.rb +8 -0
- data/lib/r2ui/compat/bubbletea/width_table.rb +228 -0
- data/lib/r2ui/compat/bubbletea.rb +16 -0
- data/lib/r2ui/compat/lipgloss/border.rb +17 -0
- data/lib/r2ui/compat/lipgloss/color.rb +78 -0
- data/lib/r2ui/compat/lipgloss/color_blend.rb +166 -0
- data/lib/r2ui/compat/lipgloss/colorful.rb +585 -0
- data/lib/r2ui/compat/lipgloss/layout.rb +461 -0
- data/lib/r2ui/compat/lipgloss/list.rb +85 -0
- data/lib/r2ui/compat/lipgloss/position.rb +30 -0
- data/lib/r2ui/compat/lipgloss/style/border.rb +134 -0
- data/lib/r2ui/compat/lipgloss/style.rb +1062 -0
- data/lib/r2ui/compat/lipgloss/table/render.rb +518 -0
- data/lib/r2ui/compat/lipgloss/table.rb +185 -0
- data/lib/r2ui/compat/lipgloss/termenv.rb +768 -0
- data/lib/r2ui/compat/lipgloss/text/unicode_tables.rb +428 -0
- data/lib/r2ui/compat/lipgloss/text.rb +1555 -0
- data/lib/r2ui/compat/lipgloss/tree/go_tree.rb +243 -0
- data/lib/r2ui/compat/lipgloss/tree.rb +105 -0
- data/lib/r2ui/compat/lipgloss/version.rb +15 -0
- data/lib/r2ui/compat/lipgloss.rb +64 -0
- data/lib/r2ui/compat/load_path/bubbletea.rb +4 -0
- data/lib/r2ui/compat/load_path/lipgloss.rb +4 -0
- data/lib/r2ui/component.rb +67 -0
- data/lib/r2ui/context.rb +87 -0
- data/lib/r2ui/drop_in.rb +16 -0
- data/lib/r2ui/dsl/column.rb +11 -3
- data/lib/r2ui/dsl/dashboard.rb +45 -9
- data/lib/r2ui/dsl/resource.rb +12 -0
- data/lib/r2ui/ext/after.rb +54 -0
- data/lib/r2ui/ext/async.rb +81 -0
- data/lib/r2ui/ext/batch_sequence.rb +54 -0
- data/lib/r2ui/ext/countdown.rb +52 -0
- data/lib/r2ui/ext/data_table.rb +71 -0
- data/lib/r2ui/ext/every.rb +66 -0
- data/lib/r2ui/ext/exec.rb +116 -0
- data/lib/r2ui/ext/file_picker.rb +78 -0
- data/lib/r2ui/ext/focus_report.rb +69 -0
- data/lib/r2ui/ext/help.rb +62 -0
- data/lib/r2ui/ext/keys.rb +64 -0
- data/lib/r2ui/ext/list.rb +145 -0
- data/lib/r2ui/ext/messages.rb +87 -0
- data/lib/r2ui/ext/mouse.rb +134 -0
- data/lib/r2ui/ext/paginator.rb +86 -0
- data/lib/r2ui/ext/paste.rb +86 -0
- data/lib/r2ui/ext/println.rb +21 -0
- data/lib/r2ui/ext/progress.rb +85 -0
- data/lib/r2ui/ext/refresh.rb +56 -0
- data/lib/r2ui/ext/resize.rb +62 -0
- data/lib/r2ui/ext/screen.rb +47 -0
- data/lib/r2ui/ext/screen_mode.rb +63 -0
- data/lib/r2ui/ext/spinner.rb +59 -0
- data/lib/r2ui/ext/state.rb +45 -0
- data/lib/r2ui/ext/stopwatch.rb +67 -0
- data/lib/r2ui/ext/suspend.rb +56 -0
- data/lib/r2ui/ext/text_area.rb +73 -0
- data/lib/r2ui/ext/text_input.rb +83 -0
- data/lib/r2ui/ext/theme.rb +112 -0
- data/lib/r2ui/ext/view.rb +37 -0
- data/lib/r2ui/ext/viewport.rb +92 -0
- data/lib/r2ui/ext/window_title.rb +51 -0
- data/lib/r2ui/extension.rb +199 -0
- data/lib/r2ui/history.rb +4 -1
- data/lib/r2ui/keys.rb +35 -8
- data/lib/r2ui/motion.rb +169 -0
- data/lib/r2ui/panel_state.rb +46 -2
- data/lib/r2ui/renderer.rb +87 -22
- data/lib/r2ui/version.rb +1 -1
- data/lib/r2ui/widgets/box.rb +6 -3
- data/lib/r2ui/widgets/glyphs.rb +149 -0
- data/lib/r2ui/widgets/table.rb +109 -25
- data/lib/r2ui.rb +12 -1
- metadata +119 -4
- data/lib/r2ui/terminal.rb +0 -53
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
require_relative "width_table"
|
|
5
|
+
|
|
6
|
+
module R2UI
|
|
7
|
+
module Compat
|
|
8
|
+
module Tea
|
|
9
|
+
# Ports of charmbracelet/x/ansi v0.8.0 StringWidth and Truncate, which
|
|
10
|
+
# the bubbletea 0.1.4 gem's renderer uses. Escape sequences are found
|
|
11
|
+
# with the same DEC ANSI transition table; text is measured per
|
|
12
|
+
# grapheme cluster the way rivo/uniseg v0.4.7 measures it.
|
|
13
|
+
module ANSI
|
|
14
|
+
# Parser states (x/ansi/parser/const.go).
|
|
15
|
+
GROUND = 0
|
|
16
|
+
CSI_ENTRY = 1
|
|
17
|
+
CSI_INTERMEDIATE = 2
|
|
18
|
+
CSI_PARAM = 3
|
|
19
|
+
DCS_ENTRY = 4
|
|
20
|
+
DCS_INTERMEDIATE = 5
|
|
21
|
+
DCS_PARAM = 6
|
|
22
|
+
DCS_STRING = 7
|
|
23
|
+
ESCAPE = 8
|
|
24
|
+
ESCAPE_INTERMEDIATE = 9
|
|
25
|
+
OSC_STRING = 10
|
|
26
|
+
SOS_STRING = 11
|
|
27
|
+
PM_STRING = 12
|
|
28
|
+
APC_STRING = 13
|
|
29
|
+
UTF8 = 14
|
|
30
|
+
|
|
31
|
+
# Parser actions.
|
|
32
|
+
NONE = 0
|
|
33
|
+
CLEAR = 1
|
|
34
|
+
COLLECT = 2
|
|
35
|
+
PREFIX = 3
|
|
36
|
+
DISPATCH = 4
|
|
37
|
+
EXECUTE = 5
|
|
38
|
+
START = 6
|
|
39
|
+
PUT = 7
|
|
40
|
+
PARAM = 8
|
|
41
|
+
PRINT = 9
|
|
42
|
+
IGNORE = NONE
|
|
43
|
+
|
|
44
|
+
CURSOR_HOME_POSITION = "\e[H"
|
|
45
|
+
ERASE_ENTIRE_SCREEN = "\e[2J"
|
|
46
|
+
|
|
47
|
+
VS15 = 0xFE0E
|
|
48
|
+
VS16 = 0xFE0F
|
|
49
|
+
|
|
50
|
+
# x/ansi parser.GenerateTransitionTable, value = action << 4 | state,
|
|
51
|
+
# indexed by state << 8 | byte.
|
|
52
|
+
TABLE = begin
|
|
53
|
+
t = Array.new(4096, (NONE << 4) | GROUND)
|
|
54
|
+
one = ->(code, state, action, nxt) { t[(state << 8) | code] = (action << 4) | nxt }
|
|
55
|
+
many = ->(codes, state, action, nxt) { codes.each { |c| one.(c, state, action, nxt) } }
|
|
56
|
+
|
|
57
|
+
(GROUND..UTF8).each do |s|
|
|
58
|
+
many.([0x18, 0x1A, 0x99, 0x9A], s, EXECUTE, GROUND)
|
|
59
|
+
many.(0x80..0x8F, s, EXECUTE, GROUND)
|
|
60
|
+
many.(0x90..0x97, s, EXECUTE, GROUND)
|
|
61
|
+
one.(0x9C, s, EXECUTE, GROUND)
|
|
62
|
+
one.(0x1B, s, CLEAR, ESCAPE)
|
|
63
|
+
one.(0x98, s, START, SOS_STRING)
|
|
64
|
+
one.(0x9E, s, START, PM_STRING)
|
|
65
|
+
one.(0x9F, s, START, APC_STRING)
|
|
66
|
+
one.(0x9B, s, CLEAR, CSI_ENTRY)
|
|
67
|
+
one.(0x90, s, CLEAR, DCS_ENTRY)
|
|
68
|
+
one.(0x9D, s, START, OSC_STRING)
|
|
69
|
+
many.(0xC2..0xDF, s, COLLECT, UTF8)
|
|
70
|
+
many.(0xE0..0xEF, s, COLLECT, UTF8)
|
|
71
|
+
many.(0xF0..0xF4, s, COLLECT, UTF8)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
many.(0x00..0x17, GROUND, EXECUTE, GROUND)
|
|
75
|
+
one.(0x19, GROUND, EXECUTE, GROUND)
|
|
76
|
+
many.(0x1C..0x1F, GROUND, EXECUTE, GROUND)
|
|
77
|
+
many.(0x20..0x7E, GROUND, PRINT, GROUND)
|
|
78
|
+
one.(0x7F, GROUND, EXECUTE, GROUND)
|
|
79
|
+
|
|
80
|
+
many.(0x00..0x17, ESCAPE_INTERMEDIATE, EXECUTE, ESCAPE_INTERMEDIATE)
|
|
81
|
+
one.(0x19, ESCAPE_INTERMEDIATE, EXECUTE, ESCAPE_INTERMEDIATE)
|
|
82
|
+
many.(0x1C..0x1F, ESCAPE_INTERMEDIATE, EXECUTE, ESCAPE_INTERMEDIATE)
|
|
83
|
+
many.(0x20..0x2F, ESCAPE_INTERMEDIATE, COLLECT, ESCAPE_INTERMEDIATE)
|
|
84
|
+
one.(0x7F, ESCAPE_INTERMEDIATE, IGNORE, ESCAPE_INTERMEDIATE)
|
|
85
|
+
many.(0x30..0x7E, ESCAPE_INTERMEDIATE, DISPATCH, GROUND)
|
|
86
|
+
|
|
87
|
+
many.(0x00..0x17, ESCAPE, EXECUTE, ESCAPE)
|
|
88
|
+
one.(0x19, ESCAPE, EXECUTE, ESCAPE)
|
|
89
|
+
many.(0x1C..0x1F, ESCAPE, EXECUTE, ESCAPE)
|
|
90
|
+
one.(0x7F, ESCAPE, IGNORE, ESCAPE)
|
|
91
|
+
many.(0x30..0x4F, ESCAPE, DISPATCH, GROUND)
|
|
92
|
+
many.(0x51..0x57, ESCAPE, DISPATCH, GROUND)
|
|
93
|
+
one.(0x59, ESCAPE, DISPATCH, GROUND)
|
|
94
|
+
one.(0x5A, ESCAPE, DISPATCH, GROUND)
|
|
95
|
+
one.(0x5C, ESCAPE, DISPATCH, GROUND)
|
|
96
|
+
many.(0x60..0x7E, ESCAPE, DISPATCH, GROUND)
|
|
97
|
+
many.(0x20..0x2F, ESCAPE, COLLECT, ESCAPE_INTERMEDIATE)
|
|
98
|
+
one.(0x58, ESCAPE, START, SOS_STRING)
|
|
99
|
+
one.(0x5E, ESCAPE, START, PM_STRING)
|
|
100
|
+
one.(0x5F, ESCAPE, START, APC_STRING)
|
|
101
|
+
one.(0x50, ESCAPE, CLEAR, DCS_ENTRY)
|
|
102
|
+
one.(0x5B, ESCAPE, CLEAR, CSI_ENTRY)
|
|
103
|
+
one.(0x5D, ESCAPE, START, OSC_STRING)
|
|
104
|
+
|
|
105
|
+
(SOS_STRING..APC_STRING).each do |s|
|
|
106
|
+
many.(0x00..0x17, s, PUT, s)
|
|
107
|
+
one.(0x19, s, PUT, s)
|
|
108
|
+
many.(0x1C..0x1F, s, PUT, s)
|
|
109
|
+
many.(0x20..0x7F, s, PUT, s)
|
|
110
|
+
one.(0x1B, s, DISPATCH, ESCAPE)
|
|
111
|
+
one.(0x9C, s, DISPATCH, GROUND)
|
|
112
|
+
many.([0x18, 0x1A], s, IGNORE, GROUND)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
many.(0x00..0x07, DCS_ENTRY, IGNORE, DCS_ENTRY)
|
|
116
|
+
many.(0x0E..0x17, DCS_ENTRY, IGNORE, DCS_ENTRY)
|
|
117
|
+
one.(0x19, DCS_ENTRY, IGNORE, DCS_ENTRY)
|
|
118
|
+
many.(0x1C..0x1F, DCS_ENTRY, IGNORE, DCS_ENTRY)
|
|
119
|
+
one.(0x7F, DCS_ENTRY, IGNORE, DCS_ENTRY)
|
|
120
|
+
many.(0x20..0x2F, DCS_ENTRY, COLLECT, DCS_INTERMEDIATE)
|
|
121
|
+
many.(0x30..0x3B, DCS_ENTRY, PARAM, DCS_PARAM)
|
|
122
|
+
many.(0x3C..0x3F, DCS_ENTRY, PREFIX, DCS_PARAM)
|
|
123
|
+
many.(0x08..0x0D, DCS_ENTRY, PUT, DCS_STRING)
|
|
124
|
+
one.(0x1B, DCS_ENTRY, PUT, DCS_STRING)
|
|
125
|
+
many.(0x40..0x7E, DCS_ENTRY, START, DCS_STRING)
|
|
126
|
+
|
|
127
|
+
many.(0x00..0x17, DCS_INTERMEDIATE, IGNORE, DCS_INTERMEDIATE)
|
|
128
|
+
one.(0x19, DCS_INTERMEDIATE, IGNORE, DCS_INTERMEDIATE)
|
|
129
|
+
many.(0x1C..0x1F, DCS_INTERMEDIATE, IGNORE, DCS_INTERMEDIATE)
|
|
130
|
+
many.(0x20..0x2F, DCS_INTERMEDIATE, COLLECT, DCS_INTERMEDIATE)
|
|
131
|
+
one.(0x7F, DCS_INTERMEDIATE, IGNORE, DCS_INTERMEDIATE)
|
|
132
|
+
many.(0x30..0x3F, DCS_INTERMEDIATE, START, DCS_STRING)
|
|
133
|
+
many.(0x40..0x7E, DCS_INTERMEDIATE, START, DCS_STRING)
|
|
134
|
+
|
|
135
|
+
many.(0x00..0x17, DCS_PARAM, IGNORE, DCS_PARAM)
|
|
136
|
+
one.(0x19, DCS_PARAM, IGNORE, DCS_PARAM)
|
|
137
|
+
many.(0x1C..0x1F, DCS_PARAM, IGNORE, DCS_PARAM)
|
|
138
|
+
many.(0x30..0x3B, DCS_PARAM, PARAM, DCS_PARAM)
|
|
139
|
+
one.(0x7F, DCS_PARAM, IGNORE, DCS_PARAM)
|
|
140
|
+
many.(0x3C..0x3F, DCS_PARAM, IGNORE, DCS_PARAM)
|
|
141
|
+
many.(0x20..0x2F, DCS_PARAM, COLLECT, DCS_INTERMEDIATE)
|
|
142
|
+
many.(0x40..0x7E, DCS_PARAM, START, DCS_STRING)
|
|
143
|
+
|
|
144
|
+
many.(0x00..0x17, DCS_STRING, PUT, DCS_STRING)
|
|
145
|
+
one.(0x19, DCS_STRING, PUT, DCS_STRING)
|
|
146
|
+
many.(0x1C..0x1F, DCS_STRING, PUT, DCS_STRING)
|
|
147
|
+
many.(0x20..0x7E, DCS_STRING, PUT, DCS_STRING)
|
|
148
|
+
one.(0x7F, DCS_STRING, PUT, DCS_STRING)
|
|
149
|
+
many.(0x80..0xFF, DCS_STRING, PUT, DCS_STRING)
|
|
150
|
+
one.(0x1B, DCS_STRING, DISPATCH, ESCAPE)
|
|
151
|
+
one.(0x9C, DCS_STRING, DISPATCH, GROUND)
|
|
152
|
+
many.([0x18, 0x1A], DCS_STRING, IGNORE, GROUND)
|
|
153
|
+
|
|
154
|
+
many.(0x00..0x17, CSI_PARAM, EXECUTE, CSI_PARAM)
|
|
155
|
+
one.(0x19, CSI_PARAM, EXECUTE, CSI_PARAM)
|
|
156
|
+
many.(0x1C..0x1F, CSI_PARAM, EXECUTE, CSI_PARAM)
|
|
157
|
+
many.(0x30..0x3B, CSI_PARAM, PARAM, CSI_PARAM)
|
|
158
|
+
one.(0x7F, CSI_PARAM, IGNORE, CSI_PARAM)
|
|
159
|
+
many.(0x3C..0x3F, CSI_PARAM, IGNORE, CSI_PARAM)
|
|
160
|
+
many.(0x40..0x7E, CSI_PARAM, DISPATCH, GROUND)
|
|
161
|
+
many.(0x20..0x2F, CSI_PARAM, COLLECT, CSI_INTERMEDIATE)
|
|
162
|
+
|
|
163
|
+
many.(0x00..0x17, CSI_INTERMEDIATE, EXECUTE, CSI_INTERMEDIATE)
|
|
164
|
+
one.(0x19, CSI_INTERMEDIATE, EXECUTE, CSI_INTERMEDIATE)
|
|
165
|
+
many.(0x1C..0x1F, CSI_INTERMEDIATE, EXECUTE, CSI_INTERMEDIATE)
|
|
166
|
+
many.(0x20..0x2F, CSI_INTERMEDIATE, COLLECT, CSI_INTERMEDIATE)
|
|
167
|
+
one.(0x7F, CSI_INTERMEDIATE, IGNORE, CSI_INTERMEDIATE)
|
|
168
|
+
many.(0x40..0x7E, CSI_INTERMEDIATE, DISPATCH, GROUND)
|
|
169
|
+
many.(0x30..0x3F, CSI_INTERMEDIATE, IGNORE, GROUND)
|
|
170
|
+
|
|
171
|
+
many.(0x00..0x17, CSI_ENTRY, EXECUTE, CSI_ENTRY)
|
|
172
|
+
one.(0x19, CSI_ENTRY, EXECUTE, CSI_ENTRY)
|
|
173
|
+
many.(0x1C..0x1F, CSI_ENTRY, EXECUTE, CSI_ENTRY)
|
|
174
|
+
one.(0x7F, CSI_ENTRY, IGNORE, CSI_ENTRY)
|
|
175
|
+
many.(0x40..0x7E, CSI_ENTRY, DISPATCH, GROUND)
|
|
176
|
+
many.(0x20..0x2F, CSI_ENTRY, COLLECT, CSI_INTERMEDIATE)
|
|
177
|
+
many.(0x30..0x3B, CSI_ENTRY, PARAM, CSI_PARAM)
|
|
178
|
+
many.(0x3C..0x3F, CSI_ENTRY, PREFIX, CSI_PARAM)
|
|
179
|
+
|
|
180
|
+
many.(0x00..0x06, OSC_STRING, IGNORE, OSC_STRING)
|
|
181
|
+
many.(0x08..0x17, OSC_STRING, IGNORE, OSC_STRING)
|
|
182
|
+
one.(0x19, OSC_STRING, IGNORE, OSC_STRING)
|
|
183
|
+
many.(0x1C..0x1F, OSC_STRING, IGNORE, OSC_STRING)
|
|
184
|
+
many.(0x20..0xFF, OSC_STRING, PUT, OSC_STRING)
|
|
185
|
+
one.(0x1B, OSC_STRING, DISPATCH, ESCAPE)
|
|
186
|
+
one.(0x07, OSC_STRING, DISPATCH, GROUND)
|
|
187
|
+
one.(0x9C, OSC_STRING, DISPATCH, GROUND)
|
|
188
|
+
many.([0x18, 0x1A], OSC_STRING, IGNORE, GROUND)
|
|
189
|
+
|
|
190
|
+
t.freeze
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
PLAIN = /[^\x20-\x7E]/n
|
|
194
|
+
PLAIN_UTF8 = /[^\x20-\x7E]/
|
|
195
|
+
GRAPHEME = /\X/m
|
|
196
|
+
FFFD = "\u{FFFD}"
|
|
197
|
+
# Bounded memo tables: codepoint => width for one-rune clusters and
|
|
198
|
+
# cluster bytes => width for longer ones (both repeat heavily).
|
|
199
|
+
CACHE_LIMIT = 4096
|
|
200
|
+
RUNE_WIDTHS = {} # rubocop:disable Style/MutableConstant
|
|
201
|
+
CLUSTER_WIDTHS = {} # rubocop:disable Style/MutableConstant
|
|
202
|
+
private_constant :PLAIN, :PLAIN_UTF8, :GRAPHEME, :FFFD, :CACHE_LIMIT, :RUNE_WIDTHS, :CLUSTER_WIDTHS
|
|
203
|
+
|
|
204
|
+
module_function
|
|
205
|
+
|
|
206
|
+
# x/ansi StringWidth: cells the string occupies; escape sequences
|
|
207
|
+
# are zero-width.
|
|
208
|
+
def string_width(str)
|
|
209
|
+
return 0 if str.empty?
|
|
210
|
+
|
|
211
|
+
s = utf8_view(str)
|
|
212
|
+
valid = s.valid_encoding?
|
|
213
|
+
if valid
|
|
214
|
+
return s.bytesize unless s.match?(PLAIN_UTF8)
|
|
215
|
+
else
|
|
216
|
+
s = s.b
|
|
217
|
+
return s.bytesize unless s.match?(PLAIN)
|
|
218
|
+
end
|
|
219
|
+
scanner = nil
|
|
220
|
+
|
|
221
|
+
pstate = GROUND
|
|
222
|
+
width = 0
|
|
223
|
+
i = 0
|
|
224
|
+
n = s.bytesize
|
|
225
|
+
while i < n
|
|
226
|
+
v = TABLE[(pstate << 8) | s.getbyte(i)]
|
|
227
|
+
state = v & 15
|
|
228
|
+
if state == UTF8
|
|
229
|
+
if valid
|
|
230
|
+
scanner ||= StringScanner.new(s)
|
|
231
|
+
scanner.pos = i
|
|
232
|
+
len = scanner.skip(GRAPHEME)
|
|
233
|
+
w = valid_cluster_width(s, i, len)
|
|
234
|
+
else
|
|
235
|
+
len, w = first_grapheme_cluster(s, i)
|
|
236
|
+
end
|
|
237
|
+
width += w
|
|
238
|
+
i += len
|
|
239
|
+
pstate = GROUND
|
|
240
|
+
next
|
|
241
|
+
end
|
|
242
|
+
width += 1 if (v >> 4) == PRINT
|
|
243
|
+
pstate = state
|
|
244
|
+
i += 1
|
|
245
|
+
end
|
|
246
|
+
width
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# The string's bytes read as UTF-8 (what Go's string is), copying
|
|
250
|
+
# only when its encoding says otherwise.
|
|
251
|
+
def utf8_view(str)
|
|
252
|
+
return str if str.encoding == Encoding::UTF_8 || str.ascii_only?
|
|
253
|
+
|
|
254
|
+
str.b.force_encoding(Encoding::UTF_8)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Width of the cluster at byte i of valid UTF-8 `s`, `len` bytes
|
|
258
|
+
# long; i is a lead byte (0xC2..0xF4).
|
|
259
|
+
def valid_cluster_width(s, i, len)
|
|
260
|
+
lead = s.getbyte(i)
|
|
261
|
+
if lead < 0xE0
|
|
262
|
+
if len == 2
|
|
263
|
+
return rune_width(((lead & 0x1F) << 6) | (s.getbyte(i + 1) & 0x3F))
|
|
264
|
+
end
|
|
265
|
+
elsif lead < 0xF0
|
|
266
|
+
if len == 3
|
|
267
|
+
return rune_width(((lead & 0x0F) << 12) | ((s.getbyte(i + 1) & 0x3F) << 6) |
|
|
268
|
+
(s.getbyte(i + 2) & 0x3F))
|
|
269
|
+
end
|
|
270
|
+
elsif len == 4
|
|
271
|
+
return rune_width(((lead & 0x07) << 18) | ((s.getbyte(i + 1) & 0x3F) << 12) |
|
|
272
|
+
((s.getbyte(i + 2) & 0x3F) << 6) | (s.getbyte(i + 3) & 0x3F))
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
cluster = s.byteslice(i, len)
|
|
276
|
+
CLUSTER_WIDTHS.fetch(cluster) do
|
|
277
|
+
CLUSTER_WIDTHS.clear if CLUSTER_WIDTHS.size >= CACHE_LIMIT
|
|
278
|
+
CLUSTER_WIDTHS[cluster] = cluster_width(cluster.codepoints)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def rune_width(cp)
|
|
283
|
+
RUNE_WIDTHS.fetch(cp) do
|
|
284
|
+
RUNE_WIDTHS.clear if RUNE_WIDTHS.size >= CACHE_LIMIT
|
|
285
|
+
RUNE_WIDTHS[cp] = WidthTable.width(cp)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# x/ansi Truncate(s, length, ""): cut the string to at most `length`
|
|
290
|
+
# cells, keeping every escape sequence (including those after the cut).
|
|
291
|
+
def truncate(str, length, tail = "")
|
|
292
|
+
return str if string_width(str) <= length
|
|
293
|
+
|
|
294
|
+
length -= string_width(tail)
|
|
295
|
+
return +"" if length.negative?
|
|
296
|
+
|
|
297
|
+
b = str.b
|
|
298
|
+
u = utf8_view(str)
|
|
299
|
+
scanner = u.valid_encoding? ? StringScanner.new(u) : nil
|
|
300
|
+
buf = +"".b
|
|
301
|
+
tail = tail.b
|
|
302
|
+
cur_width = 0
|
|
303
|
+
ignoring = false
|
|
304
|
+
pstate = GROUND
|
|
305
|
+
i = 0
|
|
306
|
+
n = b.bytesize
|
|
307
|
+
while i < n
|
|
308
|
+
byte = b.getbyte(i)
|
|
309
|
+
v = TABLE[(pstate << 8) | byte]
|
|
310
|
+
state = v & 15
|
|
311
|
+
if state == UTF8
|
|
312
|
+
if scanner
|
|
313
|
+
scanner.pos = i
|
|
314
|
+
len = scanner.skip(GRAPHEME)
|
|
315
|
+
width = valid_cluster_width(u, i, len)
|
|
316
|
+
else
|
|
317
|
+
len, width = first_grapheme_cluster(b, i)
|
|
318
|
+
end
|
|
319
|
+
cluster_start = i
|
|
320
|
+
i += len
|
|
321
|
+
next if ignoring
|
|
322
|
+
|
|
323
|
+
if cur_width + width > length
|
|
324
|
+
ignoring = true
|
|
325
|
+
buf << tail
|
|
326
|
+
next
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
cur_width += width
|
|
330
|
+
buf << b.byteslice(cluster_start, len)
|
|
331
|
+
pstate = GROUND
|
|
332
|
+
next
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
if (v >> 4) == PRINT
|
|
336
|
+
if cur_width >= length && !ignoring
|
|
337
|
+
ignoring = true
|
|
338
|
+
buf << tail
|
|
339
|
+
end
|
|
340
|
+
if ignoring
|
|
341
|
+
i += 1
|
|
342
|
+
next
|
|
343
|
+
end
|
|
344
|
+
cur_width += 1
|
|
345
|
+
end
|
|
346
|
+
buf << byte
|
|
347
|
+
i += 1
|
|
348
|
+
|
|
349
|
+
pstate = state
|
|
350
|
+
if cur_width > length && !ignoring
|
|
351
|
+
ignoring = true
|
|
352
|
+
buf << tail
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
buf.force_encoding(str.encoding)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# uniseg FirstGraphemeCluster(b[i:], -1): byte length and width of
|
|
360
|
+
# the grapheme cluster starting at byte i.
|
|
361
|
+
def first_grapheme_cluster(bytes, i)
|
|
362
|
+
want = 8
|
|
363
|
+
loop do
|
|
364
|
+
runes, lens, done = decode_runes(bytes, i, want)
|
|
365
|
+
text = runes.pack("U*")
|
|
366
|
+
cluster = text[/\A\X/m]
|
|
367
|
+
count = cluster.length
|
|
368
|
+
if count < runes.length || done
|
|
369
|
+
return [lens.first(count).sum, cluster_width(runes.first(count))]
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
want *= 2
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Decodes up to `max` runes from byte i the way Go's utf8.DecodeRune
|
|
377
|
+
# does: invalid bytes become U+FFFD, one byte each.
|
|
378
|
+
def decode_runes(bytes, i, max)
|
|
379
|
+
runes = []
|
|
380
|
+
lens = []
|
|
381
|
+
n = bytes.bytesize
|
|
382
|
+
while i < n && runes.length < max
|
|
383
|
+
lead = bytes.getbyte(i)
|
|
384
|
+
size = if lead < 0x80 then 1
|
|
385
|
+
elsif lead >= 0xC2 && lead <= 0xDF then 2
|
|
386
|
+
elsif lead >= 0xE0 && lead <= 0xEF then 3
|
|
387
|
+
elsif lead >= 0xF0 && lead <= 0xF4 then 4
|
|
388
|
+
else 0
|
|
389
|
+
end
|
|
390
|
+
char = size.positive? && bytes.byteslice(i, size).force_encoding(Encoding::UTF_8)
|
|
391
|
+
if char && char.bytesize == size && char.valid_encoding?
|
|
392
|
+
runes << char.ord
|
|
393
|
+
lens << size
|
|
394
|
+
i += size
|
|
395
|
+
else
|
|
396
|
+
runes << 0xFFFD
|
|
397
|
+
lens << 1
|
|
398
|
+
i += 1
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
[runes, lens, i >= n]
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# uniseg's width for one cluster: the first rune's width; then for
|
|
405
|
+
# Extended_Pictographic VS15/VS16 set 1/2, for regional indicators
|
|
406
|
+
# and Hangul L nothing is added, otherwise each rune's width adds.
|
|
407
|
+
def cluster_width(runes)
|
|
408
|
+
first = runes.first
|
|
409
|
+
width = WidthTable.width(first)
|
|
410
|
+
if WidthTable.extended_pictographic?(first)
|
|
411
|
+
runes.drop(1).each do |r|
|
|
412
|
+
if r == VS15 then width = 1
|
|
413
|
+
elsif r == VS16 then width = 2
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
elsif !WidthTable.width_of_first_only?(first)
|
|
417
|
+
runes.drop(1).each { |r| width += WidthTable.width(r) }
|
|
418
|
+
end
|
|
419
|
+
width
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copied unchanged from bubbletea 0.1.4 (https://github.com/marcoroth/bubbletea-ruby).
|
|
4
|
+
# Copyright (c) 2025 Marco Roth. MIT License; see LICENSE-bubbletea.txt in this directory.
|
|
5
|
+
|
|
6
|
+
module Bubbletea
|
|
7
|
+
class Command
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class QuitCommand < Command
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class BatchCommand < Command
|
|
14
|
+
attr_reader :commands
|
|
15
|
+
|
|
16
|
+
def initialize(commands)
|
|
17
|
+
super()
|
|
18
|
+
|
|
19
|
+
@commands = commands
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class TickCommand < Command
|
|
24
|
+
attr_reader :duration, :callback
|
|
25
|
+
|
|
26
|
+
def initialize(duration, &callback)
|
|
27
|
+
super()
|
|
28
|
+
|
|
29
|
+
@duration = duration
|
|
30
|
+
@callback = callback
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class SendMessage < Command
|
|
35
|
+
attr_reader :message, :delay
|
|
36
|
+
|
|
37
|
+
def initialize(message, delay: 0)
|
|
38
|
+
super()
|
|
39
|
+
|
|
40
|
+
@message = message
|
|
41
|
+
@delay = delay
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class SequenceCommand < Command
|
|
46
|
+
attr_reader :commands
|
|
47
|
+
|
|
48
|
+
def initialize(commands)
|
|
49
|
+
super()
|
|
50
|
+
|
|
51
|
+
@commands = commands
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class EnterAltScreenCommand < Command
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class ExitAltScreenCommand < Command
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class SetWindowTitleCommand < Command
|
|
62
|
+
attr_reader :title
|
|
63
|
+
|
|
64
|
+
def initialize(title)
|
|
65
|
+
super()
|
|
66
|
+
|
|
67
|
+
@title = title
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class PutsCommand < Command
|
|
72
|
+
attr_reader :text
|
|
73
|
+
|
|
74
|
+
def initialize(text)
|
|
75
|
+
super()
|
|
76
|
+
|
|
77
|
+
@text = text
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class SuspendCommand < Command
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class ExecCommand < Command
|
|
85
|
+
attr_reader :callable, :message
|
|
86
|
+
|
|
87
|
+
def initialize(callable, message: nil)
|
|
88
|
+
super()
|
|
89
|
+
@callable = callable
|
|
90
|
+
@message = message
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class << self
|
|
95
|
+
def quit
|
|
96
|
+
QuitCommand.new
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def batch(*commands)
|
|
100
|
+
BatchCommand.new(commands.flatten.compact)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def tick(duration, &)
|
|
104
|
+
TickCommand.new(duration, &)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def send_message(message, delay: 0)
|
|
108
|
+
SendMessage.new(message, delay: delay)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def sequence(*commands)
|
|
112
|
+
SequenceCommand.new(commands.flatten.compact)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def none
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def enter_alt_screen
|
|
120
|
+
EnterAltScreenCommand.new
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def exit_alt_screen
|
|
124
|
+
ExitAltScreenCommand.new
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def set_window_title(title) # rubocop:disable Naming/AccessorMethodName
|
|
128
|
+
SetWindowTitleCommand.new(title)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def puts(text)
|
|
132
|
+
PutsCommand.new(text)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def suspend
|
|
136
|
+
SuspendCommand.new
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def exec(callable, message: nil)
|
|
140
|
+
ExecCommand.new(callable, message: message)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|