tty2-reader 0.9.0.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 +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +120 -0
- data/lib/tty2/reader/completer.rb +188 -0
- data/lib/tty2/reader/completion_event.rb +36 -0
- data/lib/tty2/reader/completions.rb +107 -0
- data/lib/tty2/reader/console.rb +68 -0
- data/lib/tty2/reader/history.rb +184 -0
- data/lib/tty2/reader/key_event.rb +58 -0
- data/lib/tty2/reader/keys.rb +166 -0
- data/lib/tty2/reader/line.rb +367 -0
- data/lib/tty2/reader/mode.rb +42 -0
- data/lib/tty2/reader/version.rb +7 -0
- data/lib/tty2/reader/win_api.rb +51 -0
- data/lib/tty2/reader/win_console.rb +90 -0
- data/lib/tty2/reader.rb +632 -0
- data/lib/tty2-reader.rb +1 -0
- metadata +143 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module TTY2
|
6
|
+
class Reader
|
7
|
+
# A class responsible for storing a history of all lines entered by
|
8
|
+
# user when interacting with shell prompt.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class History
|
12
|
+
include Enumerable
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
# Default maximum size
|
16
|
+
DEFAULT_SIZE = 32 << 4
|
17
|
+
|
18
|
+
# Default exclude
|
19
|
+
DEFAULT_EXCLUDE = ->(line) { line.chomp == "" }
|
20
|
+
|
21
|
+
def_delegators :@history, :size, :length, :to_s, :inspect
|
22
|
+
|
23
|
+
# Set and retrieve the maximum size of the buffer
|
24
|
+
attr_accessor :max_size
|
25
|
+
|
26
|
+
# The current index
|
27
|
+
#
|
28
|
+
# @return [Integer]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
attr_reader :index
|
32
|
+
|
33
|
+
# Decides whether or not to allow cycling through stored lines.
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
attr_accessor :cycle
|
39
|
+
|
40
|
+
# Decides wether or not duplicate lines are stored.
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
attr_accessor :duplicates
|
46
|
+
|
47
|
+
# Dictates which lines are stored.
|
48
|
+
#
|
49
|
+
# @return [Proc]
|
50
|
+
#
|
51
|
+
# @public
|
52
|
+
attr_accessor :exclude
|
53
|
+
|
54
|
+
# Create a History buffer
|
55
|
+
#
|
56
|
+
# @param [Integer] max_size
|
57
|
+
# the maximum size for history buffer
|
58
|
+
# @param [Boolean] cycle
|
59
|
+
# whether or not the history should cycle, false by default
|
60
|
+
# @param [Boolean] duplicates
|
61
|
+
# whether or not to store duplicates, true by default
|
62
|
+
# @param [Boolean] exclude
|
63
|
+
# a Proc to exclude items from storing in history
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def initialize(max_size = DEFAULT_SIZE, duplicates: true, cycle: false,
|
67
|
+
exclude: DEFAULT_EXCLUDE)
|
68
|
+
@max_size = max_size
|
69
|
+
@index = nil
|
70
|
+
@history = []
|
71
|
+
@duplicates = duplicates
|
72
|
+
@exclude = exclude
|
73
|
+
@cycle = cycle
|
74
|
+
|
75
|
+
yield self if block_given?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Iterates over history lines
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
def each(&block)
|
82
|
+
if block_given?
|
83
|
+
@history.each(&block)
|
84
|
+
else
|
85
|
+
@history.to_enum
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Add the last typed line to history buffer
|
90
|
+
#
|
91
|
+
# @param [String] line
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def push(line)
|
95
|
+
@history.delete(line) unless @duplicates
|
96
|
+
return if line.to_s.empty? || @exclude[line]
|
97
|
+
|
98
|
+
@history.shift if size >= max_size
|
99
|
+
@history << line
|
100
|
+
@index = @history.size - 1
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
alias << push
|
105
|
+
|
106
|
+
# Replace the current line with a new one
|
107
|
+
#
|
108
|
+
# @param [String] line
|
109
|
+
# the new line to replace with
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def replace(line)
|
113
|
+
return if @index.to_i >= size
|
114
|
+
|
115
|
+
@history[index] = line.dup
|
116
|
+
end
|
117
|
+
|
118
|
+
# Move the pointer to the next line in the history
|
119
|
+
#
|
120
|
+
# @api public
|
121
|
+
def next
|
122
|
+
return if size.zero?
|
123
|
+
|
124
|
+
if @index == size - 1
|
125
|
+
@index = 0 if @cycle
|
126
|
+
else
|
127
|
+
@index += 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def next?
|
132
|
+
size > 0 && !(@index == size - 1 && !@cycle)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Move the pointer to the previous line in the history
|
136
|
+
def previous
|
137
|
+
return if size.zero?
|
138
|
+
|
139
|
+
if @index.zero?
|
140
|
+
@index = size - 1 if @cycle
|
141
|
+
else
|
142
|
+
@index -= 1
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def previous?
|
147
|
+
size > 0 && !(@index < 0 && !@cycle)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return line at the specified index
|
151
|
+
#
|
152
|
+
# @raise [IndexError] index out of range
|
153
|
+
#
|
154
|
+
# @api public
|
155
|
+
def [](index)
|
156
|
+
if index < 0
|
157
|
+
index += @history.size if index < 0
|
158
|
+
end
|
159
|
+
line = @history[index]
|
160
|
+
if line.nil?
|
161
|
+
raise IndexError, "invalid index"
|
162
|
+
end
|
163
|
+
line.dup
|
164
|
+
end
|
165
|
+
|
166
|
+
# Get current line
|
167
|
+
#
|
168
|
+
# @api public
|
169
|
+
def get
|
170
|
+
return if size.zero?
|
171
|
+
|
172
|
+
self[@index]
|
173
|
+
end
|
174
|
+
|
175
|
+
# Empty all history lines
|
176
|
+
#
|
177
|
+
# @api public
|
178
|
+
def clear
|
179
|
+
@history.clear
|
180
|
+
@index = 0
|
181
|
+
end
|
182
|
+
end # History
|
183
|
+
end # Reader
|
184
|
+
end # TTY2
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "keys"
|
4
|
+
|
5
|
+
module TTY2
|
6
|
+
class Reader
|
7
|
+
# Responsible for meta-data information about key pressed
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Key < Struct.new(:name, :ctrl, :meta, :shift)
|
11
|
+
def initialize(*)
|
12
|
+
super(nil, false, false, false)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Represents key event emitted during keyboard press
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
class KeyEvent < Struct.new(:key, :value, :line)
|
20
|
+
# Create key event from read input codes
|
21
|
+
#
|
22
|
+
# @param [Hash[Symbol]] keys
|
23
|
+
# the keys and codes mapping
|
24
|
+
# @param [Array[Integer]] codes
|
25
|
+
#
|
26
|
+
# @return [KeyEvent]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def self.from(keys, char, line = Line.new)
|
30
|
+
key = Key.new
|
31
|
+
key.name = (name = keys[char]) ? name : :ignore
|
32
|
+
|
33
|
+
case char
|
34
|
+
when proc { |c| c =~ /^[a-z]{1}$/ }
|
35
|
+
key.name = :alpha
|
36
|
+
when proc { |c| c =~ /^[A-Z]{1}$/ }
|
37
|
+
key.name = :alpha
|
38
|
+
key.shift = true
|
39
|
+
when proc { |c| c =~ /^\d+$/ }
|
40
|
+
key.name = :num
|
41
|
+
when proc { |cs| !Keys.ctrl_keys[cs].nil? }
|
42
|
+
key.ctrl = true
|
43
|
+
end
|
44
|
+
|
45
|
+
new(key, char, line)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check if key event can be triggered
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def trigger?
|
54
|
+
!key.nil? && !key.name.nil?
|
55
|
+
end
|
56
|
+
end # KeyEvent
|
57
|
+
end # Reader
|
58
|
+
end # TTY2
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY2
|
4
|
+
class Reader
|
5
|
+
# Mapping of escape codes to keys
|
6
|
+
module Keys
|
7
|
+
def ctrl_keys
|
8
|
+
{
|
9
|
+
?\C-a => :ctrl_a,
|
10
|
+
?\C-b => :ctrl_b,
|
11
|
+
?\C-c => :ctrl_c,
|
12
|
+
?\C-d => :ctrl_d,
|
13
|
+
?\C-e => :ctrl_e,
|
14
|
+
?\C-f => :ctrl_f,
|
15
|
+
?\C-g => :ctrl_g,
|
16
|
+
?\C-h => :ctrl_h, # identical to "\b"
|
17
|
+
?\C-i => :ctrl_i, # identical to "\t"
|
18
|
+
?\C-j => :ctrl_j, # identical to "\n"
|
19
|
+
?\C-k => :ctrl_k,
|
20
|
+
?\C-l => :ctrl_l,
|
21
|
+
?\C-m => :ctrl_m, # identical to "\r"
|
22
|
+
?\C-n => :ctrl_n,
|
23
|
+
?\C-o => :ctrl_o,
|
24
|
+
?\C-p => :ctrl_p,
|
25
|
+
?\C-q => :ctrl_q,
|
26
|
+
?\C-r => :ctrl_r,
|
27
|
+
?\C-s => :ctrl_s,
|
28
|
+
?\C-t => :ctrl_t,
|
29
|
+
?\C-u => :ctrl_u,
|
30
|
+
?\C-v => :ctrl_v,
|
31
|
+
?\C-w => :ctrl_w,
|
32
|
+
?\C-x => :ctrl_x,
|
33
|
+
?\C-y => :ctrl_y,
|
34
|
+
?\C-z => :ctrl_z,
|
35
|
+
?\C-@ => :ctrl_space,
|
36
|
+
?\C-| => :ctrl_backslash, # both Ctrl-| & Ctrl-\
|
37
|
+
?\C-] => :ctrl_square_close,
|
38
|
+
"\e[1;5A" => :ctrl_up,
|
39
|
+
"\e[1;5B" => :ctrl_down,
|
40
|
+
"\e[1;5C" => :ctrl_right,
|
41
|
+
"\e[1;5D" => :ctrl_left
|
42
|
+
}
|
43
|
+
end
|
44
|
+
module_function :ctrl_keys
|
45
|
+
|
46
|
+
def keys
|
47
|
+
{
|
48
|
+
"\t" => :tab,
|
49
|
+
"\n" => :enter,
|
50
|
+
"\r" => :return,
|
51
|
+
"\e" => :escape,
|
52
|
+
" " => :space,
|
53
|
+
"\x7F" => :backspace,
|
54
|
+
"\e[1~" => :home,
|
55
|
+
"\e[2~" => :insert,
|
56
|
+
"\e[3~" => :delete,
|
57
|
+
"\e[3;2~" => :shift_delete,
|
58
|
+
"\e[3;5~" => :ctrl_delete,
|
59
|
+
"\e[4~" => :end,
|
60
|
+
"\e[5~" => :page_up,
|
61
|
+
"\e[6~" => :page_down,
|
62
|
+
"\e[7~" => :home, # xrvt
|
63
|
+
"\e[8~" => :end, # xrvt
|
64
|
+
|
65
|
+
"\e[A" => :up,
|
66
|
+
"\e[B" => :down,
|
67
|
+
"\e[C" => :right,
|
68
|
+
"\e[D" => :left,
|
69
|
+
"\e[E" => :clear,
|
70
|
+
"\e[H" => :home,
|
71
|
+
"\e[F" => :end,
|
72
|
+
"\e[Z" => :shift_tab,
|
73
|
+
|
74
|
+
# xterm/gnome
|
75
|
+
"\eOA" => :up,
|
76
|
+
"\eOB" => :down,
|
77
|
+
"\eOC" => :right,
|
78
|
+
"\eOD" => :left,
|
79
|
+
"\eOE" => :clear,
|
80
|
+
"\eOF" => :end,
|
81
|
+
"\eOH" => :home,
|
82
|
+
|
83
|
+
"\eOP" => :f1, # xterm
|
84
|
+
"\eOQ" => :f2, # xterm
|
85
|
+
"\eOR" => :f3, # xterm
|
86
|
+
"\eOS" => :f4, # xterm
|
87
|
+
"\e[[A" => :f1, # linux
|
88
|
+
"\e[[B" => :f2, # linux
|
89
|
+
"\e[[C" => :f3, # linux
|
90
|
+
"\e[[D" => :f4, # linux
|
91
|
+
"\e[[E" => :f5, # linux
|
92
|
+
"\e[11~" => :f1, # rxvt-unicode
|
93
|
+
"\e[12~" => :f2, # rxvt-unicode
|
94
|
+
"\e[13~" => :f3, # rxvt-unicode
|
95
|
+
"\e[14~" => :f4, # rxvt-unicode
|
96
|
+
"\e[15~" => :f5,
|
97
|
+
"\e[17~" => :f6,
|
98
|
+
"\e[18~" => :f7,
|
99
|
+
"\e[19~" => :f8,
|
100
|
+
"\e[20~" => :f9,
|
101
|
+
"\e[21~" => :f10,
|
102
|
+
"\e[23~" => :f11,
|
103
|
+
"\e[24~" => :f12,
|
104
|
+
"\e[25~" => :f13,
|
105
|
+
"\e[26~" => :f14,
|
106
|
+
"\e[28~" => :f15,
|
107
|
+
"\e[29~" => :f16,
|
108
|
+
"\e[31~" => :f17,
|
109
|
+
"\e[32~" => :f18,
|
110
|
+
"\e[33~" => :f19,
|
111
|
+
"\e[34~" => :f20,
|
112
|
+
# xterm
|
113
|
+
"\e[1;2P" => :f13,
|
114
|
+
"\e[2;2Q" => :f14,
|
115
|
+
"\e[1;2S" => :f16,
|
116
|
+
"\e[15;2~" => :f17,
|
117
|
+
"\e[17;2~" => :f18,
|
118
|
+
"\e[18;2~" => :f19,
|
119
|
+
"\e[19;2~" => :f20,
|
120
|
+
"\e[20;2~" => :f21,
|
121
|
+
"\e[21;2~" => :f22,
|
122
|
+
"\e[23;2~" => :f23,
|
123
|
+
"\e[24;2~" => :f24,
|
124
|
+
}
|
125
|
+
end
|
126
|
+
module_function :keys
|
127
|
+
|
128
|
+
def win_keys
|
129
|
+
{
|
130
|
+
"\t" => :tab,
|
131
|
+
"\n" => :enter,
|
132
|
+
"\r" => :return,
|
133
|
+
"\e" => :escape,
|
134
|
+
" " => :space,
|
135
|
+
"\b" => :backspace,
|
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,
|
142
|
+
|
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,
|
148
|
+
|
149
|
+
"\x00;" => :f1,
|
150
|
+
"\x00<" => :f2,
|
151
|
+
"\x00" => :f3,
|
152
|
+
"\x00=" => :f4,
|
153
|
+
"\x00?" => :f5,
|
154
|
+
"\x00@" => :f6,
|
155
|
+
"\x00A" => :f7,
|
156
|
+
"\x00B" => :f8,
|
157
|
+
"\x00C" => :f9,
|
158
|
+
"\x00D" => :f10,
|
159
|
+
"\x00\x85" => :f11,
|
160
|
+
"\x00\x86" => :f12
|
161
|
+
}
|
162
|
+
end
|
163
|
+
module_function :win_keys
|
164
|
+
end # Keys
|
165
|
+
end # Reader
|
166
|
+
end # TTY2
|