tty-prompt 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|