reline 0.2.7 → 0.2.8.pre.4

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
  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: