yap-rawline 0.3.5 → 0.4.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/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
|