cli-ui 2.4.0 → 2.6.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/lib/cli/ui/ansi.rb +23 -27
- data/lib/cli/ui/color.rb +7 -13
- data/lib/cli/ui/formatter.rb +23 -23
- data/lib/cli/ui/frame/frame_stack.rb +9 -21
- data/lib/cli/ui/frame/frame_style/box.rb +11 -10
- data/lib/cli/ui/frame/frame_style/bracket.rb +11 -10
- data/lib/cli/ui/frame/frame_style.rb +31 -22
- data/lib/cli/ui/frame.rb +12 -43
- data/lib/cli/ui/glyph.rb +8 -14
- data/lib/cli/ui/os.rb +6 -10
- data/lib/cli/ui/printer.rb +1 -15
- data/lib/cli/ui/progress.rb +20 -23
- data/lib/cli/ui/progress_reporter.rb +209 -0
- data/lib/cli/ui/prompt/interactive_options.rb +46 -54
- data/lib/cli/ui/prompt/options_handler.rb +4 -6
- data/lib/cli/ui/prompt.rb +22 -45
- data/lib/cli/ui/spinner/async.rb +3 -7
- data/lib/cli/ui/spinner/spin_group.rb +205 -135
- data/lib/cli/ui/spinner.rb +3 -16
- data/lib/cli/ui/stdout_router.rb +38 -55
- data/lib/cli/ui/table.rb +3 -7
- data/lib/cli/ui/terminal.rb +4 -8
- data/lib/cli/ui/truncater.rb +5 -6
- data/lib/cli/ui/version.rb +1 -1
- data/lib/cli/ui/widgets/base.rb +13 -14
- data/lib/cli/ui/widgets/status.rb +10 -10
- data/lib/cli/ui/widgets.rb +7 -15
- data/lib/cli/ui/work_queue.rb +23 -27
- data/lib/cli/ui/wrap.rb +3 -5
- data/lib/cli/ui.rb +42 -97
- metadata +3 -3
- data/lib/cli/ui/sorbet_runtime_stub.rb +0 -168
@@ -0,0 +1,209 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CLI
|
5
|
+
module UI
|
6
|
+
# Handles terminal progress bar reporting using ConEmu OSC 9;4 sequences
|
7
|
+
# Supports:
|
8
|
+
# - Numerical progress (0-100%)
|
9
|
+
# - Indeterminate/pulsing progress
|
10
|
+
# - Success/error states
|
11
|
+
# - Paused state
|
12
|
+
module ProgressReporter
|
13
|
+
# Progress reporter instance that manages the lifecycle of progress reporting
|
14
|
+
class Reporter
|
15
|
+
# OSC (Operating System Command) escape sequences
|
16
|
+
OSC = "\e]"
|
17
|
+
ST = "\a" # String Terminator (BEL)
|
18
|
+
|
19
|
+
# Progress states
|
20
|
+
REMOVE = 0
|
21
|
+
SET_PROGRESS = 1
|
22
|
+
ERROR = 2
|
23
|
+
INDETERMINATE = 3
|
24
|
+
PAUSED = 4
|
25
|
+
|
26
|
+
#: (Symbol mode, ?io_like to, ?parent: Reporter?, ?delay_start: bool) -> void
|
27
|
+
def initialize(mode, to = $stdout, parent: nil, delay_start: false)
|
28
|
+
@mode = mode
|
29
|
+
@to = to
|
30
|
+
@parent = parent
|
31
|
+
@children = []
|
32
|
+
@active = ProgressReporter.supports_progress? && @parent.nil?
|
33
|
+
|
34
|
+
# Register with parent if nested
|
35
|
+
@parent&.add_child(self)
|
36
|
+
|
37
|
+
return unless @active
|
38
|
+
return if delay_start # Don't emit initial OSC if delayed
|
39
|
+
|
40
|
+
case mode
|
41
|
+
when :indeterminate
|
42
|
+
set_indeterminate
|
43
|
+
when :progress
|
44
|
+
set_progress(0)
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Unknown progress mode: #{mode}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#: (Reporter child) -> void
|
51
|
+
def add_child(child)
|
52
|
+
@children << child
|
53
|
+
end
|
54
|
+
|
55
|
+
#: (Reporter child) -> void
|
56
|
+
def remove_child(child)
|
57
|
+
@children.delete(child)
|
58
|
+
end
|
59
|
+
|
60
|
+
#: -> bool
|
61
|
+
def has_active_children?
|
62
|
+
@children.any?
|
63
|
+
end
|
64
|
+
|
65
|
+
#: (Integer percentage) -> void
|
66
|
+
def set_progress(percentage) # rubocop:disable Naming/AccessorMethodName
|
67
|
+
# Don't emit progress if we have active children (they own the progress)
|
68
|
+
return if has_active_children?
|
69
|
+
return unless @active
|
70
|
+
|
71
|
+
@mode = :progress # Update mode when switching to progress
|
72
|
+
percentage = percentage.clamp(0, 100)
|
73
|
+
@to.print("#{OSC}9;4;#{SET_PROGRESS};#{percentage}#{ST}")
|
74
|
+
end
|
75
|
+
|
76
|
+
#: -> void
|
77
|
+
def set_indeterminate
|
78
|
+
# Don't emit progress if we have active children
|
79
|
+
return if has_active_children?
|
80
|
+
return unless @active
|
81
|
+
|
82
|
+
@mode = :indeterminate # Update mode when switching to indeterminate
|
83
|
+
@to.print("#{OSC}9;4;#{INDETERMINATE};#{ST}")
|
84
|
+
end
|
85
|
+
|
86
|
+
# Force progress mode even if there are children - used by SpinGroup
|
87
|
+
# when a task needs to show deterministic progress
|
88
|
+
#: (Integer percentage) -> void
|
89
|
+
def force_set_progress(percentage)
|
90
|
+
return unless @active
|
91
|
+
|
92
|
+
@mode = :progress
|
93
|
+
percentage = percentage.clamp(0, 100)
|
94
|
+
@to.print("#{OSC}9;4;#{SET_PROGRESS};#{percentage}#{ST}")
|
95
|
+
end
|
96
|
+
|
97
|
+
# Force indeterminate mode even if there are children
|
98
|
+
#: -> void
|
99
|
+
def force_set_indeterminate
|
100
|
+
return unless @active
|
101
|
+
|
102
|
+
@mode = :indeterminate
|
103
|
+
@to.print("#{OSC}9;4;#{INDETERMINATE};#{ST}")
|
104
|
+
end
|
105
|
+
|
106
|
+
#: -> void
|
107
|
+
def set_error
|
108
|
+
# Error state can be set even with children
|
109
|
+
return unless @active
|
110
|
+
|
111
|
+
@to.print("#{OSC}9;4;#{ERROR};#{ST}")
|
112
|
+
end
|
113
|
+
|
114
|
+
#: (?Integer? percentage) -> void
|
115
|
+
def set_paused(percentage = nil)
|
116
|
+
return if has_active_children?
|
117
|
+
return unless @active
|
118
|
+
|
119
|
+
if percentage
|
120
|
+
percentage = percentage.clamp(0, 100)
|
121
|
+
@to.print("#{OSC}9;4;#{PAUSED};#{percentage}#{ST}")
|
122
|
+
else
|
123
|
+
@to.print("#{OSC}9;4;#{PAUSED};#{ST}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
#: -> void
|
128
|
+
def clear
|
129
|
+
# Only clear if we're the root reporter and have no active children
|
130
|
+
return unless @active
|
131
|
+
return if has_active_children?
|
132
|
+
|
133
|
+
@to.print("#{OSC}9;4;#{REMOVE};#{ST}")
|
134
|
+
end
|
135
|
+
|
136
|
+
#: -> void
|
137
|
+
def cleanup
|
138
|
+
# Remove self from parent's children list
|
139
|
+
@parent&.remove_child(self)
|
140
|
+
|
141
|
+
# If parent exists and has no more children, restore its progress state
|
142
|
+
if @parent && !@parent.has_active_children?
|
143
|
+
case @parent.instance_variable_get(:@mode)
|
144
|
+
when :indeterminate
|
145
|
+
@parent.set_indeterminate
|
146
|
+
when :progress
|
147
|
+
# Parent progress bar should maintain its last state
|
148
|
+
# The parent will handle re-emitting its progress on next tick
|
149
|
+
end
|
150
|
+
elsif !@parent
|
151
|
+
# We're the root, clear progress
|
152
|
+
clear
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class << self
|
158
|
+
# Thread-local storage for the current reporter stack
|
159
|
+
#: -> Array[Reporter]
|
160
|
+
def reporter_stack
|
161
|
+
Thread.current[:progress_reporter_stack] ||= []
|
162
|
+
end
|
163
|
+
|
164
|
+
#: -> Reporter?
|
165
|
+
def current_reporter
|
166
|
+
reporter_stack.last
|
167
|
+
end
|
168
|
+
|
169
|
+
# Block-based API that ensures progress is cleared
|
170
|
+
#: [T] (?mode: Symbol, ?to: io_like, ?delay_start: bool) { (Reporter reporter) -> T } -> T
|
171
|
+
def with_progress(mode: :indeterminate, to: $stdout, delay_start: false, &block)
|
172
|
+
parent = current_reporter
|
173
|
+
reporter = Reporter.new(mode, to, parent: parent, delay_start: delay_start)
|
174
|
+
|
175
|
+
reporter_stack.push(reporter)
|
176
|
+
yield(reporter)
|
177
|
+
ensure
|
178
|
+
reporter_stack.pop
|
179
|
+
reporter&.cleanup
|
180
|
+
end
|
181
|
+
|
182
|
+
#: -> bool
|
183
|
+
def supports_progress?
|
184
|
+
# Check if terminal supports ConEmu OSC sequences
|
185
|
+
# This is supported by:
|
186
|
+
# - ConEmu on Windows
|
187
|
+
# - Windows Terminal
|
188
|
+
# - Ghostty
|
189
|
+
# - Various terminals on Linux (with OSC 9;4 support)
|
190
|
+
|
191
|
+
# Check common environment variables that indicate support
|
192
|
+
return true if ENV['ConEmuPID']
|
193
|
+
return true if ENV['WT_SESSION'] # Windows Terminal
|
194
|
+
return true if ENV['GHOSTTY_RESOURCES_DIR'] # Ghostty
|
195
|
+
|
196
|
+
# Check TERM_PROGRAM for known supporting terminals
|
197
|
+
term_program = ENV['TERM_PROGRAM']
|
198
|
+
return true if term_program == 'ghostty'
|
199
|
+
|
200
|
+
# For now, we'll be conservative and only enable for known terminals
|
201
|
+
# Users can force-enable with an environment variable
|
202
|
+
return true if ENV['CLI_UI_ENABLE_PROGRESS'] == '1'
|
203
|
+
|
204
|
+
false
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -8,14 +8,10 @@ module CLI
|
|
8
8
|
module UI
|
9
9
|
module Prompt
|
10
10
|
class InteractiveOptions
|
11
|
-
extend T::Sig
|
12
|
-
|
13
11
|
DONE = 'Done'
|
14
12
|
CHECKBOX_ICON = { false => '☐', true => '☑' }
|
15
13
|
|
16
14
|
class << self
|
17
|
-
extend T::Sig
|
18
|
-
|
19
15
|
# Prompts the user with options
|
20
16
|
# Uses an interactive session to allow the user to pick an answer
|
21
17
|
# Can use arrows, y/n, numbers (1/2), and vim bindings to control
|
@@ -30,18 +26,17 @@ module CLI
|
|
30
26
|
# Ask an interactive question
|
31
27
|
# CLI::UI::Prompt::InteractiveOptions.call(%w(rails go python))
|
32
28
|
#
|
33
|
-
|
34
|
-
params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(String, T::Array[String])))
|
35
|
-
.returns(T.any(String, T::Array[String]))
|
36
|
-
end
|
29
|
+
#: (Array[String] options, ?multiple: bool, ?default: (String | Array[String])?) -> (String | Array[String])
|
37
30
|
def call(options, multiple: false, default: nil)
|
38
31
|
list = new(options, multiple: multiple, default: default)
|
39
32
|
selected = list.call
|
40
33
|
case selected
|
41
34
|
when Array
|
42
|
-
selected.map
|
35
|
+
selected.map do |s|
|
36
|
+
options[s - 1] #: as !nil
|
37
|
+
end
|
43
38
|
else
|
44
|
-
|
39
|
+
options[selected - 1] #: as !nil
|
45
40
|
end
|
46
41
|
end
|
47
42
|
end
|
@@ -53,10 +48,7 @@ module CLI
|
|
53
48
|
#
|
54
49
|
# CLI::UI::Prompt::InteractiveOptions.new(%w(rails go python))
|
55
50
|
#
|
56
|
-
|
57
|
-
params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(String, T::Array[String])))
|
58
|
-
.void
|
59
|
-
end
|
51
|
+
#: (Array[String] options, ?multiple: bool, ?default: (String | Array[String])?) -> void
|
60
52
|
def initialize(options, multiple: false, default: nil)
|
61
53
|
@options = options
|
62
54
|
@active = if default && (i = options.index(default))
|
@@ -82,13 +74,13 @@ module CLI
|
|
82
74
|
end
|
83
75
|
end
|
84
76
|
@redraw = true
|
85
|
-
@presented_options =
|
77
|
+
@presented_options = [] #: Array[[String, Integer?]]
|
86
78
|
end
|
87
79
|
|
88
80
|
# Calls the +InteractiveOptions+ and asks the question
|
89
81
|
# Usually used from +self.call+
|
90
82
|
#
|
91
|
-
|
83
|
+
#: -> (Integer | Array[Integer])
|
92
84
|
def call
|
93
85
|
calculate_option_line_lengths
|
94
86
|
CLI::UI.raw { print(ANSI.hide_cursor) }
|
@@ -108,7 +100,7 @@ module CLI
|
|
108
100
|
|
109
101
|
private
|
110
102
|
|
111
|
-
|
103
|
+
#: -> void
|
112
104
|
def calculate_option_line_lengths
|
113
105
|
@terminal_width_at_calculation_time = CLI::UI::Terminal.width
|
114
106
|
# options will be an array of questions but each option can be multi-line
|
@@ -138,7 +130,7 @@ module CLI
|
|
138
130
|
end
|
139
131
|
end
|
140
132
|
|
141
|
-
|
133
|
+
#: (?Integer number_of_lines) -> void
|
142
134
|
def reset_position(number_of_lines = num_lines)
|
143
135
|
# This will put us back at the beginning of the options
|
144
136
|
# When we redraw the options, they will be overwritten
|
@@ -147,7 +139,7 @@ module CLI
|
|
147
139
|
end
|
148
140
|
end
|
149
141
|
|
150
|
-
|
142
|
+
#: (?Integer number_of_lines) -> void
|
151
143
|
def clear_output(number_of_lines = num_lines)
|
152
144
|
CLI::UI.raw do
|
153
145
|
# Write over all lines with whitespace
|
@@ -163,12 +155,12 @@ module CLI
|
|
163
155
|
|
164
156
|
# Don't use this in place of +@displaying_metadata+, this updates too
|
165
157
|
# quickly to be useful when drawing to the screen.
|
166
|
-
|
158
|
+
#: -> bool
|
167
159
|
def display_metadata?
|
168
160
|
filtering? || selecting? || has_filter?
|
169
161
|
end
|
170
162
|
|
171
|
-
|
163
|
+
#: -> Integer
|
172
164
|
def num_lines
|
173
165
|
calculate_option_line_lengths if terminal_width_changed?
|
174
166
|
|
@@ -182,7 +174,7 @@ module CLI
|
|
182
174
|
option_length + (@displaying_metadata ? 1 : 0)
|
183
175
|
end
|
184
176
|
|
185
|
-
|
177
|
+
#: -> bool
|
186
178
|
def terminal_width_changed?
|
187
179
|
@terminal_width_at_calculation_time != CLI::UI::Terminal.width
|
188
180
|
end
|
@@ -192,7 +184,7 @@ module CLI
|
|
192
184
|
CTRL_C = "\u0003"
|
193
185
|
CTRL_D = "\u0004"
|
194
186
|
|
195
|
-
|
187
|
+
#: -> void
|
196
188
|
def up
|
197
189
|
active_index = @filtered_options.index { |_, num| num == @active } || 0
|
198
190
|
|
@@ -203,7 +195,7 @@ module CLI
|
|
203
195
|
@redraw = true
|
204
196
|
end
|
205
197
|
|
206
|
-
|
198
|
+
#: -> void
|
207
199
|
def down
|
208
200
|
active_index = @filtered_options.index { |_, num| num == @active } || 0
|
209
201
|
|
@@ -214,19 +206,19 @@ module CLI
|
|
214
206
|
@redraw = true
|
215
207
|
end
|
216
208
|
|
217
|
-
|
209
|
+
#: -> void
|
218
210
|
def first_option
|
219
211
|
@active = @filtered_options.first&.last || -1
|
220
212
|
@redraw = true
|
221
213
|
end
|
222
214
|
|
223
|
-
|
215
|
+
#: -> void
|
224
216
|
def last_option
|
225
217
|
@active = @filtered_options.last&.last || -1
|
226
218
|
@redraw = true
|
227
219
|
end
|
228
220
|
|
229
|
-
|
221
|
+
#: -> void
|
230
222
|
def next_page
|
231
223
|
active_index = @filtered_options.index { |_, num| num == @active } || 0
|
232
224
|
|
@@ -237,7 +229,7 @@ module CLI
|
|
237
229
|
@redraw = true
|
238
230
|
end
|
239
231
|
|
240
|
-
|
232
|
+
#: -> void
|
241
233
|
def previous_page
|
242
234
|
active_index = @filtered_options.index { |_, num| num == @active } || 0
|
243
235
|
|
@@ -251,7 +243,7 @@ module CLI
|
|
251
243
|
|
252
244
|
# n is 1-indexed selection
|
253
245
|
# n == 0 if "Done" was selected in @multiple mode
|
254
|
-
|
246
|
+
#: (Integer n, ?final: bool) -> void
|
255
247
|
def select_n(n, final: false)
|
256
248
|
if @multiple
|
257
249
|
if n == 0
|
@@ -278,7 +270,7 @@ module CLI
|
|
278
270
|
@redraw = true
|
279
271
|
end
|
280
272
|
|
281
|
-
|
273
|
+
#: (Integer n) -> bool
|
282
274
|
def should_enter_select_mode?(n)
|
283
275
|
# If we have less than 10 options, we don't need to enter select mode
|
284
276
|
# and we can just select the option directly. This just keeps the code easier
|
@@ -292,29 +284,29 @@ module CLI
|
|
292
284
|
@options.length >= (n * 10)
|
293
285
|
end
|
294
286
|
|
295
|
-
|
287
|
+
#: (String char) -> void
|
296
288
|
def select_bool(char)
|
297
289
|
return unless (@options - ['yes', 'no']).empty?
|
298
290
|
|
299
|
-
index =
|
291
|
+
index = @options.index { |o| o.start_with?(char) } #: as !nil
|
300
292
|
@active = index + 1
|
301
293
|
@answer = index + 1
|
302
294
|
@redraw = true
|
303
295
|
end
|
304
296
|
|
305
|
-
|
297
|
+
#: (String char) -> void
|
306
298
|
def build_selection(char)
|
307
299
|
@active = (@active.to_s + char).to_i
|
308
300
|
@redraw = true
|
309
301
|
end
|
310
302
|
|
311
|
-
|
303
|
+
#: -> void
|
312
304
|
def chop_selection
|
313
305
|
@active = @active.to_s.chop.to_i
|
314
306
|
@redraw = true
|
315
307
|
end
|
316
308
|
|
317
|
-
|
309
|
+
#: (String char) -> void
|
318
310
|
def update_search(char)
|
319
311
|
@redraw = true
|
320
312
|
|
@@ -332,7 +324,7 @@ module CLI
|
|
332
324
|
end
|
333
325
|
end
|
334
326
|
|
335
|
-
|
327
|
+
#: -> void
|
336
328
|
def select_current
|
337
329
|
# Prevent selection of invisible options
|
338
330
|
return unless presented_options.any? { |_, num| num == @active }
|
@@ -340,14 +332,14 @@ module CLI
|
|
340
332
|
select_n(@active, final: true)
|
341
333
|
end
|
342
334
|
|
343
|
-
|
335
|
+
#: -> void
|
344
336
|
def process_input_until_redraw_required
|
345
337
|
@redraw = false
|
346
338
|
wait_for_user_input until @redraw
|
347
339
|
end
|
348
340
|
|
349
341
|
# rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
|
350
|
-
|
342
|
+
#: -> void
|
351
343
|
def wait_for_user_input
|
352
344
|
char = Prompt.read_char
|
353
345
|
@last_char = char
|
@@ -409,42 +401,42 @@ module CLI
|
|
409
401
|
end
|
410
402
|
# rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
|
411
403
|
|
412
|
-
|
404
|
+
#: -> bool
|
413
405
|
def selecting?
|
414
406
|
@state == :line_select
|
415
407
|
end
|
416
408
|
|
417
|
-
|
409
|
+
#: -> bool
|
418
410
|
def filtering?
|
419
411
|
@state == :filter
|
420
412
|
end
|
421
413
|
|
422
|
-
|
414
|
+
#: -> bool
|
423
415
|
def has_filter?
|
424
416
|
!@filter.empty?
|
425
417
|
end
|
426
418
|
|
427
|
-
|
419
|
+
#: -> void
|
428
420
|
def start_filter
|
429
421
|
@state = :filter
|
430
422
|
@redraw = true
|
431
423
|
end
|
432
424
|
|
433
|
-
|
425
|
+
#: -> void
|
434
426
|
def start_line_select
|
435
427
|
@state = :line_select
|
436
428
|
@active = 0
|
437
429
|
@redraw = true
|
438
430
|
end
|
439
431
|
|
440
|
-
|
432
|
+
#: -> void
|
441
433
|
def stop_line_select
|
442
434
|
@state = :root
|
443
435
|
@active = 1 if @active.zero?
|
444
436
|
@redraw = true
|
445
437
|
end
|
446
438
|
|
447
|
-
|
439
|
+
#: (?recalculate: bool) -> Array[[String, Integer?]]
|
448
440
|
def presented_options(recalculate: false)
|
449
441
|
return @presented_options unless recalculate
|
450
442
|
|
@@ -487,44 +479,44 @@ module CLI
|
|
487
479
|
@presented_options
|
488
480
|
end
|
489
481
|
|
490
|
-
|
482
|
+
#: -> void
|
491
483
|
def ensure_visible_is_active
|
492
484
|
unless presented_options.any? { |_, num| num == @active }
|
493
485
|
@active = presented_options.first&.last.to_i
|
494
486
|
end
|
495
487
|
end
|
496
488
|
|
497
|
-
|
489
|
+
#: -> Integer
|
498
490
|
def distance_from_selection_to_end
|
499
491
|
@presented_options.count - index_of_active_option
|
500
492
|
end
|
501
493
|
|
502
|
-
|
494
|
+
#: -> Integer
|
503
495
|
def distance_from_start_to_selection
|
504
496
|
index_of_active_option
|
505
497
|
end
|
506
498
|
|
507
|
-
|
499
|
+
#: -> Integer
|
508
500
|
def index_of_active_option
|
509
501
|
@presented_options.index { |_, num| num == @active }.to_i
|
510
502
|
end
|
511
503
|
|
512
|
-
|
504
|
+
#: -> void
|
513
505
|
def ensure_last_item_is_continuation_marker
|
514
506
|
@presented_options.push(['...', nil]) if @presented_options.last&.last
|
515
507
|
end
|
516
508
|
|
517
|
-
|
509
|
+
#: -> void
|
518
510
|
def ensure_first_item_is_continuation_marker
|
519
511
|
@presented_options.unshift(['...', nil]) if @presented_options.first&.last
|
520
512
|
end
|
521
513
|
|
522
|
-
|
514
|
+
#: -> Integer
|
523
515
|
def max_lines
|
524
516
|
CLI::UI::Terminal.height - (@displaying_metadata ? 3 : 2) # Keeps a one line question visible
|
525
517
|
end
|
526
518
|
|
527
|
-
|
519
|
+
#: -> void
|
528
520
|
def render_options
|
529
521
|
previously_displayed_lines = num_lines
|
530
522
|
|
@@ -575,7 +567,7 @@ module CLI
|
|
575
567
|
end
|
576
568
|
end
|
577
569
|
|
578
|
-
|
570
|
+
#: (String format, String choice) -> String
|
579
571
|
def format_choice(format, choice)
|
580
572
|
eol = CLI::UI::ANSI.clear_to_end_of_line
|
581
573
|
lines = choice.split("\n")
|
@@ -6,24 +6,22 @@ module CLI
|
|
6
6
|
module Prompt
|
7
7
|
# A class that handles the various options of an InteractivePrompt and their callbacks
|
8
8
|
class OptionsHandler
|
9
|
-
|
10
|
-
|
11
|
-
sig { void }
|
9
|
+
#: -> void
|
12
10
|
def initialize
|
13
11
|
@options = {}
|
14
12
|
end
|
15
13
|
|
16
|
-
|
14
|
+
#: -> Array[String]
|
17
15
|
def options
|
18
16
|
@options.keys
|
19
17
|
end
|
20
18
|
|
21
|
-
|
19
|
+
#: (String option) { (String option) -> String } -> void
|
22
20
|
def option(option, &handler)
|
23
21
|
@options[option] = handler
|
24
22
|
end
|
25
23
|
|
26
|
-
|
24
|
+
#: ((Array[String] | String) options) -> String
|
27
25
|
def call(options)
|
28
26
|
case options
|
29
27
|
when Array
|