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,461 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Lipgloss join / place / width / height / size and the version strings: a port of lipgloss v1.1.0
|
|
4
|
+
# join.go, position.go, size.go and whitespace.go, behind the same Ruby-side argument handling as the
|
|
5
|
+
# lipgloss gem 0.2.2 C extension (extension.c) and its Go glue (go/layout.go).
|
|
6
|
+
module R2UI
|
|
7
|
+
module Compat
|
|
8
|
+
module Gloss
|
|
9
|
+
# Layout internals. The public entry points are the Lipgloss singleton methods defined below.
|
|
10
|
+
module Layout
|
|
11
|
+
UTF_8 = Encoding::UTF_8
|
|
12
|
+
|
|
13
|
+
# Sentinel: the gem's JSON round trip ([]string unmarshal) failed, so join returns "".
|
|
14
|
+
JSON_FAIL = Object.new.freeze
|
|
15
|
+
|
|
16
|
+
extend self
|
|
17
|
+
|
|
18
|
+
# --- Ruby C API argument conversions ------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
# The name rb_unexpected_type / rb_convert_type print for a value.
|
|
21
|
+
def type_name(value)
|
|
22
|
+
case value
|
|
23
|
+
when nil then "nil"
|
|
24
|
+
when true then "true"
|
|
25
|
+
when false then "false"
|
|
26
|
+
else value.class.name
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check_Type(value, T_STRING)
|
|
31
|
+
def check_string(value)
|
|
32
|
+
return if value.is_a?(String)
|
|
33
|
+
|
|
34
|
+
raise TypeError, "wrong argument type #{type_name(value)} (expected String)"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check_Type(value, T_ARRAY)
|
|
38
|
+
def check_array(value)
|
|
39
|
+
return if value.is_a?(Array)
|
|
40
|
+
|
|
41
|
+
raise TypeError, "wrong argument type #{type_name(value)} (expected Array)"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# StringValueCStr: implicit #to_str conversion, no NUL bytes.
|
|
45
|
+
def c_string(value)
|
|
46
|
+
str = String.try_convert(value)
|
|
47
|
+
raise TypeError, "no implicit conversion of #{type_name(value)} into String" if str.nil?
|
|
48
|
+
raise ArgumentError, "string contains null byte" if str.include?("\0")
|
|
49
|
+
|
|
50
|
+
str
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
INT_MIN = -(2**31)
|
|
54
|
+
INT_MAX = 2**31 - 1
|
|
55
|
+
|
|
56
|
+
# NUM2INT (rb_num2int on a 64-bit platform).
|
|
57
|
+
def num2int(value)
|
|
58
|
+
raise TypeError, "no implicit conversion from nil to integer" if value.nil?
|
|
59
|
+
|
|
60
|
+
i = case value
|
|
61
|
+
when Integer then value
|
|
62
|
+
when Float
|
|
63
|
+
unless value.finite? && value < 9_223_372_036_854_775_808.0 && value >= -9_223_372_036_854_775_808.0
|
|
64
|
+
raise RangeError, "float #{float_out_of_range_repr(value)} out of range of integer"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
value.to_i
|
|
68
|
+
else
|
|
69
|
+
conv = Integer.try_convert(value)
|
|
70
|
+
raise TypeError, "no implicit conversion of #{type_name(value)} into Integer" if conv.nil?
|
|
71
|
+
|
|
72
|
+
conv
|
|
73
|
+
end
|
|
74
|
+
if i.bit_length > 63
|
|
75
|
+
raise RangeError, "#{i.negative? ? 'bignum too small' : 'bignum too big'} to convert into 'long'"
|
|
76
|
+
end
|
|
77
|
+
raise RangeError, "integer #{i} too small to convert to 'int'" if i < INT_MIN
|
|
78
|
+
raise RangeError, "integer #{i} too big to convert to 'int'" if i > INT_MAX
|
|
79
|
+
|
|
80
|
+
i
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# NUM2DBL (rb_num2dbl).
|
|
84
|
+
def num2dbl(value)
|
|
85
|
+
case value
|
|
86
|
+
when Float then value
|
|
87
|
+
when Integer then value.to_f
|
|
88
|
+
when String then raise TypeError, "no implicit conversion to float from string"
|
|
89
|
+
when nil then raise TypeError, "no implicit conversion to float from nil"
|
|
90
|
+
when true then raise TypeError, "no implicit conversion to float from true"
|
|
91
|
+
when false then raise TypeError, "no implicit conversion to float from false"
|
|
92
|
+
else
|
|
93
|
+
raise TypeError, "can't convert #{type_name(value)} into Float" unless value.respond_to?(:to_f)
|
|
94
|
+
|
|
95
|
+
f = value.to_f
|
|
96
|
+
unless f.is_a?(Float)
|
|
97
|
+
raise TypeError, "can't convert #{type_name(value)} to Float (#{type_name(value)}#to_f gives #{type_name(f)})"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
f
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def float_out_of_range_repr(f)
|
|
105
|
+
return f.to_s if f.nan? || f.infinite?
|
|
106
|
+
|
|
107
|
+
format("%-.10g", f)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# rb_utf8_str_new_cstr(C.CString(result)): a fresh UTF-8 string, cut at the first NUL.
|
|
111
|
+
def c_result(str)
|
|
112
|
+
s = String.new(str, encoding: UTF_8)
|
|
113
|
+
nul = s.b.index("\0")
|
|
114
|
+
s = s.byteslice(0, nul).force_encoding(UTF_8) if nul
|
|
115
|
+
s
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The gem passes the array to Go as JSON (Array#to_json) and unmarshals it into []string. A
|
|
119
|
+
# JSON string element survives, null becomes "", anything else fails the whole unmarshal.
|
|
120
|
+
# Objects without a JSON representation of their own serialize as their #to_s string.
|
|
121
|
+
def json_strings(array)
|
|
122
|
+
array.map do |e|
|
|
123
|
+
case e
|
|
124
|
+
when String then e
|
|
125
|
+
when nil then ""
|
|
126
|
+
when Symbol then e.to_s
|
|
127
|
+
when Numeric, true, false, Array, Hash then return JSON_FAIL
|
|
128
|
+
else e.to_s
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# --- lipgloss v1.1.0 ----------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
def str_width(s) = Text.string_width(s)
|
|
136
|
+
|
|
137
|
+
# Position.value(): clamped to [0, 1].
|
|
138
|
+
def pos_value(pos)
|
|
139
|
+
[1.0, [0.0, pos].max].min
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# get.go getLines
|
|
143
|
+
def get_lines(s)
|
|
144
|
+
lines = s.split("\n", -1)
|
|
145
|
+
lines = [""] if lines.empty?
|
|
146
|
+
widest = 0
|
|
147
|
+
lines.each do |l|
|
|
148
|
+
w = str_width(l)
|
|
149
|
+
widest = w if w > widest
|
|
150
|
+
end
|
|
151
|
+
[lines, widest]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def join_horizontal(pos, strs)
|
|
155
|
+
return "" if strs.empty?
|
|
156
|
+
return strs[0] if strs.length == 1
|
|
157
|
+
|
|
158
|
+
blocks = []
|
|
159
|
+
max_widths = []
|
|
160
|
+
max_height = 0
|
|
161
|
+
strs.each do |str|
|
|
162
|
+
lines, w = get_lines(str)
|
|
163
|
+
blocks << lines
|
|
164
|
+
max_widths << w
|
|
165
|
+
max_height = lines.length if lines.length > max_height
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
blocks.map! do |block|
|
|
169
|
+
next block if block.length >= max_height
|
|
170
|
+
|
|
171
|
+
n = max_height - block.length
|
|
172
|
+
extra = Array.new(n, "")
|
|
173
|
+
if pos == 0.0
|
|
174
|
+
block + extra
|
|
175
|
+
elsif pos == 1.0
|
|
176
|
+
extra + block
|
|
177
|
+
else
|
|
178
|
+
split = (n * pos_value(pos)).round
|
|
179
|
+
top = n - split
|
|
180
|
+
bottom = n - top
|
|
181
|
+
Array.new(n - top, "") + block + Array.new(n - bottom, "")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
b = +""
|
|
186
|
+
rows = blocks[0].length
|
|
187
|
+
rows.times do |i|
|
|
188
|
+
blocks.each_with_index do |block, j|
|
|
189
|
+
b << block[i]
|
|
190
|
+
pad = max_widths[j] - str_width(block[i])
|
|
191
|
+
b << (" " * pad) if pad.positive?
|
|
192
|
+
end
|
|
193
|
+
b << "\n" if i < rows - 1
|
|
194
|
+
end
|
|
195
|
+
b
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def join_vertical(pos, strs)
|
|
199
|
+
return "" if strs.empty?
|
|
200
|
+
return strs[0] if strs.length == 1
|
|
201
|
+
|
|
202
|
+
blocks = []
|
|
203
|
+
max_width = 0
|
|
204
|
+
strs.each do |str|
|
|
205
|
+
lines, w = get_lines(str)
|
|
206
|
+
blocks << lines
|
|
207
|
+
max_width = w if w > max_width
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
b = +""
|
|
211
|
+
blocks.each_with_index do |block, i|
|
|
212
|
+
block.each_with_index do |line, j|
|
|
213
|
+
w = max_width - str_width(line)
|
|
214
|
+
if pos == 0.0
|
|
215
|
+
b << line << spaces(w)
|
|
216
|
+
elsif pos == 1.0
|
|
217
|
+
b << spaces(w) << line
|
|
218
|
+
elsif w < 1
|
|
219
|
+
b << line
|
|
220
|
+
else
|
|
221
|
+
split = (w * pos_value(pos)).round
|
|
222
|
+
right = w - split
|
|
223
|
+
left = w - right
|
|
224
|
+
b << spaces(left) << line << spaces(right)
|
|
225
|
+
end
|
|
226
|
+
b << "\n" unless i == blocks.length - 1 && j == block.length - 1
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
b
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def spaces(n) = n.positive? ? " " * n : ""
|
|
233
|
+
|
|
234
|
+
def width(str)
|
|
235
|
+
w = 0
|
|
236
|
+
str.split("\n", -1).each do |l|
|
|
237
|
+
lw = str_width(l)
|
|
238
|
+
w = lw if lw > w
|
|
239
|
+
end
|
|
240
|
+
w
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def height(str) = str.count("\n") + 1
|
|
244
|
+
|
|
245
|
+
def place(width, height, hpos, vpos, str, ws)
|
|
246
|
+
place_vertical(height, vpos, place_horizontal(width, hpos, str, ws), ws)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def place_horizontal(width, pos, str, ws)
|
|
250
|
+
lines, content_width = get_lines(str)
|
|
251
|
+
gap = width - content_width
|
|
252
|
+
return str if gap <= 0
|
|
253
|
+
|
|
254
|
+
b = +""
|
|
255
|
+
lines.each_with_index do |l, i|
|
|
256
|
+
short = [0, content_width - str_width(l)].max
|
|
257
|
+
total = gap + short
|
|
258
|
+
if pos == 0.0
|
|
259
|
+
b << l << ws.render(total)
|
|
260
|
+
elsif pos == 1.0
|
|
261
|
+
b << ws.render(total) << l
|
|
262
|
+
else
|
|
263
|
+
split = (total * pos_value(pos)).round
|
|
264
|
+
left = total - split
|
|
265
|
+
right = total - left
|
|
266
|
+
b << ws.render(left) << l << ws.render(right)
|
|
267
|
+
end
|
|
268
|
+
b << "\n" if i < lines.length - 1
|
|
269
|
+
end
|
|
270
|
+
b
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def place_vertical(height, pos, str, ws)
|
|
274
|
+
content_height = str.count("\n") + 1
|
|
275
|
+
gap = height - content_height
|
|
276
|
+
return str if gap <= 0
|
|
277
|
+
|
|
278
|
+
_, width = get_lines(str)
|
|
279
|
+
empty_line = ws.render(width)
|
|
280
|
+
b = +""
|
|
281
|
+
if pos == 0.0
|
|
282
|
+
b << str << "\n"
|
|
283
|
+
gap.times do |i|
|
|
284
|
+
b << empty_line
|
|
285
|
+
b << "\n" if i < gap - 1
|
|
286
|
+
end
|
|
287
|
+
elsif pos == 1.0
|
|
288
|
+
b << ((empty_line + "\n") * gap) << str
|
|
289
|
+
else
|
|
290
|
+
split = (gap * pos_value(pos)).round
|
|
291
|
+
top = gap - split
|
|
292
|
+
bottom = gap - top
|
|
293
|
+
b << ((empty_line + "\n") * top) << str
|
|
294
|
+
bottom.times { b << "\n" << empty_line }
|
|
295
|
+
end
|
|
296
|
+
b
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# whitespace.go: fills gaps with (optionally styled) characters.
|
|
300
|
+
class Whitespace
|
|
301
|
+
def initialize(chars: "", foreground: nil)
|
|
302
|
+
@chars = chars
|
|
303
|
+
@foreground = foreground
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Built at render time, as Go builds it inside Place (newWhitespace).
|
|
307
|
+
def style
|
|
308
|
+
r = Renderer.default
|
|
309
|
+
s = r.color_profile.string
|
|
310
|
+
s = s.foreground(@foreground.color(r)) if @foreground
|
|
311
|
+
s
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def render(width)
|
|
315
|
+
chars = @chars.empty? ? " " : @chars
|
|
316
|
+
runes = chars.dup.force_encoding(UTF_8).each_char.map { |c| c.valid_encoding? ? c : "�" }
|
|
317
|
+
j = 0
|
|
318
|
+
b = +""
|
|
319
|
+
i = 0
|
|
320
|
+
while i < width
|
|
321
|
+
b << runes[j]
|
|
322
|
+
j += 1
|
|
323
|
+
j = 0 if j >= runes.length
|
|
324
|
+
i += Text.string_width(runes[j])
|
|
325
|
+
end
|
|
326
|
+
short = width - Text.string_width(b)
|
|
327
|
+
b << (" " * short) if short.positive?
|
|
328
|
+
|
|
329
|
+
s = style
|
|
330
|
+
s.styled(b)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
PLAIN_WHITESPACE = Whitespace.new.freeze
|
|
335
|
+
|
|
336
|
+
# The whitespace options lipgloss_place_rb reads from its keyword hash, converted in the C
|
|
337
|
+
# extension's order. Returns [whitespace, light, dark] where light/dark are still to be
|
|
338
|
+
# converted (after the numeric arguments) for an adaptive foreground.
|
|
339
|
+
def whitespace_options(opts)
|
|
340
|
+
ws_chars_v = opts[:whitespace_chars]
|
|
341
|
+
ws_fg_v = opts[:whitespace_foreground]
|
|
342
|
+
ws_chars = ws_chars_v.nil? ? "" : c_string(ws_chars_v)
|
|
343
|
+
|
|
344
|
+
if !ws_fg_v.nil? && ws_fg_v.respond_to?(:light) && ws_fg_v.respond_to?(:dark)
|
|
345
|
+
[:adaptive, ws_chars, ws_fg_v.light, ws_fg_v.dark]
|
|
346
|
+
else
|
|
347
|
+
ws_fg = ws_fg_v.nil? ? "" : c_string(ws_fg_v)
|
|
348
|
+
fg = ws_fg.empty? ? nil : TerminalColor::Color.new(ws_fg.dup)
|
|
349
|
+
[:plain, ws_chars, fg]
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
module Lipgloss
|
|
358
|
+
class << self
|
|
359
|
+
def _join_horizontal(position, strings)
|
|
360
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
361
|
+
layout.check_array(strings)
|
|
362
|
+
strs = layout.json_strings(strings)
|
|
363
|
+
pos = layout.num2dbl(position)
|
|
364
|
+
return layout.c_result("") if strs.equal?(layout::JSON_FAIL)
|
|
365
|
+
|
|
366
|
+
layout.c_result(layout.join_horizontal(pos, strs))
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def _join_vertical(position, strings)
|
|
370
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
371
|
+
layout.check_array(strings)
|
|
372
|
+
strs = layout.json_strings(strings)
|
|
373
|
+
pos = layout.num2dbl(position)
|
|
374
|
+
return layout.c_result("") if strs.equal?(layout::JSON_FAIL)
|
|
375
|
+
|
|
376
|
+
layout.c_result(layout.join_vertical(pos, strs))
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def width(string)
|
|
380
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
381
|
+
layout.check_string(string)
|
|
382
|
+
layout.width(layout.c_string(string))
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def height(string)
|
|
386
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
387
|
+
layout.check_string(string)
|
|
388
|
+
layout.height(layout.c_string(string))
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def size(string)
|
|
392
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
393
|
+
layout.check_string(string)
|
|
394
|
+
s = layout.c_string(string)
|
|
395
|
+
[layout.width(s), layout.height(s)]
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def _place(*args, **opts)
|
|
399
|
+
unless args.length == 5
|
|
400
|
+
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 5)"
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
404
|
+
width, height, hpos, vpos, string = args
|
|
405
|
+
layout.check_string(string)
|
|
406
|
+
|
|
407
|
+
ws = layout::PLAIN_WHITESPACE
|
|
408
|
+
unless opts.empty?
|
|
409
|
+
kind, chars, *rest = layout.whitespace_options(opts)
|
|
410
|
+
if kind == :adaptive
|
|
411
|
+
light, dark = rest
|
|
412
|
+
w = layout.num2int(width)
|
|
413
|
+
h = layout.num2int(height)
|
|
414
|
+
hp = layout.num2dbl(hpos)
|
|
415
|
+
vp = layout.num2dbl(vpos)
|
|
416
|
+
str = layout.c_string(string)
|
|
417
|
+
light = layout.c_string(light)
|
|
418
|
+
dark = layout.c_string(dark)
|
|
419
|
+
fg = (light.empty? && dark.empty?) ? nil : R2UI::Compat::Gloss::TerminalColor::AdaptiveColor.new(light.dup, dark.dup)
|
|
420
|
+
ws = layout::Whitespace.new(chars: chars.dup, foreground: fg)
|
|
421
|
+
return layout.c_result(layout.place(w, h, hp, vp, str, ws))
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
ws = layout::Whitespace.new(chars: chars.dup, foreground: rest[0])
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
w = layout.num2int(width)
|
|
428
|
+
h = layout.num2int(height)
|
|
429
|
+
hp = layout.num2dbl(hpos)
|
|
430
|
+
vp = layout.num2dbl(vpos)
|
|
431
|
+
str = layout.c_string(string)
|
|
432
|
+
layout.c_result(layout.place(w, h, hp, vp, str, ws))
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def _place_horizontal(width, position, string)
|
|
436
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
437
|
+
layout.check_string(string)
|
|
438
|
+
w = layout.num2int(width)
|
|
439
|
+
pos = layout.num2dbl(position)
|
|
440
|
+
str = layout.c_string(string)
|
|
441
|
+
layout.c_result(layout.place_horizontal(w, pos, str, layout::PLAIN_WHITESPACE))
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def _place_vertical(height, position, string)
|
|
445
|
+
layout = R2UI::Compat::Gloss::Layout
|
|
446
|
+
layout.check_string(string)
|
|
447
|
+
h = layout.num2int(height)
|
|
448
|
+
pos = layout.num2dbl(position)
|
|
449
|
+
str = layout.c_string(string)
|
|
450
|
+
layout.c_result(layout.place_vertical(h, pos, str, layout::PLAIN_WHITESPACE))
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def upstream_version
|
|
454
|
+
String.new(R2UI::Compat::Gloss::UPSTREAM_VERSION, encoding: Encoding::UTF_8)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def version
|
|
458
|
+
format("lipgloss v%s (upstream %s) [Go native extension]", const_get(:VERSION), upstream_version)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tree/go_tree"
|
|
4
|
+
|
|
5
|
+
module Lipgloss
|
|
6
|
+
# Lipgloss::List as the gem's C extension (list.c) defines it over Go lipgloss list.List (a tree.Tree
|
|
7
|
+
# with list enumerators). Every method mutates the underlying list and returns a new Lipgloss::List
|
|
8
|
+
# wrapping that same list.
|
|
9
|
+
class List
|
|
10
|
+
ENUMERATORS = {
|
|
11
|
+
bullet: R2UI::Compat::Gloss::GoTree::ListEnumerators::BULLET,
|
|
12
|
+
arabic: R2UI::Compat::Gloss::GoTree::ListEnumerators::ARABIC_ENUM,
|
|
13
|
+
alphabet: R2UI::Compat::Gloss::GoTree::ListEnumerators::ALPHABET,
|
|
14
|
+
roman: R2UI::Compat::Gloss::GoTree::ListEnumerators::ROMAN_ENUM,
|
|
15
|
+
dash: R2UI::Compat::Gloss::GoTree::ListEnumerators::DASH,
|
|
16
|
+
asterisk: R2UI::Compat::Gloss::GoTree::ListEnumerators::ASTERISK
|
|
17
|
+
}.freeze
|
|
18
|
+
private_constant :ENUMERATORS
|
|
19
|
+
|
|
20
|
+
def initialize(*items)
|
|
21
|
+
node = go_tree
|
|
22
|
+
return if items.empty?
|
|
23
|
+
|
|
24
|
+
strs = R2UI::Compat::Gloss::GoTree.json_string_array(items)
|
|
25
|
+
strs&.each { |s| node.child(s) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def item(item)
|
|
29
|
+
node = go_tree
|
|
30
|
+
if item.is_a?(Lipgloss::List)
|
|
31
|
+
node.child(item.send(:go_tree))
|
|
32
|
+
else
|
|
33
|
+
node.child(R2UI::Compat::Gloss::GoTree.c_string_arg(item))
|
|
34
|
+
end
|
|
35
|
+
wrap(node)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def items(items)
|
|
39
|
+
node = go_tree
|
|
40
|
+
strs = R2UI::Compat::Gloss::GoTree.json_string_array(items)
|
|
41
|
+
strs&.each { |s| node.child(s) }
|
|
42
|
+
wrap(node)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def enumerator(enum_symbol)
|
|
46
|
+
node = go_tree
|
|
47
|
+
enum = ENUMERATORS.find { |sym, _| sym.equal?(enum_symbol) }&.last
|
|
48
|
+
node.ensure_renderer.enumerator = enum || ENUMERATORS[:bullet]
|
|
49
|
+
wrap(node)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def enumerator_style(style)
|
|
53
|
+
node = go_tree
|
|
54
|
+
node.ensure_renderer.enumerator_style = R2UI::Compat::Gloss::GoTree.style_arg(style)
|
|
55
|
+
wrap(node)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def item_style(style)
|
|
59
|
+
node = go_tree
|
|
60
|
+
node.ensure_renderer.item_style = R2UI::Compat::Gloss::GoTree.style_arg(style)
|
|
61
|
+
wrap(node)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render
|
|
65
|
+
R2UI::Compat::Gloss::GoTree.render(go_tree)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
render
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# The gem allocates the Go list (list.New()) in the alloc function.
|
|
75
|
+
def go_tree
|
|
76
|
+
@tree ||= R2UI::Compat::Gloss::GoTree.new_list
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def wrap(node)
|
|
80
|
+
list = self.class.allocate
|
|
81
|
+
list.instance_variable_set(:@tree, node)
|
|
82
|
+
list
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Same as lipgloss-ruby 0.2.2 lib/lipgloss/position.rb (MIT).
|
|
4
|
+
module Lipgloss
|
|
5
|
+
# Positions are floats from 0.0 (top/left) to 1.0 (bottom/right); 0.5 is center.
|
|
6
|
+
module Position
|
|
7
|
+
TOP = 0.0
|
|
8
|
+
BOTTOM = 1.0
|
|
9
|
+
LEFT = 0.0
|
|
10
|
+
RIGHT = 1.0
|
|
11
|
+
CENTER = 0.5
|
|
12
|
+
|
|
13
|
+
SYMBOLS = {
|
|
14
|
+
top: TOP,
|
|
15
|
+
bottom: BOTTOM,
|
|
16
|
+
left: LEFT,
|
|
17
|
+
right: RIGHT,
|
|
18
|
+
center: CENTER
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
def self.resolve(value)
|
|
22
|
+
case value
|
|
23
|
+
when Symbol then SYMBOLS.fetch(value) { raise ArgumentError, "Unknown position: #{value.inspect}" }
|
|
24
|
+
when String then SYMBOLS.fetch(value.to_sym) { raise ArgumentError, "Unknown position: #{value.inspect}" }
|
|
25
|
+
when Numeric then value.to_f
|
|
26
|
+
else raise ArgumentError, "Position must be a Symbol or Numeric, got #{value.class}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|