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,1062 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Lipgloss::Style: a port of lipgloss v1.1.0 style.go, set.go, get.go, unset.go, borders.go and
|
|
4
|
+
# align.go, behind the same Ruby-facing argument handling as the lipgloss gem 0.2.2 C extension
|
|
5
|
+
# (ext/lipgloss/style*.c, go/style*.go) and its lib/lipgloss/style.rb (align wrappers).
|
|
6
|
+
#
|
|
7
|
+
# Styles are immutable: every setter returns a new Style, like the gem (which allocates a new Go
|
|
8
|
+
# style per call). Like the gem, #dup and #clone give an empty style (the C extension allocates a
|
|
9
|
+
# fresh Go style and copies nothing).
|
|
10
|
+
#
|
|
11
|
+
# Private API for the table / list / tree ports (call with #send):
|
|
12
|
+
# get_padding / get_margin -> [top, right, bottom, left]
|
|
13
|
+
# get_horizontal_padding, get_vertical_padding, get_horizontal_margins, get_vertical_margins,
|
|
14
|
+
# get_horizontal_frame_size, get_vertical_frame_size, get_horizontal_border_size,
|
|
15
|
+
# get_vertical_border_size, get_border_style -> BorderDef, get_align_horizontal -> Float,
|
|
16
|
+
# get_align_vertical -> Float, get_max_width, get_max_height, get_inline,
|
|
17
|
+
# get_border_top_size / _right_size / _bottom_size / _left_size, get_border -> [def, t, r, b, l]
|
|
18
|
+
# set_border_def(border_def, *sides) Go Border(b, sides...)
|
|
19
|
+
# set_border_style_def(border_def) Go BorderStyle(b)
|
|
20
|
+
# set_foreground_color(terminal_color) / set_background_color(terminal_color)
|
|
21
|
+
# go_render(*strs) Go Render(strs...), no Ruby argument checks
|
|
22
|
+
require_relative "style/border"
|
|
23
|
+
|
|
24
|
+
module R2UI
|
|
25
|
+
module Compat
|
|
26
|
+
module Gloss
|
|
27
|
+
# The gem's Style is a T_DATA object with no instance variables, so it inspects as
|
|
28
|
+
# #<Lipgloss::Style:0x...>. Kept in a module so Style.public_instance_methods(false) stays
|
|
29
|
+
# exactly the gem's list.
|
|
30
|
+
module StyleInspect
|
|
31
|
+
def inspect = Object.instance_method(:to_s).bind_call(self)
|
|
32
|
+
|
|
33
|
+
def pretty_print(q)
|
|
34
|
+
q.text(inspect)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
module Lipgloss
|
|
42
|
+
class Style
|
|
43
|
+
include R2UI::Compat::Gloss::StyleInspect
|
|
44
|
+
# propKey values (style.go), in Go's order.
|
|
45
|
+
module Keys
|
|
46
|
+
names = %i[
|
|
47
|
+
bold italic underline strikethrough reverse blink faint underline_spaces strikethrough_spaces
|
|
48
|
+
color_whitespace
|
|
49
|
+
foreground background width height align_horizontal align_vertical
|
|
50
|
+
padding_top padding_right padding_bottom padding_left
|
|
51
|
+
margin_top margin_right margin_bottom margin_left margin_background
|
|
52
|
+
border_style
|
|
53
|
+
border_top border_right border_bottom border_left
|
|
54
|
+
border_top_foreground border_right_foreground border_bottom_foreground border_left_foreground
|
|
55
|
+
border_top_background border_right_background border_bottom_background border_left_background
|
|
56
|
+
inline max_width max_height tab_width
|
|
57
|
+
transform
|
|
58
|
+
]
|
|
59
|
+
names.each_with_index { |name, i| const_set(name.to_s.upcase, 1 << i) }
|
|
60
|
+
FIRST = BOLD
|
|
61
|
+
LAST = TRANSFORM
|
|
62
|
+
|
|
63
|
+
COLOR = [
|
|
64
|
+
FOREGROUND, BACKGROUND, MARGIN_BACKGROUND,
|
|
65
|
+
BORDER_TOP_FOREGROUND, BORDER_RIGHT_FOREGROUND, BORDER_BOTTOM_FOREGROUND, BORDER_LEFT_FOREGROUND,
|
|
66
|
+
BORDER_TOP_BACKGROUND, BORDER_RIGHT_BACKGROUND, BORDER_BOTTOM_BACKGROUND, BORDER_LEFT_BACKGROUND
|
|
67
|
+
].freeze
|
|
68
|
+
NON_NEGATIVE = [
|
|
69
|
+
WIDTH, HEIGHT, PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT,
|
|
70
|
+
MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM, MARGIN_LEFT, MAX_WIDTH, MAX_HEIGHT
|
|
71
|
+
].freeze
|
|
72
|
+
VALUE = [ALIGN_HORIZONTAL, ALIGN_VERTICAL, BORDER_STYLE, TAB_WIDTH, TRANSFORM].freeze
|
|
73
|
+
STORED = (COLOR + NON_NEGATIVE + VALUE).to_h { |k| [k, true] }.freeze
|
|
74
|
+
NON_NEGATIVE_SET = NON_NEGATIVE.to_h { |k| [k, true] }.freeze
|
|
75
|
+
end
|
|
76
|
+
private_constant :Keys
|
|
77
|
+
|
|
78
|
+
Gloss = R2UI::Compat::Gloss
|
|
79
|
+
private_constant :Gloss
|
|
80
|
+
|
|
81
|
+
UTF_8 = Encoding::UTF_8
|
|
82
|
+
private_constant :UTF_8
|
|
83
|
+
|
|
84
|
+
TAB_WIDTH_DEFAULT = 4
|
|
85
|
+
private_constant :TAB_WIDTH_DEFAULT
|
|
86
|
+
|
|
87
|
+
def initialize
|
|
88
|
+
reset_state
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render(string)
|
|
92
|
+
Gloss::Layout.check_string(string)
|
|
93
|
+
c_result(go_render(c_str(string)))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# --- text attributes -------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
def bold(value) = with(Keys::BOLD, value ? true : false)
|
|
99
|
+
def italic(value) = with(Keys::ITALIC, value ? true : false)
|
|
100
|
+
def underline(value) = with(Keys::UNDERLINE, value ? true : false)
|
|
101
|
+
def strikethrough(value) = with(Keys::STRIKETHROUGH, value ? true : false)
|
|
102
|
+
def reverse(value) = with(Keys::REVERSE, value ? true : false)
|
|
103
|
+
def blink(value) = with(Keys::BLINK, value ? true : false)
|
|
104
|
+
def faint(value) = with(Keys::FAINT, value ? true : false)
|
|
105
|
+
|
|
106
|
+
# --- colors ----------------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
def foreground(color)
|
|
109
|
+
with(Keys::FOREGROUND, Gloss::Renderer.terminal_color(color))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def background(color)
|
|
113
|
+
with(Keys::BACKGROUND, Gloss::Renderer.terminal_color(color))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def margin_background(color)
|
|
117
|
+
with(Keys::MARGIN_BACKGROUND, Gloss::Renderer.terminal_color(color, adaptive: false, complete: false))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# --- size ------------------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def width(width) = with(Keys::WIDTH, num2int(width))
|
|
123
|
+
def height(height) = with(Keys::HEIGHT, num2int(height))
|
|
124
|
+
def max_width(width) = with(Keys::MAX_WIDTH, num2int(width))
|
|
125
|
+
def max_height(height) = with(Keys::MAX_HEIGHT, num2int(height))
|
|
126
|
+
|
|
127
|
+
# --- alignment (lib/lipgloss/style.rb + style.c) ---------------------------------------------
|
|
128
|
+
|
|
129
|
+
def align(*positions)
|
|
130
|
+
_align(*positions.map { |p| Position.resolve(p) })
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def align_horizontal(position)
|
|
134
|
+
_align_horizontal(Position.resolve(position))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def align_vertical(position)
|
|
138
|
+
_align_vertical(Position.resolve(position))
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def _align(*positions)
|
|
142
|
+
if positions.empty? || positions.length > 2
|
|
143
|
+
raise ArgumentError, "wrong number of arguments (given #{positions.length}, expected 1..2)"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
values = positions.map { |p| Gloss::Layout.num2dbl(p) }
|
|
147
|
+
derive do |s|
|
|
148
|
+
s.__send__(:set!, Keys::ALIGN_HORIZONTAL, values[0])
|
|
149
|
+
s.__send__(:set!, Keys::ALIGN_VERTICAL, values[1]) if values.length > 1
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def _align_horizontal(position)
|
|
154
|
+
with(Keys::ALIGN_HORIZONTAL, Gloss::Layout.num2dbl(position))
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def _align_vertical(position)
|
|
158
|
+
with(Keys::ALIGN_VERTICAL, Gloss::Layout.num2dbl(position))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# --- other -----------------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def inline(value) = with(Keys::INLINE, value ? true : false)
|
|
164
|
+
|
|
165
|
+
def tab_width(width)
|
|
166
|
+
n = num2int(width)
|
|
167
|
+
n = -1 if n <= -1
|
|
168
|
+
with(Keys::TAB_WIDTH, n)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def underline_spaces(value) = with(Keys::UNDERLINE_SPACES, value ? true : false)
|
|
172
|
+
def strikethrough_spaces(value) = with(Keys::STRIKETHROUGH_SPACES, value ? true : false)
|
|
173
|
+
|
|
174
|
+
def set_string(string)
|
|
175
|
+
Gloss::Layout.check_string(string)
|
|
176
|
+
str = c_str(string)
|
|
177
|
+
derive { |s| s.instance_variable_set(:@value, str) }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def inherit(other)
|
|
181
|
+
unless other.is_a?(Style)
|
|
182
|
+
raise TypeError, "wrong argument type #{Gloss::Layout.type_name(other)} (expected Lipgloss::Style)"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
derive { |s| s.__send__(:inherit!, other) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def to_s
|
|
189
|
+
c_result(go_render)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# --- getters ---------------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
def bold? = get_as_bool(Keys::BOLD, false)
|
|
195
|
+
def italic? = get_as_bool(Keys::ITALIC, false)
|
|
196
|
+
def underline? = get_as_bool(Keys::UNDERLINE, false)
|
|
197
|
+
def strikethrough? = get_as_bool(Keys::STRIKETHROUGH, false)
|
|
198
|
+
def reverse? = get_as_bool(Keys::REVERSE, false)
|
|
199
|
+
def blink? = get_as_bool(Keys::BLINK, false)
|
|
200
|
+
def faint? = get_as_bool(Keys::FAINT, false)
|
|
201
|
+
|
|
202
|
+
def get_foreground = color_string(get_as_color(Keys::FOREGROUND))
|
|
203
|
+
def get_background = color_string(get_as_color(Keys::BACKGROUND))
|
|
204
|
+
def get_width = get_as_int(Keys::WIDTH)
|
|
205
|
+
def get_height = get_as_int(Keys::HEIGHT)
|
|
206
|
+
|
|
207
|
+
# --- padding / margin (style_spacing.c) ------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def padding(*values)
|
|
210
|
+
sides = spacing_args(values)
|
|
211
|
+
derive { |s| s.__send__(:set_sides!, :padding, sides) }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def padding_top(value) = with(Keys::PADDING_TOP, num2int(value))
|
|
215
|
+
def padding_right(value) = with(Keys::PADDING_RIGHT, num2int(value))
|
|
216
|
+
def padding_bottom(value) = with(Keys::PADDING_BOTTOM, num2int(value))
|
|
217
|
+
def padding_left(value) = with(Keys::PADDING_LEFT, num2int(value))
|
|
218
|
+
|
|
219
|
+
def margin(*values)
|
|
220
|
+
sides = spacing_args(values)
|
|
221
|
+
derive { |s| s.__send__(:set_sides!, :margin, sides) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def margin_top(value) = with(Keys::MARGIN_TOP, num2int(value))
|
|
225
|
+
def margin_right(value) = with(Keys::MARGIN_RIGHT, num2int(value))
|
|
226
|
+
def margin_bottom(value) = with(Keys::MARGIN_BOTTOM, num2int(value))
|
|
227
|
+
def margin_left(value) = with(Keys::MARGIN_LEFT, num2int(value))
|
|
228
|
+
|
|
229
|
+
# --- borders (style_border.c) ----------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
def border(*args)
|
|
232
|
+
raise ArgumentError, "wrong number of arguments (given 0, expected 1+)" if args.empty?
|
|
233
|
+
|
|
234
|
+
border = Gloss::Borders.resolve(args[0])
|
|
235
|
+
sides = args[1, 4].map { |v| v ? true : false }
|
|
236
|
+
set_border_def(border, *sides)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def border_style(type)
|
|
240
|
+
set_border_style_def(Gloss::Borders.resolve(type))
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def border_foreground(color)
|
|
244
|
+
c = Gloss::Renderer.terminal_color(color, complete: false)
|
|
245
|
+
derive { |s| s.__send__(:set_border_colors!, :foreground, [c]) }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def border_background(color)
|
|
249
|
+
c = Gloss::Renderer.terminal_color(color, complete: false)
|
|
250
|
+
derive { |s| s.__send__(:set_border_colors!, :background, [c]) }
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def border_top(value) = with(Keys::BORDER_TOP, value ? true : false)
|
|
254
|
+
def border_right(value) = with(Keys::BORDER_RIGHT, value ? true : false)
|
|
255
|
+
def border_bottom(value) = with(Keys::BORDER_BOTTOM, value ? true : false)
|
|
256
|
+
def border_left(value) = with(Keys::BORDER_LEFT, value ? true : false)
|
|
257
|
+
|
|
258
|
+
def border_top_foreground(color) = with(Keys::BORDER_TOP_FOREGROUND, side_color(color))
|
|
259
|
+
def border_right_foreground(color) = with(Keys::BORDER_RIGHT_FOREGROUND, side_color(color))
|
|
260
|
+
def border_bottom_foreground(color) = with(Keys::BORDER_BOTTOM_FOREGROUND, side_color(color))
|
|
261
|
+
def border_left_foreground(color) = with(Keys::BORDER_LEFT_FOREGROUND, side_color(color))
|
|
262
|
+
def border_top_background(color) = with(Keys::BORDER_TOP_BACKGROUND, side_color(color))
|
|
263
|
+
def border_right_background(color) = with(Keys::BORDER_RIGHT_BACKGROUND, side_color(color))
|
|
264
|
+
def border_bottom_background(color) = with(Keys::BORDER_BOTTOM_BACKGROUND, side_color(color))
|
|
265
|
+
def border_left_background(color) = with(Keys::BORDER_LEFT_BACKGROUND, side_color(color))
|
|
266
|
+
|
|
267
|
+
BORDER_CUSTOM_KEYS = %i[
|
|
268
|
+
top bottom left right top_left top_right bottom_left bottom_right
|
|
269
|
+
middle_left middle_right middle middle_top middle_bottom
|
|
270
|
+
].freeze
|
|
271
|
+
private_constant :BORDER_CUSTOM_KEYS
|
|
272
|
+
|
|
273
|
+
def border_custom(*args, **opts)
|
|
274
|
+
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 0)" unless args.empty?
|
|
275
|
+
raise ArgumentError, "keyword arguments required" if opts.empty?
|
|
276
|
+
|
|
277
|
+
fields = BORDER_CUSTOM_KEYS.map do |k|
|
|
278
|
+
v = opts[k]
|
|
279
|
+
v.nil? ? "" : c_str(v)
|
|
280
|
+
end
|
|
281
|
+
set_border_def(Gloss::BorderDef.new(*fields))
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# --- unset (style_unset.c) -------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
def unset_bold = without(Keys::BOLD)
|
|
287
|
+
def unset_italic = without(Keys::ITALIC)
|
|
288
|
+
def unset_underline = without(Keys::UNDERLINE)
|
|
289
|
+
def unset_strikethrough = without(Keys::STRIKETHROUGH)
|
|
290
|
+
def unset_reverse = without(Keys::REVERSE)
|
|
291
|
+
def unset_blink = without(Keys::BLINK)
|
|
292
|
+
def unset_faint = without(Keys::FAINT)
|
|
293
|
+
def unset_foreground = without(Keys::FOREGROUND)
|
|
294
|
+
def unset_background = without(Keys::BACKGROUND)
|
|
295
|
+
def unset_width = without(Keys::WIDTH)
|
|
296
|
+
def unset_height = without(Keys::HEIGHT)
|
|
297
|
+
def unset_padding_top = without(Keys::PADDING_TOP)
|
|
298
|
+
def unset_padding_right = without(Keys::PADDING_RIGHT)
|
|
299
|
+
def unset_padding_bottom = without(Keys::PADDING_BOTTOM)
|
|
300
|
+
def unset_padding_left = without(Keys::PADDING_LEFT)
|
|
301
|
+
def unset_margin_top = without(Keys::MARGIN_TOP)
|
|
302
|
+
def unset_margin_right = without(Keys::MARGIN_RIGHT)
|
|
303
|
+
def unset_margin_bottom = without(Keys::MARGIN_BOTTOM)
|
|
304
|
+
def unset_margin_left = without(Keys::MARGIN_LEFT)
|
|
305
|
+
def unset_border_style = without(Keys::BORDER_STYLE)
|
|
306
|
+
def unset_inline = without(Keys::INLINE)
|
|
307
|
+
|
|
308
|
+
private
|
|
309
|
+
|
|
310
|
+
# --- state -----------------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
def reset_state
|
|
313
|
+
@props = 0
|
|
314
|
+
@attrs = 0
|
|
315
|
+
@values = {}
|
|
316
|
+
@value = ""
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# The gem's dup/clone allocate a fresh, empty Go style.
|
|
320
|
+
def initialize_copy(_other)
|
|
321
|
+
super
|
|
322
|
+
reset_state
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def load_state(props, attrs, values, value)
|
|
326
|
+
@props = props
|
|
327
|
+
@attrs = attrs
|
|
328
|
+
@values = values
|
|
329
|
+
@value = value
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def derive
|
|
333
|
+
s = self.class.allocate
|
|
334
|
+
s.__send__(:load_state, @props, @attrs, @values.dup, @value)
|
|
335
|
+
yield s
|
|
336
|
+
s
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def with(key, value)
|
|
340
|
+
derive { |s| s.__send__(:set!, key, value) }
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def without(key)
|
|
344
|
+
derive { |s| s.__send__(:unset!, key) }
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Style.set (set.go)
|
|
348
|
+
def set!(key, value)
|
|
349
|
+
if Keys::STORED.key?(key)
|
|
350
|
+
value = [0, value].max if Keys::NON_NEGATIVE_SET.key?(key)
|
|
351
|
+
@values[key] = value
|
|
352
|
+
elsif value == true || value == false
|
|
353
|
+
value ? (@attrs |= key) : (@attrs &= ~key)
|
|
354
|
+
elsif value.is_a?(Integer)
|
|
355
|
+
(value & key).zero? ? (@attrs &= ~key) : (@attrs |= key)
|
|
356
|
+
end
|
|
357
|
+
@props |= key
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def unset!(key)
|
|
361
|
+
@props &= ~key
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def set?(key) = (@props & key) != 0
|
|
365
|
+
|
|
366
|
+
# Style.setFrom
|
|
367
|
+
def set_from!(key, other)
|
|
368
|
+
if Keys::STORED.key?(key)
|
|
369
|
+
set!(key, other.__send__(:raw_value, key))
|
|
370
|
+
else
|
|
371
|
+
set!(key, other.__send__(:raw_attrs))
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def raw_value(key) = @values[key]
|
|
376
|
+
def raw_attrs = @attrs
|
|
377
|
+
|
|
378
|
+
# Style.Inherit
|
|
379
|
+
def inherit!(other)
|
|
380
|
+
key = Keys::FIRST
|
|
381
|
+
while key <= Keys::LAST
|
|
382
|
+
if other.__send__(:set?, key)
|
|
383
|
+
case key
|
|
384
|
+
when Keys::MARGIN_TOP, Keys::MARGIN_RIGHT, Keys::MARGIN_BOTTOM, Keys::MARGIN_LEFT,
|
|
385
|
+
Keys::PADDING_TOP, Keys::PADDING_RIGHT, Keys::PADDING_BOTTOM, Keys::PADDING_LEFT
|
|
386
|
+
key <<= 1
|
|
387
|
+
next
|
|
388
|
+
when Keys::BACKGROUND
|
|
389
|
+
if !set?(Keys::MARGIN_BACKGROUND) && !other.__send__(:set?, Keys::MARGIN_BACKGROUND)
|
|
390
|
+
set!(Keys::MARGIN_BACKGROUND, other.__send__(:raw_value, Keys::BACKGROUND))
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
set_from!(key, other) unless set?(key)
|
|
394
|
+
end
|
|
395
|
+
key <<= 1
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# whichSidesInt + Padding / Margin
|
|
400
|
+
def set_sides!(kind, sides)
|
|
401
|
+
return unless (1..4).cover?(sides.length)
|
|
402
|
+
|
|
403
|
+
top, right, bottom, left = which_sides(sides)
|
|
404
|
+
|
|
405
|
+
if kind == :padding
|
|
406
|
+
set!(Keys::PADDING_TOP, top)
|
|
407
|
+
set!(Keys::PADDING_RIGHT, right)
|
|
408
|
+
set!(Keys::PADDING_BOTTOM, bottom)
|
|
409
|
+
set!(Keys::PADDING_LEFT, left)
|
|
410
|
+
else
|
|
411
|
+
set!(Keys::MARGIN_TOP, top)
|
|
412
|
+
set!(Keys::MARGIN_RIGHT, right)
|
|
413
|
+
set!(Keys::MARGIN_BOTTOM, bottom)
|
|
414
|
+
set!(Keys::MARGIN_LEFT, left)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# BorderForeground(c...) / BorderBackground(c...)
|
|
419
|
+
def set_border_colors!(kind, colors)
|
|
420
|
+
return if colors.empty?
|
|
421
|
+
return unless (1..4).cover?(colors.length)
|
|
422
|
+
|
|
423
|
+
top, right, bottom, left = which_sides(colors)
|
|
424
|
+
if kind == :foreground
|
|
425
|
+
set!(Keys::BORDER_TOP_FOREGROUND, top)
|
|
426
|
+
set!(Keys::BORDER_RIGHT_FOREGROUND, right)
|
|
427
|
+
set!(Keys::BORDER_BOTTOM_FOREGROUND, bottom)
|
|
428
|
+
set!(Keys::BORDER_LEFT_FOREGROUND, left)
|
|
429
|
+
else
|
|
430
|
+
set!(Keys::BORDER_TOP_BACKGROUND, top)
|
|
431
|
+
set!(Keys::BORDER_RIGHT_BACKGROUND, right)
|
|
432
|
+
set!(Keys::BORDER_BOTTOM_BACKGROUND, bottom)
|
|
433
|
+
set!(Keys::BORDER_LEFT_BACKGROUND, left)
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# whichSides*: [top, right, bottom, left], or [] when the count isn't 1..4.
|
|
438
|
+
def which_sides(i)
|
|
439
|
+
case i.length
|
|
440
|
+
when 1 then [i[0], i[0], i[0], i[0]]
|
|
441
|
+
when 2 then [i[0], i[1], i[0], i[1]]
|
|
442
|
+
when 3 then [i[0], i[1], i[2], i[1]]
|
|
443
|
+
when 4 then [i[0], i[1], i[2], i[3]]
|
|
444
|
+
else []
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# --- Go getters ------------------------------------------------------------------------------
|
|
449
|
+
|
|
450
|
+
def get_as_bool(key, default)
|
|
451
|
+
return default unless set?(key)
|
|
452
|
+
|
|
453
|
+
(@attrs & key) != 0
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def get_as_color(key)
|
|
457
|
+
return NO_COLOR unless set?(key)
|
|
458
|
+
|
|
459
|
+
@values[key] || NO_COLOR
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def get_as_int(key)
|
|
463
|
+
return 0 unless set?(key)
|
|
464
|
+
|
|
465
|
+
@values[key] || 0
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def get_as_position(key)
|
|
469
|
+
return 0.0 unless set?(key)
|
|
470
|
+
|
|
471
|
+
@values[key] || 0.0
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def get_border_style
|
|
475
|
+
return Gloss::Borders::NO_BORDER unless set?(Keys::BORDER_STYLE)
|
|
476
|
+
|
|
477
|
+
@values[Keys::BORDER_STYLE]
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def implicit_borders?
|
|
481
|
+
get_border_style != Gloss::Borders::NO_BORDER &&
|
|
482
|
+
!(set?(Keys::BORDER_TOP) || set?(Keys::BORDER_RIGHT) || set?(Keys::BORDER_BOTTOM) || set?(Keys::BORDER_LEFT))
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def get_padding
|
|
486
|
+
[get_as_int(Keys::PADDING_TOP), get_as_int(Keys::PADDING_RIGHT),
|
|
487
|
+
get_as_int(Keys::PADDING_BOTTOM), get_as_int(Keys::PADDING_LEFT)]
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def get_margin
|
|
491
|
+
[get_as_int(Keys::MARGIN_TOP), get_as_int(Keys::MARGIN_RIGHT),
|
|
492
|
+
get_as_int(Keys::MARGIN_BOTTOM), get_as_int(Keys::MARGIN_LEFT)]
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def get_horizontal_padding = get_as_int(Keys::PADDING_LEFT) + get_as_int(Keys::PADDING_RIGHT)
|
|
496
|
+
def get_vertical_padding = get_as_int(Keys::PADDING_TOP) + get_as_int(Keys::PADDING_BOTTOM)
|
|
497
|
+
def get_horizontal_margins = get_as_int(Keys::MARGIN_LEFT) + get_as_int(Keys::MARGIN_RIGHT)
|
|
498
|
+
def get_vertical_margins = get_as_int(Keys::MARGIN_TOP) + get_as_int(Keys::MARGIN_BOTTOM)
|
|
499
|
+
|
|
500
|
+
def get_border
|
|
501
|
+
[get_border_style, get_as_bool(Keys::BORDER_TOP, false), get_as_bool(Keys::BORDER_RIGHT, false),
|
|
502
|
+
get_as_bool(Keys::BORDER_BOTTOM, false), get_as_bool(Keys::BORDER_LEFT, false)]
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def get_border_top_size
|
|
506
|
+
return 0 if !get_as_bool(Keys::BORDER_TOP, false) && !implicit_borders?
|
|
507
|
+
|
|
508
|
+
get_border_style.top_size
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def get_border_right_size
|
|
512
|
+
return 0 if !get_as_bool(Keys::BORDER_RIGHT, false) && !implicit_borders?
|
|
513
|
+
|
|
514
|
+
get_border_style.right_size
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def get_border_bottom_size
|
|
518
|
+
return 0 if !get_as_bool(Keys::BORDER_BOTTOM, false) && !implicit_borders?
|
|
519
|
+
|
|
520
|
+
get_border_style.bottom_size
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def get_border_left_size
|
|
524
|
+
return 0 if !get_as_bool(Keys::BORDER_LEFT, false) && !implicit_borders?
|
|
525
|
+
|
|
526
|
+
get_border_style.left_size
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def get_horizontal_border_size = get_border_left_size + get_border_right_size
|
|
530
|
+
def get_vertical_border_size = get_border_top_size + get_border_bottom_size
|
|
531
|
+
def get_horizontal_frame_size = get_horizontal_margins + get_horizontal_padding + get_horizontal_border_size
|
|
532
|
+
def get_vertical_frame_size = get_vertical_margins + get_vertical_padding + get_vertical_border_size
|
|
533
|
+
|
|
534
|
+
def get_align_horizontal = get_as_position(Keys::ALIGN_HORIZONTAL)
|
|
535
|
+
def get_align_vertical = get_as_position(Keys::ALIGN_VERTICAL)
|
|
536
|
+
def get_max_width = get_as_int(Keys::MAX_WIDTH)
|
|
537
|
+
def get_max_height = get_as_int(Keys::MAX_HEIGHT)
|
|
538
|
+
def get_inline = get_as_bool(Keys::INLINE, false)
|
|
539
|
+
|
|
540
|
+
# --- Go setters for the table / list / tree ports -------------------------------------------
|
|
541
|
+
|
|
542
|
+
# Border(b, sides...)
|
|
543
|
+
def set_border_def(border_def, *sides)
|
|
544
|
+
b = border_def.normalized.freeze
|
|
545
|
+
derive do |s|
|
|
546
|
+
s.__send__(:set!, Keys::BORDER_STYLE, b)
|
|
547
|
+
top, right, bottom, left = (1..4).cover?(sides.length) ? s.__send__(:which_sides, sides) : [true] * 4
|
|
548
|
+
s.__send__(:set!, Keys::BORDER_TOP, top ? true : false)
|
|
549
|
+
s.__send__(:set!, Keys::BORDER_RIGHT, right ? true : false)
|
|
550
|
+
s.__send__(:set!, Keys::BORDER_BOTTOM, bottom ? true : false)
|
|
551
|
+
s.__send__(:set!, Keys::BORDER_LEFT, left ? true : false)
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# BorderStyle(b)
|
|
556
|
+
def set_border_style_def(border_def)
|
|
557
|
+
with(Keys::BORDER_STYLE, border_def.normalized.freeze)
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def set_foreground_color(terminal_color) = with(Keys::FOREGROUND, terminal_color)
|
|
561
|
+
def set_background_color(terminal_color) = with(Keys::BACKGROUND, terminal_color)
|
|
562
|
+
|
|
563
|
+
# --- Ruby argument conversions (C extension) -------------------------------------------------
|
|
564
|
+
|
|
565
|
+
def num2int(value) = Gloss::Layout.num2int(value)
|
|
566
|
+
|
|
567
|
+
def spacing_args(values)
|
|
568
|
+
if values.empty? || values.length > 4
|
|
569
|
+
raise ArgumentError, "wrong number of arguments (given #{values.length}, expected 1..4)"
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
values.map { |v| num2int(v) }
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def side_color(color)
|
|
576
|
+
Gloss::Renderer.terminal_color(color, adaptive: false, complete: false)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# terminalColorToString in go/style.go: only a plain Color has a string.
|
|
580
|
+
def color_string(color)
|
|
581
|
+
return nil unless color.is_a?(Gloss::TerminalColor::Color)
|
|
582
|
+
return nil if color.value.nil? || color.value.empty?
|
|
583
|
+
|
|
584
|
+
String.new(color.value, encoding: UTF_8)
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def utf8(str)
|
|
588
|
+
str.b.force_encoding(UTF_8)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# StringValueCStr, then the bytes C (and Go's C.GoString) sees, as a UTF-8 string. For an
|
|
592
|
+
# ASCII-incompatible encoding (UTF-16, UTF-32) Ruby only rejects a NUL character, and C stops
|
|
593
|
+
# at the first NUL byte.
|
|
594
|
+
def c_str(value)
|
|
595
|
+
str = String.try_convert(value)
|
|
596
|
+
return utf8(Gloss::Layout.c_string(value)) if str.nil? || str.encoding.ascii_compatible?
|
|
597
|
+
|
|
598
|
+
nul = begin
|
|
599
|
+
"\0".encode(str.encoding)
|
|
600
|
+
rescue EncodingError
|
|
601
|
+
nil
|
|
602
|
+
end
|
|
603
|
+
raise ArgumentError, "string contains null byte" if nul && str.b.include?(nul.b) && str.include?(nul)
|
|
604
|
+
|
|
605
|
+
b = str.b
|
|
606
|
+
cut = b.index("\0")
|
|
607
|
+
utf8(cut ? b.byteslice(0, cut) : b)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# rb_utf8_str_new_cstr: a fresh, unfrozen UTF-8 string.
|
|
611
|
+
def c_result(str)
|
|
612
|
+
String.new(str, encoding: UTF_8)
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
NO_COLOR = Gloss::TerminalColor::NoColor.new.freeze
|
|
616
|
+
RIGHT = 1.0
|
|
617
|
+
CENTER = 0.5
|
|
618
|
+
BOTTOM = 1.0
|
|
619
|
+
TOP = 0.0
|
|
620
|
+
private_constant :NO_COLOR, :RIGHT, :CENTER, :BOTTOM, :TOP
|
|
621
|
+
|
|
622
|
+
def no_color?(color) = color.is_a?(Gloss::TerminalColor::NoColor)
|
|
623
|
+
|
|
624
|
+
# --- Render (style.go) -----------------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
def go_render(*strs)
|
|
627
|
+
strs = [@value, *strs] unless @value.empty?
|
|
628
|
+
str = strs.map { |x| utf8(x) }.join(" ")
|
|
629
|
+
|
|
630
|
+
r = Gloss::Renderer.default
|
|
631
|
+
p = r.color_profile
|
|
632
|
+
te = p.string
|
|
633
|
+
te_space = p.string
|
|
634
|
+
te_whitespace = p.string
|
|
635
|
+
|
|
636
|
+
bold = get_as_bool(Keys::BOLD, false)
|
|
637
|
+
italic = get_as_bool(Keys::ITALIC, false)
|
|
638
|
+
underline = get_as_bool(Keys::UNDERLINE, false)
|
|
639
|
+
strikethrough = get_as_bool(Keys::STRIKETHROUGH, false)
|
|
640
|
+
reverse = get_as_bool(Keys::REVERSE, false)
|
|
641
|
+
blink = get_as_bool(Keys::BLINK, false)
|
|
642
|
+
faint = get_as_bool(Keys::FAINT, false)
|
|
643
|
+
|
|
644
|
+
fg = get_as_color(Keys::FOREGROUND)
|
|
645
|
+
bg = get_as_color(Keys::BACKGROUND)
|
|
646
|
+
|
|
647
|
+
width = get_as_int(Keys::WIDTH)
|
|
648
|
+
height = get_as_int(Keys::HEIGHT)
|
|
649
|
+
horizontal_align = get_as_position(Keys::ALIGN_HORIZONTAL)
|
|
650
|
+
vertical_align = get_as_position(Keys::ALIGN_VERTICAL)
|
|
651
|
+
|
|
652
|
+
top_padding = get_as_int(Keys::PADDING_TOP)
|
|
653
|
+
right_padding = get_as_int(Keys::PADDING_RIGHT)
|
|
654
|
+
bottom_padding = get_as_int(Keys::PADDING_BOTTOM)
|
|
655
|
+
left_padding = get_as_int(Keys::PADDING_LEFT)
|
|
656
|
+
|
|
657
|
+
color_whitespace = get_as_bool(Keys::COLOR_WHITESPACE, true)
|
|
658
|
+
inline = get_as_bool(Keys::INLINE, false)
|
|
659
|
+
max_width = get_as_int(Keys::MAX_WIDTH)
|
|
660
|
+
max_height = get_as_int(Keys::MAX_HEIGHT)
|
|
661
|
+
|
|
662
|
+
underline_spaces = get_as_bool(Keys::UNDERLINE_SPACES, false) ||
|
|
663
|
+
(underline && get_as_bool(Keys::UNDERLINE_SPACES, true))
|
|
664
|
+
strikethrough_spaces = get_as_bool(Keys::STRIKETHROUGH_SPACES, false) ||
|
|
665
|
+
(strikethrough && get_as_bool(Keys::STRIKETHROUGH_SPACES, true))
|
|
666
|
+
|
|
667
|
+
style_whitespace = reverse
|
|
668
|
+
use_space_styler = (underline && !underline_spaces) || (strikethrough && !strikethrough_spaces) ||
|
|
669
|
+
underline_spaces || strikethrough_spaces
|
|
670
|
+
|
|
671
|
+
return maybe_convert_tabs(str) if @props.zero?
|
|
672
|
+
|
|
673
|
+
te = te.bold if bold
|
|
674
|
+
te = te.italic if italic
|
|
675
|
+
te = te.underline if underline
|
|
676
|
+
if reverse
|
|
677
|
+
te_whitespace = te_whitespace.reverse
|
|
678
|
+
te = te.reverse
|
|
679
|
+
end
|
|
680
|
+
te = te.blink if blink
|
|
681
|
+
te = te.faint if faint
|
|
682
|
+
|
|
683
|
+
unless no_color?(fg)
|
|
684
|
+
te = te.foreground(fg.color(r))
|
|
685
|
+
te_whitespace = te_whitespace.foreground(fg.color(r)) if style_whitespace
|
|
686
|
+
te_space = te_space.foreground(fg.color(r)) if use_space_styler
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
unless no_color?(bg)
|
|
690
|
+
te = te.background(bg.color(r))
|
|
691
|
+
te_whitespace = te_whitespace.background(bg.color(r)) if color_whitespace
|
|
692
|
+
te_space = te_space.background(bg.color(r)) if use_space_styler
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
te = te.underline if underline
|
|
696
|
+
te = te.cross_out if strikethrough
|
|
697
|
+
|
|
698
|
+
te_space = te_space.underline if underline_spaces
|
|
699
|
+
te_space = te_space.cross_out if strikethrough_spaces
|
|
700
|
+
|
|
701
|
+
str = maybe_convert_tabs(str)
|
|
702
|
+
str = replace_all(str, "\r\n", "\n")
|
|
703
|
+
str = replace_all(str, "\n", "") if inline
|
|
704
|
+
|
|
705
|
+
if !inline && width.positive?
|
|
706
|
+
wrap_at = width - left_padding - right_padding
|
|
707
|
+
str = Gloss::Text.cellbuf_wrap(str, wrap_at, "")
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# Render core text
|
|
711
|
+
lines = go_split(str)
|
|
712
|
+
out = +""
|
|
713
|
+
lines.each_with_index do |line, i|
|
|
714
|
+
if use_space_styler
|
|
715
|
+
each_rune(line) do |rune, ch|
|
|
716
|
+
out << (Gloss::Text.space?(rune) ? te_space.styled(ch) : te.styled(ch))
|
|
717
|
+
end
|
|
718
|
+
else
|
|
719
|
+
out << te.styled(line)
|
|
720
|
+
end
|
|
721
|
+
out << "\n" if i != lines.length - 1
|
|
722
|
+
end
|
|
723
|
+
str = out
|
|
724
|
+
|
|
725
|
+
ws_style = color_whitespace || style_whitespace ? te_whitespace : nil
|
|
726
|
+
|
|
727
|
+
unless inline
|
|
728
|
+
str = pad(str, -left_padding, ws_style) if left_padding.positive?
|
|
729
|
+
str = pad(str, right_padding, ws_style) if right_padding.positive?
|
|
730
|
+
str = ("\n" * top_padding) + str if top_padding.positive?
|
|
731
|
+
str += "\n" * bottom_padding if bottom_padding.positive?
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
str = align_text_vertical(str, vertical_align, height) if height.positive?
|
|
735
|
+
|
|
736
|
+
num_lines = str.b.count("\n")
|
|
737
|
+
str = align_text_horizontal(str, horizontal_align, width, ws_style) if num_lines != 0 || width != 0
|
|
738
|
+
|
|
739
|
+
unless inline
|
|
740
|
+
str = apply_border(str)
|
|
741
|
+
str = apply_margins(str, inline)
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
if max_width.positive?
|
|
745
|
+
str = go_split(str).map { |l| Gloss::Text.truncate(l, max_width, "") }.join("\n")
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
if max_height.positive?
|
|
749
|
+
lines = go_split(str)
|
|
750
|
+
h = [max_height, lines.length].min
|
|
751
|
+
str = lines[0, h].join("\n") unless lines.empty?
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
str
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
def maybe_convert_tabs(str)
|
|
758
|
+
tw = set?(Keys::TAB_WIDTH) ? get_as_int(Keys::TAB_WIDTH) : TAB_WIDTH_DEFAULT
|
|
759
|
+
case tw
|
|
760
|
+
when -1 then str
|
|
761
|
+
when 0 then replace_all(str, "\t", "")
|
|
762
|
+
else replace_all(str, "\t", " " * tw)
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
def apply_margins(str, inline)
|
|
767
|
+
top_margin = get_as_int(Keys::MARGIN_TOP)
|
|
768
|
+
right_margin = get_as_int(Keys::MARGIN_RIGHT)
|
|
769
|
+
bottom_margin = get_as_int(Keys::MARGIN_BOTTOM)
|
|
770
|
+
left_margin = get_as_int(Keys::MARGIN_LEFT)
|
|
771
|
+
|
|
772
|
+
styler = Gloss::Termenv::Style.new
|
|
773
|
+
bgc = get_as_color(Keys::MARGIN_BACKGROUND)
|
|
774
|
+
styler = styler.background(bgc.color(Gloss::Renderer.default)) unless no_color?(bgc)
|
|
775
|
+
|
|
776
|
+
str = pad(str, -left_margin, styler)
|
|
777
|
+
str = pad(str, right_margin, styler)
|
|
778
|
+
|
|
779
|
+
unless inline
|
|
780
|
+
_lines, width = get_lines(str)
|
|
781
|
+
spaces = " " * width
|
|
782
|
+
str = styler.styled("#{spaces}\n" * top_margin) + str if top_margin.positive?
|
|
783
|
+
str += styler.styled("\n#{spaces}" * bottom_margin) if bottom_margin.positive?
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
str
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
def pad(str, n, style)
|
|
790
|
+
return str if n.zero?
|
|
791
|
+
|
|
792
|
+
sp = " " * n.abs
|
|
793
|
+
sp = style.styled(sp) if style
|
|
794
|
+
|
|
795
|
+
lines = go_split(str)
|
|
796
|
+
out = +""
|
|
797
|
+
lines.each_with_index do |l, i|
|
|
798
|
+
if n.positive?
|
|
799
|
+
out << l << sp
|
|
800
|
+
else
|
|
801
|
+
out << sp << l
|
|
802
|
+
end
|
|
803
|
+
out << "\n" if i != lines.length - 1
|
|
804
|
+
end
|
|
805
|
+
out
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# --- align.go --------------------------------------------------------------------------------
|
|
809
|
+
|
|
810
|
+
def align_text_horizontal(str, pos, width, style)
|
|
811
|
+
lines, widest = get_lines(str)
|
|
812
|
+
out = +""
|
|
813
|
+
lines.each_with_index do |l, i|
|
|
814
|
+
line_width = Gloss::Text.string_width(l)
|
|
815
|
+
short = widest - line_width
|
|
816
|
+
short += [0, width - (short + line_width)].max
|
|
817
|
+
if short.positive?
|
|
818
|
+
if pos == RIGHT
|
|
819
|
+
s = " " * short
|
|
820
|
+
s = style.styled(s) if style
|
|
821
|
+
l = s + l
|
|
822
|
+
elsif pos == CENTER
|
|
823
|
+
left = short / 2
|
|
824
|
+
right = left + (short % 2)
|
|
825
|
+
left_spaces = " " * left
|
|
826
|
+
right_spaces = " " * right
|
|
827
|
+
if style
|
|
828
|
+
left_spaces = style.styled(left_spaces)
|
|
829
|
+
right_spaces = style.styled(right_spaces)
|
|
830
|
+
end
|
|
831
|
+
l = left_spaces + l + right_spaces
|
|
832
|
+
else
|
|
833
|
+
s = " " * short
|
|
834
|
+
s = style.styled(s) if style
|
|
835
|
+
l += s
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
out << l
|
|
839
|
+
out << "\n" if i < lines.length - 1
|
|
840
|
+
end
|
|
841
|
+
out
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def align_text_vertical(str, pos, height)
|
|
845
|
+
str_height = str.b.count("\n") + 1
|
|
846
|
+
return str if height < str_height
|
|
847
|
+
|
|
848
|
+
if pos == TOP
|
|
849
|
+
str + ("\n" * (height - str_height))
|
|
850
|
+
elsif pos == CENTER
|
|
851
|
+
top_padding = (height - str_height) / 2
|
|
852
|
+
bottom_padding = top_padding
|
|
853
|
+
if str_height + top_padding + bottom_padding > height
|
|
854
|
+
top_padding -= 1
|
|
855
|
+
elsif str_height + top_padding + bottom_padding < height
|
|
856
|
+
bottom_padding += 1
|
|
857
|
+
end
|
|
858
|
+
("\n" * top_padding) + str + ("\n" * bottom_padding)
|
|
859
|
+
elsif pos == BOTTOM
|
|
860
|
+
("\n" * (height - str_height)) + str
|
|
861
|
+
else
|
|
862
|
+
str
|
|
863
|
+
end
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
# --- borders.go ------------------------------------------------------------------------------
|
|
867
|
+
|
|
868
|
+
def apply_border(str)
|
|
869
|
+
border = get_border_style.dup
|
|
870
|
+
has_top = get_as_bool(Keys::BORDER_TOP, false)
|
|
871
|
+
has_right = get_as_bool(Keys::BORDER_RIGHT, false)
|
|
872
|
+
has_bottom = get_as_bool(Keys::BORDER_BOTTOM, false)
|
|
873
|
+
has_left = get_as_bool(Keys::BORDER_LEFT, false)
|
|
874
|
+
|
|
875
|
+
top_fg = get_as_color(Keys::BORDER_TOP_FOREGROUND)
|
|
876
|
+
right_fg = get_as_color(Keys::BORDER_RIGHT_FOREGROUND)
|
|
877
|
+
bottom_fg = get_as_color(Keys::BORDER_BOTTOM_FOREGROUND)
|
|
878
|
+
left_fg = get_as_color(Keys::BORDER_LEFT_FOREGROUND)
|
|
879
|
+
|
|
880
|
+
top_bg = get_as_color(Keys::BORDER_TOP_BACKGROUND)
|
|
881
|
+
right_bg = get_as_color(Keys::BORDER_RIGHT_BACKGROUND)
|
|
882
|
+
bottom_bg = get_as_color(Keys::BORDER_BOTTOM_BACKGROUND)
|
|
883
|
+
left_bg = get_as_color(Keys::BORDER_LEFT_BACKGROUND)
|
|
884
|
+
|
|
885
|
+
if implicit_borders?
|
|
886
|
+
has_top = true
|
|
887
|
+
has_right = true
|
|
888
|
+
has_bottom = true
|
|
889
|
+
has_left = true
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
return str if border == Gloss::Borders::NO_BORDER || (!has_top && !has_right && !has_bottom && !has_left)
|
|
893
|
+
|
|
894
|
+
lines, width = get_lines(str)
|
|
895
|
+
|
|
896
|
+
if has_left
|
|
897
|
+
border.left = " " if border.left.empty?
|
|
898
|
+
width += Gloss::Borders.max_rune_width(border.left)
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
border.right = " " if has_right && border.right.empty?
|
|
902
|
+
border.top_left = " " if has_top && has_left && border.top_left.empty?
|
|
903
|
+
border.top_right = " " if has_top && has_right && border.top_right.empty?
|
|
904
|
+
border.bottom_left = " " if has_bottom && has_left && border.bottom_left.empty?
|
|
905
|
+
border.bottom_right = " " if has_bottom && has_right && border.bottom_right.empty?
|
|
906
|
+
|
|
907
|
+
if has_top
|
|
908
|
+
if !has_left && !has_right
|
|
909
|
+
border.top_left = ""
|
|
910
|
+
border.top_right = ""
|
|
911
|
+
elsif !has_left
|
|
912
|
+
border.top_left = ""
|
|
913
|
+
elsif !has_right
|
|
914
|
+
border.top_right = ""
|
|
915
|
+
end
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
if has_bottom
|
|
919
|
+
if !has_left && !has_right
|
|
920
|
+
border.bottom_left = ""
|
|
921
|
+
border.bottom_right = ""
|
|
922
|
+
elsif !has_left
|
|
923
|
+
border.bottom_left = ""
|
|
924
|
+
elsif !has_right
|
|
925
|
+
border.bottom_right = ""
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
border.top_left = first_rune(border.top_left)
|
|
930
|
+
border.top_right = first_rune(border.top_right)
|
|
931
|
+
border.bottom_right = first_rune(border.bottom_right)
|
|
932
|
+
border.bottom_left = first_rune(border.bottom_left)
|
|
933
|
+
|
|
934
|
+
out = +""
|
|
935
|
+
if has_top
|
|
936
|
+
top = render_horizontal_edge(border.top_left, border.top, border.top_right, width)
|
|
937
|
+
out << style_border(top, top_fg, top_bg) << "\n"
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
left_runes = go_runes(border.left)
|
|
941
|
+
left_index = 0
|
|
942
|
+
right_runes = go_runes(border.right)
|
|
943
|
+
right_index = 0
|
|
944
|
+
|
|
945
|
+
lines.each_with_index do |l, i|
|
|
946
|
+
if has_left
|
|
947
|
+
ch = left_runes[left_index]
|
|
948
|
+
left_index += 1
|
|
949
|
+
left_index = 0 if left_index >= left_runes.length
|
|
950
|
+
out << style_border(ch, left_fg, left_bg)
|
|
951
|
+
end
|
|
952
|
+
out << l
|
|
953
|
+
if has_right
|
|
954
|
+
ch = right_runes[right_index]
|
|
955
|
+
right_index += 1
|
|
956
|
+
right_index = 0 if right_index >= right_runes.length
|
|
957
|
+
out << style_border(ch, right_fg, right_bg)
|
|
958
|
+
end
|
|
959
|
+
out << "\n" if i < lines.length - 1
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
if has_bottom
|
|
963
|
+
bottom = render_horizontal_edge(border.bottom_left, border.bottom, border.bottom_right, width)
|
|
964
|
+
out << "\n" << style_border(bottom, bottom_fg, bottom_bg)
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
out
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
def render_horizontal_edge(left, middle, right, width)
|
|
971
|
+
middle = " " if middle.empty?
|
|
972
|
+
left_width = Gloss::Text.string_width(left)
|
|
973
|
+
right_width = Gloss::Text.string_width(right)
|
|
974
|
+
|
|
975
|
+
runes = go_runes(middle)
|
|
976
|
+
widths = runes.map { |ch| Gloss::Text.string_width(ch) }
|
|
977
|
+
j = 0
|
|
978
|
+
out = +""
|
|
979
|
+
out << left
|
|
980
|
+
i = left_width + right_width
|
|
981
|
+
# Go loops forever when every rune of the edge is zero-width; stop instead.
|
|
982
|
+
if widths.any?(&:positive?)
|
|
983
|
+
while i < width + right_width
|
|
984
|
+
out << runes[j]
|
|
985
|
+
j += 1
|
|
986
|
+
j = 0 if j >= runes.length
|
|
987
|
+
i += widths[j]
|
|
988
|
+
end
|
|
989
|
+
end
|
|
990
|
+
out << right
|
|
991
|
+
out
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
def style_border(border, fg, bg)
|
|
995
|
+
return border if no_color?(fg) && no_color?(bg)
|
|
996
|
+
|
|
997
|
+
r = Gloss::Renderer.default
|
|
998
|
+
style = Gloss::Termenv::Style.new
|
|
999
|
+
style = style.foreground(fg.color(r)) unless no_color?(fg)
|
|
1000
|
+
style = style.background(bg.color(r)) unless no_color?(bg)
|
|
1001
|
+
style.styled(border)
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
def first_rune(str)
|
|
1005
|
+
return str if str.empty?
|
|
1006
|
+
|
|
1007
|
+
go_runes(str).first
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
# --- Go string helpers -----------------------------------------------------------------------
|
|
1011
|
+
|
|
1012
|
+
# strings.Split(s, "\n")
|
|
1013
|
+
def go_split(str)
|
|
1014
|
+
return [+""] if str.empty?
|
|
1015
|
+
|
|
1016
|
+
str.b.split("\n", -1).map { |l| l.force_encoding(UTF_8) }
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
# strings.ReplaceAll on bytes.
|
|
1020
|
+
def replace_all(str, from, to)
|
|
1021
|
+
b = str.b
|
|
1022
|
+
return str unless b.include?(from)
|
|
1023
|
+
|
|
1024
|
+
b.gsub(from.b, to.b).force_encoding(UTF_8)
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
# getLines
|
|
1028
|
+
def get_lines(str)
|
|
1029
|
+
lines = go_split(str)
|
|
1030
|
+
widest = 0
|
|
1031
|
+
lines.each do |l|
|
|
1032
|
+
w = Gloss::Text.string_width(l)
|
|
1033
|
+
widest = w if widest < w
|
|
1034
|
+
end
|
|
1035
|
+
[lines, widest]
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
# Ranges over a Go string: yields [rune, utf-8 string]; invalid bytes yield U+FFFD.
|
|
1039
|
+
def each_rune(str)
|
|
1040
|
+
if str.ascii_only?
|
|
1041
|
+
str.each_char { |ch| yield ch.ord, ch }
|
|
1042
|
+
return
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
b = str.b
|
|
1046
|
+
n = b.bytesize
|
|
1047
|
+
i = 0
|
|
1048
|
+
while i < n
|
|
1049
|
+
r, size = Gloss::Text.decode_rune(b, i, n)
|
|
1050
|
+
yield r, (r < 0x80 ? r.chr : [r].pack("U"))
|
|
1051
|
+
i += size
|
|
1052
|
+
end
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
# []rune(s) as strings.
|
|
1056
|
+
def go_runes(str)
|
|
1057
|
+
out = []
|
|
1058
|
+
each_rune(str) { |_r, ch| out << ch }
|
|
1059
|
+
out
|
|
1060
|
+
end
|
|
1061
|
+
end
|
|
1062
|
+
end
|