tty-reader 0.4.0 → 0.9.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 +99 -0
- data/LICENSE.txt +1 -1
- data/README.md +75 -25
- data/lib/tty-reader.rb +0 -2
- data/lib/tty/reader.rb +134 -84
- data/lib/tty/reader/console.rb +23 -11
- data/lib/tty/reader/history.rb +39 -15
- data/lib/tty/reader/key_event.rb +4 -5
- data/lib/tty/reader/keys.rb +15 -16
- data/lib/tty/reader/line.rb +30 -17
- data/lib/tty/reader/mode.rb +1 -2
- data/lib/tty/reader/version.rb +2 -2
- data/lib/tty/reader/win_api.rb +2 -5
- data/lib/tty/reader/win_console.rb +18 -18
- metadata +35 -57
- data/Rakefile +0 -10
- data/bin/console +0 -6
- data/bin/setup +0 -8
- data/examples/keypress.rb +0 -16
- data/examples/line.rb +0 -7
- data/examples/multiline.rb +0 -7
- data/examples/noecho.rb +0 -6
- data/examples/shell.rb +0 -12
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-reader.gemspec +0 -30
data/lib/tty/reader/console.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
require "io/wait"
|
4
|
+
|
5
|
+
require_relative "keys"
|
6
|
+
require_relative "mode"
|
6
7
|
|
7
8
|
module TTY
|
8
9
|
class Reader
|
9
10
|
class Console
|
10
|
-
ESC = "\e"
|
11
|
-
CSI = "\e["
|
11
|
+
ESC = "\e"
|
12
|
+
CSI = "\e["
|
13
|
+
|
14
|
+
TIMEOUT = 0.1
|
12
15
|
|
13
16
|
# Key codes
|
14
17
|
#
|
@@ -33,16 +36,25 @@ module TTY
|
|
33
36
|
|
34
37
|
# Get a character from console with echo
|
35
38
|
#
|
36
|
-
# @param [
|
37
|
-
#
|
38
|
-
#
|
39
|
+
# @param [Boolean] echo
|
40
|
+
# whether to echo input back or not, defaults to true
|
41
|
+
# @param [Boolean] raw
|
42
|
+
# whether to use raw mode or not, defaults to false
|
43
|
+
# @param [Boolean] nonblock
|
44
|
+
# whether to wait for input or not, defaults to false
|
39
45
|
#
|
40
46
|
# @return [String]
|
41
47
|
#
|
42
48
|
# @api private
|
43
|
-
def get_char(
|
44
|
-
mode.raw(
|
45
|
-
mode.echo(
|
49
|
+
def get_char(echo: true, raw: false, nonblock: false)
|
50
|
+
mode.raw(raw) do
|
51
|
+
mode.echo(echo) do
|
52
|
+
if nonblock
|
53
|
+
input.wait_readable(TIMEOUT) ? input.getc : nil
|
54
|
+
else
|
55
|
+
input.getc
|
56
|
+
end
|
57
|
+
end
|
46
58
|
end
|
47
59
|
end
|
48
60
|
|
data/lib/tty/reader/history.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require
|
3
|
+
require "forwardable"
|
5
4
|
|
6
5
|
module TTY
|
7
6
|
class Reader
|
@@ -16,49 +15,72 @@ module TTY
|
|
16
15
|
# Default maximum size
|
17
16
|
DEFAULT_SIZE = 32 << 4
|
18
17
|
|
18
|
+
# Default exclude
|
19
|
+
DEFAULT_EXCLUDE = ->(line) { line.chomp == "" }
|
20
|
+
|
19
21
|
def_delegators :@history, :size, :length, :to_s, :inspect
|
20
22
|
|
21
23
|
# Set and retrieve the maximum size of the buffer
|
22
24
|
attr_accessor :max_size
|
23
25
|
|
26
|
+
# The current index
|
27
|
+
#
|
28
|
+
# @return [Integer]
|
29
|
+
#
|
30
|
+
# @api private
|
24
31
|
attr_reader :index
|
25
32
|
|
33
|
+
# Decides whether or not to allow cycling through stored lines.
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
#
|
37
|
+
# @api public
|
26
38
|
attr_accessor :cycle
|
27
39
|
|
40
|
+
# Decides wether or not duplicate lines are stored.
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
# @api public
|
28
45
|
attr_accessor :duplicates
|
29
46
|
|
47
|
+
# Dictates which lines are stored.
|
48
|
+
#
|
49
|
+
# @return [Proc]
|
50
|
+
#
|
51
|
+
# @public
|
30
52
|
attr_accessor :exclude
|
31
53
|
|
32
54
|
# Create a History buffer
|
33
55
|
#
|
34
|
-
# param [Integer] max_size
|
56
|
+
# @param [Integer] max_size
|
35
57
|
# the maximum size for history buffer
|
36
|
-
#
|
37
|
-
# param [Hash[Symbol]] options
|
38
|
-
# @option options [Boolean] :cycle
|
58
|
+
# @param [Boolean] cycle
|
39
59
|
# whether or not the history should cycle, false by default
|
40
|
-
# @
|
60
|
+
# @param [Boolean] duplicates
|
41
61
|
# whether or not to store duplicates, true by default
|
42
|
-
# @
|
62
|
+
# @param [Boolean] exclude
|
43
63
|
# a Proc to exclude items from storing in history
|
44
64
|
#
|
45
65
|
# @api public
|
46
|
-
def initialize(max_size = DEFAULT_SIZE,
|
66
|
+
def initialize(max_size = DEFAULT_SIZE, duplicates: true, cycle: false,
|
67
|
+
exclude: DEFAULT_EXCLUDE)
|
47
68
|
@max_size = max_size
|
48
69
|
@index = nil
|
49
70
|
@history = []
|
50
|
-
@duplicates =
|
51
|
-
@exclude =
|
52
|
-
@cycle =
|
71
|
+
@duplicates = duplicates
|
72
|
+
@exclude = exclude
|
73
|
+
@cycle = cycle
|
74
|
+
|
53
75
|
yield self if block_given?
|
54
76
|
end
|
55
77
|
|
56
78
|
# Iterates over history lines
|
57
79
|
#
|
58
80
|
# @api public
|
59
|
-
def each
|
81
|
+
def each(&block)
|
60
82
|
if block_given?
|
61
|
-
@history.each
|
83
|
+
@history.each(&block)
|
62
84
|
else
|
63
85
|
@history.to_enum
|
64
86
|
end
|
@@ -86,6 +108,7 @@ module TTY
|
|
86
108
|
# @api public
|
87
109
|
def next
|
88
110
|
return if size.zero?
|
111
|
+
|
89
112
|
if @index == size - 1
|
90
113
|
@index = 0 if @cycle
|
91
114
|
else
|
@@ -100,6 +123,7 @@ module TTY
|
|
100
123
|
# Move the pointer to the previous line in the history
|
101
124
|
def previous
|
102
125
|
return if size.zero?
|
126
|
+
|
103
127
|
if @index.zero?
|
104
128
|
@index = size - 1 if @cycle
|
105
129
|
else
|
@@ -122,7 +146,7 @@ module TTY
|
|
122
146
|
end
|
123
147
|
line = @history[index]
|
124
148
|
if line.nil?
|
125
|
-
raise IndexError,
|
149
|
+
raise IndexError, "invalid index"
|
126
150
|
end
|
127
151
|
line.dup
|
128
152
|
end
|
data/lib/tty/reader/key_event.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require_relative
|
3
|
+
require_relative "keys"
|
5
4
|
|
6
5
|
module TTY
|
7
6
|
class Reader
|
@@ -17,7 +16,7 @@ module TTY
|
|
17
16
|
# Represents key event emitted during keyboard press
|
18
17
|
#
|
19
18
|
# @api public
|
20
|
-
class KeyEvent < Struct.new(:value, :
|
19
|
+
class KeyEvent < Struct.new(:key, :value, :line)
|
21
20
|
# Create key event from read input codes
|
22
21
|
#
|
23
22
|
# @param [Hash[Symbol]] keys
|
@@ -27,7 +26,7 @@ module TTY
|
|
27
26
|
# @return [KeyEvent]
|
28
27
|
#
|
29
28
|
# @api public
|
30
|
-
def self.from(keys, char)
|
29
|
+
def self.from(keys, char, line = "")
|
31
30
|
key = Key.new
|
32
31
|
key.name = (name = keys[char]) ? name : :ignore
|
33
32
|
|
@@ -43,7 +42,7 @@ module TTY
|
|
43
42
|
key.ctrl = true
|
44
43
|
end
|
45
44
|
|
46
|
-
new(char,
|
45
|
+
new(key, char, line)
|
47
46
|
end
|
48
47
|
|
49
48
|
# Check if key event can be triggered
|
data/lib/tty/reader/keys.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module TTY
|
@@ -14,12 +13,12 @@ module TTY
|
|
14
13
|
?\C-e => :ctrl_e,
|
15
14
|
?\C-f => :ctrl_f,
|
16
15
|
?\C-g => :ctrl_g,
|
17
|
-
?\C-h => :ctrl_h, # identical to
|
18
|
-
?\C-i => :ctrl_i, # identical to
|
19
|
-
?\C-j => :ctrl_j, # identical to
|
16
|
+
?\C-h => :ctrl_h, # identical to "\b"
|
17
|
+
?\C-i => :ctrl_i, # identical to "\t"
|
18
|
+
?\C-j => :ctrl_j, # identical to "\n"
|
20
19
|
?\C-k => :ctrl_k,
|
21
20
|
?\C-l => :ctrl_l,
|
22
|
-
?\C-m => :ctrl_m, # identical to
|
21
|
+
?\C-m => :ctrl_m, # identical to "\r"
|
23
22
|
?\C-n => :ctrl_n,
|
24
23
|
?\C-o => :ctrl_o,
|
25
24
|
?\C-p => :ctrl_p,
|
@@ -134,18 +133,18 @@ module TTY
|
|
134
133
|
"\e" => :escape,
|
135
134
|
" " => :space,
|
136
135
|
"\b" => :backspace,
|
137
|
-
[224, 71].pack(
|
138
|
-
[224, 79].pack(
|
139
|
-
[224, 82].pack(
|
140
|
-
[224, 83].pack(
|
141
|
-
[224, 73].pack(
|
142
|
-
[224, 81].pack(
|
136
|
+
[224, 71].pack("U*") => :home,
|
137
|
+
[224, 79].pack("U*") => :end,
|
138
|
+
[224, 82].pack("U*") => :insert,
|
139
|
+
[224, 83].pack("U*") => :delete,
|
140
|
+
[224, 73].pack("U*") => :page_up,
|
141
|
+
[224, 81].pack("U*") => :page_down,
|
143
142
|
|
144
|
-
[224, 72].pack(
|
145
|
-
[224, 80].pack(
|
146
|
-
[224, 77].pack(
|
147
|
-
[224, 75].pack(
|
148
|
-
[224, 83].pack(
|
143
|
+
[224, 72].pack("U*") => :up,
|
144
|
+
[224, 80].pack("U*") => :down,
|
145
|
+
[224, 77].pack("U*") => :right,
|
146
|
+
[224, 75].pack("U*") => :left,
|
147
|
+
[224, 83].pack("U*") => :clear,
|
149
148
|
|
150
149
|
"\x00;" => :f1,
|
151
150
|
"\x00<" => :f2,
|
data/lib/tty/reader/line.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "forwardable"
|
4
4
|
|
5
5
|
module TTY
|
6
6
|
class Reader
|
@@ -15,7 +15,7 @@ module TTY
|
|
15
15
|
#
|
16
16
|
# @api public
|
17
17
|
def self.sanitize(text)
|
18
|
-
text.dup.gsub(ANSI_MATCHER,
|
18
|
+
text.dup.gsub(ANSI_MATCHER, "")
|
19
19
|
end
|
20
20
|
|
21
21
|
# The editable text
|
@@ -30,13 +30,19 @@ module TTY
|
|
30
30
|
# @api public
|
31
31
|
attr_reader :mode
|
32
32
|
|
33
|
+
# The prompt displayed before input
|
34
|
+
# @api public
|
33
35
|
attr_reader :prompt
|
34
36
|
|
35
|
-
|
37
|
+
# Create a Line instance
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def initialize(text = "", prompt: "")
|
36
41
|
@prompt = prompt.dup
|
37
42
|
@text = text.dup
|
38
43
|
@cursor = [0, @text.length].max
|
39
44
|
@mode = :edit
|
45
|
+
|
40
46
|
yield self if block_given?
|
41
47
|
end
|
42
48
|
|
@@ -128,14 +134,18 @@ module TTY
|
|
128
134
|
# @param [Integer] i
|
129
135
|
# the index to insert at
|
130
136
|
#
|
137
|
+
# @param [String] chars
|
138
|
+
# the characters to insert
|
139
|
+
#
|
131
140
|
# @example
|
132
|
-
# text =
|
133
|
-
# line[5]=
|
134
|
-
# =>
|
141
|
+
# text = "aaa"
|
142
|
+
# line[5]= "b"
|
143
|
+
# => "aaa b"
|
135
144
|
#
|
136
145
|
# @api public
|
137
146
|
def []=(i, chars)
|
138
147
|
edit_mode
|
148
|
+
|
139
149
|
if i.is_a?(Range)
|
140
150
|
@text[i] = chars
|
141
151
|
@cursor += chars.length
|
@@ -143,12 +153,9 @@ module TTY
|
|
143
153
|
end
|
144
154
|
|
145
155
|
if i <= 0
|
146
|
-
before_text =
|
156
|
+
before_text = ""
|
147
157
|
after_text = @text.dup
|
148
|
-
elsif i
|
149
|
-
before_text = @text.dup
|
150
|
-
after_text = ''
|
151
|
-
elsif i > @text.length - 1
|
158
|
+
elsif i > @text.length - 1 # insert outside of line input
|
152
159
|
before_text = @text.dup
|
153
160
|
after_text = ?\s * (i - @text.length)
|
154
161
|
@cursor += after_text.length
|
@@ -202,16 +209,19 @@ module TTY
|
|
202
209
|
# Remove char from the line at current position
|
203
210
|
#
|
204
211
|
# @api public
|
205
|
-
def delete
|
206
|
-
@text.slice!(@cursor,
|
212
|
+
def delete(n = 1)
|
213
|
+
@text.slice!(@cursor, n)
|
207
214
|
end
|
208
215
|
|
209
216
|
# Remove char from the line in front of the cursor
|
210
217
|
#
|
218
|
+
# @param [Integer] n
|
219
|
+
# the number of chars to remove
|
220
|
+
#
|
211
221
|
# @api public
|
212
|
-
def remove
|
213
|
-
left
|
214
|
-
@text.slice!(@cursor,
|
222
|
+
def remove(n = 1)
|
223
|
+
left(n)
|
224
|
+
@text.slice!(@cursor, n)
|
215
225
|
end
|
216
226
|
|
217
227
|
# Full line with prompt as string
|
@@ -226,7 +236,10 @@ module TTY
|
|
226
236
|
#
|
227
237
|
# @api public
|
228
238
|
def prompt_size
|
229
|
-
self.class.sanitize(@prompt).
|
239
|
+
p = self.class.sanitize(@prompt).split(/\r?\n/)
|
240
|
+
# return the length of each line + screen width for every line past the first
|
241
|
+
# which accounts for multi-line prompts
|
242
|
+
p.join.length + ((p.length - 1) * TTY::Screen.width )
|
230
243
|
end
|
231
244
|
|
232
245
|
# Text size
|
data/lib/tty/reader/mode.rb
CHANGED
data/lib/tty/reader/version.rb
CHANGED
data/lib/tty/reader/win_api.rb
CHANGED
@@ -1,16 +1,13 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require
|
3
|
+
require "fiddle"
|
5
4
|
|
6
5
|
module TTY
|
7
6
|
class Reader
|
8
7
|
module WinAPI
|
9
8
|
include Fiddle
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
CRT_HANDLE = Handle.new("msvcrt") rescue Handle.new("crtdll")
|
10
|
+
CRT_HANDLE = Fiddle::Handle.new("msvcrt") rescue Fiddle::Handle.new("crtdll")
|
14
11
|
|
15
12
|
# Get a character from the console without echo.
|
16
13
|
#
|
@@ -1,14 +1,13 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require_relative
|
3
|
+
require_relative "keys"
|
5
4
|
|
6
5
|
module TTY
|
7
6
|
class Reader
|
8
7
|
class WinConsole
|
9
|
-
ESC = "\e"
|
10
|
-
NUL_HEX = "\x00"
|
11
|
-
EXT_HEX = "\xE0"
|
8
|
+
ESC = "\e"
|
9
|
+
NUL_HEX = "\x00"
|
10
|
+
EXT_HEX = "\xE0"
|
12
11
|
|
13
12
|
# Key codes
|
14
13
|
#
|
@@ -25,7 +24,7 @@ module TTY
|
|
25
24
|
attr_reader :escape_codes
|
26
25
|
|
27
26
|
def initialize(input)
|
28
|
-
require_relative
|
27
|
+
require_relative "win_api"
|
29
28
|
@input = input
|
30
29
|
@keys = Keys.ctrl_keys.merge(Keys.win_keys)
|
31
30
|
@escape_codes = [[NUL_HEX.ord], [ESC.ord], EXT_HEX.bytes.to_a]
|
@@ -33,26 +32,27 @@ module TTY
|
|
33
32
|
|
34
33
|
# Get a character from console blocking for input
|
35
34
|
#
|
36
|
-
# @param [
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
35
|
+
# @param [Boolean] echo
|
36
|
+
# whether to echo input back or not, defaults to true
|
37
|
+
# @param [Boolean] raw
|
38
|
+
# whether to use raw mode or not, defaults to false
|
39
|
+
# @param [Boolean] nonblock
|
40
|
+
# whether to wait for input or not, defaults to false
|
41
41
|
#
|
42
42
|
# @return [String]
|
43
43
|
#
|
44
44
|
# @api private
|
45
|
-
def get_char(
|
46
|
-
if
|
47
|
-
if
|
45
|
+
def get_char(echo: true, raw: false, nonblock: false)
|
46
|
+
if raw && echo
|
47
|
+
if nonblock
|
48
48
|
get_char_echo_non_blocking
|
49
49
|
else
|
50
50
|
get_char_echo_blocking
|
51
51
|
end
|
52
|
-
elsif
|
53
|
-
|
54
|
-
elsif !
|
55
|
-
|
52
|
+
elsif raw && !echo
|
53
|
+
nonblock ? get_char_non_blocking : get_char_blocking
|
54
|
+
elsif !raw && !echo
|
55
|
+
nonblock ? get_char_non_blocking : get_char_blocking
|
56
56
|
else
|
57
57
|
@input.getc
|
58
58
|
end
|