yap-rawline 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rawline.rb +2 -0
- data/lib/rawline/dom_tree.rb +15 -0
- data/lib/rawline/editor.rb +136 -203
- data/lib/rawline/key_bindings.rb +109 -0
- data/lib/rawline/line.rb +1 -1
- data/lib/rawline/line_editor.rb +194 -0
- data/lib/rawline/renderer.rb +2 -2
- data/lib/rawline/terminal.rb +12 -0
- data/lib/rawline/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3cf478bbbaca65f44353988eb4f3a6522dff7db
|
4
|
+
data.tar.gz: b6b4edd7affe941e8ea350bcf8aa0d660481de25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63b4b7ab19f77f8a6c4d57d40aa7bc5e3def8509700d67cb25559655bb2c24f3d1017232ce4a6da11b52e4fff86566108dd21cfca3fdf20dd85978e7f6cf81f7
|
7
|
+
data.tar.gz: d73ad94f1951fc5eb7254b0c998b53233ba757bd5a2846d32593b427c05e797c203fd2a7cb29cd851fe76505172aead24ca765f96268e2f2a6511bfa0d57c3be
|
data/lib/rawline.rb
CHANGED
@@ -50,7 +50,9 @@ require "#{dir}/rawline/prompt"
|
|
50
50
|
require "#{dir}/rawline/completer"
|
51
51
|
require "#{dir}/rawline/event_loop"
|
52
52
|
require "#{dir}/rawline/event_registry"
|
53
|
+
require "#{dir}/rawline/key_bindings"
|
53
54
|
require "#{dir}/rawline/keycode_parser"
|
55
|
+
require "#{dir}/rawline/line_editor"
|
54
56
|
require "#{dir}/rawline/non_blocking_input"
|
55
57
|
require "#{dir}/rawline/renderer"
|
56
58
|
require "#{dir}/rawline/dom_tree"
|
data/lib/rawline/dom_tree.rb
CHANGED
@@ -4,6 +4,8 @@ module RawLine
|
|
4
4
|
class DomTree < TerminalLayout::Box
|
5
5
|
attr_accessor :prompt_box, :input_box, :content_box
|
6
6
|
|
7
|
+
attr_accessor :focused_input_box
|
8
|
+
|
7
9
|
def initialize(children: nil)
|
8
10
|
unless children
|
9
11
|
@prompt_box = TerminalLayout::Box.new(content: "default-prompt>", style: {display: :inline})
|
@@ -12,7 +14,20 @@ module RawLine
|
|
12
14
|
super(style: {}, children: [@prompt_box, @input_box, @content_box])
|
13
15
|
else
|
14
16
|
super(style: {}, children: children)
|
17
|
+
@input_box = find_child_of_type(TerminalLayout::InputBox)
|
15
18
|
end
|
19
|
+
focus_input_box(@input_box)
|
20
|
+
end
|
21
|
+
|
22
|
+
def focus_input_box(box)
|
23
|
+
@focused_input_box.remove_focus! if @focused_input_box
|
24
|
+
@focused_input_box = box
|
25
|
+
@focused_input_box.focus! if @focused_input_box
|
26
|
+
end
|
27
|
+
|
28
|
+
def input_box=(box)
|
29
|
+
@input_box = box
|
30
|
+
focus_input_box(@input_box)
|
16
31
|
end
|
17
32
|
end
|
18
33
|
end
|
data/lib/rawline/editor.rb
CHANGED
@@ -49,7 +49,7 @@ module RawLine
|
|
49
49
|
attr_accessor :dom
|
50
50
|
|
51
51
|
# TODO: dom traversal for lookup rather than assignment
|
52
|
-
attr_accessor :prompt_box, :input_box, :content_box
|
52
|
+
attr_accessor :prompt_box, :input_box, :content_box, :focused_input_box
|
53
53
|
|
54
54
|
def self.create(dom: nil, &blk)
|
55
55
|
terminal = nil
|
@@ -76,6 +76,8 @@ module RawLine
|
|
76
76
|
height: terminal.height
|
77
77
|
)
|
78
78
|
|
79
|
+
KeyBindings.default_terminal = terminal
|
80
|
+
|
79
81
|
new(
|
80
82
|
dom: dom,
|
81
83
|
input: NonBlockingInput.new(input),
|
@@ -87,13 +89,15 @@ module RawLine
|
|
87
89
|
|
88
90
|
class Environment
|
89
91
|
attr_accessor :keys, :completion_class, :history, :word_separator
|
92
|
+
attr_accessor :keyboard_input_processors
|
90
93
|
|
91
94
|
# * <tt>@history_size</tt> - the size of the editor history buffer (30).
|
92
95
|
# * <tt>@keys</tt> - the keys (arrays of character codes) bound to specific actions.
|
93
96
|
# * <tt>@line_history_size</tt> - the size of the editor line history buffer (50).
|
94
97
|
def initialize(env: nil)
|
95
98
|
@env = env
|
96
|
-
@keys =
|
99
|
+
@keys = KeyBindings.new
|
100
|
+
@keyboard_input_processors = []
|
97
101
|
|
98
102
|
@completion_class = Completer
|
99
103
|
|
@@ -139,7 +143,7 @@ module RawLine
|
|
139
143
|
@match_hidden_files = false
|
140
144
|
set_default_keys
|
141
145
|
@add_history = false
|
142
|
-
|
146
|
+
push_keyboard_input_processor self
|
143
147
|
yield self if block_given?
|
144
148
|
update_word_separator
|
145
149
|
@char = nil
|
@@ -154,11 +158,14 @@ module RawLine
|
|
154
158
|
def env ; @env_stack.last ; end
|
155
159
|
def new_env ; Environment.new ; end
|
156
160
|
def push_env(env) ; @env_stack.push env ; end
|
157
|
-
def pop_env
|
161
|
+
def pop_env ; @env_stack.pop ; end
|
162
|
+
def keyboard_input_processor ; env.keyboard_input_processors.last ; end
|
163
|
+
def push_keyboard_input_processor(kip) ; env.keyboard_input_processors.push kip ; end
|
164
|
+
def pop_keyboard_input_processor ; env.keyboard_input_processors.pop ; end
|
158
165
|
|
159
|
-
def completion_class ;
|
160
|
-
def history ;
|
161
|
-
def keys ;
|
166
|
+
def completion_class ; @env_stack.first.completion_class ; end
|
167
|
+
def history ; @env_stack.first.history ; end
|
168
|
+
def keys ; @env_stack.first.keys ; end
|
162
169
|
|
163
170
|
#
|
164
171
|
# Return the current RawLine version
|
@@ -174,10 +181,12 @@ module RawLine
|
|
174
181
|
def prompt=(text)
|
175
182
|
return if @line && @line.prompt == text
|
176
183
|
@prompt = Prompt.new(text)
|
184
|
+
Treefell['editor'].puts "prompt=#{prompt.inspect}"
|
177
185
|
@dom.prompt_box.content = @prompt
|
178
186
|
end
|
179
187
|
|
180
188
|
def redraw_prompt
|
189
|
+
Treefell['editor'].puts "redrawing prompt=#{prompt.inspect} reset=true"
|
181
190
|
render(reset: true)
|
182
191
|
end
|
183
192
|
|
@@ -185,6 +194,7 @@ module RawLine
|
|
185
194
|
def terminal_height ; @terminal.height ; end
|
186
195
|
|
187
196
|
def content_box ; @dom.content_box ; end
|
197
|
+
def focused_input_box ; @dom.focused_input_box ; end
|
188
198
|
def input_box ; @dom.input_box ; end
|
189
199
|
def prompt_box ; @dom.prompt_box ; end
|
190
200
|
|
@@ -201,11 +211,14 @@ module RawLine
|
|
201
211
|
at_exit { @terminal.cooked! }
|
202
212
|
|
203
213
|
Signal.trap("SIGWINCH") do
|
214
|
+
Treefell['editor'].puts "terminal-resized trap=SIGWINCH"
|
204
215
|
@event_loop.add_event name: "terminal-resized", source: self
|
205
216
|
end
|
206
217
|
|
207
218
|
@event_registry.subscribe("terminal-resized") do
|
208
|
-
|
219
|
+
width, height = terminal_width, terminal_height
|
220
|
+
Treefell['editor'].puts "terminal-resizing width=#{width} height=#{height}"
|
221
|
+
@renderer.update_dimensions(width: width, height: height)
|
209
222
|
@event_loop.add_event name: "render", source: self
|
210
223
|
end
|
211
224
|
|
@@ -232,7 +245,7 @@ module RawLine
|
|
232
245
|
def check_for_keyboard_input
|
233
246
|
bytes = @input.read
|
234
247
|
if bytes.any?
|
235
|
-
|
248
|
+
keyboard_input_processor.read_bytes(bytes)
|
236
249
|
end
|
237
250
|
@event_loop.add_event name: 'check_for_keyboard_input', source: self
|
238
251
|
end
|
@@ -244,28 +257,32 @@ module RawLine
|
|
244
257
|
key_code_sequences = parse_key_code_sequences(bytes)
|
245
258
|
key_code_sequences.each do |sequence|
|
246
259
|
@char = sequence
|
247
|
-
process_character
|
248
|
-
|
249
|
-
new_position = @line.position
|
250
|
-
|
251
260
|
if @char == @terminal.keys[:enter] || !@char
|
252
261
|
process_line
|
262
|
+
else
|
263
|
+
process_character
|
264
|
+
new_position = @line.position
|
253
265
|
end
|
254
266
|
end
|
255
267
|
end
|
256
268
|
|
257
269
|
def process_line
|
258
270
|
@event_loop.add_event(name: "process_line", source: self) do
|
271
|
+
add_to_history
|
272
|
+
|
259
273
|
@terminal.snapshot_tty_attrs
|
260
274
|
@terminal.pseudo_cooked!
|
261
275
|
|
262
|
-
|
276
|
+
@terminal.move_to_beginning_of_row
|
263
277
|
@terminal.puts
|
264
278
|
end
|
265
279
|
|
266
|
-
@event_loop.add_event
|
280
|
+
@event_loop.add_event(name: "line_read", source: self, payload: { line: @line.text.without_ansi.dup })
|
281
|
+
@event_loop.add_event(name: "prepare_new_line", source: self) do
|
282
|
+
move_to_beginning_of_input
|
283
|
+
end
|
267
284
|
@event_loop.add_event(name: "restore_tty_attrs", source: self) { @terminal.restore_tty_attrs }
|
268
|
-
@event_loop.add_event
|
285
|
+
@event_loop.add_event(name: "render", source: self, payload: { reset: true })
|
269
286
|
end
|
270
287
|
|
271
288
|
#
|
@@ -299,59 +316,18 @@ module RawLine
|
|
299
316
|
# * The value can be a Fixnum, a String or an Array.
|
300
317
|
#
|
301
318
|
def bind(key, &block)
|
302
|
-
|
303
|
-
when 'Symbol' then
|
304
|
-
raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys[key]
|
305
|
-
keys[@terminal.keys[key]] = block
|
306
|
-
when 'Array' then
|
307
|
-
raise BindingException, "Unknown key or key sequence '#{key.join(", ")}' (#{key.class.to_s})" unless @terminal.keys.has_value? key
|
308
|
-
keys[key] = block
|
309
|
-
when 'Fixnum' then
|
310
|
-
raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys.has_value? [key]
|
311
|
-
keys[[key]] = block
|
312
|
-
when 'String' then
|
313
|
-
if key.length == 1 then
|
314
|
-
keys[[key.ord]] = block
|
315
|
-
else
|
316
|
-
bind_hash({:"#{key}" => key}, block)
|
317
|
-
end
|
318
|
-
when 'Hash' then
|
319
|
-
raise BindingException, "Cannot bind more than one key or key sequence at once" unless key.values.length == 1
|
320
|
-
bind_hash(key, block)
|
321
|
-
else
|
322
|
-
raise BindingException, "Unable to bind '#{key.to_s}' (#{key.class.to_s})"
|
323
|
-
end
|
324
|
-
@terminal.update
|
319
|
+
keys.bind(key, &block)
|
325
320
|
end
|
326
321
|
|
327
322
|
def unbind(key)
|
328
|
-
|
329
|
-
when 'Symbol' then
|
330
|
-
keys.delete @terminal.keys[key]
|
331
|
-
when 'Array' then
|
332
|
-
keys.delete keys[key]
|
333
|
-
when 'Fixnum' then
|
334
|
-
keys.delete[[key]]
|
335
|
-
when 'String' then
|
336
|
-
if key.length == 1 then
|
337
|
-
keys.delete([key.ord])
|
338
|
-
else
|
339
|
-
raise NotImplementedError, "This is no implemented yet. It needs to return the previously bound block"
|
340
|
-
bind_hash({:"#{key}" => key}, block)
|
341
|
-
end
|
342
|
-
when 'Hash' then
|
343
|
-
raise BindingException, "Cannot bind more than one key or key sequence at once" unless key.values.length == 1
|
344
|
-
bind_hash(key, -> { })
|
345
|
-
end
|
346
|
-
@terminal.update
|
347
|
-
block
|
323
|
+
keys.unbind(key)
|
348
324
|
end
|
349
325
|
|
350
326
|
#
|
351
327
|
# Return true if the last character read via <tt>read</tt> is bound to an action.
|
352
328
|
#
|
353
329
|
def key_bound?
|
354
|
-
keys
|
330
|
+
keys.bound?(@char)
|
355
331
|
end
|
356
332
|
|
357
333
|
#
|
@@ -403,14 +379,14 @@ module RawLine
|
|
403
379
|
# This action is bound to ctrl+k by default.
|
404
380
|
#
|
405
381
|
def clear_line
|
382
|
+
Treefell['editor'].puts "clear_line"
|
406
383
|
add_to_line_history
|
407
|
-
@
|
408
|
-
@line.position = 0
|
409
|
-
@dom.input_box.position = @line.position
|
384
|
+
@line_editor.clear_line
|
410
385
|
history.clear_position
|
411
386
|
end
|
412
387
|
|
413
388
|
def clear_screen
|
389
|
+
Treefell['editor'].puts "clear_screen"
|
414
390
|
@terminal.clear_screen
|
415
391
|
render(reset: true)
|
416
392
|
end
|
@@ -422,21 +398,19 @@ module RawLine
|
|
422
398
|
# This action is bound to the backspace key by default.
|
423
399
|
#
|
424
400
|
def delete_left_character(no_line_history=false)
|
425
|
-
|
426
|
-
|
401
|
+
Treefell['editor'].puts "delete_left_character"
|
402
|
+
if @line_editor.delete_left_character
|
403
|
+
add_to_line_history unless no_line_history
|
404
|
+
history.clear_position
|
427
405
|
end
|
428
406
|
end
|
429
407
|
|
430
408
|
def delete_n_characters(number_of_characters_to_delete, no_line_history=false)
|
431
|
-
|
432
|
-
|
433
|
-
|
409
|
+
Treefell['editor'].puts "delete_n_characters n=#{number_of_characters_to_delete}"
|
410
|
+
if @line_editor.delete_n_characters(number_of_characters_to_delete)
|
411
|
+
add_to_line_history unless no_line_history
|
412
|
+
history.clear_position
|
434
413
|
end
|
435
|
-
|
436
|
-
@dom.input_box.position = @line.position
|
437
|
-
@dom.input_box.content = @line.text
|
438
|
-
add_to_line_history unless no_line_history
|
439
|
-
history.clear_position
|
440
414
|
end
|
441
415
|
|
442
416
|
#
|
@@ -446,37 +420,54 @@ module RawLine
|
|
446
420
|
# This action is bound to the delete key by default.
|
447
421
|
#
|
448
422
|
def delete_character(no_line_history=false)
|
449
|
-
|
450
|
-
|
451
|
-
chars = (@line.eol?) ? ' ' : select_characters_from_cursor(1)
|
452
|
-
#remove character from line
|
453
|
-
@line[@line.position] = ''
|
454
|
-
@dom.input_box.content = @line.text
|
455
|
-
@dom.input_box.position = @line.position
|
423
|
+
Treefell['editor'].puts "delete_character"
|
424
|
+
if @line_editor.delete_character
|
456
425
|
add_to_line_history unless no_line_history
|
457
426
|
history.clear_position
|
458
427
|
end
|
459
428
|
end
|
460
429
|
|
461
430
|
def highlight_text_up_to(text, position)
|
462
|
-
|
431
|
+
Treefell['editor'].puts "highlight_text_up_to text=#{text} position=#{position}"
|
432
|
+
@line_editor.highlight_text_up_to(text, position)
|
433
|
+
end
|
434
|
+
|
435
|
+
#
|
436
|
+
# Inserts a string at the current line position, shifting characters
|
437
|
+
# to right if necessary.
|
438
|
+
#
|
439
|
+
def insert(string, add_to_line_history: true)
|
440
|
+
Treefell['editor'].puts "insert string=#{string} add_to_line_history=#{add_to_line_history}"
|
441
|
+
if @line_editor.insert(string)
|
442
|
+
self.add_to_line_history if add_to_line_history
|
443
|
+
end
|
463
444
|
end
|
464
445
|
|
465
446
|
def kill_forward
|
466
|
-
|
467
|
-
@
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
447
|
+
Treefell['editor'].puts "kill_forward"
|
448
|
+
@line_editor.kill_forward.tap do
|
449
|
+
add_to_line_history(allow_empty: true)
|
450
|
+
history.clear_position
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
#
|
455
|
+
# Write a string starting from the cursor position ovewriting any character
|
456
|
+
# at the current position if necessary.
|
457
|
+
#
|
458
|
+
def write(string, add_to_line_history: true)
|
459
|
+
Treefell['editor'].puts "write string=#{string}"
|
460
|
+
if @line_editor.write(string)
|
461
|
+
self.add_to_line_history if add_to_line_history
|
462
|
+
end
|
472
463
|
end
|
473
464
|
|
474
465
|
def yank_forward(text)
|
475
|
-
|
476
|
-
@
|
477
|
-
|
478
|
-
|
479
|
-
|
466
|
+
Treefell['editor'].puts "yank_forward"
|
467
|
+
@line_editor.yank_forward(text).tap do
|
468
|
+
add_to_line_history
|
469
|
+
history.clear_position
|
470
|
+
end
|
480
471
|
end
|
481
472
|
|
482
473
|
#
|
@@ -485,12 +476,8 @@ module RawLine
|
|
485
476
|
# This action is bound to the left arrow key by default.
|
486
477
|
#
|
487
478
|
def move_left
|
488
|
-
|
489
|
-
|
490
|
-
@dom.input_box.position = @line.position
|
491
|
-
return true
|
492
|
-
end
|
493
|
-
false
|
479
|
+
Treefell['editor'].puts "move_left"
|
480
|
+
@line_editor.move_left
|
494
481
|
end
|
495
482
|
|
496
483
|
#
|
@@ -500,28 +487,25 @@ module RawLine
|
|
500
487
|
# This action is bound to the right arrow key by default.
|
501
488
|
#
|
502
489
|
def move_right
|
503
|
-
|
504
|
-
|
505
|
-
@dom.input_box.position = @line.position
|
506
|
-
return true
|
507
|
-
end
|
508
|
-
false
|
490
|
+
Treefell['editor'].puts "move_right"
|
491
|
+
@line_editor.move_right
|
509
492
|
end
|
510
493
|
|
511
494
|
def move_to_beginning_of_input
|
512
|
-
|
513
|
-
@
|
495
|
+
Treefell['editor'].puts "move_to_beginning_of_input"
|
496
|
+
@line_editor.move_to_beginning_of_input
|
514
497
|
end
|
515
498
|
|
516
499
|
def move_to_end_of_input
|
517
|
-
|
518
|
-
@
|
500
|
+
Treefell['editor'].puts "move_to_end_of_input"
|
501
|
+
@line_editor.move_to_end_of_input
|
519
502
|
end
|
520
503
|
|
521
504
|
#
|
522
505
|
# Move the cursor to <tt>pos</tt>.
|
523
506
|
#
|
524
507
|
def move_to_position(pos)
|
508
|
+
Treefell['editor'].puts "move_to_position position=#{pos}"
|
525
509
|
rows_to_move = current_terminal_row - terminal_row_for_line_position(pos)
|
526
510
|
if rows_to_move > 0
|
527
511
|
# rows_to_move.times { @output.print @terminal.term_info.control_string("cuu1") }
|
@@ -533,20 +517,7 @@ module RawLine
|
|
533
517
|
column = (@line.prompt.length + pos) % terminal_width
|
534
518
|
# @output.print @terminal.term_info.control_string("hpa", column)
|
535
519
|
# @terminal.move_to_column((@line.prompt.length + pos) % terminal_width)
|
536
|
-
@
|
537
|
-
@dom.input_box.position = @line.position
|
538
|
-
end
|
539
|
-
|
540
|
-
def move_to_end_of_line
|
541
|
-
rows_to_move_down = number_of_terminal_rows - current_terminal_row
|
542
|
-
# rows_to_move_down.times { @output.print @terminal.term_info.control_string("cud1") }
|
543
|
-
# @terminal.move_down_n_rows rows_to_move_down
|
544
|
-
@line.position = @line.length
|
545
|
-
@dom.input_box.position = @line.position
|
546
|
-
|
547
|
-
column = (@line.prompt.length + @line.position) % terminal_width
|
548
|
-
# @output.print @terminal.term_info.control_string("hpa", column)
|
549
|
-
# @terminal.move_to_column((@line.prompt.length + @line.position) % terminal_width)
|
520
|
+
@line_editor.position = pos
|
550
521
|
end
|
551
522
|
|
552
523
|
#
|
@@ -555,22 +526,14 @@ module RawLine
|
|
555
526
|
# <tt>position</tt>.
|
556
527
|
#
|
557
528
|
def overwrite_line(new_line, position=nil, options={})
|
558
|
-
|
559
|
-
@
|
560
|
-
|
561
|
-
if options[:highlight_up_to]
|
562
|
-
@highlighting = true
|
563
|
-
new_line = highlight_text_up_to(new_line, options[:highlight_up_to])
|
529
|
+
Treefell['editor'].puts "overwrite_line new_line=#{new_line} position=#{position} options=#{options.inspect}"
|
530
|
+
if @line_editor.overwrite_line(new_line, position, options)
|
531
|
+
@event_loop.add_event name: "render", source: focused_input_box
|
564
532
|
end
|
565
|
-
|
566
|
-
@line.position = position || new_line.length
|
567
|
-
@line.text = new_line
|
568
|
-
@dom.input_box.content = @line.text
|
569
|
-
@dom.input_box.position = @line.position
|
570
|
-
@event_loop.add_event name: "render", source: @dom.input_box
|
571
533
|
end
|
572
534
|
|
573
535
|
def reset_line
|
536
|
+
Treefell['editor'].puts "reset_line"
|
574
537
|
initialize_line
|
575
538
|
render(reset: true)
|
576
539
|
end
|
@@ -599,32 +562,6 @@ module RawLine
|
|
599
562
|
render(reset: true)
|
600
563
|
end
|
601
564
|
|
602
|
-
#
|
603
|
-
# Inserts a string at the current line position, shifting characters
|
604
|
-
# to right if necessary.
|
605
|
-
#
|
606
|
-
def insert(string, add_to_line_history: true)
|
607
|
-
@line.text.insert @line.position, string
|
608
|
-
string.length.times { @line.right }
|
609
|
-
@dom.input_box.position = @line.position
|
610
|
-
@dom.input_box.content = @line.text
|
611
|
-
|
612
|
-
self.add_to_line_history if add_to_line_history
|
613
|
-
end
|
614
|
-
|
615
|
-
#
|
616
|
-
# Write a string starting from the cursor position ovewriting any character
|
617
|
-
# at the current position if necessary.
|
618
|
-
#
|
619
|
-
def write(string, add_to_line_history: true)
|
620
|
-
@line.text[@line.position] = string
|
621
|
-
string.length.times { @line.right }
|
622
|
-
@dom.input_box.position = @line.position
|
623
|
-
@dom.input_box.content = @line.text
|
624
|
-
|
625
|
-
self.add_to_line_history if add_to_line_history
|
626
|
-
end
|
627
|
-
|
628
565
|
############################################################################
|
629
566
|
#
|
630
567
|
# COMPLETION
|
@@ -643,7 +580,7 @@ module RawLine
|
|
643
580
|
# pressed again.
|
644
581
|
#
|
645
582
|
def complete
|
646
|
-
|
583
|
+
focused_input_box.cursor_off
|
647
584
|
completer = completion_class.new(
|
648
585
|
char: @char,
|
649
586
|
line: @line,
|
@@ -660,15 +597,15 @@ module RawLine
|
|
660
597
|
done: -> (*leftover_bytes){
|
661
598
|
completion_done
|
662
599
|
leftover_bytes = leftover_bytes.flatten
|
663
|
-
|
600
|
+
pop_keyboard_input_processor
|
664
601
|
if leftover_bytes.any?
|
665
|
-
|
602
|
+
keyboard_input_processor.read_bytes(leftover_bytes)
|
666
603
|
end
|
667
|
-
|
604
|
+
focused_input_box.cursor_on
|
668
605
|
},
|
669
606
|
keys: terminal.keys
|
670
607
|
)
|
671
|
-
|
608
|
+
push_keyboard_input_processor(completer)
|
672
609
|
completer.read_bytes(@char)
|
673
610
|
end
|
674
611
|
|
@@ -826,15 +763,21 @@ module RawLine
|
|
826
763
|
# line history, to allow undo/redo
|
827
764
|
# operations.
|
828
765
|
#
|
829
|
-
def add_to_line_history
|
830
|
-
|
766
|
+
def add_to_line_history(allow_empty: false)
|
767
|
+
if allow_empty || !@line.text.empty?
|
768
|
+
Treefell['editor'].puts "add_to_line_history text=#{@line.text}"
|
769
|
+
@line.history << @line.text.dup
|
770
|
+
end
|
831
771
|
end
|
832
772
|
|
833
773
|
#
|
834
774
|
# Add the current line (<tt>@line.text</tt>) to the editor history.
|
835
775
|
#
|
836
|
-
def add_to_history
|
837
|
-
|
776
|
+
def add_to_history(allow_empty: false)
|
777
|
+
if @add_history && (allow_empty || !@line.text.empty?)
|
778
|
+
Treefell['editor'].puts "add_to_history text=#{@line.text}"
|
779
|
+
history << @line.text.dup
|
780
|
+
end
|
838
781
|
end
|
839
782
|
|
840
783
|
############################################################################
|
@@ -861,6 +804,11 @@ module RawLine
|
|
861
804
|
overwrite_line(text, pos)
|
862
805
|
end
|
863
806
|
|
807
|
+
def focus_input_box(box)
|
808
|
+
@dom.focus_input_box(box)
|
809
|
+
@renderer.render_cursor
|
810
|
+
end
|
811
|
+
|
864
812
|
private
|
865
813
|
|
866
814
|
def initialize_events
|
@@ -870,14 +818,24 @@ module RawLine
|
|
870
818
|
end
|
871
819
|
@event_loop = Rawline::EventLoop.new(registry: @event_registry)
|
872
820
|
|
821
|
+
@dom.on(:content_changed) do |*args|
|
822
|
+
Treefell['editor'].puts 'DOM content changed, re-rendering'
|
823
|
+
@event_loop.add_event name: "render"
|
824
|
+
end
|
825
|
+
|
873
826
|
@dom.on(:child_changed) do |*args|
|
874
827
|
Treefell['editor'].puts 'DOM child changed, re-rendering'
|
875
|
-
@event_loop.add_event name: "render"
|
828
|
+
@event_loop.add_event name: "render"
|
876
829
|
end
|
877
830
|
|
878
831
|
@dom.on :position_changed do |*args|
|
879
832
|
Treefell['editor'].puts 'DOM position changed, rendering cursor'
|
880
|
-
@renderer.
|
833
|
+
@renderer.render
|
834
|
+
end
|
835
|
+
|
836
|
+
@dom.on :focus_changed do |*args|
|
837
|
+
Treefell['editor'].puts 'DOM focused changed, re-rendering'
|
838
|
+
@renderer.render
|
881
839
|
end
|
882
840
|
|
883
841
|
@event_registry.subscribe :render, -> (_) { render(reset: false) }
|
@@ -889,13 +847,17 @@ module RawLine
|
|
889
847
|
end
|
890
848
|
|
891
849
|
def initialize_line
|
892
|
-
|
850
|
+
focused_input_box.content = ""
|
893
851
|
update_word_separator
|
894
852
|
@add_history = true
|
895
853
|
@line = env.initialize_line do |l|
|
896
854
|
l.prompt = @dom.prompt_box.content
|
897
855
|
l.word_separator = @word_separator
|
898
856
|
end
|
857
|
+
@line_editor = LineEditor.new(
|
858
|
+
@line,
|
859
|
+
sync_with: -> { focused_input_box }
|
860
|
+
)
|
899
861
|
add_to_line_history
|
900
862
|
@allow_prompt_updates = true
|
901
863
|
end
|
@@ -911,32 +873,10 @@ module RawLine
|
|
911
873
|
@word_separator = /(?<!\\)[#{chars.join}]/
|
912
874
|
end
|
913
875
|
|
914
|
-
def bind_hash(key, block)
|
915
|
-
key.each_pair do |j,k|
|
916
|
-
raise BindingException, "'#{k[0].chr}' is not a legal escape code for '#{@terminal.class.to_s}'." unless k.length > 1 && @terminal.escape_codes.include?(k[0].ord)
|
917
|
-
code = []
|
918
|
-
case k.class.to_s
|
919
|
-
when 'Fixnum' then
|
920
|
-
code = [k]
|
921
|
-
when 'String' then
|
922
|
-
k.each_byte { |b| code << b }
|
923
|
-
when 'Array' then
|
924
|
-
code = k
|
925
|
-
else
|
926
|
-
raise BindingException, "Unable to bind '#{k.to_s}' (#{k.class.to_s})"
|
927
|
-
end
|
928
|
-
@terminal.keys[j] = code
|
929
|
-
keys[code] = block
|
930
|
-
end
|
931
|
-
end
|
932
|
-
|
933
|
-
def select_characters_from_cursor(offset=0)
|
934
|
-
select_characters(:right, @line.length-@line.position, offset)
|
935
|
-
end
|
936
|
-
|
937
876
|
def generic_history_back(history)
|
938
877
|
unless history.empty?
|
939
878
|
history.back
|
879
|
+
Treefell['editor'].puts "generic_history_back position=#{history.position} history=#{history.to_a.inspect}"
|
940
880
|
line = history.get
|
941
881
|
return unless line
|
942
882
|
|
@@ -947,6 +887,7 @@ module RawLine
|
|
947
887
|
|
948
888
|
def generic_history_forward(history)
|
949
889
|
if history.forward
|
890
|
+
Treefell['editor'].puts "generic_history_back position=#{history.position} history=#{history.to_a.inspect}"
|
950
891
|
line = history.get
|
951
892
|
return unless line
|
952
893
|
|
@@ -955,14 +896,6 @@ module RawLine
|
|
955
896
|
end
|
956
897
|
end
|
957
898
|
|
958
|
-
def select_characters(direction, n, offset=0)
|
959
|
-
if direction == :right then
|
960
|
-
@line.text[@line.position+offset..@line.position+offset+n]
|
961
|
-
elsif direction == :left then
|
962
|
-
@line.text[@line.position-offset-n..@line.position-offset]
|
963
|
-
end
|
964
|
-
end
|
965
|
-
|
966
899
|
def set_default_keys
|
967
900
|
bind(:space) { insert(' ') }
|
968
901
|
bind(:enter) { newline }
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module RawLine
|
2
|
+
class KeyBindings
|
3
|
+
attr_reader :keys
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :default_terminal
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(terminal: self.class.default_terminal)
|
10
|
+
@terminal = terminal
|
11
|
+
@keys = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](char)
|
15
|
+
keys[char]
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Bind a key to an action specified via <tt>block</tt>.
|
20
|
+
# <tt>key</tt> can be:
|
21
|
+
#
|
22
|
+
# * A Symbol identifying a character or character sequence defined for the current terminal
|
23
|
+
# * A Fixnum identifying a character defined for the current terminal
|
24
|
+
# * An Array identifying a character or character sequence defined for the current terminal
|
25
|
+
# * A String identifying a character or character sequence, even if it is not defined for the current terminal
|
26
|
+
# * An Hash identifying a character or character sequence, even if it is not defined for the current terminal
|
27
|
+
#
|
28
|
+
# If <tt>key</tt> is a hash, then:
|
29
|
+
#
|
30
|
+
# * It must contain only one key/value pair
|
31
|
+
# * The key identifies the name of the character or character sequence
|
32
|
+
# * The value identifies the code(s) corresponding to the character or character sequence
|
33
|
+
# * The value can be a Fixnum, a String or an Array.
|
34
|
+
#
|
35
|
+
def bind(key, &block)
|
36
|
+
case key.class.to_s
|
37
|
+
when 'Symbol' then
|
38
|
+
raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys[key]
|
39
|
+
keys[@terminal.keys[key]] = block
|
40
|
+
when 'Array' then
|
41
|
+
raise BindingException, "Unknown key or key sequence '#{key.join(", ")}' (#{key.class.to_s})" unless @terminal.keys.has_value? key
|
42
|
+
keys[key] = block
|
43
|
+
when 'Fixnum' then
|
44
|
+
raise BindingException, "Unknown key or key sequence '#{key.to_s}' (#{key.class.to_s})" unless @terminal.keys.has_value? [key]
|
45
|
+
keys[[key]] = block
|
46
|
+
when 'String' then
|
47
|
+
if key.length == 1 then
|
48
|
+
keys[[key.ord]] = block
|
49
|
+
else
|
50
|
+
bind_hash({:"#{key}" => key}, block)
|
51
|
+
end
|
52
|
+
when 'Hash' then
|
53
|
+
raise BindingException, "Cannot bind more than one key or key sequence at once" unless key.values.length == 1
|
54
|
+
bind_hash(key, block)
|
55
|
+
else
|
56
|
+
raise BindingException, "Unable to bind '#{key.to_s}' (#{key.class.to_s})"
|
57
|
+
end
|
58
|
+
@terminal.update
|
59
|
+
end
|
60
|
+
|
61
|
+
def bound?(char)
|
62
|
+
keys[char] ? true : false
|
63
|
+
end
|
64
|
+
|
65
|
+
def unbind(key)
|
66
|
+
block = case key.class.to_s
|
67
|
+
when 'Symbol' then
|
68
|
+
keys.delete @terminal.keys[key]
|
69
|
+
when 'Array' then
|
70
|
+
keys.delete keys[key]
|
71
|
+
when 'Fixnum' then
|
72
|
+
keys.delete[[key]]
|
73
|
+
when 'String' then
|
74
|
+
if key.length == 1 then
|
75
|
+
keys.delete([key.ord])
|
76
|
+
else
|
77
|
+
raise NotImplementedError, "This is no implemented yet. It needs to return the previously bound block"
|
78
|
+
bind_hash({:"#{key}" => key}, block)
|
79
|
+
end
|
80
|
+
when 'Hash' then
|
81
|
+
raise BindingException, "Cannot bind more than one key or key sequence at once" unless key.values.length == 1
|
82
|
+
bind_hash(key, -> { })
|
83
|
+
end
|
84
|
+
@terminal.update
|
85
|
+
block
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def bind_hash(key, block)
|
91
|
+
key.each_pair do |j,k|
|
92
|
+
raise BindingException, "'#{k[0].chr}' is not a legal escape code for '#{@terminal.class.to_s}'." unless k.length > 1 && @terminal.escape_codes.include?(k[0].ord)
|
93
|
+
code = []
|
94
|
+
case k.class.to_s
|
95
|
+
when 'Fixnum' then
|
96
|
+
code = [k]
|
97
|
+
when 'String' then
|
98
|
+
k.each_byte { |b| code << b }
|
99
|
+
when 'Array' then
|
100
|
+
code = k
|
101
|
+
else
|
102
|
+
raise BindingException, "Unable to bind '#{k.to_s}' (#{k.class.to_s})"
|
103
|
+
end
|
104
|
+
@terminal.keys[j] = code
|
105
|
+
keys[code] = block
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/rawline/line.rb
CHANGED
@@ -32,7 +32,7 @@ module RawLine
|
|
32
32
|
# * <tt>@position</tt> - the current cursor position within the line.
|
33
33
|
# * <tt>@prompt</tt> - a prompt to prepend to the line text.
|
34
34
|
#
|
35
|
-
def initialize(history_size)
|
35
|
+
def initialize(history_size=30)
|
36
36
|
@text = ANSIString.new("")
|
37
37
|
@history_size = history_size
|
38
38
|
@position = 0
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module RawLine
|
2
|
+
class LineEditor
|
3
|
+
def initialize(line, sync_with: -> {})
|
4
|
+
@line = line
|
5
|
+
@sync_with_proc = sync_with
|
6
|
+
end
|
7
|
+
|
8
|
+
#
|
9
|
+
# Clear the current line, i.e.
|
10
|
+
# <tt>@line.text</tt> and <tt>@line.position</tt>.
|
11
|
+
# This action is bound to ctrl+k by default.
|
12
|
+
#
|
13
|
+
def clear_line
|
14
|
+
@line.text = ""
|
15
|
+
@line.position = 0
|
16
|
+
sync!
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Delete the character at the left of the cursor.
|
22
|
+
# If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
|
23
|
+
# recorded in the line history.
|
24
|
+
# This action is bound to the backspace key by default.
|
25
|
+
#
|
26
|
+
def delete_left_character
|
27
|
+
if move_left then
|
28
|
+
delete_character
|
29
|
+
sync!
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete_n_characters(number_of_characters_to_delete)
|
36
|
+
number_of_characters_to_delete.times do |n|
|
37
|
+
@line[@line.position] = ''
|
38
|
+
@line.left
|
39
|
+
end
|
40
|
+
sync!
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Delete the character under the cursor.
|
46
|
+
# If <tt>no_line_hisytory</tt> is set to true, the deletion won't be
|
47
|
+
# recorded in the line history.
|
48
|
+
# This action is bound to the delete key by default.
|
49
|
+
#
|
50
|
+
def delete_character(no_line_history=false)
|
51
|
+
unless @line.position > @line.eol
|
52
|
+
# save characters to shift
|
53
|
+
chars = (@line.eol?) ? ' ' : select_characters_from_cursor(1)
|
54
|
+
#remove character from line
|
55
|
+
@line[@line.position] = ''
|
56
|
+
sync!
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def highlight_text_up_to(text, position)
|
63
|
+
ANSIString.new("\e[1m#{text[0...position]}\e[0m#{text[position..-1]}")
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Inserts a string at the current line position, shifting characters
|
68
|
+
# to right if necessary.
|
69
|
+
#
|
70
|
+
def insert(string)
|
71
|
+
return false if string.empty?
|
72
|
+
@line.text.insert @line.position, string
|
73
|
+
string.length.times { @line.right }
|
74
|
+
sync!
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def kill_forward
|
79
|
+
@line.text[@line.position..-1].tap do
|
80
|
+
@line.text[@line.position..-1] = ANSIString.new("")
|
81
|
+
sync!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Write a string starting from the cursor position ovewriting any character
|
87
|
+
# at the current position if necessary.
|
88
|
+
#
|
89
|
+
def write(string)
|
90
|
+
if @line.eol?
|
91
|
+
@line.text[@line.position] = string
|
92
|
+
else
|
93
|
+
@line.text << string
|
94
|
+
end
|
95
|
+
string.length.times { @line.right }
|
96
|
+
sync!
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def yank_forward(text)
|
101
|
+
@line.text[@line.position...@line.position] = text
|
102
|
+
@line.position = @line.position + text.length
|
103
|
+
sync!
|
104
|
+
@line.text
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Move the cursor left (if possible) by printing a
|
109
|
+
# backspace, updating <tt>@line.position</tt> accordingly.
|
110
|
+
# This action is bound to the left arrow key by default.
|
111
|
+
#
|
112
|
+
def move_left
|
113
|
+
unless @line.bol? then
|
114
|
+
@line.left
|
115
|
+
sync!
|
116
|
+
return true
|
117
|
+
end
|
118
|
+
false
|
119
|
+
end
|
120
|
+
|
121
|
+
def move_right
|
122
|
+
unless @line.position > @line.eol then
|
123
|
+
@line.right
|
124
|
+
sync!
|
125
|
+
return true
|
126
|
+
end
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
def move_to_beginning_of_input
|
131
|
+
@line.position = @line.bol
|
132
|
+
sync!
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
136
|
+
def move_to_end_of_input
|
137
|
+
@line.position = @line.length
|
138
|
+
sync!
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Overwrite the current line (<tt>@line.text</tt>)
|
144
|
+
# with <tt>new_line</tt>, and optionally reset the cursor position to
|
145
|
+
# <tt>position</tt>.
|
146
|
+
#
|
147
|
+
def overwrite_line(new_line, position=nil, options={})
|
148
|
+
text = @line.text
|
149
|
+
@highlighting = false
|
150
|
+
|
151
|
+
if options[:highlight_up_to]
|
152
|
+
@highlighting = true
|
153
|
+
new_line = highlight_text_up_to(new_line, options[:highlight_up_to])
|
154
|
+
end
|
155
|
+
|
156
|
+
@line.position = position || new_line.length
|
157
|
+
@line.text = new_line
|
158
|
+
sync!
|
159
|
+
true
|
160
|
+
end
|
161
|
+
|
162
|
+
def position=(position)
|
163
|
+
@line.position = position
|
164
|
+
sync!
|
165
|
+
end
|
166
|
+
|
167
|
+
def text
|
168
|
+
@line.text
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def select_characters_from_cursor(offset=0)
|
174
|
+
select_characters(:right, @line.length-@line.position, offset)
|
175
|
+
end
|
176
|
+
|
177
|
+
def select_characters(direction, n, offset=0)
|
178
|
+
if direction == :right then
|
179
|
+
@line.text[@line.position+offset..@line.position+offset+n]
|
180
|
+
elsif direction == :left then
|
181
|
+
@line.text[@line.position-offset-n..@line.position-offset]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def sync!
|
186
|
+
positionable_object = @sync_with_proc.call
|
187
|
+
if positionable_object
|
188
|
+
positionable_object.position = @line.position
|
189
|
+
positionable_object.content = @line.text
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
data/lib/rawline/renderer.rb
CHANGED
@@ -17,8 +17,8 @@ module RawLine
|
|
17
17
|
@renderer.render(@render_tree, reset: reset)
|
18
18
|
end
|
19
19
|
|
20
|
-
def render_cursor
|
21
|
-
@renderer.render_cursor(
|
20
|
+
def render_cursor
|
21
|
+
@renderer.render_cursor(@dom.focused_input_box)
|
22
22
|
end
|
23
23
|
|
24
24
|
def update_dimensions(width:, height:)
|
data/lib/rawline/terminal.rb
CHANGED
@@ -129,6 +129,10 @@ module RawLine
|
|
129
129
|
term_info.control "el1"
|
130
130
|
end
|
131
131
|
|
132
|
+
def clear_to_end_of_line
|
133
|
+
term_info.control "el"
|
134
|
+
end
|
135
|
+
|
132
136
|
def clear_screen
|
133
137
|
term_info.control "clear"
|
134
138
|
end
|
@@ -137,6 +141,14 @@ module RawLine
|
|
137
141
|
term_info.control "ed"
|
138
142
|
end
|
139
143
|
|
144
|
+
def hide_cursor
|
145
|
+
term_info.control_string "civis"
|
146
|
+
end
|
147
|
+
|
148
|
+
def show_cursor
|
149
|
+
term_info.control_string "cnorm"
|
150
|
+
end
|
151
|
+
|
140
152
|
def move_to_beginning_of_row
|
141
153
|
move_to_column 0
|
142
154
|
end
|
data/lib/rawline/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yap-rawline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fabio Cevasco
|
@@ -37,14 +37,14 @@ dependencies:
|
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.4.0
|
41
41
|
type: :runtime
|
42
42
|
prerelease: false
|
43
43
|
version_requirements: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
47
|
+
version: 0.4.0
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: term-ansicolor
|
50
50
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,8 +110,10 @@ files:
|
|
110
110
|
- lib/rawline/event_loop.rb
|
111
111
|
- lib/rawline/event_registry.rb
|
112
112
|
- lib/rawline/history_buffer.rb
|
113
|
+
- lib/rawline/key_bindings.rb
|
113
114
|
- lib/rawline/keycode_parser.rb
|
114
115
|
- lib/rawline/line.rb
|
116
|
+
- lib/rawline/line_editor.rb
|
115
117
|
- lib/rawline/non_blocking_input.rb
|
116
118
|
- lib/rawline/prompt.rb
|
117
119
|
- lib/rawline/renderer.rb
|