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.
@@ -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