reline 0.2.7 → 0.2.8.pre.4

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
  SHA256:
3
- metadata.gz: 1bb2255429504a1beb1a5a9f863f7cba5c4c65781b7cd8b2aa843b0df639fb26
4
- data.tar.gz: c9372db87ad831e06d0e99d20981cb14a08e8b15ca3011f4b6df2a05aaeef7b3
3
+ metadata.gz: 5e7c5785424de4deda942cdd54cab39bc7d96fd79b1451095626996b875a32d9
4
+ data.tar.gz: 3afcf3fc84d2b3d4dd48481bca613a8fc08de049983619772e9fcd502caffa83
5
5
  SHA512:
6
- metadata.gz: 63e8b6f4a9de8a69a8f22c979b6fee92026143be658b9e928da43dddb868fb6b641b5f140de436c1dd5ed1f3cdbc3c42c7c41084fcc1d811cd713c684aedda8e
7
- data.tar.gz: 7a459412f528f43d5e65f4c7c6a5c74055e2c5334f21a65411bc4f187b55b649c15fd612ed27f2dc0032f7fdc5973c7fa57b674b0a323ac2d9537b23fff19664
6
+ metadata.gz: c1be959cdf8fc499ec71184bc3768964dbc1eb078085bbd8303a6d9b4b0ade3ccd25a8d90486c620316b38c4dfe7b7fb224d99912ff855bd8bc3fc64946f1d5d
7
+ data.tar.gz: cfe31c94298208f8cda2c7fddeba8940e71589c60b06167fcb81e8c0d278055ffd94261713d1aa3a2d6031d6572c3dc9194412d15e57f83d0fb81e87361496c0
data/lib/reline/ansi.rb CHANGED
@@ -37,6 +37,7 @@ class Reline::ANSI
37
37
  # default bindings
38
38
  [27, 32] => :em_set_mark, # M-<space>
39
39
  [24, 24] => :em_exchange_mark, # C-x C-x
40
+ [27, 91, 90] => :completion_journey_up, # S-Tab
40
41
  }.each_pair do |key, func|
41
42
  config.add_default_key_binding_by_keymap(:emacs, key, func)
42
43
  end
@@ -274,6 +275,22 @@ class Reline::ANSI
274
275
  end
275
276
  end
276
277
 
278
+ def self.hide_cursor
279
+ if Reline::Terminfo.enabled?
280
+ @@output.write Reline::Terminfo.tigetstr('civis')
281
+ else
282
+ # ignored
283
+ end
284
+ end
285
+
286
+ def self.show_cursor
287
+ if Reline::Terminfo.enabled?
288
+ @@output.write Reline::Terminfo.tigetstr('cnorm')
289
+ else
290
+ # ignored
291
+ end
292
+ end
293
+
277
294
  def self.erase_after_cursor
278
295
  @@output.write "\e[K"
279
296
  end
@@ -295,8 +312,6 @@ class Reline::ANSI
295
312
 
296
313
  def self.prep
297
314
  retrieve_keybuffer
298
- int_handle = Signal.trap('INT', 'IGNORE')
299
- Signal.trap('INT', int_handle)
300
315
  nil
301
316
  end
302
317
 
data/lib/reline/config.rb CHANGED
@@ -65,6 +65,7 @@ class Reline::Config
65
65
  @history_size = -1 # unlimited
66
66
  @keyseq_timeout = 500
67
67
  @test_mode = false
68
+ @autocompletion = false
68
69
  end
69
70
 
70
71
  def reset
