yap-rawline 0.1.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.
@@ -0,0 +1,12 @@
1
+ require 'delegate'
2
+
3
+ module RawLine
4
+
5
+ class Prompt < SimpleDelegator
6
+ # Length returns the length of the prompt minus any ANSI escape sequences.
7
+ def length
8
+ self.gsub(/\033\[[0-9;]*m/, "").length
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,161 @@
1
+ #!usr/bin/env ruby
2
+
3
+ require 'terminfo'
4
+ require 'io/console'
5
+ require 'ostruct'
6
+
7
+ #
8
+ # terminal.rb
9
+ #
10
+ # Created by Fabio Cevasco on 2008-03-01.
11
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
12
+ #
13
+ # This is Free Software. See LICENSE for details.
14
+ #
15
+ #
16
+ #
17
+ module RawLine
18
+
19
+ #
20
+ # The Terminal class defines character codes and code sequences which can be
21
+ # bound to actions by editors.
22
+ # An OS-dependent subclass of RawLine::Terminal is automatically instantiated by
23
+ # RawLine::Editor.
24
+ #
25
+ class Terminal
26
+
27
+ include HighLine::SystemExtensions
28
+
29
+ attr_accessor :escape_codes
30
+ attr_reader :keys, :escape_sequences
31
+
32
+ #
33
+ # Create an instance of RawLine::Terminal.
34
+ #
35
+ def initialize
36
+ @keys =
37
+ {
38
+ :tab => [?\t.ord],
39
+ :return => [?\r.ord],
40
+ :newline => [?\n.ord],
41
+ :escape => [?\e.ord],
42
+
43
+ :ctrl_a => [?\C-a.ord],
44
+ :ctrl_b => [?\C-b.ord],
45
+ :ctrl_c => [?\C-c.ord],
46
+ :ctrl_d => [?\C-d.ord],
47
+ :ctrl_e => [?\C-e.ord],
48
+ :ctrl_f => [?\C-f.ord],
49
+ :ctrl_g => [?\C-g.ord],
50
+ :ctrl_h => [?\C-h.ord],
51
+ :ctrl_i => [?\C-i.ord],
52
+ :ctrl_j => [?\C-j.ord],
53
+ :ctrl_k => [?\C-k.ord],
54
+ :ctrl_l => [?\C-l.ord],
55
+ :ctrl_m => [?\C-m.ord],
56
+ :ctrl_n => [?\C-n.ord],
57
+ :ctrl_o => [?\C-o.ord],
58
+ :ctrl_p => [?\C-p.ord],
59
+ :ctrl_q => [?\C-q.ord],
60
+ :ctrl_r => [?\C-r.ord],
61
+ :ctrl_s => [?\C-s.ord],
62
+ :ctrl_t => [?\C-t.ord],
63
+ :ctrl_u => [?\C-u.ord],
64
+ :ctrl_v => [?\C-v.ord],
65
+ :ctrl_w => [?\C-w.ord],
66
+ :ctrl_x => [?\C-x.ord],
67
+ :ctrl_y => [?\C-y.ord],
68
+ :ctrl_z => [?\C-z.ord]
69
+ }
70
+ @escape_codes = []
71
+ @escape_sequences = []
72
+ update
73
+ end
74
+
75
+ CursorPosition = Struct.new(:column, :row)
76
+
77
+ def cursor_position
78
+ res = ''
79
+ $stdin.raw do |stdin|
80
+ $stdout << "\e[6n"
81
+ $stdout.flush
82
+ while (c = stdin.getc) != 'R'
83
+ res << c if c
84
+ end
85
+ end
86
+ m = res.match /(?<row>\d+);(?<column>\d+)/
87
+ CursorPosition.new(Integer(m[:column]), Integer(m[:row]))
88
+ end
89
+
90
+ def clear_to_beginning_of_line
91
+ term_info.control "el1"
92
+ end
93
+
94
+ def clear_screen
95
+ term_info.control "clear"
96
+ end
97
+
98
+ def clear_screen_down
99
+ term_info.control "ed"
100
+ end
101
+
102
+ def move_to_beginning_of_row
103
+ move_to_column 0
104
+ end
105
+
106
+ def move_left
107
+ move_left_n_characters 1
108
+ end
109
+
110
+ def move_left_n_characters(n)
111
+ n.times { term_info.control "cub1" }
112
+ end
113
+
114
+ def move_right_n_characters(n)
115
+ n.times { term_info.control "cuf1" }
116
+ end
117
+
118
+ def move_to_column_and_row(column, row)
119
+ term_info.control "cup", column, row
120
+ end
121
+
122
+ def move_to_column(n)
123
+ term_info.control "hpa", n
124
+ end
125
+
126
+ def move_up_n_rows(n)
127
+ n.times { term_info.control "cuu1" }
128
+ end
129
+
130
+ def move_down_n_rows(n)
131
+ n.times { term_info.control "cud1" }
132
+ end
133
+
134
+ def preserve_cursor(&blk)
135
+ term_info.control "sc" # store cursor position
136
+ blk.call
137
+ ensure
138
+ term_info.control "rc" # restore cursor position
139
+ end
140
+
141
+ #
142
+ # Update the terminal escape sequences. This method is called automatically
143
+ # by RawLine::Editor#bind().
144
+ #
145
+ def update
146
+ @keys.each_value do |k|
147
+ l = k.length
148
+ if l > 1 then
149
+ @escape_sequences << k unless @escape_sequences.include? k
150
+ end
151
+ end
152
+ end
153
+
154
+ def term_info
155
+ @term_info ||= TermInfo.new(ENV["TERM"], $stdout)
156
+ end
157
+
158
+ end
159
+
160
+
161
+ end
@@ -0,0 +1,66 @@
1
+ #!usr/bin/env ruby
2
+
3
+ #
4
+ # vt220_terminal.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module RawLine
13
+
14
+ #
15
+ # This class is used to define all the most common character codes and
16
+ # escape sequences used on *nix systems.
17
+ #
18
+ class VT220Terminal < Terminal
19
+
20
+ def initialize
21
+ super
22
+ @escape_codes = [?\e.ord]
23
+ @keys.merge!(
24
+ {
25
+ :up_arrow => [?\e.ord, ?[.ord, ?A.ord],
26
+ :down_arrow => [?\e.ord, ?[.ord, ?B.ord],
27
+ :right_arrow => [?\e.ord, ?[.ord, ?C.ord],
28
+ :left_arrow => [?\e.ord, ?[.ord, ?D.ord],
29
+ :insert => [?\e.ord, ?[, ?2.ord, ?~.ord],
30
+ :delete => [?\e.ord, ?[, ?3.ord, ?~.ord],
31
+ :backspace => [?\C-?.ord],
32
+ :enter => (HighLine::SystemExtensions::CHARACTER_MODE == 'termios' ? [?\n.ord] : [?\r.ord]),
33
+
34
+ :ctrl_alt_a => [?\e.ord, ?\C-a.ord],
35
+ :ctrl_alt_b => [?\e.ord, ?\C-b.ord],
36
+ :ctrl_alt_c => [?\e.ord, ?\C-c.ord],
37
+ :ctrl_alt_d => [?\e.ord, ?\C-d.ord],
38
+ :ctrl_alt_e => [?\e.ord, ?\C-e.ord],
39
+ :ctrl_alt_f => [?\e.ord, ?\C-f.ord],
40
+ :ctrl_alt_g => [?\e.ord, ?\C-g.ord],
41
+ :ctrl_alt_h => [?\e.ord, ?\C-h.ord],
42
+ :ctrl_alt_i => [?\e.ord, ?\C-i.ord],
43
+ :ctrl_alt_j => [?\e.ord, ?\C-j.ord],
44
+ :ctrl_alt_k => [?\e.ord, ?\C-k.ord],
45
+ :ctrl_alt_l => [?\e.ord, ?\C-l.ord],
46
+ :ctrl_alt_m => [?\e.ord, ?\C-m.ord],
47
+ :ctrl_alt_n => [?\e.ord, ?\C-n.ord],
48
+ :ctrl_alt_o => [?\e.ord, ?\C-o.ord],
49
+ :ctrl_alt_p => [?\e.ord, ?\C-p.ord],
50
+ :ctrl_alt_q => [?\e.ord, ?\C-q.ord],
51
+ :ctrl_alt_r => [?\e.ord, ?\C-r.ord],
52
+ :ctrl_alt_s => [?\e.ord, ?\C-s.ord],
53
+ :ctrl_alt_t => [?\e.ord, ?\C-t.ord],
54
+ :ctrl_alt_u => [?\e.ord, ?\C-u.ord],
55
+ :ctrl_alt_v => [?\e.ord, ?\C-v.ord],
56
+ :ctrl_alt_w => [?\e.ord, ?\C-w.ord],
57
+ :ctrl_alt_x => [?\e.ord, ?\C-x.ord],
58
+ :ctrl_alt_y => [?\e.ord, ?\C-y.ord],
59
+ :ctrl_alt_z => [?\e.ord, ?\C-z.ord]
60
+ })
61
+ end
62
+
63
+ end
64
+
65
+
66
+ end
@@ -0,0 +1,66 @@
1
+ #!usr/bin/env ruby
2
+
3
+ #
4
+ # windows_terminal.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module RawLine
13
+
14
+ #
15
+ # This class is used to define all the most common character codes and
16
+ # escape sequences used on Windows systems.
17
+ #
18
+ class WindowsTerminal < Terminal
19
+
20
+ def initialize
21
+ super
22
+ @escape_codes = [0, 27, 224]
23
+ @keys.merge!(
24
+ {
25
+ :left_arrow => [224, 75],
26
+ :right_arrow => [224, 77],
27
+ :up_arrow => [224, 72],
28
+ :down_arrow => [224, 80],
29
+ :insert => [224, 82],
30
+ :delete => [224, 83],
31
+ :backspace => [8],
32
+ :enter => [13],
33
+
34
+ :ctrl_alt_a => [0, 30],
35
+ :ctrl_alt_b => [0, 48],
36
+ :ctrl_alt_c => [0, 46],
37
+ :ctrl_alt_d => [0, 32],
38
+ :ctrl_alt_e => [0, 63],
39
+ :ctrl_alt_f => [0, 33],
40
+ :ctrl_alt_g => [0, 34],
41
+ :ctrl_alt_h => [0, 35],
42
+ :ctrl_alt_i => [0, 23],
43
+ :ctrl_alt_j => [0, 36],
44
+ :ctrl_alt_k => [0, 37],
45
+ :ctrl_alt_l => [0, 26],
46
+ :ctrl_alt_m => [0, 32],
47
+ :ctrl_alt_n => [0, 31],
48
+ :ctrl_alt_o => [0, 24],
49
+ :ctrl_alt_p => [0, 25],
50
+ :ctrl_alt_q => [0, 16],
51
+ :ctrl_alt_r => [0, 19],
52
+ :ctrl_alt_s => [0, 31],
53
+ :ctrl_alt_t => [0, 20],
54
+ :ctrl_alt_u => [0, 22],
55
+ :ctrl_alt_v => [0, 47],
56
+ :ctrl_alt_w => [0, 17],
57
+ :ctrl_alt_x => [0, 45],
58
+ :ctrl_alt_y => [0, 21],
59
+ :ctrl_alt_z => [0, 44]
60
+ })
61
+ end
62
+
63
+ end
64
+
65
+
66
+ end
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'highline/system_extensions'
4
+
5
+ module HighLine::SystemExtensions
6
+ # Override Windows' character reading so it's not tied to STDIN.
7
+ def get_character( input = STDIN )
8
+ (RUBY_VERSION.gsub(/1\./, '').to_f >= 8.7) ? input.getbyte : input.getc
9
+ end
10
+ end
11
+
12
+ require 'stringio'
13
+ require_relative "../lib/rawline.rb"
14
+
15
+ describe RawLine::Editor do
16
+
17
+ before :each do
18
+ @output = StringIO.new
19
+ @input = StringIO.new
20
+ @editor = RawLine::Editor.new(@input, @output)
21
+ end
22
+
23
+ it "reads raw characters from @input" do
24
+ @input << "test #1"
25
+ @input.rewind
26
+ @editor.read
27
+ expect(@editor.line.text).to eq("test #1")
28
+ expect(@output.string).to eq("test #1\n")
29
+ end
30
+
31
+ it "can bind keys to code blocks" do
32
+ @editor.bind(:ctrl_w) { @editor.write "test #2a" }
33
+ @editor.bind(?\C-q) { "test #2b" }
34
+ @editor.bind(21) { "test #2c" }
35
+ @editor.bind([22]) { "test #2d" }
36
+ @editor.terminal.escape_codes = [] # remove any existing escape codes
37
+ expect {@editor.bind({:test => [?\e.ord, ?t.ord, ?e.ord, ?s.ord, ?t.ord]}) { "test #2e" }}.to raise_error(RawLine::BindingException)
38
+ @editor.terminal.escape_codes << ?\e.ord
39
+ expect {@editor.bind({:test => "\etest"}) { "test #2e" }}.to_not raise_error
40
+ expect {@editor.bind("\etest2") { "test #2f" }}.to_not raise_error
41
+ @input << ?\C-w.chr
42
+ @input.rewind
43
+ @editor.read
44
+ expect(@editor.line.text).to eq("test #2a")
45
+ @editor.char = [?\C-q.ord]
46
+ expect(@editor.press_key).to eq("test #2b")
47
+ @editor.char = [?\C-u.ord]
48
+ expect(@editor.press_key).to eq("test #2c")
49
+ @editor.char = [?\C-v.ord]
50
+ expect(@editor.press_key).to eq("test #2d")
51
+ @editor.char = [?\e.ord, ?t.ord, ?e.ord, ?s.ord, ?t.ord]
52
+ expect(@editor.press_key).to eq("test #2e")
53
+ @editor.char = [?\e.ord, ?t.ord, ?e.ord, ?s.ord, ?t.ord, ?2.ord]
54
+ expect(@editor.press_key).to eq("test #2f")
55
+ end
56
+
57
+ it "keeps track of the cursor position" do
58
+ @input << "test #4"
59
+ @input.rewind
60
+ @editor.read
61
+ expect(@editor.line.position).to eq(7)
62
+ 3.times { @editor.move_left }
63
+ expect(@editor.line.position).to eq(4)
64
+ 2.times { @editor.move_right }
65
+ expect(@editor.line.position).to eq(6)
66
+ end
67
+
68
+ describe "keeping track of the cursor position across terminal lines (e.g. multi-line editing)" do
69
+ let(:terminal_width){ 3 }
70
+ let(:terminal_height){ 7 }
71
+ let(:output){ @output.rewind ; @output.read }
72
+ let(:arrow_key_left_ansi){ "\e[D" }
73
+ let(:arrow_key_right_ansi){ "\e[C" }
74
+
75
+ before do
76
+ allow(@editor).to receive(:terminal_size).and_return [terminal_width, terminal_height]
77
+ end
78
+
79
+ context "and the cursor position is at the first character of the second line" do
80
+ before do
81
+ @input << "123"
82
+ end
83
+
84
+ it "is at the first character of a second line" do
85
+ @input.rewind
86
+ @editor.read
87
+ expect(@editor.line.position).to eq(3)
88
+ end
89
+
90
+ describe "moving left from the first position of the second line" do
91
+ before do
92
+ @input << arrow_key_left_ansi
93
+ @input.rewind
94
+ @editor.read
95
+ end
96
+
97
+ it "sends the escape sequences moving the cursor to the end of the previous line" do
98
+ expected_ansi_sequence = "\e[A\e[#{terminal_width}C"
99
+ expect(output).to eq("123#{expected_ansi_sequence}\n")
100
+ end
101
+
102
+ it "correctly sets the line's position" do
103
+ expect(@editor.line.position).to eq(2)
104
+ end
105
+ end
106
+
107
+ describe "moving right from the first position of the second line" do
108
+ before do
109
+ @input << arrow_key_right_ansi
110
+ @input.rewind
111
+ @editor.read
112
+ end
113
+
114
+ it "doesn't send any escape sequences" do
115
+ expected_ansi_sequence = "\e[A\e[#{terminal_width}C"
116
+ expect(output).to_not include("\e")
117
+ end
118
+
119
+ it "doesn't move the cursor when it's at the end of the input" do
120
+ expect(@editor.line.position).to eq(3)
121
+ end
122
+ end
123
+
124
+ describe "moving left to the previous line then right to the next line" do
125
+ before do
126
+ # this is the one that moves us to the previous line
127
+ @input << arrow_key_left_ansi
128
+
129
+ # these are for fun to show that we don't generate unnecessary
130
+ # escape sequences
131
+ @input << arrow_key_left_ansi
132
+ @input << arrow_key_left_ansi
133
+ @input << arrow_key_left_ansi
134
+
135
+ # now let's move right again
136
+ @input << arrow_key_right_ansi
137
+ @input << arrow_key_right_ansi
138
+ @input << arrow_key_right_ansi
139
+
140
+ # this is the one that puts us on the next line
141
+ @input << arrow_key_right_ansi
142
+
143
+ @input.rewind
144
+ @editor.read
145
+ end
146
+
147
+ it "sends the the escape sequence for moving to the previous line just once" do
148
+ expected_ansi_sequence = "\e[A\e[#{terminal_width}C"
149
+ expect(output.scan(expected_ansi_sequence).flatten.length).to eq(1)
150
+ end
151
+
152
+ it "sends the the escape sequence for moving to the next line just once" do
153
+ expected_ansi_sequence = "\e[B\e[#{terminal_width}D"
154
+ expect(output.scan(expected_ansi_sequence).flatten.length).to eq(1)
155
+ end
156
+
157
+ it "correctly sets the line's position" do
158
+ expect(@editor.line.position).to eq(3)
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ it "can delete characters" do
165
+ @input << "test #5"
166
+ @input.rewind
167
+ @editor.read
168
+ 3.times { @editor.move_left }
169
+ 4.times { @editor.delete_left_character }
170
+ 3.times { @editor.delete_character }
171
+ expect(@editor.line.text).to eq("")
172
+ expect(@editor.line.position).to eq(0)
173
+ end
174
+
175
+ it "can clear the whole line" do
176
+ @input << "test #5"
177
+ @input.rewind
178
+ @editor.read
179
+ @editor.clear_line
180
+ expect(@editor.line.text).to eq("")
181
+ expect(@editor.line.position).to eq(0)
182
+ end
183
+
184
+ it "supports undo and redo" do
185
+ @input << "test #6"
186
+ @input.rewind
187
+ @editor.read
188
+ 3.times { @editor.delete_left_character }
189
+ 2.times { @editor.undo }
190
+ expect(@editor.line.text).to eq("test #")
191
+ 2.times { @editor.redo }
192
+ expect(@editor.line.text).to eq("test")
193
+ end
194
+
195
+ it "supports history" do
196
+ @input << "test #7a"
197
+ @input.rewind
198
+ @editor.read "", true
199
+ @editor.newline
200
+ @input << "test #7b"
201
+ @input.pos = 8
202
+ @editor.read "", true
203
+ @editor.newline
204
+ @input << "test #7c"
205
+ @input.pos = 16
206
+ @editor.read "", true
207
+ @editor.newline
208
+ @input << "test #7d"
209
+ @input.pos = 24
210
+ @editor.read "", true
211
+ @editor.newline
212
+ @editor.history_back
213
+ expect(@editor.line.text).to eq("test #7c")
214
+ 10.times { @editor.history_back }
215
+ expect(@editor.line.text).to eq("test #7a")
216
+ 2.times { @editor.history_forward }
217
+ expect(@editor.line.text).to eq("test #7c")
218
+ end
219
+
220
+ it "can overwrite lines" do
221
+ @input << "test #8a"
222
+ @input.rewind
223
+ @editor.read
224
+ @editor.overwrite_line("test #8b", 2)
225
+ expect(@editor.line.text).to eq("test #8b")
226
+ expect(@editor.line.position).to eq(2)
227
+ end
228
+
229
+ it "can complete words" do
230
+ @editor.completion_append_string = "\t"
231
+ @editor.bind(:tab) { @editor.complete }
232
+ @editor.completion_proc = lambda do |word|
233
+ if word then
234
+ ['select', 'update', 'delete', 'debug', 'destroy'].find_all { |e| e.match(/^#{Regexp.escape(word)}/) }
235
+ end
236
+ end
237
+ @input << "test #9 de" << ?\t.chr << ?\t.chr
238
+ @input.rewind
239
+ @editor.read
240
+ expect(@editor.line.text).to eq("test #9 delete\t")
241
+ end
242
+
243
+ it "supports INSERT and REPLACE modes" do
244
+ @input << "test 0"
245
+ @editor.terminal.keys[:left_arrow].each { |k| @input << k.chr }
246
+ @input << "#1"
247
+ @input.rewind
248
+ @editor.read
249
+ expect(@editor.line.text).to eq("test #10")
250
+ @editor.toggle_mode
251
+ @input << "test 0"
252
+ @editor.terminal.keys[:left_arrow].each { |k| @input << k.chr }
253
+ @input << "#1"
254
+ @input.rewind
255
+ @editor.read
256
+ expect(@editor.line.text).to eq("test #1test #1")
257
+ end
258
+
259
+
260
+ end