yap-rawline 0.1.1 → 0.2.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.
- 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
|
|