yap-rawline 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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