cli-ui 2.2.3 → 2.3.1
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 +1 -1
- data/lib/cli/ui/ansi.rb +25 -3
- data/lib/cli/ui/color.rb +3 -0
- data/lib/cli/ui/formatter.rb +1 -0
- data/lib/cli/ui/frame/frame_stack.rb +1 -0
- data/lib/cli/ui/frame/frame_style/box.rb +15 -14
- data/lib/cli/ui/frame/frame_style/bracket.rb +14 -13
- data/lib/cli/ui/frame/frame_style.rb +3 -2
- data/lib/cli/ui/frame.rb +9 -8
- data/lib/cli/ui/glyph.rb +2 -1
- data/lib/cli/ui/os.rb +1 -0
- data/lib/cli/ui/printer.rb +1 -0
- data/lib/cli/ui/progress.rb +36 -8
- data/lib/cli/ui/prompt/interactive_options.rb +33 -12
- data/lib/cli/ui/prompt/options_handler.rb +1 -0
- data/lib/cli/ui/prompt.rb +27 -30
- data/lib/cli/ui/spinner/async.rb +1 -0
- data/lib/cli/ui/spinner/spin_group.rb +182 -51
- data/lib/cli/ui/spinner.rb +11 -5
- data/lib/cli/ui/stdout_router.rb +6 -4
- data/lib/cli/ui/table.rb +87 -0
- data/lib/cli/ui/terminal.rb +1 -0
- data/lib/cli/ui/version.rb +2 -1
- data/lib/cli/ui/widgets/base.rb +1 -0
- data/lib/cli/ui/widgets.rb +2 -1
- data/lib/cli/ui/work_queue.rb +146 -0
- data/lib/cli/ui/wrap.rb +4 -4
- data/lib/cli/ui.rb +44 -11
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99ed2ae4a35b13db165864297acf7cfc4e636283e51683a7954ca2e2f43ac94c
|
4
|
+
data.tar.gz: 30eb1a74cdbd50b95d9c48209c97258261970c9678eb71e089bb894a018bcb0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 219e73a9baa10649b9cd14280ce9fe8d2f18a614006c9b4f5c8c79b3df3da3ef236c00ec290816ab866ff3d67cf058ecf5c88f0034e331a1ab77cdef1df25111
|
7
|
+
data.tar.gz: 79de546a04f2ca9fe49246be7df31582cafc791799bbacd868af7fa1e381e42a462685e619a734745500203a25587a7eb6eaee04465b269cee8242ad67334a1b
|
data/README.md
CHANGED
@@ -162,7 +162,7 @@ CLI::UI.frame_style = :box
|
|
162
162
|
To style an individual frame:
|
163
163
|
|
164
164
|
```ruby
|
165
|
-
CLI::UI.frame('New Style!', frame_style: :bracket) { puts
|
165
|
+
CLI::UI.frame('New Style!', frame_style: :bracket) { puts "It's pretty cool!" }
|
166
166
|
```
|
167
167
|
|
168
168
|
The default style - `:box` - is what has been used up until now. The other style - `:bracket` - looks like this:
|
data/lib/cli/ui/ansi.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'cli/ui'
|
4
5
|
|
@@ -45,7 +46,7 @@ module CLI
|
|
45
46
|
#
|
46
47
|
sig { params(str: String).returns(String) }
|
47
48
|
def strip_codes(str)
|
48
|
-
str.gsub(/\x1b\[[\d;]+[A-z]|\r/, '')
|
49
|
+
str.gsub(/\x1b\[[\d;]+[A-Za-z]|\x1b\][\d;]+.*?\x1b\\|\r/, '')
|
49
50
|
end
|
50
51
|
|
51
52
|
# Returns an ANSI control sequence
|
@@ -145,7 +146,7 @@ module CLI
|
|
145
146
|
|
146
147
|
sig { returns(Regexp) }
|
147
148
|
def match_alternate_screen
|
148
|
-
/#{Regexp.escape(control(
|
149
|
+
/#{Regexp.escape(control("?1049", ""))}[hl]/
|
149
150
|
end
|
150
151
|
|
151
152
|
# Show the cursor
|
@@ -187,13 +188,34 @@ module CLI
|
|
187
188
|
#
|
188
189
|
sig { returns(String) }
|
189
190
|
def previous_line
|
190
|
-
|
191
|
+
previous_lines(1)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Move to the previous n lines
|
195
|
+
#
|
196
|
+
# ==== Attributes
|
197
|
+
#
|
198
|
+
# * +n+ - number of lines by which to move the cursor up
|
199
|
+
#
|
200
|
+
sig { params(n: Integer).returns(String) }
|
201
|
+
def previous_lines(n = 1)
|
202
|
+
cursor_up(n) + cursor_horizontal_absolute
|
191
203
|
end
|
192
204
|
|
193
205
|
sig { returns(String) }
|
194
206
|
def clear_to_end_of_line
|
195
207
|
control('', 'K')
|
196
208
|
end
|
209
|
+
|
210
|
+
sig { returns(String) }
|
211
|
+
def insert_line
|
212
|
+
insert_lines(1)
|
213
|
+
end
|
214
|
+
|
215
|
+
sig { params(n: Integer).returns(String) }
|
216
|
+
def insert_lines(n = 1)
|
217
|
+
control(n.to_s, 'L')
|
218
|
+
end
|
197
219
|
end
|
198
220
|
end
|
199
221
|
end
|
data/lib/cli/ui/color.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'cli/ui'
|
4
5
|
|
@@ -42,6 +43,8 @@ module CLI
|
|
42
43
|
|
43
44
|
# 240 is very dark gray; 255 is very light gray. 244 is somewhat dark.
|
44
45
|
GRAY = new('38;5;244', :gray)
|
46
|
+
# Using color 214 from the 256-color palette for a more distinct orange
|
47
|
+
ORANGE = new('38;5;214', :orange)
|
45
48
|
|
46
49
|
MAP = {
|
47
50
|
red: RED,
|
data/lib/cli/ui/formatter.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CLI
|
4
5
|
module UI
|
@@ -94,7 +95,8 @@ module CLI
|
|
94
95
|
|
95
96
|
preamble = +''
|
96
97
|
|
97
|
-
preamble << color.code
|
98
|
+
preamble << color.code if CLI::UI.enable_color?
|
99
|
+
preamble << first << (HORIZONTAL * 2)
|
98
100
|
|
99
101
|
unless text.empty?
|
100
102
|
preamble << ' ' << CLI::UI.resolve_text("{{#{color.name}:#{text}}}") << ' '
|
@@ -128,18 +130,17 @@ module CLI
|
|
128
130
|
|
129
131
|
o = +''
|
130
132
|
|
131
|
-
|
132
|
-
# the fancier features that we normally use to draw frames
|
133
|
-
# extra-reliably, so we fall back to a less foolproof strategy. This
|
134
|
-
# is probably better in general for cases with impoverished terminal
|
135
|
-
# emulators and no active user.
|
136
|
-
unless [0, '', nil].include?(ENV['CI'])
|
133
|
+
unless CLI::UI.enable_cursor?
|
137
134
|
linewidth = [0, termwidth - (preamble_end + suffix_width + 1)].max
|
138
135
|
|
139
|
-
o << color.code
|
140
|
-
o <<
|
141
|
-
o << color.code
|
142
|
-
o <<
|
136
|
+
o << color.code if CLI::UI.enable_color?
|
137
|
+
o << preamble
|
138
|
+
o << color.code if CLI::UI.enable_color?
|
139
|
+
o << (HORIZONTAL * linewidth)
|
140
|
+
o << color.code if CLI::UI.enable_color?
|
141
|
+
o << suffix
|
142
|
+
o << CLI::UI::Color::RESET.code if CLI::UI.enable_color?
|
143
|
+
o << "\n"
|
143
144
|
return o
|
144
145
|
end
|
145
146
|
|
@@ -158,12 +159,12 @@ module CLI
|
|
158
159
|
# | | | | |
|
159
160
|
# V V V V V
|
160
161
|
# --- Preamble text --------------------- suffix text --
|
161
|
-
o << color.code
|
162
|
+
o << color.code if CLI::UI.enable_color?
|
162
163
|
o << print_at_x(preamble_start, HORIZONTAL * (termwidth - preamble_start)) # draw a full line
|
163
164
|
o << print_at_x(preamble_start, preamble)
|
164
|
-
o << color.code
|
165
|
+
o << color.code if CLI::UI.enable_color?
|
165
166
|
o << print_at_x(suffix_start, suffix)
|
166
|
-
o << CLI::UI::Color::RESET.code
|
167
|
+
o << CLI::UI::Color::RESET.code if CLI::UI.enable_color?
|
167
168
|
o << CLI::UI::ANSI.show_cursor
|
168
169
|
o << "\n"
|
169
170
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CLI
|
4
5
|
module UI
|
@@ -94,7 +95,8 @@ module CLI
|
|
94
95
|
|
95
96
|
preamble = +''
|
96
97
|
|
97
|
-
preamble << color.code
|
98
|
+
preamble << color.code if CLI::UI.enable_color?
|
99
|
+
preamble << first << (HORIZONTAL * 2)
|
98
100
|
|
99
101
|
unless text.empty?
|
100
102
|
preamble << ' ' << CLI::UI.resolve_text("{{#{color.name}:#{text}}}") << ' '
|
@@ -108,15 +110,12 @@ module CLI
|
|
108
110
|
|
109
111
|
o = +''
|
110
112
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
o << color.code << preamble
|
118
|
-
o << color.code << suffix
|
119
|
-
o << CLI::UI::Color::RESET.code
|
113
|
+
unless CLI::UI.enable_cursor?
|
114
|
+
o << color.code if CLI::UI.enable_color?
|
115
|
+
o << preamble
|
116
|
+
o << color.code if CLI::UI.enable_color?
|
117
|
+
o << suffix
|
118
|
+
o << CLI::UI::Color::RESET.code if CLI::UI.enable_color?
|
120
119
|
o << "\n"
|
121
120
|
|
122
121
|
return o
|
@@ -134,9 +133,11 @@ module CLI
|
|
134
133
|
# reset to column 1 so that things like ^C don't ruin formatting
|
135
134
|
o << "\r"
|
136
135
|
|
137
|
-
o << color.code
|
138
|
-
o << print_at_x(preamble_start, preamble
|
139
|
-
o << CLI::UI
|
136
|
+
o << color.code if CLI::UI.enable_color?
|
137
|
+
o << print_at_x(preamble_start, preamble)
|
138
|
+
o << color.code if CLI::UI.enable_color?
|
139
|
+
o << suffix
|
140
|
+
o << CLI::UI::Color::RESET.code if CLI::UI.enable_color?
|
140
141
|
o << CLI::UI::ANSI.show_cursor
|
141
142
|
o << "\n"
|
142
143
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'cli/ui/frame'
|
4
5
|
|
@@ -106,8 +107,8 @@ module CLI
|
|
106
107
|
sig { returns(String) }
|
107
108
|
def message
|
108
109
|
keys = FrameStyle::MAP.keys.map(&:inspect).join(', ')
|
109
|
-
"invalid frame style: #{@name.inspect}" \
|
110
|
-
'
|
110
|
+
"invalid frame style: #{@name.inspect} " \
|
111
|
+
'-- must be one of CLI::UI::Frame::FrameStyle::MAP ' \
|
111
112
|
"(#{keys})"
|
112
113
|
end
|
113
114
|
end
|
data/lib/cli/ui/frame.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
4
3
|
|
5
4
|
require 'cli/ui'
|
6
5
|
require 'cli/ui/frame/frame_stack'
|
@@ -250,19 +249,21 @@ module CLI
|
|
250
249
|
#
|
251
250
|
sig { params(color: T.nilable(Colorable)).returns(String) }
|
252
251
|
def prefix(color: Thread.current[:cliui_frame_color_override])
|
253
|
-
+''.tap do |output|
|
252
|
+
(+'').tap do |output|
|
254
253
|
items = FrameStack.items
|
255
254
|
|
256
255
|
items[0..-2].to_a.each do |item|
|
257
|
-
output << item.color.code
|
256
|
+
output << item.color.code if CLI::UI.enable_color?
|
257
|
+
output << item.frame_style.prefix
|
258
|
+
output << CLI::UI::Color::RESET.code if CLI::UI.enable_color?
|
258
259
|
end
|
259
260
|
|
260
261
|
if (item = items.last)
|
261
262
|
final_color = color || item.color
|
262
|
-
output << CLI::UI.resolve_color(final_color).code
|
263
|
-
|
264
|
-
|
265
|
-
|
263
|
+
output << CLI::UI.resolve_color(final_color).code if CLI::UI.enable_color?
|
264
|
+
output << item.frame_style.prefix
|
265
|
+
output << CLI::UI::Color::RESET.code if CLI::UI.enable_color?
|
266
|
+
output << ' '
|
266
267
|
end
|
267
268
|
end
|
268
269
|
end
|
data/lib/cli/ui/glyph.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'cli/ui'
|
4
5
|
|
@@ -63,7 +64,7 @@ module CLI
|
|
63
64
|
X = new('x', 0x2717, 'X', Color::RED) # RED BALLOT X (✗)
|
64
65
|
BUG = new('b', 0x1f41b, '!', Color::WHITE) # Bug emoji (🐛)
|
65
66
|
CHEVRON = new('>', 0xbb, '»', Color::YELLOW) # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (»)
|
66
|
-
HOURGLASS = new('H',
|
67
|
+
HOURGLASS = new('H', 0x29d6, 'H', Color::ORANGE) # HOURGLASS (⧖)
|
67
68
|
WARNING = new('!', [0x26a0, 0xfe0f], '!', Color::YELLOW) # WARNING SIGN + VARIATION SELECTOR 16 (⚠️ )
|
68
69
|
|
69
70
|
class << self
|
data/lib/cli/ui/os.rb
CHANGED
data/lib/cli/ui/printer.rb
CHANGED
data/lib/cli/ui/progress.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'cli/ui'
|
4
5
|
|
@@ -35,13 +36,23 @@ module CLI
|
|
35
36
|
# CLI::UI::Progress.progress do |bar|
|
36
37
|
# bar.tick(percent: 0.05)
|
37
38
|
# end
|
39
|
+
#
|
40
|
+
# Update the title
|
41
|
+
# CLI::UI::Progress.progress('Title') do |bar|
|
42
|
+
# bar.tick(percent: 0.05)
|
43
|
+
# bar.update_title('New title')
|
44
|
+
# end
|
38
45
|
sig do
|
39
46
|
type_parameters(:T)
|
40
|
-
.params(
|
47
|
+
.params(
|
48
|
+
title: T.nilable(String),
|
49
|
+
width: Integer,
|
50
|
+
block: T.proc.params(bar: Progress).returns(T.type_parameter(:T)),
|
51
|
+
)
|
41
52
|
.returns(T.type_parameter(:T))
|
42
53
|
end
|
43
|
-
def progress(width: Terminal.width, &block)
|
44
|
-
bar = Progress.new(width: width)
|
54
|
+
def progress(title = nil, width: Terminal.width, &block)
|
55
|
+
bar = Progress.new(title, width: width)
|
45
56
|
print(CLI::UI::ANSI.hide_cursor)
|
46
57
|
yield(bar)
|
47
58
|
ensure
|
@@ -55,13 +66,14 @@ module CLI
|
|
55
66
|
# Initialize a progress bar. Typically used in a +Progress.progress+ block
|
56
67
|
#
|
57
68
|
# ==== Options
|
58
|
-
# One of the follow can be used, but not both together
|
59
69
|
#
|
70
|
+
# * +:title+ - The title of the progress bar
|
60
71
|
# * +:width+ - The width of the terminal
|
61
72
|
#
|
62
|
-
sig { params(width: Integer).void }
|
63
|
-
def initialize(width: Terminal.width)
|
73
|
+
sig { params(title: T.nilable(String), width: Integer).void }
|
74
|
+
def initialize(title = nil, width: Terminal.width)
|
64
75
|
@percent_done = T.let(0, Numeric)
|
76
|
+
@title = title
|
65
77
|
@max_width = width
|
66
78
|
end
|
67
79
|
|
@@ -84,7 +96,20 @@ module CLI
|
|
84
96
|
@percent_done = [@percent_done, 1.0].min # Make sure we can't go above 1.0
|
85
97
|
|
86
98
|
print(self)
|
87
|
-
|
99
|
+
|
100
|
+
printed_lines = @title ? 2 : 1
|
101
|
+
print(CLI::UI::ANSI.previous_lines(printed_lines) + "\n")
|
102
|
+
end
|
103
|
+
|
104
|
+
# Update the progress bar title
|
105
|
+
#
|
106
|
+
# ==== Attributes
|
107
|
+
#
|
108
|
+
# * +new_title+ - title to change the progress bar to
|
109
|
+
#
|
110
|
+
sig { params(new_title: String).void }
|
111
|
+
def update_title(new_title)
|
112
|
+
@title = new_title
|
88
113
|
end
|
89
114
|
|
90
115
|
# Format the progress bar to be printed to terminal
|
@@ -96,11 +121,14 @@ module CLI
|
|
96
121
|
filled = [(@percent_done * workable_width.to_f).ceil, 0].max
|
97
122
|
unfilled = [workable_width - filled, 0].max
|
98
123
|
|
99
|
-
CLI::UI.resolve_text(
|
124
|
+
title = CLI::UI.resolve_text(@title, truncate_to: @max_width - Frame.prefix_width) if @title
|
125
|
+
bar = CLI::UI.resolve_text([
|
100
126
|
FILLED_BAR + ' ' * filled,
|
101
127
|
UNFILLED_BAR + ' ' * unfilled,
|
102
128
|
CLI::UI::Color::RESET.code + suffix,
|
103
129
|
].join)
|
130
|
+
|
131
|
+
[title, bar].compact.join("\n")
|
104
132
|
end
|
105
133
|
end
|
106
134
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
3
2
|
# typed: true
|
3
|
+
# frozen_string_literal: true
|
4
4
|
|
5
5
|
require 'io/console'
|
6
6
|
|
@@ -59,7 +59,11 @@ module CLI
|
|
59
59
|
end
|
60
60
|
def initialize(options, multiple: false, default: nil)
|
61
61
|
@options = options
|
62
|
-
@active =
|
62
|
+
@active = if default && (i = options.index(default))
|
63
|
+
i + 1
|
64
|
+
else
|
65
|
+
1
|
66
|
+
end
|
63
67
|
@marker = '>'
|
64
68
|
@answer = nil
|
65
69
|
@state = :root
|
@@ -114,23 +118,20 @@ module CLI
|
|
114
118
|
# determine how many extra lines would be taken up by them.
|
115
119
|
#
|
116
120
|
# To accomplish this we split the string by new lines and add the
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
|
122
|
-
# options.count.to_s.size gets us the max size of the number we will display
|
123
|
-
extra_chars = @marker.length + 3 + @options.count.to_s.size + (@multiple ? 1 : 0)
|
121
|
+
# prefix to the first line. We use the options count as the number since
|
122
|
+
# it will be the widest number we will display, and we pad the others to
|
123
|
+
# align with it. Then we calculate how many lines would be needed to
|
124
|
+
# render the string based on the terminal width.
|
125
|
+
prefix = "#{@marker} #{@options.count}. #{@multiple ? "☐ " : ""}"
|
124
126
|
|
125
127
|
@option_lengths = @options.map do |text|
|
126
128
|
next 1 if text.empty?
|
127
129
|
|
128
130
|
# Find the length of all the lines in this string
|
129
|
-
non_empty_line_lengths = text.split("\n").reject(&:empty?).map do |line|
|
131
|
+
non_empty_line_lengths = "#{prefix}#{text}".split("\n").reject(&:empty?).map do |line|
|
130
132
|
CLI::UI.fmt(line, enable_color: false).length
|
131
133
|
end
|
132
|
-
|
133
|
-
non_empty_line_lengths[0] += extra_chars
|
134
|
+
|
134
135
|
# Finally, we need to calculate how many lines each one will take. We can do that by dividing each one
|
135
136
|
# by the width of the terminal, rounding up to the nearest natural number
|
136
137
|
non_empty_line_lengths.sum { |length| (length.to_f / @terminal_width_at_calculation_time).ceil }
|
@@ -229,6 +230,12 @@ module CLI
|
|
229
230
|
end
|
230
231
|
elsif n == 0
|
231
232
|
# Ignore pressing "0" when not in multiple mode
|
233
|
+
elsif should_enter_select_mode?(n)
|
234
|
+
# When we have more than 9 options, we need to enter select mode
|
235
|
+
# to avoid pre-selecting (e.g) 1 when the user wanted 10.
|
236
|
+
# This also applies to 2 and 20+ options, 3/30+, etc.
|
237
|
+
start_line_select
|
238
|
+
@active = n
|
232
239
|
else
|
233
240
|
@active = n
|
234
241
|
@answer = n
|
@@ -236,6 +243,20 @@ module CLI
|
|
236
243
|
@redraw = true
|
237
244
|
end
|
238
245
|
|
246
|
+
sig { params(n: Integer).returns(T::Boolean) }
|
247
|
+
def should_enter_select_mode?(n)
|
248
|
+
# If we have less than 10 options, we don't need to enter select mode
|
249
|
+
# and we can just select the option directly. This just keeps the code easier
|
250
|
+
# by making the cases simpler to understand
|
251
|
+
return false if @options.length <= 9
|
252
|
+
|
253
|
+
# At this point we have 10+ options so always need to check if we should run.
|
254
|
+
# This can be simplified to checking if the length of options is >= to the option selected * 10:
|
255
|
+
# n == 1 && options.length >= 10 (1 * 10), n == 2 && options.length >= 20 (2 * 10), etc.
|
256
|
+
# which can be further simplified to just:
|
257
|
+
@options.length >= (n * 10)
|
258
|
+
end
|
259
|
+
|
239
260
|
sig { params(char: String).void }
|
240
261
|
def select_bool(char)
|
241
262
|
return unless (@options - ['yes', 'no']).empty?
|
data/lib/cli/ui/prompt.rb
CHANGED
@@ -1,23 +1,13 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
3
2
|
# typed: true
|
3
|
+
# frozen_string_literal: true
|
4
4
|
|
5
5
|
require 'cli/ui'
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
directory = input[-1] == '/' ? input : File.dirname(input)
|
12
|
-
filename = input[-1] == '/' ? '' : File.basename(input)
|
13
|
-
|
14
|
-
(Dir.entries(directory).select do |fp|
|
15
|
-
fp.start_with?(filename)
|
16
|
-
end - (input[-1] == '.' ? [] : ['.', '..'])).map do |fp|
|
17
|
-
File.join(directory, fp).gsub(/\A\.\//, '')
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
6
|
+
begin
|
7
|
+
require 'reline' # For 2.7+
|
8
|
+
rescue LoadError
|
9
|
+
require 'readline' # For 2.6
|
10
|
+
Object.const_set(:Reline, Readline)
|
21
11
|
end
|
22
12
|
|
23
13
|
module CLI
|
@@ -137,10 +127,6 @@ module CLI
|
|
137
127
|
&options_proc
|
138
128
|
)
|
139
129
|
has_options = !!(options || block_given?)
|
140
|
-
if has_options && default && !multiple
|
141
|
-
raise(ArgumentError, 'conflicting arguments: default may not be provided with options when not multiple')
|
142
|
-
end
|
143
|
-
|
144
130
|
if has_options && is_file
|
145
131
|
raise(ArgumentError, 'conflicting arguments: is_file is only useful when options are not provided')
|
146
132
|
end
|
@@ -310,6 +296,8 @@ module CLI
|
|
310
296
|
instructions += ", filter with 'f'" if filter_ui
|
311
297
|
instructions += ", enter option with 'e'" if select_ui && (options.size > 9)
|
312
298
|
|
299
|
+
resp = T.let([], T.any(String, T::Array[String]))
|
300
|
+
|
313
301
|
CLI::UI::StdoutRouter::Capture.in_alternate_screen do
|
314
302
|
puts_question("#{question} " + instructions_color.code + "(#{instructions})" + Color::RESET.code)
|
315
303
|
resp = interactive_prompt(options, multiple: multiple, default: default)
|
@@ -334,12 +322,12 @@ module CLI
|
|
334
322
|
resp
|
335
323
|
end
|
336
324
|
puts_question("#{question} (You chose: {{italic:#{resp_text}}})")
|
325
|
+
end
|
337
326
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
end
|
327
|
+
if block_given?
|
328
|
+
T.must(handler).call(resp)
|
329
|
+
else
|
330
|
+
resp
|
343
331
|
end
|
344
332
|
end
|
345
333
|
|
@@ -375,11 +363,20 @@ module CLI
|
|
375
363
|
sig { params(is_file: T::Boolean).returns(String) }
|
376
364
|
def readline(is_file: false)
|
377
365
|
if is_file
|
378
|
-
|
379
|
-
|
366
|
+
Reline.completion_proc = proc do |input|
|
367
|
+
directory = input[-1] == '/' ? input : File.dirname(input)
|
368
|
+
filename = input[-1] == '/' ? '' : File.basename(input)
|
369
|
+
|
370
|
+
(Dir.entries(directory).select do |fp|
|
371
|
+
fp.start_with?(filename)
|
372
|
+
end - (input[-1] == '.' ? [] : ['.', '..'])).map do |fp|
|
373
|
+
File.join(directory, fp).gsub(/\A\.\//, '')
|
374
|
+
end
|
375
|
+
end
|
376
|
+
Reline.completion_append_character = ''
|
380
377
|
else
|
381
|
-
|
382
|
-
|
378
|
+
Reline.completion_proc = proc {}
|
379
|
+
Reline.completion_append_character = ' '
|
383
380
|
end
|
384
381
|
|
385
382
|
# because Readline is a C library, CLI::UI's hooks into $stdout don't
|
@@ -393,7 +390,7 @@ module CLI
|
|
393
390
|
prompt += CLI::UI::Color::YELLOW.code if CLI::UI::OS.current.use_color_prompt?
|
394
391
|
|
395
392
|
begin
|
396
|
-
line =
|
393
|
+
line = Reline.readline(prompt, true)
|
397
394
|
print(CLI::UI::Color::RESET.code)
|
398
395
|
line.to_s.chomp
|
399
396
|
rescue Interrupt
|