yap-rawline 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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