tty-prompt 0.11.0 → 0.12.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 +25 -0
- data/README.md +66 -7
- data/examples/key_events.rb +11 -0
- data/examples/keypress.rb +3 -5
- data/examples/multiline.rb +9 -0
- data/examples/pause.rb +7 -0
- data/lib/tty/prompt.rb +82 -44
- data/lib/tty/prompt/confirm_question.rb +20 -36
- data/lib/tty/prompt/enum_list.rb +32 -23
- data/lib/tty/prompt/expander.rb +35 -31
- data/lib/tty/prompt/keypress.rb +91 -0
- data/lib/tty/prompt/list.rb +38 -23
- data/lib/tty/prompt/mask_question.rb +4 -7
- data/lib/tty/prompt/multi_list.rb +3 -1
- data/lib/tty/prompt/multiline.rb +71 -0
- data/lib/tty/prompt/question.rb +33 -35
- data/lib/tty/prompt/reader.rb +154 -38
- data/lib/tty/prompt/reader/codes.rb +4 -4
- data/lib/tty/prompt/reader/console.rb +1 -1
- data/lib/tty/prompt/reader/history.rb +145 -0
- data/lib/tty/prompt/reader/key_event.rb +4 -0
- data/lib/tty/prompt/reader/line.rb +162 -0
- data/lib/tty/prompt/reader/mode.rb +2 -2
- data/lib/tty/prompt/reader/win_console.rb +5 -1
- data/lib/tty/prompt/slider.rb +18 -12
- data/lib/tty/prompt/timeout.rb +48 -0
- data/lib/tty/prompt/version.rb +1 -1
- data/spec/unit/ask_spec.rb +15 -0
- data/spec/unit/converters/convert_bool_spec.rb +1 -0
- data/spec/unit/keypress_spec.rb +35 -6
- data/spec/unit/multi_select_spec.rb +18 -0
- data/spec/unit/multiline_spec.rb +67 -9
- data/spec/unit/question/default_spec.rb +1 -0
- data/spec/unit/question/echo_spec.rb +8 -0
- data/spec/unit/question/in_spec.rb +13 -0
- data/spec/unit/question/required_spec.rb +31 -2
- data/spec/unit/question/validate_spec.rb +39 -9
- data/spec/unit/reader/history_spec.rb +172 -0
- data/spec/unit/reader/key_event_spec.rb +12 -8
- data/spec/unit/reader/line_spec.rb +110 -0
- data/spec/unit/reader/publish_keypress_event_spec.rb +11 -0
- data/spec/unit/reader/read_line_spec.rb +32 -2
- data/spec/unit/reader/read_multiline_spec.rb +21 -7
- data/spec/unit/select_spec.rb +40 -1
- data/spec/unit/yes_no_spec.rb +48 -4
- metadata +14 -3
- data/lib/tty/prompt/history.rb +0 -16
@@ -44,8 +44,10 @@ module TTY
|
|
44
44
|
escape: "\e",
|
45
45
|
space: " ",
|
46
46
|
backspace: ?\C-?,
|
47
|
+
home: "\e[1~",
|
47
48
|
insert: "\e[2~",
|
48
49
|
delete: "\e[3~",
|
50
|
+
end: "\e[4~",
|
49
51
|
page_up: "\e[5~",
|
50
52
|
page_down: "\e[6~",
|
51
53
|
|
@@ -54,8 +56,6 @@ module TTY
|
|
54
56
|
right: "\e[C",
|
55
57
|
left: "\e[D",
|
56
58
|
clear: "\e[E",
|
57
|
-
end: "\e[F",
|
58
|
-
home: "\e[H",
|
59
59
|
|
60
60
|
f1_xterm: "\eOP",
|
61
61
|
f2_xterm: "\eOQ",
|
@@ -86,6 +86,8 @@ module TTY
|
|
86
86
|
escape: "\e",
|
87
87
|
space: " ",
|
88
88
|
backspace: "\b",
|
89
|
+
home: [224, 71].pack('U*'),
|
90
|
+
end: [224, 79].pack('U*'),
|
89
91
|
insert: [224, 82].pack('U*'),
|
90
92
|
delete: [224, 83].pack('U*'),
|
91
93
|
page_up: [224, 73].pack('U*'),
|
@@ -96,8 +98,6 @@ module TTY
|
|
96
98
|
right: [224, 77].pack('U*'),
|
97
99
|
left: [224, 75].pack('U*'),
|
98
100
|
clear: [224, 83].pack('U*'),
|
99
|
-
end: [224, 79].pack('U*'),
|
100
|
-
home: [224, 71].pack('U*'),
|
101
101
|
|
102
102
|
f1: "\x00;",
|
103
103
|
f2: "\x00<",
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Prompt
|
7
|
+
class Reader
|
8
|
+
# A class responsible for storing a history of all lines entered by
|
9
|
+
# user when interacting with shell prompt.
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class History
|
13
|
+
include Enumerable
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
# Default maximum size
|
17
|
+
DEFAULT_SIZE = 32 << 4
|
18
|
+
|
19
|
+
def_delegators :@history, :size, :length, :to_s, :inspect
|
20
|
+
|
21
|
+
# Set and retrieve the maximum size of the buffer
|
22
|
+
attr_accessor :max_size
|
23
|
+
|
24
|
+
attr_reader :index
|
25
|
+
|
26
|
+
attr_accessor :cycle
|
27
|
+
|
28
|
+
attr_accessor :duplicates
|
29
|
+
|
30
|
+
attr_accessor :exclude
|
31
|
+
|
32
|
+
# Create a History buffer
|
33
|
+
#
|
34
|
+
# param [Integer] max_size
|
35
|
+
# the maximum size for history buffer
|
36
|
+
#
|
37
|
+
# param [Hash[Symbol]] options
|
38
|
+
# @option options [Boolean] :duplicates
|
39
|
+
# whether or not to store duplicates, true by default
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def initialize(max_size = DEFAULT_SIZE, options = {})
|
43
|
+
@max_size = max_size
|
44
|
+
@index = 0
|
45
|
+
@history = []
|
46
|
+
@duplicates = options.fetch(:duplicates) { true }
|
47
|
+
@exclude = options.fetch(:exclude) { proc {} }
|
48
|
+
@cycle = options.fetch(:cycle) { false }
|
49
|
+
yield self if block_given?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Iterates over history lines
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def each
|
56
|
+
if block_given?
|
57
|
+
@history.each { |line| yield line }
|
58
|
+
else
|
59
|
+
@history.to_enum
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add the last typed line to history buffer
|
64
|
+
#
|
65
|
+
# @param [String] line
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def push(line)
|
69
|
+
@history.delete(line) unless @duplicates
|
70
|
+
return if line.to_s.empty? || @exclude[line]
|
71
|
+
|
72
|
+
@history.shift if size >= max_size
|
73
|
+
@history << line
|
74
|
+
@index = @history.size - 1
|
75
|
+
|
76
|
+
self
|
77
|
+
end
|
78
|
+
alias << push
|
79
|
+
|
80
|
+
# Move the pointer to the next line in the history
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
def next
|
84
|
+
return if size.zero?
|
85
|
+
if @index == size - 1
|
86
|
+
@index = 0 if @cycle
|
87
|
+
else
|
88
|
+
@index += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def next?
|
93
|
+
size > 0 && !(@index == size - 1 && !@cycle)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Move the pointer to the previous line in the history
|
97
|
+
def previous
|
98
|
+
return if size.zero?
|
99
|
+
if @index.zero?
|
100
|
+
@index = size - 1 if @cycle
|
101
|
+
else
|
102
|
+
@index -= 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def previous?
|
107
|
+
size > 0 && !(@index < 0 && !@cycle)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return line at the specified index
|
111
|
+
#
|
112
|
+
# @raise [IndexError] index out of range
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
def [](index)
|
116
|
+
if index < 0
|
117
|
+
index += @history.size if index < 0
|
118
|
+
end
|
119
|
+
line = @history[index]
|
120
|
+
if line.nil?
|
121
|
+
raise IndexError, 'invalid index'
|
122
|
+
end
|
123
|
+
line.dup
|
124
|
+
end
|
125
|
+
|
126
|
+
# Get current line
|
127
|
+
#
|
128
|
+
# @api public
|
129
|
+
def get
|
130
|
+
return if size.zero?
|
131
|
+
|
132
|
+
self[@index]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Empty all history lines
|
136
|
+
#
|
137
|
+
# @api public
|
138
|
+
def clear
|
139
|
+
@history.clear
|
140
|
+
@index = 0
|
141
|
+
end
|
142
|
+
end # History
|
143
|
+
end # Reader
|
144
|
+
end # Prompt
|
145
|
+
end # TTY
|
@@ -49,9 +49,13 @@ module TTY
|
|
49
49
|
when keys[:down] then key.name = :down
|
50
50
|
when keys[:left] then key.name = :left
|
51
51
|
when keys[:right] then key.name = :right
|
52
|
+
# editing
|
52
53
|
when keys[:clear] then key.name = :clear
|
53
54
|
when keys[:end] then key.name = :end
|
54
55
|
when keys[:home] then key.name = :home
|
56
|
+
when keys[:insert] then key.name = :insert
|
57
|
+
when keys[:page_up] then key.name = :page_up
|
58
|
+
when keys[:page_down] then key.name = :page_down
|
55
59
|
when proc { |cs| ctrls.any? { |name| keys[name] == cs } }
|
56
60
|
key.name = keys.key(char)
|
57
61
|
key.ctrl = true
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Prompt
|
7
|
+
class Reader
|
8
|
+
class Line
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :@text, :size, :length, :to_s, :inspect,
|
12
|
+
:slice!, :empty?
|
13
|
+
|
14
|
+
attr_accessor :text
|
15
|
+
|
16
|
+
attr_accessor :cursor
|
17
|
+
|
18
|
+
def initialize(text = "")
|
19
|
+
@text = text
|
20
|
+
@cursor = [0, @text.length].max
|
21
|
+
yield self if block_given?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check if cursor reached beginning of the line
|
25
|
+
#
|
26
|
+
# @return [Boolean]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def start?
|
30
|
+
@cursor == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if cursor reached end of the line
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def end?
|
39
|
+
@cursor == @text.length
|
40
|
+
end
|
41
|
+
|
42
|
+
# Move line position to the left by n chars
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def left(n = 1)
|
46
|
+
@cursor = [0, @cursor - n].max
|
47
|
+
end
|
48
|
+
|
49
|
+
# Move line position to the right by n chars
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def right(n = 1)
|
53
|
+
@cursor = [@text.length, @cursor + n].min
|
54
|
+
end
|
55
|
+
|
56
|
+
# Move cursor to beginning position
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def move_to_start
|
60
|
+
@cursor = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
# Move cursor to end position
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def move_to_end
|
67
|
+
@cursor = @text.length # put cursor outside of text
|
68
|
+
end
|
69
|
+
|
70
|
+
# Insert characters inside a line. When the lines exceeds
|
71
|
+
# maximum length, an extra space is added to accomodate index.
|
72
|
+
#
|
73
|
+
# @param [Integer] i
|
74
|
+
# the index to insert at
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# text = 'aaa'
|
78
|
+
# line[5]= 'b'
|
79
|
+
# => 'aaa b'
|
80
|
+
#
|
81
|
+
# @api public
|
82
|
+
def []=(i, chars)
|
83
|
+
if i.is_a?(Range)
|
84
|
+
@text[i] = chars
|
85
|
+
@cursor += chars.length
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
if i <= 0
|
90
|
+
before_text = ''
|
91
|
+
after_text = @text.dup
|
92
|
+
elsif i == @text.length - 1
|
93
|
+
before_text = @text.dup
|
94
|
+
after_text = ''
|
95
|
+
elsif i > @text.length - 1
|
96
|
+
before_text = @text.dup
|
97
|
+
after_text = ?\s * (i - @text.length)
|
98
|
+
@cursor += after_text.length
|
99
|
+
else
|
100
|
+
before_text = @text[0..i-1].dup
|
101
|
+
after_text = @text[i..-1].dup
|
102
|
+
end
|
103
|
+
|
104
|
+
if i > @text.length - 1
|
105
|
+
@text = before_text << after_text << chars
|
106
|
+
else
|
107
|
+
@text = before_text << chars << after_text
|
108
|
+
end
|
109
|
+
|
110
|
+
@cursor = i + chars.length
|
111
|
+
end
|
112
|
+
|
113
|
+
# Read character
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def [](i)
|
117
|
+
@text[i]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Replace current line with new text
|
121
|
+
#
|
122
|
+
# @param [String] text
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
def replace(text)
|
126
|
+
@text = text
|
127
|
+
@cursor = @text.length # put cursor outside of text
|
128
|
+
end
|
129
|
+
|
130
|
+
# Insert char(s) at cursor position
|
131
|
+
#
|
132
|
+
# @api public
|
133
|
+
def insert(chars)
|
134
|
+
self[@cursor] = chars
|
135
|
+
end
|
136
|
+
|
137
|
+
# Add char and move cursor
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
def <<(char)
|
141
|
+
@text << char
|
142
|
+
@cursor += 1
|
143
|
+
end
|
144
|
+
|
145
|
+
# Remove char from the line at current position
|
146
|
+
#
|
147
|
+
# @api public
|
148
|
+
def delete
|
149
|
+
@text.slice!(@cursor, 1)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Remove char from the line in front of the cursor
|
153
|
+
#
|
154
|
+
# @api public
|
155
|
+
def remove
|
156
|
+
left
|
157
|
+
@text.slice!(@cursor, 1)
|
158
|
+
end
|
159
|
+
end # Line
|
160
|
+
end # Reader
|
161
|
+
end # Prompt
|
162
|
+
end # TTY
|
@@ -19,7 +19,7 @@ module TTY
|
|
19
19
|
#
|
20
20
|
# @api public
|
21
21
|
def echo(is_on = true, &block)
|
22
|
-
if is_on
|
22
|
+
if is_on || !@input.tty?
|
23
23
|
yield
|
24
24
|
else
|
25
25
|
@input.noecho(&block)
|
@@ -32,7 +32,7 @@ module TTY
|
|
32
32
|
#
|
33
33
|
# @api public
|
34
34
|
def raw(is_on = true, &block)
|
35
|
-
if is_on
|
35
|
+
if is_on && @input.tty?
|
36
36
|
@input.raw(&block)
|
37
37
|
else
|
38
38
|
yield
|
@@ -41,7 +41,11 @@ module TTY
|
|
41
41
|
#
|
42
42
|
# @api private
|
43
43
|
def get_char(options)
|
44
|
-
options[:
|
44
|
+
if options[:raw]
|
45
|
+
WinAPI.getch.chr
|
46
|
+
else
|
47
|
+
options[:echo] ? @input.getc : WinAPI.getch.chr
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end # Console
|
47
51
|
end # Reader
|
data/lib/tty/prompt/slider.rb
CHANGED
@@ -109,48 +109,54 @@ module TTY
|
|
109
109
|
def render
|
110
110
|
@prompt.print(@prompt.hide)
|
111
111
|
until @done
|
112
|
-
render_question
|
112
|
+
question = render_question
|
113
|
+
@prompt.print(question)
|
113
114
|
@prompt.read_keypress
|
114
|
-
refresh
|
115
|
+
refresh(question.lines.count)
|
115
116
|
end
|
116
|
-
render_question
|
117
|
-
answer
|
117
|
+
@prompt.print(render_question)
|
118
|
+
answer
|
118
119
|
ensure
|
119
120
|
@prompt.print(@prompt.show)
|
120
|
-
answer
|
121
121
|
end
|
122
122
|
|
123
123
|
# Clear screen
|
124
124
|
#
|
125
|
+
# @param [Integer] lines
|
126
|
+
# the lines to clear
|
127
|
+
#
|
125
128
|
# @api private
|
126
|
-
def refresh
|
127
|
-
lines = @question.scan("\n").length + 2
|
129
|
+
def refresh(lines)
|
128
130
|
@prompt.print(@prompt.clear_lines(lines))
|
129
131
|
end
|
130
132
|
|
131
133
|
# @return [Integer]
|
132
134
|
#
|
133
135
|
# @api private
|
134
|
-
def
|
136
|
+
def answer
|
135
137
|
range[@active]
|
136
138
|
end
|
137
139
|
|
138
140
|
# Render question with the slider
|
139
141
|
#
|
142
|
+
# @return [String]
|
143
|
+
#
|
140
144
|
# @api private
|
141
145
|
def render_question
|
142
|
-
header = "#{@prefix}#{@question} #{render_header}"
|
143
|
-
@prompt.puts(header)
|
146
|
+
header = "#{@prefix}#{@question} #{render_header}\n"
|
144
147
|
@first_render = false
|
145
|
-
|
148
|
+
header << render_slider unless @done
|
149
|
+
header
|
146
150
|
end
|
147
151
|
|
148
152
|
# Render actual answer or help
|
149
153
|
#
|
154
|
+
# @return [String]
|
155
|
+
#
|
150
156
|
# @api private
|
151
157
|
def render_header
|
152
158
|
if @done
|
153
|
-
@prompt.decorate(
|
159
|
+
@prompt.decorate(answer.to_s, @active_color)
|
154
160
|
elsif @first_render
|
155
161
|
@prompt.decorate(HELP, @help_color)
|
156
162
|
end
|