yap-rawline 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/rawline_shell.rb +1 -4
- data/lib/rawline.rb +3 -0
- data/lib/rawline/completer.rb +4 -0
- data/lib/rawline/dom_tree.rb +18 -0
- data/lib/rawline/editor.rb +443 -391
- data/lib/rawline/event_loop.rb +63 -26
- data/lib/rawline/non_blocking_input.rb +34 -0
- data/lib/rawline/renderer.rb +29 -0
- data/lib/rawline/terminal.rb +58 -3
- data/lib/rawline/terminal/vt220_terminal.rb +2 -2
- data/spec/editor_spec.rb +147 -92
- data/spec/spec_helper.rb +2 -0
- metadata +26 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27dea4694161e07942a1244b30298efd3c7473b5
|
4
|
+
data.tar.gz: 483fbc976ef8fbb074aba07df0ade32d47b821b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 757786ae22ee40296c746e29183b7cba37c0011422f97c21b0bc3efc5fa7b736d1e8686fa08534f23deafb7eda52bb448a566ee0e9c818b6c9efcf664a091122
|
7
|
+
data.tar.gz: aa640a935bcbe38c9afe54b8a583a7441673eb879c5e98969e9b692126b9d5b49b6faa5289e6dc39bd7e4b6b9a8ac1ccceba70547f29bb755154d9e7c7721109
|
data/examples/rawline_shell.rb
CHANGED
@@ -4,16 +4,13 @@ require File.dirname(File.expand_path(__FILE__))+'/../lib/rawline'
|
|
4
4
|
require 'io/console'
|
5
5
|
require 'term/ansicolor'
|
6
6
|
|
7
|
-
$z = File.open("/tmp/z.log", "w+")
|
8
|
-
$z.sync = true
|
9
|
-
|
10
7
|
# puts "*** Rawline Editor Test Shell ***"
|
11
8
|
# puts " * Press CTRL+X to exit"
|
12
9
|
# puts " * Press CTRL+G to clear command history"
|
13
10
|
# puts " * Press CTRL+D for line-related information"
|
14
11
|
# puts " * Press CTRL+E to view command history"
|
15
12
|
|
16
|
-
editor = RawLine::Editor.
|
13
|
+
editor = RawLine::Editor.create
|
17
14
|
kill_ring = []
|
18
15
|
|
19
16
|
editor.terminal.keys.merge!(enter: [13])
|
data/lib/rawline.rb
CHANGED
@@ -54,6 +54,9 @@ require "#{dir}/rawline/completer"
|
|
54
54
|
require "#{dir}/rawline/event_loop"
|
55
55
|
require "#{dir}/rawline/event_registry"
|
56
56
|
require "#{dir}/rawline/keycode_parser"
|
57
|
+
require "#{dir}/rawline/non_blocking_input"
|
58
|
+
require "#{dir}/rawline/renderer"
|
59
|
+
require "#{dir}/rawline/dom_tree"
|
57
60
|
require "#{dir}/rawline/editor"
|
58
61
|
|
59
62
|
module RawLine
|
data/lib/rawline/completer.rb
CHANGED
@@ -23,6 +23,10 @@ module RawLine
|
|
23
23
|
def read_bytes(bytes)
|
24
24
|
return unless bytes.any?
|
25
25
|
|
26
|
+
# this is to prevent a series of bytes from coming in at one time
|
27
|
+
# E.g. holding down the tab key or arrow keys
|
28
|
+
bytes = bytes.uniq
|
29
|
+
|
26
30
|
if bytes.map(&:ord) == @keys[:left_arrow]
|
27
31
|
@completion_matches.forward
|
28
32
|
match = @completion_matches.get
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'terminal_layout'
|
2
|
+
|
3
|
+
module RawLine
|
4
|
+
class DomTree < TerminalLayout::Box
|
5
|
+
attr_accessor :prompt_box, :input_box, :content_box
|
6
|
+
|
7
|
+
def initialize(children: nil)
|
8
|
+
unless children
|
9
|
+
@prompt_box = TerminalLayout::Box.new(content: "default-prompt>", style: {display: :inline})
|
10
|
+
@input_box = TerminalLayout::InputBox.new(content: "", style: {display: :inline})
|
11
|
+
@content_box = TerminalLayout::Box.new(content: "", style: {display: :block})
|
12
|
+
super(style: {}, children: [@prompt_box, @input_box, @content_box])
|
13
|
+
else
|
14
|
+
super(style: {}, children: children)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rawline/editor.rb
CHANGED
@@ -46,12 +46,45 @@ module RawLine
|
|
46
46
|
attr_accessor :completion_class, :completion_proc, :line, :history, :completion_append_string
|
47
47
|
attr_accessor :match_hidden_files
|
48
48
|
attr_accessor :word_break_characters
|
49
|
-
attr_reader :output
|
50
49
|
attr_accessor :dom
|
51
50
|
|
52
51
|
# TODO: dom traversal for lookup rather than assignment
|
53
52
|
attr_accessor :prompt_box, :input_box, :content_box
|
54
53
|
|
54
|
+
def self.create(dom: nil, &blk)
|
55
|
+
terminal = nil
|
56
|
+
|
57
|
+
input = STDIN
|
58
|
+
output = STDOUT
|
59
|
+
|
60
|
+
case RUBY_PLATFORM
|
61
|
+
when /mswin/i then
|
62
|
+
terminal = WindowsTerminal.new(input, output)
|
63
|
+
if RawLine.win32console? then
|
64
|
+
win32_io = Win32::Console::ANSI::IO.new
|
65
|
+
end
|
66
|
+
else
|
67
|
+
terminal = VT220Terminal.new(input, output)
|
68
|
+
end
|
69
|
+
|
70
|
+
dom ||= DomTree.new
|
71
|
+
|
72
|
+
renderer = RawLine::Renderer.new(
|
73
|
+
dom: dom,
|
74
|
+
output: terminal.output,
|
75
|
+
width: terminal.width,
|
76
|
+
height: terminal.height
|
77
|
+
)
|
78
|
+
|
79
|
+
new(
|
80
|
+
dom: dom,
|
81
|
+
input: NonBlockingInput.new(input),
|
82
|
+
renderer: renderer,
|
83
|
+
terminal: terminal,
|
84
|
+
&blk
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
55
88
|
#
|
56
89
|
# Create an instance of RawLine::Editor which can be used
|
57
90
|
# to read from input and perform line-editing operations.
|
@@ -66,19 +99,12 @@ module RawLine
|
|
66
99
|
# * <tt>@completion_append_string</tt> - a string to append to completed words ('').
|
67
100
|
# * <tt>@terminal</tt> - a RawLine::Terminal containing character key codes.
|
68
101
|
#
|
69
|
-
def initialize(input
|
102
|
+
def initialize(dom:, input:, renderer:, terminal:)
|
103
|
+
@dom = dom
|
70
104
|
@input = input
|
71
|
-
|
105
|
+
@renderer = renderer
|
106
|
+
@terminal = terminal
|
72
107
|
|
73
|
-
case RUBY_PLATFORM
|
74
|
-
when /mswin/i then
|
75
|
-
@terminal = WindowsTerminal.new
|
76
|
-
if RawLine.win32console? then
|
77
|
-
@win32_io = Win32::Console::ANSI::IO.new
|
78
|
-
end
|
79
|
-
else
|
80
|
-
@terminal = VT220Terminal.new
|
81
|
-
end
|
82
108
|
@history_size = 30
|
83
109
|
@line_history_size = 50
|
84
110
|
@keys = {}
|
@@ -96,27 +122,17 @@ module RawLine
|
|
96
122
|
h.exclude = lambda { |item| item.strip == "" }
|
97
123
|
end
|
98
124
|
@keyboard_input_processors = [self]
|
125
|
+
# @allow_prompt_updates = true
|
99
126
|
yield self if block_given?
|
100
127
|
update_word_separator
|
101
128
|
@char = nil
|
102
129
|
|
103
|
-
|
104
|
-
registry.subscribe :default, -> (_) { self.check_for_keyboard_input }
|
105
|
-
registry.subscribe :dom_tree_change, -> (_) { self.render }
|
106
|
-
end
|
107
|
-
@event_loop = Rawline::EventLoop.new(registry: @event_registry)
|
108
|
-
|
109
|
-
@dom ||= build_dom_tree
|
110
|
-
@renderer ||= build_renderer
|
111
|
-
|
130
|
+
initialize_events
|
112
131
|
initialize_line
|
113
132
|
end
|
114
133
|
|
115
|
-
attr_reader :dom
|
116
|
-
|
117
|
-
def events
|
118
|
-
@event_loop
|
119
|
-
end
|
134
|
+
attr_reader :dom, :event_loop, :input
|
135
|
+
attr_reader :keyboard_input_processors
|
120
136
|
|
121
137
|
#
|
122
138
|
# Return the current RawLine version
|
@@ -126,49 +142,73 @@ module RawLine
|
|
126
142
|
end
|
127
143
|
|
128
144
|
def prompt
|
129
|
-
@
|
145
|
+
@prompt
|
130
146
|
end
|
131
147
|
|
132
148
|
def prompt=(text)
|
133
|
-
return if
|
134
|
-
@
|
149
|
+
return if @line && @line.prompt == text
|
150
|
+
@prompt = Prompt.new(text)
|
151
|
+
@dom.prompt_box.content = @prompt
|
135
152
|
end
|
136
153
|
|
137
|
-
def
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
154
|
+
def redraw_prompt
|
155
|
+
render(reset: true)
|
156
|
+
end
|
157
|
+
|
158
|
+
def terminal_width ; @terminal.width ; end
|
159
|
+
def terminal_height ; @terminal.height ; end
|
160
|
+
|
161
|
+
def content_box ; @dom.content_box ; end
|
162
|
+
def input_box ; @dom.input_box ; end
|
163
|
+
def prompt_box ; @dom.prompt_box ; end
|
164
|
+
|
165
|
+
############################################################################
|
166
|
+
#
|
167
|
+
# EVENTS
|
168
|
+
#
|
169
|
+
############################################################################
|
170
|
+
|
171
|
+
# Starts the editor event loop. Must be called before the editor
|
172
|
+
# can be interacted with.
|
173
|
+
def start
|
174
|
+
@terminal.raw!
|
175
|
+
at_exit { @terminal.cooked! }
|
176
|
+
|
177
|
+
Signal.trap("SIGWINCH") do
|
178
|
+
@event_loop.add_event name: "terminal-resized", source: self
|
144
179
|
end
|
145
|
-
|
146
|
-
@
|
180
|
+
|
181
|
+
@event_registry.subscribe("terminal-resized") do
|
182
|
+
@renderer.update_dimensions(width: terminal_width, height: terminal_height)
|
183
|
+
@event_loop.add_event name: "render", source: self
|
184
|
+
end
|
185
|
+
|
186
|
+
@event_loop.add_event name: "render", source: self
|
187
|
+
@event_loop.start
|
147
188
|
end
|
148
189
|
|
149
|
-
|
150
|
-
|
151
|
-
|
190
|
+
# Subscribes to an event with the given block as a callback.
|
191
|
+
def subscribe(*args, &blk)
|
192
|
+
@event_registry.subscribe(*args, &blk)
|
152
193
|
end
|
153
194
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
loop do
|
159
|
-
string = @input.read_nonblock(4096)
|
160
|
-
bytes.concat string.bytes
|
161
|
-
end
|
162
|
-
rescue IO::WaitReadable
|
163
|
-
# reset flags so O_NONBLOCK is turned off on the file descriptor
|
164
|
-
# if it was turned on during the read_nonblock above
|
165
|
-
retry if IO.select([@input], [], [], 0.01)
|
195
|
+
# Returns the Editor's event loop.
|
196
|
+
def events
|
197
|
+
@event_loop
|
198
|
+
end
|
166
199
|
|
167
|
-
|
168
|
-
|
200
|
+
############################################################################
|
201
|
+
#
|
202
|
+
# INPUT
|
203
|
+
#
|
204
|
+
############################################################################
|
169
205
|
|
170
|
-
|
206
|
+
def check_for_keyboard_input
|
207
|
+
bytes = @input.read
|
208
|
+
if bytes.any?
|
209
|
+
@keyboard_input_processors.last.read_bytes(bytes)
|
171
210
|
end
|
211
|
+
@event_loop.add_event name: 'check_for_keyboard_input', source: self
|
172
212
|
end
|
173
213
|
|
174
214
|
def read_bytes(bytes)
|
@@ -187,68 +227,23 @@ module RawLine
|
|
187
227
|
|
188
228
|
@ignore_position_change = false
|
189
229
|
if @char == @terminal.keys[:enter] || !@char
|
190
|
-
|
191
|
-
move_to_beginning_of_input
|
192
|
-
@event_loop.add_event name: "line_read", source: self, payload: { line: @line.text.without_ansi.dup }
|
230
|
+
process_line
|
193
231
|
end
|
194
232
|
end
|
195
233
|
end
|
196
234
|
|
197
|
-
def
|
198
|
-
@
|
199
|
-
|
200
|
-
|
201
|
-
def start
|
202
|
-
@input.raw!
|
203
|
-
at_exit { @input.cooked! }
|
204
|
-
|
205
|
-
Signal.trap("SIGWINCH") do
|
206
|
-
@event_loop.add_event name: "terminal-resized", source: self
|
207
|
-
end
|
235
|
+
def process_line
|
236
|
+
@event_loop.add_event(name: "process_line", source: self) do
|
237
|
+
@terminal.snapshot_tty_attrs
|
238
|
+
@terminal.pseudo_cooked!
|
208
239
|
|
209
|
-
|
210
|
-
@
|
211
|
-
@render_tree.height = terminal_height
|
212
|
-
@event_loop.add_event name: "render", source: self
|
240
|
+
move_to_beginning_of_input
|
241
|
+
@terminal.puts
|
213
242
|
end
|
214
243
|
|
215
|
-
@event_loop.add_event name: "
|
216
|
-
@event_loop.
|
217
|
-
|
218
|
-
|
219
|
-
def subscribe(*args, &blk)
|
220
|
-
@event_registry.subscribe(*args, &blk)
|
221
|
-
end
|
222
|
-
|
223
|
-
#
|
224
|
-
# Parse a key or key sequence into the corresponding codes.
|
225
|
-
#
|
226
|
-
def parse_key_codes(bytes)
|
227
|
-
KeycodeParser.new(@terminal.keys).parse_bytes(bytes)
|
228
|
-
end
|
229
|
-
|
230
|
-
#
|
231
|
-
# Write a string to <tt># @output</tt> starting from the cursor position.
|
232
|
-
# Characters at the right of the cursor are shifted to the right if
|
233
|
-
# <tt>@mode == :insert</tt>, deleted otherwise.
|
234
|
-
#
|
235
|
-
def write(string)
|
236
|
-
string.each_byte { |c| print_character c, true }
|
237
|
-
add_to_line_history
|
238
|
-
end
|
239
|
-
|
240
|
-
#
|
241
|
-
# Write a new line to <tt># @output</tt>, overwriting any existing text
|
242
|
-
# and printing an end of line character.
|
243
|
-
#
|
244
|
-
def write_line(string)
|
245
|
-
clear_line
|
246
|
-
# @output.print string
|
247
|
-
@line.text = string
|
248
|
-
@input_box.position = @line.position
|
249
|
-
add_to_line_history
|
250
|
-
add_to_history
|
251
|
-
@char = nil
|
244
|
+
@event_loop.add_event name: "line_read", source: self, payload: { line: @line.text.without_ansi.dup }
|
245
|
+
@event_loop.add_event(name: "restore_tty_attrs", source: self) { @terminal.restore_tty_attrs }
|
246
|
+
@event_loop.add_event name: "render", source: self, payload: { reset: true }
|
252
247
|
end
|
253
248
|
|
254
249
|
#
|
@@ -308,6 +303,29 @@ module RawLine
|
|
308
303
|
@terminal.update
|
309
304
|
end
|
310
305
|
|
306
|
+
def unbind(key)
|
307
|
+
block = case key.class.to_s
|
308
|
+
when 'Symbol' then
|
309
|
+
@keys.delete @terminal.keys[key]
|
310
|
+
when 'Array' then
|
311
|
+
@keys.delete @keys[key]
|
312
|
+
when 'Fixnum' then
|
313
|
+
@keys.delete[[key]]
|
314
|
+
when 'String' then
|
315
|
+
if key.length == 1 then
|
316
|
+
@keys.delete([key.ord])
|
317
|
+
else
|
318
|
+
raise NotImplementedError, "This is no implemented yet. It needs to return the previously bound block"
|
319
|
+
bind_hash({:"#{key}" => key}, block)
|
320
|
+
end
|
321
|
+
when 'Hash' then
|
322
|
+
raise BindingException, "Cannot bind more than one key or key sequence at once" unless key.values.length == 1
|
323
|
+
bind_hash(key, -> { })
|
324
|
+
end
|
325
|
+
@terminal.update
|
326
|
+
block
|
327
|
+
end
|
328
|
+
|
311
329
|
#
|
312
330
|
# Return true if the last character read via <tt>read</tt> is bound to an action.
|
313
331
|
#
|
@@ -324,17 +342,238 @@ module RawLine
|
|
324
342
|
end
|
325
343
|
|
326
344
|
#
|
327
|
-
# Execute the default action for the last character read via <tt>read</tt>.
|
328
|
-
# By default it prints the character to the screen via <tt>print_character</tt>.
|
329
|
-
# This method is called automatically by <tt>process_character</tt>.
|
345
|
+
# Execute the default action for the last character read via <tt>read</tt>.
|
346
|
+
# By default it prints the character to the screen via <tt>print_character</tt>.
|
347
|
+
# This method is called automatically by <tt>process_character</tt>.
|
348
|
+
#
|
349
|
+
def default_action
|
350
|
+
@dom.input_box.content += @char.chr
|
351
|
+
print_character
|
352
|
+
end
|
353
|
+
|
354
|
+
#
|
355
|
+
# Parse a key or key sequence into the corresponding codes.
|
356
|
+
#
|
357
|
+
def parse_key_codes(bytes)
|
358
|
+
KeycodeParser.new(@terminal.keys).parse_bytes(bytes)
|
359
|
+
end
|
360
|
+
|
361
|
+
#
|
362
|
+
# Adds <tt>@line.text</tt> to the editor history. This action is
|
363
|
+
# bound to the enter key by default.
|
364
|
+
#
|
365
|
+
def newline
|
366
|
+
add_to_history
|
367
|
+
@history.clear_position
|
368
|
+
end
|
369
|
+
|
370
|
+
def on_read_line(&blk)
|
371
|
+
@event_registry.subscribe :line_read, &blk
|
372
|
+
end
|
373
|
+
|
374
|
+
############################################################################
|
375
|
+
#
|
376
|
+
# LINE EDITING
|
377
|
+
#
|
378
|
+
############################################################################
|
379
|
+
|
380
|
+
#
|
381
|
+
# Clear the current line, i.e.
|
382
|
+
# <tt>@line.text</tt> and <tt>@line.position</tt>.
|
383
|
+
# This action is bound to ctrl+k by default.
|
384
|
+
#
|
385
|
+
def clear_line
|
386
|
+
add_to_line_history
|
387
|
+
@line.text = ""
|
388
|
+
@line.position = 0
|
389
|
+
@dom.input_box.position = @line.position
|
390
|
+
@history.clear_position
|
391
|
+
end
|
392
|
+
|
393
|
+
def clear_screen
|
394
|
+
@terminal.clear_screen
|
395
|
+
render(reset: true)
|
396
|
+
end
|
397
|
+
|
398
|
+
#
|
399
|
+
# Delete the character at the left of the cursor.
|
400
|
+
# If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
|
401
|
+
# recorded in the line history.
|
402
|
+
# This action is bound to the backspace key by default.
|
403
|
+
#
|
404
|
+
def delete_left_character(no_line_history=false)
|
405
|
+
if move_left then
|
406
|
+
delete_character(no_line_history)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def delete_n_characters(number_of_characters_to_delete, no_line_history=false)
|
411
|
+
number_of_characters_to_delete.times do |n|
|
412
|
+
@line[@line.position] = ''
|
413
|
+
@line.left
|
414
|
+
end
|
415
|
+
|
416
|
+
@dom.input_box.position = @line.position
|
417
|
+
@dom.input_box.content = @line.text
|
418
|
+
add_to_line_history unless no_line_history
|
419
|
+
@history.clear_position
|
420
|
+
end
|
421
|
+
|
422
|
+
#
|
423
|
+
# Delete the character under the cursor.
|
424
|
+
# If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
|
425
|
+
# recorded in the line history.
|
426
|
+
# This action is bound to the delete key by default.
|
427
|
+
#
|
428
|
+
def delete_character(no_line_history=false)
|
429
|
+
unless @line.position > @line.eol
|
430
|
+
# save characters to shift
|
431
|
+
chars = (@line.eol?) ? ' ' : select_characters_from_cursor(1)
|
432
|
+
#remove character from line
|
433
|
+
@line[@line.position] = ''
|
434
|
+
@dom.input_box.content = @line.text
|
435
|
+
@dom.input_box.position = @line.position
|
436
|
+
add_to_line_history unless no_line_history
|
437
|
+
@history.clear_position
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def highlight_text_up_to(text, position)
|
442
|
+
ANSIString.new("\e[1m#{text[0...position]}\e[0m#{text[position..-1]}")
|
443
|
+
end
|
444
|
+
|
445
|
+
def kill_forward
|
446
|
+
killed_text = @line.text[@line.position..-1]
|
447
|
+
@line.text[@line.position..-1] = ANSIString.new("")
|
448
|
+
@dom.input_box.content = line.text
|
449
|
+
@dom.input_box.position = @line.position
|
450
|
+
@history.clear_position
|
451
|
+
killed_text
|
452
|
+
end
|
453
|
+
|
454
|
+
def yank_forward(text)
|
455
|
+
@line.text[line.position] = text
|
456
|
+
@line.position = line.position + text.length
|
457
|
+
@dom.input_box.content = line.text
|
458
|
+
@dom.input_box.position = @line.position
|
459
|
+
@history.clear_position
|
460
|
+
end
|
461
|
+
|
462
|
+
#
|
463
|
+
# Move the cursor left (if possible) by printing a
|
464
|
+
# backspace, updating <tt>@line.position</tt> accordingly.
|
465
|
+
# This action is bound to the left arrow key by default.
|
466
|
+
#
|
467
|
+
def move_left
|
468
|
+
unless @line.bol? then
|
469
|
+
@line.left
|
470
|
+
@dom.input_box.position = @line.position
|
471
|
+
return true
|
472
|
+
end
|
473
|
+
false
|
474
|
+
end
|
475
|
+
|
476
|
+
#
|
477
|
+
# Move the cursor right (if possible) by re-printing the
|
478
|
+
# character at the right of the cursor, if any, and updating
|
479
|
+
# <tt>@line.position</tt> accordingly.
|
480
|
+
# This action is bound to the right arrow key by default.
|
481
|
+
#
|
482
|
+
def move_right
|
483
|
+
unless @line.position > @line.eol then
|
484
|
+
@line.right
|
485
|
+
@dom.input_box.position = @line.position
|
486
|
+
return true
|
487
|
+
end
|
488
|
+
false
|
489
|
+
end
|
490
|
+
|
491
|
+
def move_to_beginning_of_input
|
492
|
+
@line.position = @line.bol
|
493
|
+
@dom.input_box.position = @line.position
|
494
|
+
end
|
495
|
+
|
496
|
+
def move_to_end_of_input
|
497
|
+
@line.position = @line.length
|
498
|
+
@dom.input_box.position = @line.position
|
499
|
+
end
|
500
|
+
|
501
|
+
#
|
502
|
+
# Move the cursor to <tt>pos</tt>.
|
503
|
+
#
|
504
|
+
def move_to_position(pos)
|
505
|
+
rows_to_move = current_terminal_row - terminal_row_for_line_position(pos)
|
506
|
+
if rows_to_move > 0
|
507
|
+
# rows_to_move.times { @output.print @terminal.term_info.control_string("cuu1") }
|
508
|
+
# @terminal.move_up_n_rows(rows_to_move)
|
509
|
+
else
|
510
|
+
# rows_to_move.abs.times { @output.print @terminal.term_info.control_string("cud1") }
|
511
|
+
# @terminal.move_down_n_rows(rows_to_move.abs)
|
512
|
+
end
|
513
|
+
column = (@line.prompt.length + pos) % terminal_width
|
514
|
+
# @output.print @terminal.term_info.control_string("hpa", column)
|
515
|
+
# @terminal.move_to_column((@line.prompt.length + pos) % terminal_width)
|
516
|
+
@line.position = pos
|
517
|
+
@dom.input_box.position = @line.position
|
518
|
+
end
|
519
|
+
|
520
|
+
def move_to_end_of_line
|
521
|
+
rows_to_move_down = number_of_terminal_rows - current_terminal_row
|
522
|
+
# rows_to_move_down.times { @output.print @terminal.term_info.control_string("cud1") }
|
523
|
+
# @terminal.move_down_n_rows rows_to_move_down
|
524
|
+
@line.position = @line.length
|
525
|
+
@dom.input_box.position = @line.position
|
526
|
+
|
527
|
+
column = (@line.prompt.length + @line.position) % terminal_width
|
528
|
+
# @output.print @terminal.term_info.control_string("hpa", column)
|
529
|
+
# @terminal.move_to_column((@line.prompt.length + @line.position) % terminal_width)
|
530
|
+
end
|
531
|
+
|
532
|
+
#
|
533
|
+
# Overwrite the current line (<tt>@line.text</tt>)
|
534
|
+
# with <tt>new_line</tt>, and optionally reset the cursor position to
|
535
|
+
# <tt>position</tt>.
|
536
|
+
#
|
537
|
+
def overwrite_line(new_line, position=nil, options={})
|
538
|
+
text = @line.text
|
539
|
+
@highlighting = false
|
540
|
+
|
541
|
+
if options[:highlight_up_to]
|
542
|
+
@highlighting = true
|
543
|
+
new_line = highlight_text_up_to(new_line, options[:highlight_up_to])
|
544
|
+
end
|
545
|
+
|
546
|
+
@ignore_position_change = true
|
547
|
+
@line.position = position || new_line.length
|
548
|
+
@line.text = new_line
|
549
|
+
@dom.input_box.content = @line.text
|
550
|
+
@dom.input_box.position = @line.position
|
551
|
+
@event_loop.add_event name: "render", source: @dom.input_box
|
552
|
+
end
|
553
|
+
|
554
|
+
def reset_line
|
555
|
+
initialize_line
|
556
|
+
render(reset: true)
|
557
|
+
end
|
558
|
+
|
559
|
+
#
|
560
|
+
# Toggle the editor <tt>@mode</tt> to :replace or :insert (default).
|
330
561
|
#
|
331
|
-
def
|
332
|
-
|
333
|
-
|
562
|
+
def toggle_mode
|
563
|
+
case @mode
|
564
|
+
when :insert then @mode = :replace
|
565
|
+
when :replace then @mode = :insert
|
566
|
+
end
|
334
567
|
end
|
335
568
|
|
569
|
+
############################################################################
|
336
570
|
#
|
337
|
-
#
|
571
|
+
# OUTPUT
|
572
|
+
#
|
573
|
+
############################################################################
|
574
|
+
|
575
|
+
#
|
576
|
+
# Write a character to <tt>output</tt> at cursor position,
|
338
577
|
# shifting characters as appropriate.
|
339
578
|
# If <tt>no_line_history</tt> is set to <tt>true</tt>, the updated
|
340
579
|
# won't be saved in the history of the current line.
|
@@ -344,7 +583,7 @@ module RawLine
|
|
344
583
|
chars = select_characters_from_cursor if @mode == :insert
|
345
584
|
@line.text[@line.position] = (@mode == :insert) ? "#{char.chr}#{@line.text[@line.position]}" : "#{char.chr}"
|
346
585
|
@line.right
|
347
|
-
@input_box.position = @line.position
|
586
|
+
@dom.input_box.position = @line.position
|
348
587
|
# if @mode == :insert then
|
349
588
|
# chars.length.times { @line.left } # move cursor back
|
350
589
|
# end
|
@@ -352,11 +591,37 @@ module RawLine
|
|
352
591
|
@line.right
|
353
592
|
@line << char
|
354
593
|
end
|
355
|
-
@input_box.content = @line.text
|
356
|
-
@input_box.position = @line.position
|
594
|
+
@dom.input_box.content = @line.text
|
595
|
+
@dom.input_box.position = @line.position
|
357
596
|
add_to_line_history unless no_line_history
|
358
597
|
end
|
359
598
|
|
599
|
+
#
|
600
|
+
# Write to <tt>output</tt> and then immediately re-render.
|
601
|
+
#
|
602
|
+
def puts(*args)
|
603
|
+
@terminal.puts(*args)
|
604
|
+
render(reset: true)
|
605
|
+
end
|
606
|
+
|
607
|
+
#
|
608
|
+
# Write a string starting from the cursor position.
|
609
|
+
#
|
610
|
+
def write(string)
|
611
|
+
@line.text[@line.position] = string
|
612
|
+
string.length.times { @line.right }
|
613
|
+
@dom.input_box.position = @line.position
|
614
|
+
@dom.input_box.content = @line.text
|
615
|
+
|
616
|
+
add_to_line_history
|
617
|
+
end
|
618
|
+
|
619
|
+
############################################################################
|
620
|
+
#
|
621
|
+
# COMPLETION
|
622
|
+
#
|
623
|
+
############################################################################
|
624
|
+
|
360
625
|
#
|
361
626
|
# Complete the current word according to what returned by
|
362
627
|
# <tt>@completion_proc</tt>. Characters can be appended to the
|
@@ -369,6 +634,7 @@ module RawLine
|
|
369
634
|
# pressed again.
|
370
635
|
#
|
371
636
|
def complete
|
637
|
+
@dom.input_box.cursor_off
|
372
638
|
completer = @completion_class.new(
|
373
639
|
char: @char,
|
374
640
|
line: @line,
|
@@ -386,6 +652,7 @@ module RawLine
|
|
386
652
|
if leftover_bytes.any?
|
387
653
|
@keyboard_input_processors.last.read_bytes(leftover_bytes)
|
388
654
|
end
|
655
|
+
@dom.input_box.cursor_on
|
389
656
|
},
|
390
657
|
keys: terminal.keys
|
391
658
|
)
|
@@ -400,11 +667,8 @@ module RawLine
|
|
400
667
|
@on_word_complete.call(name: "word-completion", payload: { sub_word: sub_word, word: word, completion: completion, possible_completions: possible_completions })
|
401
668
|
end
|
402
669
|
|
403
|
-
|
404
|
-
|
405
|
-
move_to_position(@line.word[:end]+@completion_append_string.to_s.length+1)
|
406
|
-
end
|
407
|
-
(@line.position-@line.word[:start]).times { delete_left_character(true) }
|
670
|
+
move_to_position @line.word[:end]
|
671
|
+
delete_n_characters(@line.word[:end] - @line.word[:start], true)
|
408
672
|
write completion.to_s + @completion_append_string.to_s
|
409
673
|
end
|
410
674
|
|
@@ -452,63 +716,11 @@ module RawLine
|
|
452
716
|
end
|
453
717
|
end
|
454
718
|
|
455
|
-
|
456
|
-
#
|
457
|
-
# Adds <tt>@line.text</tt> to the editor history. This action is
|
458
|
-
# bound to the enter key by default.
|
459
|
-
#
|
460
|
-
def newline
|
461
|
-
add_to_history
|
462
|
-
@history.clear_position
|
463
|
-
end
|
464
|
-
|
465
|
-
#
|
466
|
-
# Move the cursor left (if possible) by printing a
|
467
|
-
# backspace, updating <tt>@line.position</tt> accordingly.
|
468
|
-
# This action is bound to the left arrow key by default.
|
469
|
-
#
|
470
|
-
def move_left
|
471
|
-
unless @line.bol? then
|
472
|
-
@line.left
|
473
|
-
@input_box.position = @line.position
|
474
|
-
return true
|
475
|
-
end
|
476
|
-
false
|
477
|
-
end
|
478
|
-
|
479
|
-
#
|
480
|
-
# Move the cursor right (if possible) by re-printing the
|
481
|
-
# character at the right of the cursor, if any, and updating
|
482
|
-
# <tt>@line.position</tt> accordingly.
|
483
|
-
# This action is bound to the right arrow key by default.
|
484
|
-
#
|
485
|
-
def move_right
|
486
|
-
unless @line.position > @line.eol then
|
487
|
-
@line.right
|
488
|
-
@input_box.position = @line.position
|
489
|
-
return true
|
490
|
-
end
|
491
|
-
false
|
492
|
-
end
|
493
|
-
|
719
|
+
############################################################################
|
494
720
|
#
|
495
|
-
#
|
496
|
-
# the message is displayed, the line text and position will be restored.
|
721
|
+
# HISTORY
|
497
722
|
#
|
498
|
-
|
499
|
-
pos = @line.position
|
500
|
-
text = @line.text
|
501
|
-
word = @line.word
|
502
|
-
# @output.puts
|
503
|
-
# @output.puts "Text: [#{text}]"
|
504
|
-
# @output.puts "Length: #{@line.length}"
|
505
|
-
# @output.puts "Position: #{pos}"
|
506
|
-
# @output.puts "Character at Position: [#{text[pos].chr}] (#{text[pos]})" unless pos >= @line.length
|
507
|
-
# @output.puts "Current Word: [#{word[:text]}] (#{word[:start]} -- #{word[:end]})"
|
508
|
-
clear_line
|
509
|
-
raw_print text
|
510
|
-
overwrite_line(text, pos)
|
511
|
-
end
|
723
|
+
############################################################################
|
512
724
|
|
513
725
|
#
|
514
726
|
# Print the content of the editor history. Note that after
|
@@ -517,8 +729,6 @@ module RawLine
|
|
517
729
|
def show_history
|
518
730
|
pos = @line.position
|
519
731
|
text = @line.text
|
520
|
-
# @output.puts
|
521
|
-
# @output.puts "History:"
|
522
732
|
@history.each {|l| puts "- [#{l}]"}
|
523
733
|
overwrite_line(text, pos)
|
524
734
|
end
|
@@ -530,68 +740,6 @@ module RawLine
|
|
530
740
|
@history.empty
|
531
741
|
end
|
532
742
|
|
533
|
-
#
|
534
|
-
# Delete the character at the left of the cursor.
|
535
|
-
# If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
|
536
|
-
# recorded in the line history.
|
537
|
-
# This action is bound to the backspace key by default.
|
538
|
-
#
|
539
|
-
def delete_left_character(no_line_history=false)
|
540
|
-
if move_left then
|
541
|
-
delete_character(no_line_history)
|
542
|
-
end
|
543
|
-
end
|
544
|
-
|
545
|
-
#
|
546
|
-
# Delete the character under the cursor.
|
547
|
-
# If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
|
548
|
-
# recorded in the line history.
|
549
|
-
# This action is bound to the delete key by default.
|
550
|
-
#
|
551
|
-
def delete_character(no_line_history=false)
|
552
|
-
unless @line.position > @line.eol
|
553
|
-
# save characters to shift
|
554
|
-
chars = (@line.eol?) ? ' ' : select_characters_from_cursor(1)
|
555
|
-
# remove character from console and shift characters
|
556
|
-
# (chars.length+1).times { # @output.putc ?\b.ord }
|
557
|
-
#remove character from line
|
558
|
-
@line[@line.position] = ''
|
559
|
-
@input_box.content = @line.text
|
560
|
-
@input_box.position = @line.position
|
561
|
-
add_to_line_history unless no_line_history
|
562
|
-
end
|
563
|
-
end
|
564
|
-
|
565
|
-
#
|
566
|
-
# Clear the current line, i.e.
|
567
|
-
# <tt>@line.text</tt> and <tt>@line.position</tt>.
|
568
|
-
# This action is bound to ctrl+k by default.
|
569
|
-
#
|
570
|
-
def clear_line
|
571
|
-
# @output.putc ?\r
|
572
|
-
# @output.print @line.prompt
|
573
|
-
# @line.length.times { @output.putc ?\s.ord }
|
574
|
-
# @line.length.times { @output.putc ?\b.ord }
|
575
|
-
add_to_line_history
|
576
|
-
@line.text = ""
|
577
|
-
@line.position = 0
|
578
|
-
@input_box.position = @line.position
|
579
|
-
@history.clear_position
|
580
|
-
end
|
581
|
-
|
582
|
-
def clear_screen
|
583
|
-
# @output.print @terminal.term_info.control_string("clear")
|
584
|
-
# @terminal.clear_screen
|
585
|
-
# @output.print @line.prompt
|
586
|
-
# @output.print @line.text
|
587
|
-
# (@line.length - @line.position).times { @output.putc ?\b.ord }
|
588
|
-
end
|
589
|
-
|
590
|
-
def clear_screen_down
|
591
|
-
# @output.print @terminal.term_info.control_string("ed")
|
592
|
-
# @terminal.clear_screen_down
|
593
|
-
end
|
594
|
-
|
595
743
|
#
|
596
744
|
# Undo the last modification to the current line (<tt>@line.text</tt>).
|
597
745
|
# This action is bound to ctrl+z by default.
|
@@ -646,158 +794,67 @@ module RawLine
|
|
646
794
|
@history << @line.text.dup if @add_history && @line.text != ""
|
647
795
|
end
|
648
796
|
|
797
|
+
############################################################################
|
649
798
|
#
|
650
|
-
#
|
799
|
+
# DEBUGGING
|
651
800
|
#
|
652
|
-
|
653
|
-
case @mode
|
654
|
-
when :insert then @mode = :replace
|
655
|
-
when :replace then @mode = :insert
|
656
|
-
end
|
657
|
-
end
|
658
|
-
|
659
|
-
def terminal_row_for_line_position(line_position)
|
660
|
-
((@line.prompt.length + line_position) / terminal_width.to_f).ceil
|
661
|
-
end
|
662
|
-
|
663
|
-
def current_terminal_row
|
664
|
-
((@line.position + @line.prompt.length + 1) / terminal_width.to_f).ceil
|
665
|
-
end
|
666
|
-
|
667
|
-
def number_of_terminal_rows
|
668
|
-
((@line.length + @line.prompt.length) / terminal_width.to_f).ceil
|
669
|
-
end
|
670
|
-
|
671
|
-
def kill_forward
|
672
|
-
@line.text[@line.position..-1].tap do
|
673
|
-
@line[line.position..-1] = ""
|
674
|
-
@input_box.content = line.text
|
675
|
-
@input_box.position = @line.position
|
676
|
-
end
|
677
|
-
end
|
678
|
-
|
679
|
-
def yank_forward(text)
|
680
|
-
@line.text[line.position] = text
|
681
|
-
@line.position = line.position + text.length
|
682
|
-
@input_box.content = line.text
|
683
|
-
@input_box.position = @line.position
|
684
|
-
end
|
801
|
+
############################################################################
|
685
802
|
|
686
803
|
#
|
687
|
-
#
|
688
|
-
#
|
689
|
-
# <tt>position</tt>.
|
804
|
+
# Print debug information about the current line. Note that after
|
805
|
+
# the message is displayed, the line text and position will be restored.
|
690
806
|
#
|
691
|
-
def
|
807
|
+
def debug_line
|
808
|
+
pos = @line.position
|
692
809
|
text = @line.text
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
@line.text = new_line
|
703
|
-
@input_box.content = @line.text
|
704
|
-
@input_box.position = @line.position
|
705
|
-
@event_loop.add_event name: "render", source: @input_box
|
706
|
-
end
|
707
|
-
|
708
|
-
def highlight_text_up_to(text, position)
|
709
|
-
ANSIString.new("\e[1m#{text[0...position]}\e[0m#{text[position..-1]}")
|
710
|
-
end
|
711
|
-
|
712
|
-
def move_to_beginning_of_input
|
713
|
-
@line.position = @line.bol
|
714
|
-
@input_box.position = @line.position
|
715
|
-
end
|
716
|
-
|
717
|
-
def move_to_end_of_input
|
718
|
-
@line.position = @line.length
|
719
|
-
@input_box.position = @line.position
|
720
|
-
end
|
721
|
-
|
722
|
-
#
|
723
|
-
# Move the cursor to <tt>pos</tt>.
|
724
|
-
#
|
725
|
-
def move_to_position(pos)
|
726
|
-
rows_to_move = current_terminal_row - terminal_row_for_line_position(pos)
|
727
|
-
if rows_to_move > 0
|
728
|
-
# rows_to_move.times { @output.print @terminal.term_info.control_string("cuu1") }
|
729
|
-
# @terminal.move_up_n_rows(rows_to_move)
|
730
|
-
else
|
731
|
-
# rows_to_move.abs.times { @output.print @terminal.term_info.control_string("cud1") }
|
732
|
-
# @terminal.move_down_n_rows(rows_to_move.abs)
|
733
|
-
end
|
734
|
-
column = (@line.prompt.length + pos) % terminal_width
|
735
|
-
# @output.print @terminal.term_info.control_string("hpa", column)
|
736
|
-
# @terminal.move_to_column((@line.prompt.length + pos) % terminal_width)
|
737
|
-
@line.position = pos
|
738
|
-
@input_box.position = @line.position
|
739
|
-
end
|
740
|
-
|
741
|
-
def move_to_end_of_line
|
742
|
-
rows_to_move_down = number_of_terminal_rows - current_terminal_row
|
743
|
-
# rows_to_move_down.times { @output.print @terminal.term_info.control_string("cud1") }
|
744
|
-
# @terminal.move_down_n_rows rows_to_move_down
|
745
|
-
@line.position = @line.length
|
746
|
-
@input_box.position = @line.position
|
747
|
-
|
748
|
-
column = (@line.prompt.length + @line.position) % terminal_width
|
749
|
-
# @output.print @terminal.term_info.control_string("hpa", column)
|
750
|
-
# @terminal.move_to_column((@line.prompt.length + @line.position) % terminal_width)
|
751
|
-
end
|
752
|
-
|
753
|
-
def move_up_n_lines(n)
|
754
|
-
# n.times { @output.print @terminal.term_info.control_string("cuu1") }
|
755
|
-
# @terminal.move_up_n_rows(n)
|
756
|
-
end
|
757
|
-
|
758
|
-
def move_down_n_lines(n)
|
759
|
-
# n.times { @output.print @terminal.term_info.control_string("cud1") }
|
760
|
-
# @terminal.move_down_n_rows(n)
|
810
|
+
word = @line.word
|
811
|
+
# terminal.puts
|
812
|
+
# terminal.puts "Text: [#{text}]"
|
813
|
+
# terminal.puts "Length: #{@line.length}"
|
814
|
+
# terminal.puts "Position: #{pos}"
|
815
|
+
# terminal.puts "Character at Position: [#{text[pos].chr}] (#{text[pos]})" unless pos >= @line.length
|
816
|
+
# terminal.puts "Current Word: [#{word[:text]}] (#{word[:start]} -- #{word[:end]})"
|
817
|
+
clear_line
|
818
|
+
overwrite_line(text, pos)
|
761
819
|
end
|
762
820
|
|
763
821
|
private
|
764
822
|
|
765
|
-
def
|
766
|
-
@
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
def build_renderer
|
773
|
-
@renderer = TerminalLayout::TerminalRenderer.new(output: $stdout)
|
774
|
-
@render_tree = TerminalLayout::RenderTree.new(
|
775
|
-
@dom,
|
776
|
-
parent: nil,
|
777
|
-
style: { width:terminal_width, height:terminal_height },
|
778
|
-
renderer: @renderer
|
779
|
-
)
|
823
|
+
def initialize_events
|
824
|
+
@event_registry = Rawline::EventRegistry.new do |registry|
|
825
|
+
registry.subscribe :default, -> (_) { self.check_for_keyboard_input }
|
826
|
+
registry.subscribe :dom_tree_change, -> (_) { self.render }
|
827
|
+
end
|
828
|
+
@event_loop = Rawline::EventLoop.new(registry: @event_registry)
|
780
829
|
|
781
830
|
@dom.on(:child_changed) do |*args|
|
782
831
|
@event_loop.add_event name: "render", source: @dom#, target: event[:target]
|
783
832
|
end
|
784
833
|
|
785
834
|
@dom.on :cursor_position_changed do |*args|
|
786
|
-
@renderer.render_cursor(@input_box)
|
835
|
+
@renderer.render_cursor(@dom.input_box)
|
787
836
|
end
|
788
837
|
|
789
838
|
@event_registry.subscribe :render, -> (_) { render(reset: false) }
|
790
|
-
|
791
|
-
@renderer
|
792
839
|
end
|
793
840
|
|
794
841
|
def render(reset: false)
|
795
|
-
@
|
796
|
-
@renderer.reset if reset
|
797
|
-
@renderer.render(@render_tree)
|
842
|
+
@renderer.render(reset: reset)
|
798
843
|
@event_loop.add_event name: "check_for_keyboard_input"
|
799
844
|
end
|
800
845
|
|
846
|
+
def initialize_line
|
847
|
+
@dom.input_box.content = ""
|
848
|
+
update_word_separator
|
849
|
+
@add_history = true #add_history
|
850
|
+
@line = Line.new(@line_history_size) do |l|
|
851
|
+
l.prompt = @dom.prompt_box.content
|
852
|
+
l.word_separator = @word_separator
|
853
|
+
end
|
854
|
+
add_to_line_history
|
855
|
+
@allow_prompt_updates = true
|
856
|
+
end
|
857
|
+
|
801
858
|
def update_word_separator
|
802
859
|
return @word_separator = "" if @word_break_characters.to_s == ""
|
803
860
|
chars = []
|
@@ -832,10 +889,6 @@ module RawLine
|
|
832
889
|
select_characters(:right, @line.length-@line.position, offset)
|
833
890
|
end
|
834
891
|
|
835
|
-
def raw_print(string)
|
836
|
-
# string.each_byte { |c| @output.putc c }
|
837
|
-
end
|
838
|
-
|
839
892
|
def generic_history_back(history)
|
840
893
|
unless history.empty?
|
841
894
|
history.back(matching_text: matching_text)
|
@@ -881,6 +934,7 @@ module RawLine
|
|
881
934
|
end
|
882
935
|
|
883
936
|
def set_default_keys
|
937
|
+
bind(:space) { write(' ') }
|
884
938
|
bind(:enter) { newline }
|
885
939
|
bind(:tab) { complete }
|
886
940
|
bind(:backspace) { delete_left_character }
|
@@ -905,33 +959,31 @@ module RawLine
|
|
905
959
|
@matching_text = @line[0...@line.position]
|
906
960
|
end
|
907
961
|
end
|
962
|
+
|
963
|
+
def terminal_row_for_line_position(line_position)
|
964
|
+
((@line.prompt.length + line_position) / terminal_width.to_f).ceil
|
965
|
+
end
|
966
|
+
|
967
|
+
def current_terminal_row
|
968
|
+
((@line.position + @line.prompt.length + 1) / terminal_width.to_f).ceil
|
969
|
+
end
|
970
|
+
|
971
|
+
def number_of_terminal_rows
|
972
|
+
((@line.length + @line.prompt.length) / terminal_width.to_f).ceil
|
973
|
+
end
|
908
974
|
end
|
909
975
|
|
910
976
|
if RawLine.ansi? then
|
911
|
-
|
912
977
|
class Editor
|
913
|
-
|
914
978
|
if RUBY_PLATFORM.match(/mswin/) && RawLine.win32console? then
|
915
979
|
def escape(string)
|
916
980
|
string.each_byte { |c| @win32_io.putc c }
|
917
981
|
end
|
918
982
|
else
|
919
983
|
def escape(string)
|
920
|
-
# @
|
984
|
+
# @terminal.print string
|
921
985
|
end
|
922
986
|
end
|
923
|
-
|
924
|
-
def terminal_width
|
925
|
-
terminal_size[0]
|
926
|
-
end
|
927
|
-
|
928
|
-
def terminal_height
|
929
|
-
terminal_size[1]
|
930
|
-
end
|
931
|
-
|
932
|
-
def cursor_position
|
933
|
-
terminal.cursor_position
|
934
|
-
end
|
935
987
|
end
|
936
988
|
end
|
937
989
|
|