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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4658b19ad0748955062a2bb15a923376f463bb3b
4
- data.tar.gz: 34445d368f9cd1ec0be6bd4214f89bab269ca174
3
+ metadata.gz: 27dea4694161e07942a1244b30298efd3c7473b5
4
+ data.tar.gz: 483fbc976ef8fbb074aba07df0ade32d47b821b7
5
5
  SHA512:
6
- metadata.gz: 05ec5c1a59bd35812e6d61aa6866fb6da500ec730e50f7084d6da244572f0387dbb0a611d14411f9e6b8dedada4ea8418905f54489daa2c0417b48b3a618875c
7
- data.tar.gz: 19305570b3e546dc452df02f1b6155c8a6d8716f2fdfa1d7900ee05d82523be003dfedd0b29676d8d97ab3dbecde8129341406e7d10dd48cc301f922c4277c45
6
+ metadata.gz: 757786ae22ee40296c746e29183b7cba37c0011422f97c21b0bc3efc5fa7b736d1e8686fa08534f23deafb7eda52bb448a566ee0e9c818b6c9efcf664a091122
7
+ data.tar.gz: aa640a935bcbe38c9afe54b8a583a7441673eb879c5e98969e9b692126b9d5b49b6faa5289e6dc39bd7e4b6b9a8ac1ccceba70547f29bb755154d9e7c7721109
@@ -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.new
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
@@ -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
@@ -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=STDIN, output=STDOUT)
102
+ def initialize(dom:, input:, renderer:, terminal:)
103
+ @dom = dom
70
104
  @input = input
71
- # @output = output
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
- @event_registry = Rawline::EventRegistry.new do |registry|
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
- @line.prompt if @line
145
+ @prompt
130
146
  end
131
147
 
132
148
  def prompt=(text)
133
- return if !@allow_prompt_updates || @line.nil? || @line.prompt == text
134
- @prompt_box.content = Prompt.new(text)
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 initialize_line
138
- @input_box.content = ""
139
- update_word_separator
140
- @add_history = true #add_history
141
- @line = Line.new(@line_history_size) do |l|
142
- l.prompt = @prompt_box.content
143
- l.word_separator = @word_separator
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
- add_to_line_history
146
- @allow_prompt_updates = true
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
- def reset_line
150
- initialize_line
151
- render(reset: true)
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
- def check_for_keyboard_input
155
- bytes = []
156
- begin
157
- file_descriptor_flags = @input.fcntl(Fcntl::F_GETFL, 0)
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
- @input.fcntl(Fcntl::F_SETFL, file_descriptor_flags)
168
- @keyboard_input_processors.last.read_bytes(bytes)
200
+ ############################################################################
201
+ #
202
+ # INPUT
203
+ #
204
+ ############################################################################
169
205
 
170
- @event_loop.add_event name: 'check_for_keyboard_input', source: self
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
- @allow_prompt_updates = false
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 on_read_line(&blk)
198
- @event_registry.subscribe :line_read, &blk
199
- end
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
- @event_registry.subscribe("terminal-resized") do
210
- @render_tree.width = terminal_width
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: "render", source: self
216
- @event_loop.start
217
- end
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 default_action
332
- @input_box.content += @char.chr
333
- print_character
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
- # Write a character to <tt># @output</tt> at cursor position,
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
- if @line.word[:text].length > 0
404
- # If not in a word, print the match, otherwise continue existing word
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
- # Print debug information about the current line. Note that after
496
- # the message is displayed, the line text and position will be restored.
721
+ # HISTORY
497
722
  #
498
- def debug_line
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
- # Toggle the editor <tt>@mode</tt> to :replace or :insert (default).
799
+ # DEBUGGING
651
800
  #
652
- def toggle_mode
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
- # Overwrite the current line (<tt>@line.text</tt>)
688
- # with <tt>new_line</tt>, and optionally reset the cursor position to
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 overwrite_line(new_line, position=nil, options={})
807
+ def debug_line
808
+ pos = @line.position
692
809
  text = @line.text
693
- @highlighting = false
694
-
695
- if options[:highlight_up_to]
696
- @highlighting = true
697
- new_line = highlight_text_up_to(new_line, options[:highlight_up_to])
698
- end
699
-
700
- @ignore_position_change = true
701
- @line.position = new_line.length
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 build_dom_tree
766
- @prompt_box = TerminalLayout::Box.new(content: "default-prompt>", style: {display: :inline})
767
- @input_box = TerminalLayout::InputBox.new(content: "", style: {display: :inline})
768
- @content_box = TerminalLayout::Box.new(content: "", style: {display: :block})
769
- TerminalLayout::Box.new(children:[@prompt_box, @input_box, @content_box])
770
- end
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
- @render_tree.layout
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
- # @output.print string
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