natty-ui 0.35.0 → 1.0.2
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/README.md +6 -6
- data/examples/24bit-colors.rb +9 -5
- data/examples/3bit-colors.rb +7 -7
- data/examples/8bit-colors.rb +5 -7
- data/examples/attributes.rb +2 -3
- data/examples/elements.rb +9 -6
- data/examples/examples.rb +9 -9
- data/examples/frames.rb +31 -0
- data/examples/hbars.rb +6 -3
- data/examples/info.rb +13 -10
- data/examples/key-codes.rb +8 -9
- data/examples/ls.rb +24 -22
- data/examples/named-colors.rb +4 -3
- data/examples/sections.rb +26 -24
- data/examples/select.rb +28 -0
- data/examples/sh.rb +25 -7
- data/examples/tables.rb +19 -37
- data/examples/tasks.rb +32 -22
- data/examples/vbars.rb +5 -3
- data/lib/natty-ui/dumb_progress.rb +68 -0
- data/lib/natty-ui/element.rb +61 -70
- data/lib/natty-ui/features.rb +771 -949
- data/lib/natty-ui/frame.rb +87 -0
- data/lib/natty-ui/helper/table.rb +1376 -0
- data/lib/natty-ui/margin.rb +83 -0
- data/lib/natty-ui/progress.rb +116 -152
- data/lib/natty-ui/renderer/bars.rb +93 -0
- data/lib/natty-ui/renderer/choice.rb +56 -0
- data/lib/natty-ui/renderer/dumb_choice.rb +34 -0
- data/lib/natty-ui/renderer/dumb_select.rb +60 -0
- data/lib/natty-ui/renderer/dumb_shell_runner.rb +19 -0
- data/lib/natty-ui/renderer/heading.rb +26 -0
- data/lib/natty-ui/renderer/horizontal_rule.rb +32 -0
- data/lib/natty-ui/{ls_renderer.rb → renderer/ls.rb} +15 -27
- data/lib/natty-ui/renderer/mark.rb +13 -0
- data/lib/natty-ui/renderer/quote.rb +13 -0
- data/lib/natty-ui/renderer/select.rb +63 -0
- data/lib/natty-ui/renderer/shell.rb +15 -0
- data/lib/natty-ui/renderer/shell_runner.rb +29 -0
- data/lib/natty-ui/renderer/table_renderer.rb +429 -0
- data/lib/natty-ui/section.rb +144 -32
- data/lib/natty-ui/task.rb +38 -25
- data/lib/natty-ui/temporary.rb +27 -14
- data/lib/natty-ui/utils/border.rb +139 -0
- data/lib/natty-ui/utils/str_const.rb +62 -0
- data/lib/natty-ui/utils/utils.rb +47 -0
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui.rb +76 -35
- metadata +31 -28
- data/examples/cols.rb +0 -38
- data/examples/illustration.rb +0 -60
- data/examples/options.rb +0 -28
- data/examples/themes.rb +0 -51
- data/lib/natty-ui/attributes.rb +0 -593
- data/lib/natty-ui/choice.rb +0 -67
- data/lib/natty-ui/dumb_choice.rb +0 -47
- data/lib/natty-ui/dumb_options.rb +0 -64
- data/lib/natty-ui/framed.rb +0 -50
- data/lib/natty-ui/hbars_renderer.rb +0 -66
- data/lib/natty-ui/options.rb +0 -78
- data/lib/natty-ui/shell_renderer.rb +0 -91
- data/lib/natty-ui/table.rb +0 -325
- data/lib/natty-ui/table_renderer.rb +0 -165
- data/lib/natty-ui/theme.rb +0 -403
- data/lib/natty-ui/utils.rb +0 -111
- data/lib/natty-ui/vbars_renderer.rb +0 -49
- data/lib/natty-ui/width_finder.rb +0 -137
- data/natty-ui.gemspec +0 -34
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'element'
|
|
4
|
+
|
|
5
|
+
module NattyUI
|
|
6
|
+
# An {Element} that adds horizontal indentation and vertical spacing.
|
|
7
|
+
#
|
|
8
|
+
# Instances are created by {#margin}.
|
|
9
|
+
#
|
|
10
|
+
# All {Features} methods are available on this element.
|
|
11
|
+
#
|
|
12
|
+
# @example Horizontal indent via block
|
|
13
|
+
# ui.margin 0, 4 do
|
|
14
|
+
# ui.puts 'This line is indented by 4 characters on each side.'
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example Manual close with left spacing
|
|
18
|
+
# m = ui.margin left: 2
|
|
19
|
+
# ui.puts 'Indented content.'
|
|
20
|
+
# m.end
|
|
21
|
+
class Margin < Element
|
|
22
|
+
# @private
|
|
23
|
+
def puts(*, **options)
|
|
24
|
+
return self if @done != 0
|
|
25
|
+
options[:width] = @parent.columns - @prefix.width - @suffix.width
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def done
|
|
32
|
+
@parent.space(@bottom) if (@done += 1) == 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(parent, *args)
|
|
36
|
+
super(parent)
|
|
37
|
+
|
|
38
|
+
top, right, @bottom, left =
|
|
39
|
+
case args.size
|
|
40
|
+
when 0
|
|
41
|
+
[0, 1, 0, 1]
|
|
42
|
+
when 1
|
|
43
|
+
if (arg = args[0]).is_a?(Hash)
|
|
44
|
+
[
|
|
45
|
+
arg.fetch(:top, 0),
|
|
46
|
+
arg.fetch(:right, 0),
|
|
47
|
+
arg.fetch(:bottom, 0),
|
|
48
|
+
arg.fetch(:left, 0)
|
|
49
|
+
]
|
|
50
|
+
else
|
|
51
|
+
Array.new(4, arg)
|
|
52
|
+
end
|
|
53
|
+
when 2
|
|
54
|
+
args * 2
|
|
55
|
+
when 3
|
|
56
|
+
args << args[1]
|
|
57
|
+
when 4
|
|
58
|
+
args
|
|
59
|
+
else
|
|
60
|
+
raise(
|
|
61
|
+
ArgumentError,
|
|
62
|
+
"wrong number of arguments (given #{args.size}, expected 0..4)"
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
top = [0, Float === top ? top.round : top.to_int].max
|
|
67
|
+
@bottom = [0, Float === @bottom ? @bottom.round : @bottom.to_int].max
|
|
68
|
+
|
|
69
|
+
width = parent.columns
|
|
70
|
+
if width < 2
|
|
71
|
+
@prefix = @suffix = StrConst.empty
|
|
72
|
+
return parent.space(top)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
right = (width * right).round if Float === right
|
|
76
|
+
left = (width * left).round if Float === left
|
|
77
|
+
|
|
78
|
+
parent.space(top)
|
|
79
|
+
@prefix = StrConst.spacer(left.to_int.clamp(0, width))
|
|
80
|
+
@suffix = StrConst.spacer(right.to_int.clamp(0, width))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/natty-ui/progress.rb
CHANGED
|
@@ -1,185 +1,149 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
3
|
+
require_relative 'temporary'
|
|
4
4
|
|
|
5
5
|
module NattyUI
|
|
6
|
-
#
|
|
6
|
+
# A {Temporary} element that displays an progress indicator.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
8
|
+
# Instances are created by {#progress}. When a `max` value is set the
|
|
9
|
+
# progress is displayed as a percentage bar that fills as {#value} increases.
|
|
10
|
+
# Without a `max`, an open-ended dot animation grows.
|
|
9
11
|
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
# All {Features} methods are available on this element, making it possible to
|
|
13
|
+
# nest output or sub-tasks inside a progress block.
|
|
14
|
+
#
|
|
15
|
+
# @example Bounded progress (percentage bar)
|
|
16
|
+
# ui.progress 'Loading files', max: files.size do |bar|
|
|
17
|
+
# files.each { load(it); bar.step }
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Open-ended progress (dot animation)
|
|
21
|
+
# ui.progress 'Connecting…' do
|
|
22
|
+
# loop { ui.step; break if connected? }
|
|
23
|
+
# end
|
|
24
|
+
class Progress < Temporary
|
|
25
|
+
# Maximum value set at creation time.
|
|
26
|
+
#
|
|
27
|
+
# @return [Numeric] the maximum value
|
|
28
|
+
# @return [nil] when no maximum was set (open-ended mode)
|
|
29
|
+
attr_reader :max
|
|
21
30
|
|
|
22
|
-
#
|
|
31
|
+
# Current progress value.
|
|
32
|
+
#
|
|
33
|
+
# When {#max} is set the value is clamped to `0..max`. When there is no
|
|
34
|
+
# maximum the value is clamped to a minimum of `0`. Setting the same
|
|
35
|
+
# value twice is a no-op.
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# bar.value += 42
|
|
39
|
+
#
|
|
40
|
+
# @return [Numeric] current value
|
|
23
41
|
attr_reader :value
|
|
24
42
|
|
|
25
43
|
# @attribute [w] value
|
|
26
44
|
def value=(val)
|
|
27
|
-
@
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
return if @done != 0
|
|
46
|
+
val = @max ? val.clamp(0, @max) : [0, val].max
|
|
47
|
+
return if @value == val
|
|
48
|
+
@value = val
|
|
49
|
+
draw_bar
|
|
31
50
|
end
|
|
32
51
|
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# Increment {value} and/or change {title}.
|
|
52
|
+
# Updates the progress title and/or value.
|
|
53
|
+
#
|
|
54
|
+
# Any number of the positional `title` arguments and the `value:` keyword
|
|
55
|
+
# may be omitted independently.
|
|
56
|
+
#
|
|
57
|
+
# @example Update title only
|
|
58
|
+
# bar.update 'Finalising…'
|
|
59
|
+
#
|
|
60
|
+
# @example Update value only
|
|
61
|
+
# bar.update value: 50
|
|
62
|
+
#
|
|
63
|
+
# @example Update both
|
|
64
|
+
# bar.update 'Step 2 of 3', value: 66
|
|
48
65
|
#
|
|
49
|
-
# @param
|
|
50
|
-
# @param
|
|
51
|
-
# @
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
# @param title [#to_s, ...] new title text
|
|
67
|
+
# @param value [Numeric] new progress value
|
|
68
|
+
# @param popts [Hash] title print options, see {Features#puts}
|
|
69
|
+
# @return [Progress] itself
|
|
70
|
+
def update(*title, value: nil, **popts)
|
|
71
|
+
return if @done != 0
|
|
72
|
+
draw_title(*title, **popts) unless title.empty?
|
|
73
|
+
self.value = value if value
|
|
55
74
|
self
|
|
56
75
|
end
|
|
57
76
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
# Increments the progress value and optionally updates the title.
|
|
78
|
+
#
|
|
79
|
+
# @example Increment by 1
|
|
80
|
+
# bar.step
|
|
81
|
+
#
|
|
82
|
+
# @example Increment by a custom amount
|
|
83
|
+
# bar.step count: 5
|
|
84
|
+
#
|
|
85
|
+
# @example Increment and update the title
|
|
86
|
+
# bar.step 'Processing item 3', count: 1.5
|
|
87
|
+
#
|
|
88
|
+
# @param title (see #update)
|
|
89
|
+
# @param count [Numeric] amount to add
|
|
90
|
+
# @param popts (see #update)
|
|
91
|
+
# @return (see #update)
|
|
92
|
+
def step(*title, count: 1, **popts)
|
|
93
|
+
update(*title, value: @value + count, **popts)
|
|
75
94
|
end
|
|
76
95
|
|
|
77
|
-
# @private
|
|
78
|
-
def inspect = "#{_to_s.chop} #{self}>"
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
class Progress
|
|
82
|
-
include ProgressHelper
|
|
83
|
-
|
|
84
96
|
private
|
|
85
97
|
|
|
86
|
-
def
|
|
87
|
-
NattyUI.back_to_line(@
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def _failed
|
|
98
|
-
NattyUI.back_to_line(NattyUI.lines_written - 1) if @last&.size == 2
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def initialize(parent, title, max, pin)
|
|
102
|
-
@parent = parent
|
|
103
|
-
@value = 0
|
|
104
|
-
@title = title
|
|
105
|
-
@pin = pin
|
|
106
|
-
@pin_line = NattyUI.lines_written
|
|
107
|
-
@style = Theme.current.task_style
|
|
108
|
-
cm = Theme.current.mark(:current)
|
|
109
|
-
@redraw_opts = {
|
|
110
|
-
first_line_prefix: "#{cm}#{@style}",
|
|
111
|
-
first_line_prefix_width: cm.width
|
|
112
|
-
}
|
|
113
|
-
max ? self.max = max : redraw
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def redraw
|
|
117
|
-
return if @status
|
|
118
|
-
bar = @max ? bar(@value / @max) : moving_bar
|
|
119
|
-
curr = bar ? [@title, bar] : [@title]
|
|
120
|
-
return if @last == curr
|
|
121
|
-
@pin_line = NattyUI.back_to_line(@pin_line) if @last
|
|
122
|
-
@parent.puts(*curr, **@redraw_opts)
|
|
123
|
-
@last = curr
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def moving_bar
|
|
127
|
-
"#{@style}#{'•' * @value}" if @value >= 1
|
|
98
|
+
def draw_bar
|
|
99
|
+
@bar_line = NattyUI.back_to_line(@bar_line)
|
|
100
|
+
if @max
|
|
101
|
+
@parent.puts(bar(@value / @max.to_f), prefix: @prefix, align: :right)
|
|
102
|
+
else
|
|
103
|
+
@parent.puts(simple, prefix: @prefix)
|
|
104
|
+
end
|
|
105
|
+
@pins&.each do |objects, opts|
|
|
106
|
+
@parent.puts(*objects, **opts, prefix: @prefix)
|
|
107
|
+
end
|
|
128
108
|
end
|
|
129
109
|
|
|
130
110
|
def bar(diff)
|
|
131
|
-
size =
|
|
132
|
-
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def _done(text)
|
|
147
|
-
cm = Theme.current.mark(:checkmark)
|
|
148
|
-
@parent.puts(
|
|
149
|
-
*text,
|
|
150
|
-
pin: @pin,
|
|
151
|
-
first_line_prefix: cm,
|
|
152
|
-
first_line_prefix_width: cm.width
|
|
153
|
-
)
|
|
111
|
+
size = @parent.columns - 8
|
|
112
|
+
size -= @prefix.width if @prefix
|
|
113
|
+
return diff == 1 ? '100.0%' : format('%.2f%%', 100 * diff) if size < 10
|
|
114
|
+
|
|
115
|
+
if diff == 1
|
|
116
|
+
"[bright_green]100.0%[bright_black]▕[bright_green on_bright_black]#{
|
|
117
|
+
'█' * (size * diff)
|
|
118
|
+
}[/bg bright_black]▏"
|
|
119
|
+
else
|
|
120
|
+
"[bright_green]#{
|
|
121
|
+
format('%.2f%%', 100 * diff)
|
|
122
|
+
}[bright_black]▕[green on_bright_black]#{
|
|
123
|
+
fill = '█' * (size * diff)
|
|
124
|
+
}[/bg bright_black]#{'░' * (size - fill.size)}▏"
|
|
125
|
+
end
|
|
154
126
|
end
|
|
155
127
|
|
|
156
|
-
def
|
|
157
|
-
|
|
128
|
+
def simple
|
|
129
|
+
v = Float === @value ? @value.round(2) : @value
|
|
130
|
+
"[bright_green]#{'•' * @value}▏[/fg]#{v}"
|
|
158
131
|
end
|
|
159
132
|
|
|
160
|
-
def
|
|
161
|
-
@
|
|
162
|
-
|
|
163
|
-
@
|
|
164
|
-
|
|
133
|
+
def draw_title(*, **options)
|
|
134
|
+
@start_line = NattyUI.back_to_line(@start_line)
|
|
135
|
+
options[:cprefix] = Utils.mark[:active]
|
|
136
|
+
options[:prefix] = @prefix = Utils.mark[:none]
|
|
137
|
+
@parent.puts(*, **options)
|
|
138
|
+
@bar_line = NattyUI.lines_written
|
|
165
139
|
end
|
|
166
140
|
|
|
167
|
-
def
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
first_line_prefix_width: 2
|
|
174
|
-
)
|
|
175
|
-
end
|
|
176
|
-
return if @max.nil? || @value < 1
|
|
177
|
-
percent = format('%3.0f %%', 100.0 * (@value / @max))
|
|
178
|
-
return if @last_percent == percent
|
|
179
|
-
@parent.puts(percent, first_line_prefix: ' ', first_line_prefix_width: 2)
|
|
180
|
-
@last_percent = percent
|
|
141
|
+
def initialize(parent, max, *title, **)
|
|
142
|
+
super(parent)
|
|
143
|
+
@value = 0
|
|
144
|
+
@max = [0, max].max if max
|
|
145
|
+
draw_title(*title, **) unless title.empty?
|
|
146
|
+
draw_bar
|
|
181
147
|
end
|
|
182
148
|
end
|
|
183
|
-
|
|
184
|
-
private_constant :Progress, :DumbProgress
|
|
185
149
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
class VBars
|
|
6
|
+
def initialize(values, min, max, normalize)
|
|
7
|
+
@integers = values.all?(Integer)
|
|
8
|
+
@values = values.map(&:to_f)
|
|
9
|
+
@min = min
|
|
10
|
+
@max = max
|
|
11
|
+
@normalize = normalize
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def empty? = @values.empty?
|
|
15
|
+
def valid? = @values.all?(&:positive?)
|
|
16
|
+
|
|
17
|
+
def lines(columns, height, bar_width, style)
|
|
18
|
+
return nil if (height = height.to_int) < 3
|
|
19
|
+
width = columns - 1
|
|
20
|
+
values = adjusted(height)
|
|
21
|
+
rbw = (width - values.size) / values.size
|
|
22
|
+
rbw = bar_width.to_int.clamp(0, rbw) if rbw > 0 && bar_width != :auto
|
|
23
|
+
style, elyts = Utils.affix(style)
|
|
24
|
+
if rbw < 2
|
|
25
|
+
values = values.take(width) if values.size > width
|
|
26
|
+
db = '▉'
|
|
27
|
+
ds = ' '
|
|
28
|
+
el = values.size
|
|
29
|
+
else
|
|
30
|
+
db = "#{'█' * rbw} "
|
|
31
|
+
ds = ' ' * (rbw += 1)
|
|
32
|
+
el = (rbw * values.size) - 1
|
|
33
|
+
end
|
|
34
|
+
(height - 1)
|
|
35
|
+
.downto(1)
|
|
36
|
+
.map do |i|
|
|
37
|
+
"#{style}#{values.map { it < i ? ds : db }.join}#{elyts}"
|
|
38
|
+
end << "#{style}#{'▔' * el}#{elyts}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def adjusted(size)
|
|
44
|
+
return normalized(size) if @normalize
|
|
45
|
+
max = @values.max
|
|
46
|
+
max = @max.to_f if @max&.>(max)
|
|
47
|
+
return Array.new(@values.size, size) if max <= 0
|
|
48
|
+
@values.map { ((it / max) * size).round }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def normalized(size)
|
|
54
|
+
min, max = @values.minmax
|
|
55
|
+
min = @min.to_f if @min&.<(min)
|
|
56
|
+
max = @max.to_f if @max&.>(max)
|
|
57
|
+
max -= min
|
|
58
|
+
return Array.new(@values.size, size) if max <= 0
|
|
59
|
+
@values.map { (((it - min) / max) * size).round }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @private
|
|
64
|
+
class HBars < VBars
|
|
65
|
+
def with_text(style)
|
|
66
|
+
@texts =
|
|
67
|
+
if @integers
|
|
68
|
+
@values.map { StrConst[it] }
|
|
69
|
+
else
|
|
70
|
+
@values.map { StrConst[it.round(2)] }
|
|
71
|
+
end
|
|
72
|
+
@texts_width = @texts.max_by(&:width).width
|
|
73
|
+
style, elyts = Utils.affix(style)
|
|
74
|
+
@texts.map! { "#{style}#{it.rjust(@texts_width)}#{elyts}" }
|
|
75
|
+
@texts_width += 1
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def lines(columns, width, style)
|
|
80
|
+
width = Utils.as_width(width, columns)
|
|
81
|
+
return if width < 2
|
|
82
|
+
if @texts_width
|
|
83
|
+
w = width - @texts_width
|
|
84
|
+
w < 2 ? @texts = nil : width = w
|
|
85
|
+
end
|
|
86
|
+
style, elyts = Utils.affix(style)
|
|
87
|
+
lines = adjusted(width).map! { "#{style}▕#{'▆' * it}#{elyts}" }
|
|
88
|
+
@texts ? @texts.zip(lines).map!(&:join) : lines
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private_constant :VBars, :HBars
|
|
93
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
class Choice
|
|
6
|
+
def select(abortable, selected)
|
|
7
|
+
start_line = pin_line = NattyUI.lines_written
|
|
8
|
+
draw(last = current = @results.index(selected) || 0)
|
|
9
|
+
Terminal.on_key_event do |event|
|
|
10
|
+
case event.name
|
|
11
|
+
when 'Esc', 'Ctrl+c'
|
|
12
|
+
break if abortable
|
|
13
|
+
when 'Enter', 'Space'
|
|
14
|
+
break @results[current]
|
|
15
|
+
when 'Home'
|
|
16
|
+
current = 0
|
|
17
|
+
when 'End'
|
|
18
|
+
current = @results.size - 1
|
|
19
|
+
when 'Up', 'Back', 'Shift+Tab', 'i', 'w'
|
|
20
|
+
current = @results.size - 1 if (current -= 1) < 0
|
|
21
|
+
when 'Down', 'Tab', 'k', 's'
|
|
22
|
+
current = 0 if (current += 1) == @results.size
|
|
23
|
+
else
|
|
24
|
+
next unless event.simple?
|
|
25
|
+
c = event.key.ord
|
|
26
|
+
current = (c - 48).clamp(0, @results.size - 1) if c.between?(48, 57)
|
|
27
|
+
end
|
|
28
|
+
next if last == current
|
|
29
|
+
pin_line = NattyUI.back_to_line(pin_line, erase: false)
|
|
30
|
+
draw(last = current)
|
|
31
|
+
end
|
|
32
|
+
ensure
|
|
33
|
+
NattyUI.back_to_line(start_line)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def draw(current)
|
|
39
|
+
@texts.each_with_index do |str, idx|
|
|
40
|
+
@parent.puts(
|
|
41
|
+
str,
|
|
42
|
+
cprefix: Utils.mark[idx == current ? :current_item : :item],
|
|
43
|
+
prefix: Utils.mark[:none]
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(parent, texts, results)
|
|
49
|
+
@parent = parent
|
|
50
|
+
@texts = texts
|
|
51
|
+
@results = results
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private_constant :Choice
|
|
56
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
class DumbChoice
|
|
6
|
+
def select(abortable, _selected)
|
|
7
|
+
Terminal.on_key_event do |event|
|
|
8
|
+
break if abortable && %w[Esc Ctrl+c].include?(event.name)
|
|
9
|
+
next unless event.simple?
|
|
10
|
+
code = event.raw.upcase
|
|
11
|
+
if @results.size <= 9
|
|
12
|
+
next unless ('1'..'9').include?(code)
|
|
13
|
+
code = @results[code.ord - 49] and return code
|
|
14
|
+
elsif ('A'..'Z').include?(code)
|
|
15
|
+
code = @results[code.ord - 65] and return code
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def initialize(parent, texts, results)
|
|
23
|
+
@results = results
|
|
24
|
+
glyph = results.size <= 9 ? 1 : 'A'
|
|
25
|
+
prefix = StrConst.spacer(4)
|
|
26
|
+
texts.each do |str|
|
|
27
|
+
parent.puts(str, prefix:, cprefix: StrConst.new("[#{glyph}] ", 4))
|
|
28
|
+
glyph = glyph.succ
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private_constant :DumbChoice
|
|
34
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module NattyUI
|
|
2
|
+
# @private
|
|
3
|
+
class DumbSelect
|
|
4
|
+
def select(abortable)
|
|
5
|
+
draw
|
|
6
|
+
Terminal.on_key_event do |event|
|
|
7
|
+
if abortable && %w[Esc Ctrl+c].include?(event.name)
|
|
8
|
+
@parent.space
|
|
9
|
+
break []
|
|
10
|
+
end
|
|
11
|
+
if event.name == 'Enter'
|
|
12
|
+
@parent.space
|
|
13
|
+
break @items.filter_map { |ret, _str, selected| ret if selected }
|
|
14
|
+
end
|
|
15
|
+
next unless event.simple?
|
|
16
|
+
code = event.raw.upcase
|
|
17
|
+
if @items.size <= 9
|
|
18
|
+
next unless ('1'..'9').include?(code)
|
|
19
|
+
current = code.ord - 49
|
|
20
|
+
elsif ('A'..'Z').include?(code)
|
|
21
|
+
current = code.ord - 65
|
|
22
|
+
else
|
|
23
|
+
next
|
|
24
|
+
end
|
|
25
|
+
next if current >= @items.size
|
|
26
|
+
*last, selected = @items[current]
|
|
27
|
+
@items[current] = last << !selected
|
|
28
|
+
@parent.space
|
|
29
|
+
draw
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def draw
|
|
36
|
+
@parent.ls(
|
|
37
|
+
*@items.map do |_ret, str, selected|
|
|
38
|
+
"[\\#{selected ? 'X' : ' '}] #{str}"
|
|
39
|
+
end,
|
|
40
|
+
glyph: @items.size <= 9 ? 1 : 'A'
|
|
41
|
+
)
|
|
42
|
+
# # glyph = @items.size <= 9 ? 1 : 'A'
|
|
43
|
+
# # @items.each do |_ret, str, selected|
|
|
44
|
+
# # @parent.puts(
|
|
45
|
+
# # str,
|
|
46
|
+
# # cprefix: true,
|
|
47
|
+
# # prefix: "#{glyph}: [\\#{selected ? 'X' : ' '}] "
|
|
48
|
+
# # )
|
|
49
|
+
# # glyph = glyph.succ
|
|
50
|
+
# # end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def initialize(parent, items)
|
|
54
|
+
@parent = parent
|
|
55
|
+
@items = items
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private_constant :DumbSelect
|
|
60
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
module DumbShellRunner
|
|
6
|
+
def self.render(parent, _tail, *, **)
|
|
7
|
+
out, err = [], []
|
|
8
|
+
width = parent.columns
|
|
9
|
+
ret =
|
|
10
|
+
Terminal.sh(*, **) do |line, kind|
|
|
11
|
+
(kind == :error ? err : out) << line
|
|
12
|
+
parent.puts(line, width:, spaces: true, prefix: Utils.mark[kind])
|
|
13
|
+
end
|
|
14
|
+
[ret, out, err] if ret
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private_constant :DumbShellRunner
|
|
19
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
class Heading
|
|
6
|
+
def self.render(parent, level, title)
|
|
7
|
+
prefix, suffix = @defined[level.to_int.clamp(1, 6) - 1]
|
|
8
|
+
parent.puts(title, prefix: prefix, suffix: suffix, eol: false)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :to_ary
|
|
12
|
+
|
|
13
|
+
def initialize(str, style)
|
|
14
|
+
@to_ary = [
|
|
15
|
+
StrConst["#{style}#{str}#{Ansi::RESET} "],
|
|
16
|
+
StrConst[" #{style}#{str.reverse}#{Ansi::RESET}"]
|
|
17
|
+
]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
style = Ansi[:bright_blue]
|
|
21
|
+
@defined =
|
|
22
|
+
%w[╸╸╺╸╺━━━ ╴╶╴╶─═══ ╴╶╴╶─── ════ ━━━━ ────].map! { new(it, style) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private_constant :Heading
|
|
26
|
+
end
|