@@ -89,6 +90,14 @@ class Reline::Config
89
90
  (val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
90
91
  end
91
92
 
93
+ def autocompletion=(val)
94
+ @autocompletion = val
95
+ end
96
+
97
+ def autocompletion
98
+ @autocompletion
99
+ end
100
+
92
101
  def keymap
93
102
  @key_actors[@keymap_label]
94
103
  end
@@ -151,6 +151,7 @@ class Reline::LineEditor
151
151
  @screen_height = @screen_size.first
152
152
  reset_variables(prompt, encoding: encoding)
153
153
  @old_trap = Signal.trap(:INT) {
154
+ clear_dialog
154
155
  if @scroll_partial_screen
155
156
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
156
157
  else
@@ -249,6 +250,7 @@ class Reline::LineEditor
249
250
  @drop_terminate_spaces = false
250
251
  @in_pasting = false
251
252
  @auto_indent_proc = nil
253
+ @dialogs = []
252
254
  reset_line
253
255
  end
254
256
 
@@ -414,6 +416,7 @@ class Reline::LineEditor
414
416
  Reline::IOGate.erase_after_cursor
415
417
  end
416
418
  @output.flush
419
+ clear_dialog
417
420
  return
418
421
  end
419
422
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
@@ -424,6 +427,7 @@ class Reline::LineEditor
424
427
  else
425
428
  if @just_cursor_moving and not @rerender_all
426
429
  rendered = just_move_cursor
430
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
427
431
  @just_cursor_moving = false
428
432
  return
429
433
  elsif @previous_line_index or new_highest_in_this != @highest_in_this
@@ -446,18 +450,20 @@ class Reline::LineEditor
446
450
  new_lines = whole_lines
447
451
  end
448
452
  line = modify_lines(new_lines)[@line_index]
453
+ clear_dialog
449
454
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
450
455
  render_partial(prompt, prompt_width, line, @first_line_started_from)
451
456
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
452
457
  scroll_down(1)
453
458
  Reline::IOGate.move_cursor_column(0)
454
459
  Reline::IOGate.erase_after_cursor
455
- elsif not rendered
456
- unless @in_pasting
460
+ else
461
+ if not rendered and not @in_pasting
457
462
  line = modify_lines(whole_lines)[@line_index]
458
463
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
459
464
  render_partial(prompt, prompt_width, line, @first_line_started_from)
460
465
  end
466
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
461
467
  end
462
468
  @buffer_of_lines[@line_index] = @line
463
469
  @rest_height = 0 if @scroll_partial_screen
@@ -472,6 +478,319 @@ class Reline::LineEditor
472
478
  end
473
479
  end
474
480
 
481
+ class DialogProcScope
482
+ def initialize(line_editor, config, proc_to_exec, context)
483
+ @line_editor = line_editor
484
+ @config = config
485
+ @proc_to_exec = proc_to_exec
486
+ @context = context
487
+ @cursor_pos = Reline::CursorPos.new
488
+ end
489
+
490
+ def context
491
+ @context
492
+ end
493
+
494
+ def retrieve_completion_block(set_completion_quote_character = false)
495
+ @line_editor.retrieve_completion_block(set_completion_quote_character)
496
+ end
497
+
498
+ def call_completion_proc_with_checking_args(pre, target, post)
499
+ @line_editor.call_completion_proc_with_checking_args(pre, target, post)
500
+ end
501
+
502
+ def set_dialog(dialog)
503
+ @dialog = dialog
504
+ end
505
+
506
+ def dialog
507
+ @dialog
508
+ end
509
+
510
+ def set_cursor_pos(col, row)
511
+ @cursor_pos.x = col
512
+ @cursor_pos.y = row
513
+ end
514
+
515
+ def cursor_pos
516
+ @cursor_pos
517
+ end
518
+
519
+ def just_cursor_moving
520
+ @line_editor.instance_variable_get(:@just_cursor_moving)
521
+ end
522
+
523
+ def screen_width
524
+ @line_editor.instance_variable_get(:@screen_size).last
525
+ end
526
+
527
+ def completion_journey_data
528
+ @line_editor.instance_variable_get(:@completion_journey_data)
529
+ end
530
+
531
+ def config
532
+ @config
533
+ end
534
+
535
+ def call
536
+ instance_exec(&@proc_to_exec)
537
+ end
538
+ end
539
+
540
+ class Dialog
541
+ attr_reader :name
542
+ attr_accessor :scroll_top, :column, :vertical_offset, :contents, :lines_backup
543
+
544
+ def initialize(name, proc_scope)
545
+ @name = name
546
+ @proc_scope = proc_scope
547
+ @scroll_top = 0
548
+ end
549
+
550
+ def set_cursor_pos(col, row)
551
+ @proc_scope.set_cursor_pos(col, row)
552
+ end
553
+
554
+ def call
555
+ @proc_scope.set_dialog(self)
556
+ @proc_scope.call
557
+ end
558
+ end
559
+
560
+ def add_dialog_proc(name, p, context = nil)
561
+ return if @dialogs.any? { |d| d.name == name }
562
+ @dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
563
+ end
564
+
565
+ DIALOG_HEIGHT = 20
566
+ DIALOG_WIDTH = 40
567
+ private def render_dialog(cursor_column)
568
+ @dialogs.each do |dialog|
569
+ render_each_dialog(dialog, cursor_column)
570
+ end
571
+ end
572
+
573
+ private def render_each_dialog(dialog, cursor_column)
574
+ if @in_pasting
575
+ dialog.contents = nil
576
+ return
577
+ end
578
+ dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
579
+ dialog_render_info = dialog.call
580
+ old_dialog_contents = dialog.contents
581
+ old_dialog_column = dialog.column
582
+ old_dialog_vertical_offset = dialog.vertical_offset
583
+ start = 0
584
+ if dialog_render_info and dialog_render_info.contents and not dialog_render_info.contents.empty?
585
+ height = dialog_render_info.height || DIALOG_HEIGHT
586
+ pointer = dialog_render_info.pointer
587
+ dialog.contents = dialog_render_info.contents
588
+ if dialog.contents.size > height
589
+ if dialog_render_info.pointer
590
+ if dialog_render_info.pointer < 0
591
+ dialog.scroll_top = 0
592
+ elsif (dialog_render_info.pointer - dialog.scroll_top) >= (height - 1)
593
+ dialog.scroll_top = dialog_render_info.pointer - (height - 1)
594
+ elsif (dialog_render_info.pointer - dialog.scroll_top) < 0
595
+ dialog.scroll_top = dialog_render_info.pointer
596
+ end
597
+ pointer = dialog_render_info.pointer - dialog.scroll_top
598
+ end
599
+ dialog.contents = dialog.contents[dialog.scroll_top, height]
600
+ end
601
+ else
602
+ dialog.lines_backup = {
603
+ lines: modify_lines(whole_lines),
604
+ line_index: @line_index,
605
+ first_line_started_from: @first_line_started_from,
606
+ started_from: @started_from,
607
+ byte_pointer: @byte_pointer
608
+ }
609
+ clear_each_dialog(dialog)
610
+ dialog.contents = nil
611
+ return
612
+ end
613
+ upper_space = @first_line_started_from - @started_from
614
+ lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
615
+ dialog.column = dialog_render_info.pos.x
616
+ diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
617
+ if diff > 0
618
+ dialog.column -= diff
619
+ end
620
+ if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
621
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
622
+ elsif upper_space >= height
623
+ dialog.vertical_offset = dialog_render_info.pos.y + -(height + 1)
624
+ else
625
+ if (lower_space + @rest_height - dialog_render_info.pos.y) < height
626
+ scroll_down(height + dialog_render_info.pos.y)
627
+ move_cursor_up(height + dialog_render_info.pos.y)
628
+ end
629
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
630
+ end
631
+ Reline::IOGate.hide_cursor
632
+ reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
633
+ move_cursor_down(dialog.vertical_offset)
634
+ Reline::IOGate.move_cursor_column(dialog.column)
635
+ dialog.contents.each_with_index do |item, i|
636
+ if i == pointer
637
+ bg_color = '45'
638
+ else
639
+ if dialog_render_info.bg_color
640
+ bg_color = dialog_render_info.bg_color
641
+ else
642
+ bg_color = '46'
643
+ end
644
+ end
645
+ @output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
646
+ Reline::IOGate.move_cursor_column(dialog.column)
647
+ move_cursor_down(1) if i < (dialog.contents.size - 1)
648
+ end
649
+ Reline::IOGate.move_cursor_column(cursor_column)
650
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
651
+ Reline::IOGate.show_cursor
652
+ dialog.lines_backup = {
653
+ lines: modify_lines(whole_lines),
654
+ line_index: @line_index,
655
+ first_line_started_from: @first_line_started_from,
656
+ started_from: @started_from,
657
+ byte_pointer: @byte_pointer
658
+ }
659
+ end
660
+
661
+ private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
662
+ return if dialog.lines_backup.nil? or old_dialog_contents.nil?
663
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
664
+ visual_lines = []
665
+ visual_start = nil
666
+ dialog.lines_backup[:lines].each_with_index { |l, i|
667
+ pr = prompt_list ? prompt_list[i] : prompt
668
+ vl, _ = split_by_width(pr + l, @screen_size.last)
669
+ vl.compact!
670
+ if i == dialog.lines_backup[:line_index]
671
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from]
672
+ end
673
+ visual_lines.concat(vl)
674
+ }
675
+ old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
676
+ y = @first_line_started_from + @started_from
677
+ y_diff = y - old_y
678
+ if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
679
+ # rerender top
680
+ move_cursor_down(old_dialog_vertical_offset - y_diff)
681
+ start = visual_start + old_dialog_vertical_offset
682
+ line_num = dialog.vertical_offset - old_dialog_vertical_offset
683
+ line_num.times do |i|
684
+ Reline::IOGate.move_cursor_column(old_dialog_column)
685
+ if visual_lines[start + i].nil?
686
+ s = ' ' * DIALOG_WIDTH
687
+ else
688
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
689
+ end
690
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
691
+ move_cursor_down(1) if i < (line_num - 1)
692
+ end
693
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
694
+ end
695
+ if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
696
+ # rerender bottom
697
+ move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
698
+ start = visual_start + dialog.vertical_offset + dialog.contents.size
699
+ line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
700
+ line_num.times do |i|
701
+ Reline::IOGate.move_cursor_column(old_dialog_column)
702
+ if visual_lines[start + i].nil?
703
+ s = ' ' * DIALOG_WIDTH
704
+ else
705
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
706
+ end
707
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
708
+ move_cursor_down(1) if i < (line_num - 1)
709
+ end
710
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
711
+ end
712
+ if old_dialog_column < dialog.column
713
+ # rerender left
714
+ move_cursor_down(old_dialog_vertical_offset - y_diff)
715
+ width = dialog.column - old_dialog_column
716
+ start = visual_start + old_dialog_vertical_offset
717
+ line_num = old_dialog_contents.size
718
+ line_num.times do |i|
719
+ Reline::IOGate.move_cursor_column(old_dialog_column)
720
+ if visual_lines[start + i].nil?
721
+ s = ' ' * width
722
+ else
723
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
724
+ end
725
+ @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
726
+ move_cursor_down(1) if i < (line_num - 1)
727
+ end
728
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
729
+ end
730
+ if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
731
+ # rerender right
732
+ move_cursor_down(old_dialog_vertical_offset + y_diff)
733
+ width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
734
+ start = visual_start + old_dialog_vertical_offset
735
+ line_num = old_dialog_contents.size
736
+ line_num.times do |i|
737
+ Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
738
+ if visual_lines[start + i].nil?
739
+ s = ' ' * width
740
+ else
741
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
742
+ end
743
+ Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
744
+ @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
745
+ move_cursor_down(1) if i < (line_num - 1)
746
+ end
747
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
748
+ end
749
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
750
+ end
751
+
752
+ private def clear_dialog
753
+ @dialogs.each do |dialog|
754
+ clear_each_dialog(dialog)
755
+ end
756
+ end
757
+
758
+ private def clear_each_dialog(dialog)
759
+ return unless dialog.contents
760
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
761
+ visual_lines = []
762
+ visual_lines_under_dialog = []
763
+ visual_start = nil
764
+ dialog.lines_backup[:lines].each_with_index { |l, i|
765
+ pr = prompt_list ? prompt_list[i] : prompt
766
+ vl, _ = split_by_width(pr + l, @screen_size.last)
767
+ vl.compact!
768
+ if i == dialog.lines_backup[:line_index]
769
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
770
+ end
771
+ visual_lines.concat(vl)
772
+ }
773
+ visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
774
+ visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
775
+ Reline::IOGate.hide_cursor
776
+ move_cursor_down(dialog.vertical_offset)
777
+ dialog_vertical_size = dialog.contents.size
778
+ dialog_vertical_size.times do |i|
779
+ if i < visual_lines_under_dialog.size
780
+ Reline::IOGate.move_cursor_column(0)
781
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
782
+ else
783
+ Reline::IOGate.move_cursor_column(dialog.column)
784
+ @output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
785
+ end
786
+ Reline::IOGate.erase_after_cursor
787
+ move_cursor_down(1) if i < (dialog_vertical_size - 1)
788
+ end
789
+ move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
790
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
791
+ Reline::IOGate.show_cursor
792
+ end
793
+
475
794
  private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
476
795
  if @screen_height < highest_in_all
477
796
  old_scroll_partial_screen = @scroll_partial_screen
@@ -932,6 +1251,16 @@ class Reline::LineEditor
932
1251
  @completion_journey_data = CompletionJourneyData.new(
933
1252
  preposing, postposing,
934
1253
  [target] + list.select{ |item| item.start_with?(target) }, 0)
1254
+ if @completion_journey_data.list.size == 1
1255
+ @completion_journey_data.pointer = 0
1256
+ else
1257
+ case direction
1258
+ when :up
1259
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1260
+ when :down
1261
+ @completion_journey_data.pointer = 1
1262
+ end
1263
+ end
935
1264
  @completion_state = CompletionState::JOURNEY
936
1265
  else
937
1266
  case direction
@@ -946,13 +1275,13 @@ class Reline::LineEditor
946
1275
  @completion_journey_data.pointer = 0
947
1276
  end
948
1277
  end
949
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
950
- @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
951
- line_to_pointer = @completion_journey_data.preposing + completed
952
- @cursor_max = calculate_width(@line)
953
- @cursor = calculate_width(line_to_pointer)
954
- @byte_pointer = line_to_pointer.bytesize
955
1278
  end
1279
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
1280
+ @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
1281
+ line_to_pointer = @completion_journey_data.preposing + completed
1282
+ @cursor_max = calculate_width(@line)
1283
+ @cursor = calculate_width(line_to_pointer)
1284
+ @byte_pointer = line_to_pointer.bytesize
956
1285
  end
957
1286
 
958
1287
  private def run_for_operators(key, method_symbol, &block)
@@ -1127,7 +1456,20 @@ class Reline::LineEditor
1127
1456
  if result.is_a?(Array)
1128
1457
  completion_occurs = true
1129
1458
  process_insert
1130
- complete(result)
1459
+ if @config.autocompletion
1460
+ move_completed_list(result, :down)
1461
+ else
1462
+ complete(result)
1463
+ end
1464
+ end
1465
+ end
1466
+ elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
1467
+ if not @config.disable_completion and @config.autocompletion
1468
+ result = call_completion_proc
1469
+ if result.is_a?(Array)
1470
+ completion_occurs = true
1471
+ process_insert
1472
+ move_completed_list(result, :up)
1131
1473
  end
1132
1474
  end
1133
1475
  elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
@@ -1146,6 +1488,7 @@ class Reline::LineEditor
1146
1488
  end
1147
1489
  unless completion_occurs
1148
1490
  @completion_state = CompletionState::NORMAL
1491
+ @completion_journey_data = nil
1149
1492
  end
1150
1493
  if not @in_pasting and @just_cursor_moving.nil?
1151
1494
  if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
@@ -1165,7 +1508,13 @@ class Reline::LineEditor
1165
1508
 
1166
1509
  def call_completion_proc
1167
1510
  result = retrieve_completion_block(true)
1168
- preposing, target, postposing = result
1511
+ pre, target, post = result
1512
+ result = call_completion_proc_with_checking_args(pre, target, post)
1513
+ Reline.core.instance_variable_set(:@completion_quote_character, nil)
1514
+ result
1515
+ end
1516
+
1517
+ def call_completion_proc_with_checking_args(pre, target, post)
1169
1518
  if @completion_proc and target
1170
1519
  argnum = @completion_proc.parameters.inject(0) { |result, item|
1171
1520
  case item.first
@@ -1179,12 +1528,11 @@ class Reline::LineEditor
1179
1528
  when 1
1180
1529
  result = @completion_proc.(target)
1181
1530
  when 2
1182
- result = @completion_proc.(target, preposing)
1531
+ result = @completion_proc.(target, pre)
1183
1532
  when 3..Float::INFINITY
1184
- result = @completion_proc.(target, preposing, postposing)
1533
+ result = @completion_proc.(target, pre, post)
1185
1534
  end
1186
1535
  end
1187
- Reline.core.instance_variable_set(:@completion_quote_character, nil)
1188
1536
  result
1189
1537
  end
1190
1538
 
@@ -185,6 +185,36 @@ class Reline::Unicode
185
185
  [lines, height]
186
186
  end
187
187
 
188
+ # Take a chunk of a String with escape sequences.
189
+ def self.take_range(str, col, length, encoding = str.encoding)
190
+ chunk = String.new(encoding: encoding)
191
+ width = 0
192
+ rest = str.encode(Encoding::UTF_8)
193
+ in_zero_width = false
194
+ rest.scan(WIDTH_SCANNER) do |gc|
195
+ case
196
+ when gc[NON_PRINTING_START_INDEX]
197
+ in_zero_width = true
198
+ when gc[NON_PRINTING_END_INDEX]
199
+ in_zero_width = false
200
+ when gc[CSI_REGEXP_INDEX]
201
+ chunk << gc[CSI_REGEXP_INDEX]
202
+ when gc[OSC_REGEXP_INDEX]
203
+ chunk << gc[OSC_REGEXP_INDEX]
204
+ when gc[GRAPHEME_CLUSTER_INDEX]
205
+ gc = gc[GRAPHEME_CLUSTER_INDEX]
206
+ if in_zero_width
207
+ chunk << gc
208
+ else
209
+ width = get_mbchar_width(gc)
210
+ break if (width + length) <= col
211
+ chunk << gc if col <= width
212
+ end
213
+ end
214
+ end
215
+ chunk
216
+ end
217
+
188
218
  def self.get_next_mbchar_size(line, byte_pointer)
189
219
  grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
190
220
  grapheme ? grapheme.bytesize : 0
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.2.7'
2
+ VERSION = '0.2.8.pre.4'
3
3
  end
@@ -42,6 +42,14 @@ class Reline::Windows
42
42
  }.each_pair do |key, func|
43
43
  config.add_default_key_binding_by_keymap(:emacs, key, func)
44
44
  end
45
+
46
+ # Emulate ANSI key sequence.
47
+ {
48
+ [27, 91, 90] => :completion_journey_up, # S-Tab
49
+ }.each_pair do |key, func|
50
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
51
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
52
+ end
45
53
  end
46
54
 
47
55
  if defined? JRUBY_VERSION
@@ -106,6 +114,7 @@ class Reline::Windows
106
114
  SCROLLLOCK_ON = 0x0040
107
115
  SHIFT_PRESSED = 0x0010
108
116
 
117
+ VK_TAB = 0x09
109
118
  VK_END = 0x23
110
119
  VK_HOME = 0x24
111
120
  VK_LEFT = 0x25
@@ -133,9 +142,11 @@ class Reline::Windows
133
142
  @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
134
143
  @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
135
144
  @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
145
+ @@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
136
146
 
137
147
  @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
138
148
  @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
149
+ @@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
139
150
  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
140
151
 
141
152
  private_class_method def self.getconsolemode
@@ -197,6 +208,9 @@ class Reline::Windows
197
208
  [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
198
209
  [ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ],
199
210
  [ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ],
211
+
212
+ # Emulate ANSI key sequence.
213
+ [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
200
214
  ]
201
215
 
202
216
  def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
@@ -218,6 +232,7 @@ class Reline::Windows
218
232
  def self.check_input_event
219
233
  num_of_events = 0.chr * 8
220
234
  while @@output_buf.empty? #or true
235
+ next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
221
236
  next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
222
237
  input_record = 0.chr * 18
223
238
  read_event = 0.chr * 4
@@ -341,6 +356,20 @@ class Reline::Windows
341
356
  raise NotImplementedError
342
357
  end
343
358
 
359
+ def self.hide_cursor
360
+ size = 100
361
+ visible = 0 # 0 means false
362
+ cursor_info = [size, visible].pack('Li')
363
+ @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
364
+ end
365
+
366
+ def self.show_cursor
367
+ size = 100
368
+ visible = 1 # 1 means true
369
+ cursor_info = [size, visible].pack('Li')
370
+ @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
371
+ end
372
+
344
373
  def self.set_winch_handler(&handler)
345
374
  @@winch_handler = handler
346
375
  end
data/lib/reline.rb CHANGED
@@ -18,6 +18,7 @@ module Reline
18
18
 
19
19
  Key = Struct.new('Key', :char, :combined_char, :with_meta)
20
20
  CursorPos = Struct.new(:x, :y)
21
+ DialogRenderInfo = Struct.new(:pos, :contents, :pointer, :bg_color, :height, keyword_init: true)
21
22
 
22
23
  class Core
23
24
  ATTR_READER_NAMES = %i(
@@ -44,6 +45,7 @@ module Reline
44
45
 
45
46
  def initialize
46
47
  self.output = STDOUT
48
+ @dialog_proc_list = []
47
49
  yield self
48
50
  @completion_quote_character = nil
49
51
  @bracketed_paste_finished = false
@@ -106,6 +108,14 @@ module Reline
106
108
  @completion_proc = p
107
109
  end
108
110
 
111
+ def autocompletion
112
+ @config.autocompletion
113
+ end
114
+
115
+ def autocompletion=(val)
116
+ @config.autocompletion = val
117
+ end
118
+
109
119
  def output_modifier_proc=(p)
110
120
  raise ArgumentError unless p.respond_to?(:call) or p.nil?
111
121
  @output_modifier_proc = p
@@ -130,6 +140,12 @@ module Reline
130
140
  @dig_perfect_match_proc = p
131
141
  end
132
142
 
143
+ def add_dialog_proc(name_sym, p, context = nil)
144
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
145
+ raise ArgumentError unless name_sym.instance_of?(Symbol)
146
+ @dialog_proc_list << [name_sym, p, context]
147
+ end
148
+
133
149
  def input=(val)
134
150
  raise TypeError unless val.respond_to?(:getc) or val.nil?
135
151
  if val.respond_to?(:getc)
@@ -171,6 +187,45 @@ module Reline
171
187
  Reline::IOGate.get_screen_size
172
188
  end
173
189
 
190
+ Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
191
+ # autocomplete
192
+ return nil unless config.autocompletion
193
+ if just_cursor_moving and completion_journey_data.nil?
194
+ # Auto complete starts only when edited
195
+ return nil
196
+ end
197
+ pre, target, post= retrieve_completion_block(true)
198
+ if target.nil? or target.empty?# or target.size <= 3
199
+ return nil
200
+ end
201
+ if completion_journey_data and completion_journey_data.list
202
+ result = completion_journey_data.list.dup
203
+ result.shift
204
+ pointer = completion_journey_data.pointer - 1
205
+ else
206
+ result = call_completion_proc_with_checking_args(pre, target, post)
207
+ pointer = nil
208
+ end
209
+ if result and result.size == 1 and result[0] == target
210
+ result = nil
211
+ end
212
+ target_width = Reline::Unicode.calculate_width(target)
213
+ x = cursor_pos.x - target_width
214
+ if x < 0
215
+ x = screen_width + x
216
+ y = -1
217
+ else
218
+ y = 0
219
+ end
220
+ cursor_pos_to_render = Reline::CursorPos.new(x, y)
221
+ if context and context.is_a?(Array)
222
+ context.clear
223
+ context.push(cursor_pos_to_render, result, pointer, dialog)
224
+ end
225
+ DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, pointer: pointer, height: 15)
226
+ }
227
+ Reline::DEFAULT_DIALOG_CONTEXT = Array.new
228
+
174
229
  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
175
230
  unless confirm_multiline_termination
176
231
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
@@ -230,6 +285,10 @@ module Reline
230
285
  line_editor.auto_indent_proc = auto_indent_proc
231
286
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
232
287
  line_editor.pre_input_hook = pre_input_hook
288
+ @dialog_proc_list.each do |d|
289
+ name_sym, dialog_proc, context = d
290
+ line_editor.add_dialog_proc(name_sym, dialog_proc, context)
291
+ end
233
292
 
234
293
  unless config.test_mode
235
294
  config.read
@@ -424,6 +483,8 @@ module Reline
424
483
  def_single_delegators :core, :ambiguous_width
425
484
  def_single_delegators :core, :last_incremental_search
426
485
  def_single_delegators :core, :last_incremental_search=
486
+ def_single_delegators :core, :add_dialog_proc
487
+ def_single_delegators :core, :autocompletion, :autocompletion=
427
488
 
428
489
  def_single_delegators :core, :readmultiline
429
490
  def_instance_delegators self, :readmultiline
@@ -445,6 +506,7 @@ module Reline
445
506
  core.completer_quote_characters = '"\''
446
507
  core.filename_quote_characters = ""
447
508
  core.special_prefixes = ""
509
+ core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
448
510
  }
449
511
  end
450
512
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8.pre.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-12 00:00:00.000000000 Z
11
+ date: 2021-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -69,9 +69,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
69
  version: '2.5'
70
70
  required_rubygems_version: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ">="
72
+ - - ">"
73
73
  - !ruby/object:Gem::Version
74
- version: '0'
74
+ version: 1.3.1
75
75
  requirements: []
76
76
  rubygems_version: 3.2.22
77
77
  signing_key: