cli-ui 1.2.1 โ 1.5.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/.dependabot/config.yml +8 -0
- data/.github/CODEOWNERS +1 -0
- data/.github/probots.yml +2 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +28 -4
- data/.travis.yml +5 -3
- data/Gemfile +3 -2
- data/Gemfile.lock +60 -0
- data/README.md +50 -3
- data/Rakefile +2 -2
- data/bin/console +3 -3
- data/cli-ui.gemspec +13 -13
- data/dev.yml +1 -1
- data/lib/cli/ui.rb +77 -31
- data/lib/cli/ui/ansi.rb +10 -6
- data/lib/cli/ui/color.rb +12 -7
- data/lib/cli/ui/formatter.rb +34 -21
- data/lib/cli/ui/frame.rb +111 -152
- data/lib/cli/ui/frame/frame_stack.rb +98 -0
- data/lib/cli/ui/frame/frame_style.rb +120 -0
- data/lib/cli/ui/frame/frame_style/box.rb +166 -0
- data/lib/cli/ui/frame/frame_style/bracket.rb +139 -0
- data/lib/cli/ui/glyph.rb +23 -17
- data/lib/cli/ui/os.rb +67 -0
- data/lib/cli/ui/printer.rb +59 -0
- data/lib/cli/ui/progress.rb +10 -8
- data/lib/cli/ui/prompt.rb +107 -21
- data/lib/cli/ui/prompt/interactive_options.rb +271 -67
- data/lib/cli/ui/prompt/options_handler.rb +7 -2
- data/lib/cli/ui/spinner.rb +23 -5
- data/lib/cli/ui/spinner/spin_group.rb +41 -13
- data/lib/cli/ui/stdout_router.rb +13 -8
- data/lib/cli/ui/terminal.rb +26 -16
- data/lib/cli/ui/truncater.rb +4 -4
- data/lib/cli/ui/version.rb +1 -1
- data/lib/cli/ui/widgets.rb +77 -0
- data/lib/cli/ui/widgets/base.rb +27 -0
- data/lib/cli/ui/widgets/status.rb +61 -0
- data/lib/cli/ui/wrap.rb +56 -0
- metadata +22 -24
- data/lib/cli/ui/box.rb +0 -15
data/lib/cli/ui/glyph.rb
CHANGED
@@ -5,6 +5,7 @@ module CLI
|
|
5
5
|
class Glyph
|
6
6
|
class InvalidGlyphHandle < ArgumentError
|
7
7
|
def initialize(handle)
|
8
|
+
super
|
8
9
|
@handle = handle
|
9
10
|
end
|
10
11
|
|
@@ -15,7 +16,7 @@ module CLI
|
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
|
-
attr_reader :handle, :codepoint, :color, :
|
19
|
+
attr_reader :handle, :codepoint, :color, :to_s, :fmt
|
19
20
|
|
20
21
|
# Creates a new glyph
|
21
22
|
#
|
@@ -23,35 +24,40 @@ module CLI
|
|
23
24
|
#
|
24
25
|
# * +handle+ - The handle in the +MAP+ constant
|
25
26
|
# * +codepoint+ - The codepoint used to create the glyph (e.g. +0x2717+ for a ballot X)
|
27
|
+
# * +plain+ - A fallback plain string to be used in case glyphs are disabled
|
26
28
|
# * +color+ - What color to output the glyph. Check +CLI::UI::Color+ for options.
|
27
29
|
#
|
28
|
-
def initialize(handle, codepoint, color)
|
30
|
+
def initialize(handle, codepoint, plain, color)
|
29
31
|
@handle = handle
|
30
32
|
@codepoint = codepoint
|
31
33
|
@color = color
|
32
|
-
@
|
34
|
+
@plain = plain
|
35
|
+
@char = Array(codepoint).pack('U*')
|
33
36
|
@to_s = color.code + char + Color::RESET.code
|
34
37
|
@fmt = "{{#{color.name}:#{char}}}"
|
35
38
|
|
36
39
|
MAP[handle] = self
|
37
40
|
end
|
38
41
|
|
42
|
+
# Fetches the actual character(s) to be displayed for a glyph, based on the current OS support
|
43
|
+
#
|
44
|
+
# ==== Returns
|
45
|
+
# Returns the glyph string
|
46
|
+
def char
|
47
|
+
CLI::UI::OS.current.supports_emoji? ? @char : @plain
|
48
|
+
end
|
49
|
+
|
39
50
|
# Mapping of glyphs to terminal output
|
40
51
|
MAP = {}
|
41
|
-
#
|
42
|
-
|
43
|
-
# BLUE
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
#
|
50
|
-
X = new('x', 0x2717, Color::RED)
|
51
|
-
# Bug emoji (๐)
|
52
|
-
BUG = new('b', 0x1f41b, Color::WHITE)
|
53
|
-
# RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (ยป)
|
54
|
-
CHEVRON = new('>', 0xbb, Color::YELLOW)
|
52
|
+
STAR = new('*', 0x2b51, '*', Color::YELLOW) # YELLOW SMALL STAR (โญ)
|
53
|
+
INFO = new('i', 0x1d4be, 'i', Color::BLUE) # BLUE MATHEMATICAL SCRIPT SMALL i (๐พ)
|
54
|
+
QUESTION = new('?', 0x003f, '?', Color::BLUE) # BLUE QUESTION MARK (?)
|
55
|
+
CHECK = new('v', 0x2713, 'โ', Color::GREEN) # GREEN CHECK MARK (โ)
|
56
|
+
X = new('x', 0x2717, 'X', Color::RED) # RED BALLOT X (โ)
|
57
|
+
BUG = new('b', 0x1f41b, '!', Color::WHITE) # Bug emoji (๐)
|
58
|
+
CHEVRON = new('>', 0xbb, 'ยป', Color::YELLOW) # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (ยป)
|
59
|
+
HOURGLASS = new('H', [0x231b, 0xfe0e], 'H', Color::BLUE) # HOURGLASS + VARIATION SELECTOR 15 (โ๏ธ)
|
60
|
+
WARNING = new('!', [0x26a0, 0xfe0f], '!', Color::YELLOW) # WARNING SIGN + VARIATION SELECTOR 16 (โ ๏ธ )
|
55
61
|
|
56
62
|
# Looks up a glyph by name
|
57
63
|
#
|
data/lib/cli/ui/os.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
module OS
|
6
|
+
# Determines which OS is currently running the UI, to make it easier to
|
7
|
+
# adapt its behaviour to the features of the OS.
|
8
|
+
def self.current
|
9
|
+
@current_os ||= case RbConfig::CONFIG['host_os']
|
10
|
+
when /darwin/
|
11
|
+
Mac
|
12
|
+
when /linux/
|
13
|
+
Linux
|
14
|
+
else
|
15
|
+
if RUBY_PLATFORM !~ /cygwin/ && ENV['OS'] == 'Windows_NT'
|
16
|
+
Windows
|
17
|
+
else
|
18
|
+
raise "Could not determine OS from host_os #{RbConfig::CONFIG["host_os"]}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Mac
|
24
|
+
class << self
|
25
|
+
def supports_emoji?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def supports_color_prompt?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def supports_arrow_keys?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def shift_cursor_on_line_reset?
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Linux < Mac
|
44
|
+
end
|
45
|
+
|
46
|
+
class Windows
|
47
|
+
class << self
|
48
|
+
def supports_emoji?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def supports_color_prompt?
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def supports_arrow_keys?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def shift_cursor_on_line_reset?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'cli/ui'
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
module UI
|
5
|
+
class Printer
|
6
|
+
# Print a message to a stream with common utilities.
|
7
|
+
# Allows overriding the color, encoding, and target stream.
|
8
|
+
# By default, it formats the string using CLI:UI and rescues common stream errors.
|
9
|
+
#
|
10
|
+
# ==== Attributes
|
11
|
+
#
|
12
|
+
# * +msg+ - (required) the string to output. Can be frozen.
|
13
|
+
#
|
14
|
+
# ==== Options
|
15
|
+
#
|
16
|
+
# * +:frame_color+ - Override the frame color. Defaults to nil.
|
17
|
+
# * +:to+ - Target stream, like $stdout or $stderr. Can be anything with a puts method. Defaults to $stdout.
|
18
|
+
# * +:encoding+ - Force the output to be in a certain encoding. Defaults to UTF-8.
|
19
|
+
# * +:format+ - Whether to format the string using CLI::UI.fmt. Defaults to true.
|
20
|
+
# * +:graceful+ - Whether to gracefully ignore common I/O errors. Defaults to true.
|
21
|
+
# * +:wrap+ - Whether to wrap text at word boundaries to terminal width. Defaults to true.
|
22
|
+
#
|
23
|
+
# ==== Returns
|
24
|
+
# Returns whether the message was successfully printed,
|
25
|
+
# which can be useful if +:graceful+ is set to true.
|
26
|
+
#
|
27
|
+
# ==== Example
|
28
|
+
#
|
29
|
+
# CLI::UI::Printer.puts('{{x}} Ouch', to: $stderr)
|
30
|
+
#
|
31
|
+
def self.puts(
|
32
|
+
msg,
|
33
|
+
frame_color:
|
34
|
+
nil,
|
35
|
+
to:
|
36
|
+
$stdout,
|
37
|
+
encoding: Encoding::UTF_8,
|
38
|
+
format: true,
|
39
|
+
graceful: true,
|
40
|
+
wrap: true
|
41
|
+
)
|
42
|
+
msg = (+msg).force_encoding(encoding) if encoding
|
43
|
+
msg = CLI::UI.fmt(msg) if format
|
44
|
+
msg = CLI::UI.wrap(msg) if wrap
|
45
|
+
|
46
|
+
if frame_color
|
47
|
+
CLI::UI::Frame.with_frame_color_override(frame_color) { to.puts(msg) }
|
48
|
+
else
|
49
|
+
to.puts(msg)
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
rescue Errno::EIO, Errno::EPIPE, IOError => e
|
54
|
+
raise(e) unless graceful
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/cli/ui/progress.rb
CHANGED
@@ -26,11 +26,11 @@ module CLI
|
|
26
26
|
#
|
27
27
|
# Increase the percent by X
|
28
28
|
# CLI::UI::Progress.progress do |bar|
|
29
|
-
# bar.tick(percent:
|
29
|
+
# bar.tick(percent: 0.05)
|
30
30
|
# end
|
31
31
|
def self.progress(width: Terminal.width)
|
32
32
|
bar = Progress.new(width: width)
|
33
|
-
print
|
33
|
+
print(CLI::UI::ANSI.hide_cursor)
|
34
34
|
yield(bar)
|
35
35
|
ensure
|
36
36
|
puts bar.to_s
|
@@ -59,29 +59,31 @@ module CLI
|
|
59
59
|
# * +:percent+ - Increment progress by a specific percent amount
|
60
60
|
# * +:set_percent+ - Set progress to a specific percent
|
61
61
|
#
|
62
|
+
# *Note:* The +:percent+ and +:set_percent must be between 0.00 and 1.0
|
63
|
+
#
|
62
64
|
def tick(percent: 0.01, set_percent: nil)
|
63
65
|
raise ArgumentError, 'percent and set_percent cannot both be specified' if percent != 0.01 && set_percent
|
64
66
|
@percent_done += percent
|
65
67
|
@percent_done = set_percent if set_percent
|
66
68
|
@percent_done = [@percent_done, 1.0].min # Make sure we can't go above 1.0
|
67
69
|
|
68
|
-
print
|
69
|
-
print
|
70
|
+
print(to_s)
|
71
|
+
print(CLI::UI::ANSI.previous_line + "\n")
|
70
72
|
end
|
71
73
|
|
72
74
|
# Format the progress bar to be printed to terminal
|
73
75
|
#
|
74
76
|
def to_s
|
75
|
-
suffix = " #{(@percent_done * 100).
|
77
|
+
suffix = " #{(@percent_done * 100).floor}%".ljust(5)
|
76
78
|
workable_width = @max_width - Frame.prefix_width - suffix.size
|
77
79
|
filled = [(@percent_done * workable_width.to_f).ceil, 0].max
|
78
80
|
unfilled = [workable_width - filled, 0].max
|
79
81
|
|
80
|
-
CLI::UI.resolve_text
|
82
|
+
CLI::UI.resolve_text([
|
81
83
|
FILLED_BAR + ' ' * filled,
|
82
84
|
UNFILLED_BAR + ' ' * unfilled,
|
83
|
-
CLI::UI::Color::RESET.code + suffix
|
84
|
-
].join
|
85
|
+
CLI::UI::Color::RESET.code + suffix,
|
86
|
+
].join)
|
85
87
|
end
|
86
88
|
end
|
87
89
|
end
|
data/lib/cli/ui/prompt.rb
CHANGED
@@ -1,6 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
require 'cli/ui'
|
2
3
|
require 'readline'
|
3
4
|
|
5
|
+
module Readline
|
6
|
+
unless const_defined?(:FILENAME_COMPLETION_PROC)
|
7
|
+
FILENAME_COMPLETION_PROC = proc do |input|
|
8
|
+
directory = input[-1] == '/' ? input : File.dirname(input)
|
9
|
+
filename = input[-1] == '/' ? '' : File.basename(input)
|
10
|
+
|
11
|
+
(Dir.entries(directory).select do |fp|
|
12
|
+
fp.start_with?(filename)
|
13
|
+
end - (input[-1] == '.' ? [] : ['.', '..'])).map do |fp|
|
14
|
+
File.join(directory, fp).gsub(/\A\.\//, '')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
4
20
|
module CLI
|
5
21
|
module UI
|
6
22
|
module Prompt
|
@@ -30,11 +46,16 @@ module CLI
|
|
30
46
|
# * +:default+ - The default answer to the question (e.g. they just press enter and don't input anything)
|
31
47
|
# * +:is_file+ - Tells the input to use file auto-completion (tab completion)
|
32
48
|
# * +:allow_empty+ - Allows the answer to be empty
|
49
|
+
# * +:multiple+ - Allow multiple options to be selected
|
50
|
+
# * +:filter_ui+ - Enable option filtering (default: true)
|
51
|
+
# * +:select_ui+ - Enable long-form option selection (default: true)
|
33
52
|
#
|
34
53
|
# Note:
|
35
|
-
# * +:options+ or providing a +Block+ conflicts with +:default+ and +:is_file+,
|
54
|
+
# * +:options+ or providing a +Block+ conflicts with +:default+ and +:is_file+,
|
55
|
+
# you cannot set options with either of these keywords
|
36
56
|
# * +:default+ conflicts with +:allow_empty:, you cannot set these together
|
37
57
|
# * +:options+ conflicts with providing a +Block+ , you may only set one
|
58
|
+
# * +:multiple+ can only be used with +:options+ or a +Block+; it is ignored, otherwise.
|
38
59
|
#
|
39
60
|
# ==== Block (optional)
|
40
61
|
#
|
@@ -44,7 +65,7 @@ module CLI
|
|
44
65
|
# ==== Return Value
|
45
66
|
#
|
46
67
|
# * If a +Block+ was not provided, the selected option or response to the free form question will be returned
|
47
|
-
# * If a +Block+ was provided, the
|
68
|
+
# * If a +Block+ was provided, the evaluated value of the +Block+ will be returned
|
48
69
|
#
|
49
70
|
# ==== Example Usage:
|
50
71
|
#
|
@@ -71,18 +92,67 @@ module CLI
|
|
71
92
|
# handler.option('python') { |selection| selection }
|
72
93
|
# end
|
73
94
|
#
|
74
|
-
def ask(
|
75
|
-
|
95
|
+
def ask(
|
96
|
+
question,
|
97
|
+
options: nil,
|
98
|
+
default: nil,
|
99
|
+
is_file: nil,
|
100
|
+
allow_empty: true,
|
101
|
+
multiple: false,
|
102
|
+
filter_ui: true,
|
103
|
+
select_ui: true,
|
104
|
+
&options_proc
|
105
|
+
)
|
106
|
+
if (options || block_given?) && ((default && !multiple) || is_file)
|
76
107
|
raise(ArgumentError, 'conflicting arguments: options provided with default or is_file')
|
77
108
|
end
|
78
109
|
|
110
|
+
if options && multiple && default && !(default - options).empty?
|
111
|
+
raise(ArgumentError, 'conflicting arguments: default should only include elements present in options')
|
112
|
+
end
|
113
|
+
|
79
114
|
if options || block_given?
|
80
|
-
ask_interactive(
|
115
|
+
ask_interactive(
|
116
|
+
question,
|
117
|
+
options,
|
118
|
+
multiple: multiple,
|
119
|
+
default: default,
|
120
|
+
filter_ui: filter_ui,
|
121
|
+
select_ui: select_ui,
|
122
|
+
&options_proc
|
123
|
+
)
|
81
124
|
else
|
82
125
|
ask_free_form(question, default, is_file, allow_empty)
|
83
126
|
end
|
84
127
|
end
|
85
128
|
|
129
|
+
# Asks the user for a single-line answer, without displaying the characters while typing.
|
130
|
+
# Typically used for password prompts
|
131
|
+
#
|
132
|
+
# ==== Return Value
|
133
|
+
#
|
134
|
+
# The password, without a trailing newline.
|
135
|
+
# If the user simply presses "Enter" without typing any password, this will return an empty string.
|
136
|
+
def ask_password(question)
|
137
|
+
require 'io/console'
|
138
|
+
|
139
|
+
CLI::UI.with_frame_color(:blue) do
|
140
|
+
STDOUT.print(CLI::UI.fmt('{{?}} ' + question)) # Do not use puts_question to avoid the new line.
|
141
|
+
|
142
|
+
# noecho interacts poorly with Readline under system Ruby, so do a manual `gets` here.
|
143
|
+
# No fancy Readline integration (like echoing back) is required for a password prompt anyway.
|
144
|
+
password = STDIN.noecho do
|
145
|
+
# Chomp will remove the one new line character added by `gets`, without touching potential extra spaces:
|
146
|
+
# " 123 \n".chomp => " 123 "
|
147
|
+
STDIN.gets.chomp
|
148
|
+
end
|
149
|
+
|
150
|
+
STDOUT.puts # Complete the line
|
151
|
+
|
152
|
+
password
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
86
156
|
# Asks the user a yes/no question.
|
87
157
|
# Can use arrows, y/n, numbers (1/2), and vim bindings to control
|
88
158
|
#
|
@@ -91,14 +161,18 @@ module CLI
|
|
91
161
|
# Confirmation question
|
92
162
|
# CLI::UI::Prompt.confirm('Is the sky blue?')
|
93
163
|
#
|
94
|
-
|
95
|
-
|
164
|
+
# CLI::UI::Prompt.confirm('Do a dangerous thing?', default: false)
|
165
|
+
#
|
166
|
+
def confirm(question, default: true)
|
167
|
+
ask_interactive(question, default ? %w(yes no) : %w(no yes), filter_ui: false) == 'yes'
|
96
168
|
end
|
97
169
|
|
98
170
|
private
|
99
171
|
|
100
172
|
def ask_free_form(question, default, is_file, allow_empty)
|
101
|
-
|
173
|
+
if default && !allow_empty
|
174
|
+
raise(ArgumentError, 'conflicting arguments: default enabled but allow_empty is false')
|
175
|
+
end
|
102
176
|
|
103
177
|
if default
|
104
178
|
puts_question("#{question} (empty = #{default})")
|
@@ -121,7 +195,7 @@ module CLI
|
|
121
195
|
end
|
122
196
|
end
|
123
197
|
|
124
|
-
def ask_interactive(question, options = nil, multiple: false)
|
198
|
+
def ask_interactive(question, options = nil, multiple: false, default: nil, filter_ui: true, select_ui: true)
|
125
199
|
raise(ArgumentError, 'conflicting arguments: options and block given') if options && block_given?
|
126
200
|
|
127
201
|
options ||= if block_given?
|
@@ -130,24 +204,32 @@ module CLI
|
|
130
204
|
handler.options
|
131
205
|
end
|
132
206
|
|
133
|
-
raise(ArgumentError, 'insufficient options') if options.nil? || options.
|
134
|
-
|
207
|
+
raise(ArgumentError, 'insufficient options') if options.nil? || options.empty?
|
208
|
+
navigate_text = if CLI::UI::OS.current.supports_arrow_keys?
|
209
|
+
'Choose with โ โ โ'
|
210
|
+
else
|
211
|
+
"Navigate up with 'k' and down with 'j', press Enter to select"
|
212
|
+
end
|
213
|
+
|
214
|
+
instructions = (multiple ? 'Toggle options. ' : '') + navigate_text
|
215
|
+
instructions += ", filter with 'f'" if filter_ui
|
216
|
+
instructions += ", enter option with 'e'" if select_ui && (options.size > 9)
|
135
217
|
puts_question("#{question} {{yellow:(#{instructions})}}")
|
136
|
-
resp = interactive_prompt(options, multiple: multiple)
|
218
|
+
resp = interactive_prompt(options, multiple: multiple, default: default)
|
137
219
|
|
138
220
|
# Clear the line
|
139
|
-
print
|
221
|
+
print(ANSI.previous_line + ANSI.clear_to_end_of_line)
|
140
222
|
# Force StdoutRouter to prefix
|
141
|
-
print
|
223
|
+
print(ANSI.previous_line + "\n")
|
142
224
|
|
143
225
|
# reset the question to include the answer
|
144
226
|
resp_text = resp
|
145
227
|
if multiple
|
146
228
|
resp_text = case resp.size
|
147
229
|
when 0
|
148
|
-
|
230
|
+
'<nothing>'
|
149
231
|
when 1..2
|
150
|
-
resp.join(
|
232
|
+
resp.join(' and ')
|
151
233
|
else
|
152
234
|
"#{resp.size} items"
|
153
235
|
end
|
@@ -159,8 +241,8 @@ module CLI
|
|
159
241
|
end
|
160
242
|
|
161
243
|
# Useful for stubbing in tests
|
162
|
-
def interactive_prompt(options, multiple: false)
|
163
|
-
InteractiveOptions.call(options, multiple: multiple)
|
244
|
+
def interactive_prompt(options, multiple: false, default: nil)
|
245
|
+
InteractiveOptions.call(options, multiple: multiple, default: default)
|
164
246
|
end
|
165
247
|
|
166
248
|
def write_default_over_empty_input(default)
|
@@ -184,10 +266,10 @@ module CLI
|
|
184
266
|
def readline(is_file: false)
|
185
267
|
if is_file
|
186
268
|
Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC
|
187
|
-
Readline.completion_append_character =
|
269
|
+
Readline.completion_append_character = ''
|
188
270
|
else
|
189
271
|
Readline.completion_proc = proc { |*| nil }
|
190
|
-
Readline.completion_append_character =
|
272
|
+
Readline.completion_append_character = ' '
|
191
273
|
end
|
192
274
|
|
193
275
|
# because Readline is a C library, CLI::UI's hooks into $stdout don't
|
@@ -195,10 +277,14 @@ module CLI
|
|
195
277
|
# thread to manage output, but the current strategy feels like a
|
196
278
|
# better tradeoff.
|
197
279
|
prefix = CLI::UI.with_frame_color(:blue) { CLI::UI::Frame.prefix }
|
198
|
-
prompt
|
280
|
+
# If a prompt is interrupted on Windows it locks the colour of the terminal from that point on, so we should
|
281
|
+
# not change the colour here.
|
282
|
+
prompt = prefix + CLI::UI.fmt('{{blue:> }}')
|
283
|
+
prompt += CLI::UI::Color::YELLOW.code if CLI::UI::OS.current.supports_color_prompt?
|
199
284
|
|
200
285
|
begin
|
201
286
|
line = Readline.readline(prompt, true)
|
287
|
+
print(CLI::UI::Color::RESET.code)
|
202
288
|
line.to_s.chomp
|
203
289
|
rescue Interrupt
|
204
290
|
CLI::UI.raw { STDERR.puts('^C' + CLI::UI::Color::RESET.code) }
|