reline 0.2.8.pre.3 → 0.2.8.pre.7

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: 92eb26edb27bc79f95c9f54a4396b86e764acb928a4d704db8b8d1016331aa67
4
- data.tar.gz: 04b7af1f6107bc8cd302494a057e6cfd812c60ec3768b3cd475105d8e119d20c
3
+ metadata.gz: c109228f50bec62ad64f9231151865fc6767b95ea5e714c254f2ce8f4480d3e6
4
+ data.tar.gz: 8fe386600dca93bb0fce22c72a628392840ad0922d5285afedd5542bdb9e1f3b
5
5
  SHA512:
6
- metadata.gz: 40503d95634408feb981bcb3448679ed6c847123465b5b965399c75c4bbc9ac0ddbb5422386d209613e45b34a85f2ecb275fac7587272da88de7a16b2feb40b0
7
- data.tar.gz: '030383e1c77101f296344c794443439af32c6fd7f2f86f0230666287df901e5aea9f25d78242ab9ea16253ab17faa97c02fc234137e166a71edbac2de2f44f16'
6
+ metadata.gz: c1652f7c8c852328fe3371e8881e1fdf13e0c41c4e34894e08c36c15c7521f33035bdfad34cdf0264a3ef4bdf23e1cb45a4a25e3231f592831dd6e8fb6cbeb8f
7
+ data.tar.gz: 9aadb3ba73cd2c8ca051e0310d297dc76e6a7a210c4048620c143bf76afce3b7a780f642d2f8167894978301daa82c7229fdd443718ba25a14e2970ead7f9702
@@ -24,6 +24,7 @@ class Reline::GeneralIO
24
24
  end
25
25
 
26
26
  @@buf = []
27
+ @@input = STDIN
27
28
 
28
29
  def self.input=(val)
29
30
  @@input = val
@@ -251,6 +251,7 @@ class Reline::LineEditor
251
251
  @in_pasting = false
252
252
  @auto_indent_proc = nil
253
253
  @dialogs = []
254
+ @last_key = nil
254
255
  reset_line
255
256
  end
256
257
 
@@ -499,11 +500,27 @@ class Reline::LineEditor
499
500
  @line_editor.call_completion_proc_with_checking_args(pre, target, post)
500
501
  end
501
502
 
503
+ def set_dialog(dialog)
504
+ @dialog = dialog
505
+ end
506
+
507
+ def dialog
508
+ @dialog
509
+ end
510
+
502
511
  def set_cursor_pos(col, row)
503
512
  @cursor_pos.x = col
504
513
  @cursor_pos.y = row
505
514
  end
506
515
 
516
+ def set_key(key)
517
+ @key = key
518
+ end
519
+
520
+ def key
521
+ @key
522
+ end
523
+
507
524
  def cursor_pos
508
525
  @cursor_pos
509
526
  end
@@ -530,19 +547,34 @@ class Reline::LineEditor
530
547
  end
531
548
 
532
549
  class Dialog
533
- attr_reader :name
534
- attr_accessor :column, :vertical_offset, :contents, :lines_backup
550
+ attr_reader :name, :contents, :width
551
+ attr_accessor :scroll_top, :column, :vertical_offset, :lines_backup, :trap_key
535
552
 
536
553
  def initialize(name, proc_scope)
537
554
  @name = name
538
555
  @proc_scope = proc_scope
556
+ @width = nil
557
+ @scroll_top = 0
539
558
  end
540
559
 
541
560
  def set_cursor_pos(col, row)
542
561
  @proc_scope.set_cursor_pos(col, row)
543
562
  end
544
563
 
545
- def call
564
+ def width=(v)
565
+ @width = v
566
+ end
567
+
568
+ def contents=(contents)
569
+ @contents = contents
570
+ if contents and @width.nil?
571
+ @width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max
572
+ end
573
+ end
574
+
575
+ def call(key)
576
+ @proc_scope.set_dialog(self)
577
+ @proc_scope.set_key(key)
546
578
  @proc_scope.call
547
579
  end
548
580
  end
@@ -553,27 +585,25 @@ class Reline::LineEditor
553
585
  end
554
586
 
555
587
  DIALOG_HEIGHT = 20
556
- DIALOG_WIDTH = 40
557
588
  private def render_dialog(cursor_column)
558
589
  @dialogs.each do |dialog|
559
590
  render_each_dialog(dialog, cursor_column)
560
591
  end
561
592
  end
562
593
 
594
+ private def padding_space_with_escape_sequences(str, width)
595
+ str + (' ' * (width - calculate_width(str, true)))
596
+ end
597
+
563
598
  private def render_each_dialog(dialog, cursor_column)
564
599
  if @in_pasting
565
600
  dialog.contents = nil
601
+ dialog.trap_key = nil
566
602
  return
567
603
  end
568
604
  dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
569
- pos, result, pointer, bg = dialog.call
570
- old_dialog_contents = dialog.contents
571
- old_dialog_column = dialog.column
572
- old_dialog_vertical_offset = dialog.vertical_offset
573
- if result and not result.empty?
574
- dialog.contents = result
575
- dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
576
- else
605
+ dialog_render_info = dialog.call(@last_key)
606
+ if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
577
607
  dialog.lines_backup = {
578
608
  lines: modify_lines(whole_lines),
579
609
  line_index: @line_index,
@@ -583,41 +613,66 @@ class Reline::LineEditor
583
613
  }
584
614
  clear_each_dialog(dialog)
585
615
  dialog.contents = nil
616
+ dialog.trap_key = nil
586
617
  return
587
618
  end
619
+ old_dialog = dialog.clone
620
+ dialog.contents = dialog_render_info.contents
621
+ pointer = dialog_render_info.pointer
622
+ if dialog_render_info.width
623
+ dialog.width = dialog_render_info.width
624
+ else
625
+ dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
626
+ end
627
+ height = dialog_render_info.height || DIALOG_HEIGHT
628
+ height = dialog.contents.size if dialog.contents.size < height
629
+ if dialog.contents.size > height
630
+ if dialog_render_info.pointer
631
+ if dialog_render_info.pointer < 0
632
+ dialog.scroll_top = 0
633
+ elsif (dialog_render_info.pointer - dialog.scroll_top) >= (height - 1)
634
+ dialog.scroll_top = dialog_render_info.pointer - (height - 1)
635
+ elsif (dialog_render_info.pointer - dialog.scroll_top) < 0
636
+ dialog.scroll_top = dialog_render_info.pointer
637
+ end
638
+ pointer = dialog_render_info.pointer - dialog.scroll_top
639
+ end
640
+ dialog.contents = dialog.contents[dialog.scroll_top, height]
641
+ end
588
642
  upper_space = @first_line_started_from - @started_from
589
643
  lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
590
- dialog.column = pos.x
591
- diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
644
+ dialog.column = dialog_render_info.pos.x
645
+ diff = (dialog.column + dialog.width) - (@screen_size.last - 1)
592
646
  if diff > 0
593
647
  dialog.column -= diff
594
648
  end
595
- if (lower_space + @rest_height) >= DIALOG_HEIGHT
596
- dialog.vertical_offset = pos.y + 1
597
- elsif upper_space >= DIALOG_HEIGHT
598
- dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
649
+ if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
650
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
651
+ elsif upper_space >= height
652
+ dialog.vertical_offset = dialog_render_info.pos.y - height
599
653
  else
600
- if (lower_space + @rest_height) < DIALOG_HEIGHT
601
- scroll_down(DIALOG_HEIGHT)
602
- move_cursor_up(DIALOG_HEIGHT)
654
+ if (lower_space + @rest_height - dialog_render_info.pos.y) < height
655
+ scroll_down(height + dialog_render_info.pos.y)
656
+ move_cursor_up(height + dialog_render_info.pos.y)
603
657
  end
604
- dialog.vertical_offset = pos.y + 1
658
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
605
659
  end
606
660
  Reline::IOGate.hide_cursor
607
- reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
661
+ reset_dialog(dialog, old_dialog)
608
662
  move_cursor_down(dialog.vertical_offset)
609
663
  Reline::IOGate.move_cursor_column(dialog.column)
610
664
  dialog.contents.each_with_index do |item, i|
611
665
  if i == pointer
612
666
  bg_color = '45'
613
667
  else
614
- if bg
615
- bg_color = bg
668
+ if dialog_render_info.bg_color
669
+ bg_color = dialog_render_info.bg_color
616
670
  else
617
671
  bg_color = '46'
618
672
  end
619
673
  end
620
- @output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
674
+ str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, dialog.width), dialog.width)
675
+ @output.write "\e[#{bg_color}m#{str}\e[49m"
621
676
  Reline::IOGate.move_cursor_column(dialog.column)
622
677
  move_cursor_down(1) if i < (dialog.contents.size - 1)
623
678
  end
@@ -633,8 +688,8 @@ class Reline::LineEditor
633
688
  }
634
689
  end
635
690
 
636
- private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
637
- return if dialog.lines_backup.nil? or old_dialog_contents.nil?
691
+ private def reset_dialog(dialog, old_dialog)
692
+ return if dialog.lines_backup.nil? or old_dialog.contents.nil?
638
693
  prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
639
694
  visual_lines = []
640
695
  visual_start = nil
@@ -650,76 +705,80 @@ class Reline::LineEditor
650
705
  old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
651
706
  y = @first_line_started_from + @started_from
652
707
  y_diff = y - old_y
653
- if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
708
+ if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
654
709
  # rerender top
655
- move_cursor_down(old_dialog_vertical_offset - y_diff)
656
- start = visual_start + old_dialog_vertical_offset
657
- line_num = dialog.vertical_offset - old_dialog_vertical_offset
710
+ move_cursor_down(old_dialog.vertical_offset - y_diff)
711
+ start = visual_start + old_dialog.vertical_offset
712
+ line_num = dialog.vertical_offset - old_dialog.vertical_offset
658
713
  line_num.times do |i|
659
- Reline::IOGate.move_cursor_column(old_dialog_column)
714
+ Reline::IOGate.move_cursor_column(old_dialog.column)
660
715
  if visual_lines[start + i].nil?
661
- s = ' ' * DIALOG_WIDTH
716
+ s = ' ' * dialog.width
662
717
  else
663
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
718
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
719
+ s = padding_space_with_escape_sequences(s, dialog.width)
664
720
  end
665
- @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
721
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
666
722
  move_cursor_down(1) if i < (line_num - 1)
667
723
  end
668
- move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
724
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
669
725
  end
670
- if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
726
+ if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
671
727
  # rerender bottom
672
728
  move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
673
729
  start = visual_start + dialog.vertical_offset + dialog.contents.size
674
- line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
730
+ line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
675
731
  line_num.times do |i|
676
- Reline::IOGate.move_cursor_column(old_dialog_column)
732
+ Reline::IOGate.move_cursor_column(old_dialog.column)
677
733
  if visual_lines[start + i].nil?
678
- s = ' ' * DIALOG_WIDTH
734
+ s = ' ' * dialog.width
679
735
  else
680
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
736
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
737
+ s = padding_space_with_escape_sequences(s, dialog.width)
681
738
  end
682
- @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
739
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
683
740
  move_cursor_down(1) if i < (line_num - 1)
684
741
  end
685
742
  move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
686
743
  end
687
- if old_dialog_column < dialog.column
744
+ if old_dialog.column < dialog.column
688
745
  # rerender left
689
- move_cursor_down(old_dialog_vertical_offset - y_diff)
690
- width = dialog.column - old_dialog_column
691
- start = visual_start + old_dialog_vertical_offset
692
- line_num = old_dialog_contents.size
746
+ move_cursor_down(old_dialog.vertical_offset - y_diff)
747
+ width = dialog.column - old_dialog.column
748
+ start = visual_start + old_dialog.vertical_offset
749
+ line_num = old_dialog.contents.size
693
750
  line_num.times do |i|
694
- Reline::IOGate.move_cursor_column(old_dialog_column)
751
+ Reline::IOGate.move_cursor_column(old_dialog.column)
695
752
  if visual_lines[start + i].nil?
696
753
  s = ' ' * width
697
754
  else
698
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
755
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
756
+ s = padding_space_with_escape_sequences(s, dialog.width)
699
757
  end
700
- @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
758
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
701
759
  move_cursor_down(1) if i < (line_num - 1)
702
760
  end
703
- move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
761
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
704
762
  end
705
- if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
763
+ if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
706
764
  # rerender right
707
- move_cursor_down(old_dialog_vertical_offset + y_diff)
708
- width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
709
- start = visual_start + old_dialog_vertical_offset
710
- line_num = old_dialog_contents.size
765
+ move_cursor_down(old_dialog.vertical_offset + y_diff)
766
+ width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
767
+ start = visual_start + old_dialog.vertical_offset
768
+ line_num = old_dialog.contents.size
711
769
  line_num.times do |i|
712
- Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
770
+ Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
713
771
  if visual_lines[start + i].nil?
714
772
  s = ' ' * width
715
773
  else
716
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
774
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
775
+ s = padding_space_with_escape_sequences(s, dialog.width)
717
776
  end
718
- Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
719
- @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
777
+ Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
778
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
720
779
  move_cursor_down(1) if i < (line_num - 1)
721
780
  end
722
- move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
781
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
723
782
  end
724
783
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
725
784
  end
@@ -731,6 +790,7 @@ class Reline::LineEditor
731
790
  end
732
791
 
733
792
  private def clear_each_dialog(dialog)
793
+ dialog.trap_key = nil
734
794
  return unless dialog.contents
735
795
  prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
736
796
  visual_lines = []
@@ -752,13 +812,14 @@ class Reline::LineEditor
752
812
  dialog_vertical_size = dialog.contents.size
753
813
  dialog_vertical_size.times do |i|
754
814
  if i < visual_lines_under_dialog.size
755
- Reline::IOGate.move_cursor_column(0)
756
- @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
815
+ Reline::IOGate.move_cursor_column(dialog.column)
816
+ str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
817
+ str = padding_space_with_escape_sequences(str, dialog.width)
818
+ @output.write "\e[39m\e[49m#{str}\e[39m\e[49m"
757
819
  else
758
820
  Reline::IOGate.move_cursor_column(dialog.column)
759
- @output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
821
+ @output.write "\e[39m\e[49m#{' ' * dialog.width}\e[39m\e[49m"
760
822
  end
761
- Reline::IOGate.erase_after_cursor
762
823
  move_cursor_down(1) if i < (dialog_vertical_size - 1)
763
824
  end
764
825
  move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
@@ -1252,8 +1313,10 @@ class Reline::LineEditor
1252
1313
  end
1253
1314
  end
1254
1315
  completed = @completion_journey_data.list[@completion_journey_data.pointer]
1255
- @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
1256
- line_to_pointer = @completion_journey_data.preposing + completed
1316
+ new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
1317
+ @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
1318
+ line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
1319
+ line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
1257
1320
  @cursor_max = calculate_width(@line)
1258
1321
  @cursor = calculate_width(line_to_pointer)
1259
1322
  @byte_pointer = line_to_pointer.bytesize
@@ -1414,6 +1477,13 @@ class Reline::LineEditor
1414
1477
  end
1415
1478
 
1416
1479
  def input_key(key)
1480
+ @last_key = key
1481
+ @dialogs.each do |dialog|
1482
+ # The dialog will intercept the key if trap_key is set.
1483
+ if dialog.trap_key and dialog.trap_key.match?(key)
1484
+ return
1485
+ end
1486
+ end
1417
1487
  @just_cursor_moving = nil
1418
1488
  if key.char.nil?
1419
1489
  if @first_char
@@ -0,0 +1,3156 @@
1
+ require 'reline/kill_ring'
2
+ require 'reline/unicode'
3
+
4
+ require 'tempfile'
5
+
6
+ class Reline::LineEditor
7
+ # TODO: undo
8
+ attr_reader :line
9
+ attr_reader :byte_pointer
10
+ attr_accessor :confirm_multiline_termination_proc
11
+ attr_accessor :completion_proc
12
+ attr_accessor :completion_append_character
13
+ attr_accessor :output_modifier_proc
14
+ attr_accessor :prompt_proc
15
+ attr_accessor :auto_indent_proc
16
+ attr_accessor :pre_input_hook
17
+ attr_accessor :dig_perfect_match_proc
18
+ attr_writer :output
19
+
20
+ VI_MOTIONS = %i{
21
+ ed_prev_char
22
+ ed_next_char
23
+ vi_zero
24
+ ed_move_to_beg
25
+ ed_move_to_end
26
+ vi_to_column
27
+ vi_next_char
28
+ vi_prev_char
29
+ vi_next_word
30
+ vi_prev_word
31
+ vi_to_next_char
32
+ vi_to_prev_char
33
+ vi_end_word
34
+ vi_next_big_word
35
+ vi_prev_big_word
36
+ vi_end_big_word
37
+ vi_repeat_next_char
38
+ vi_repeat_prev_char
39
+ }
40
+
41
+ module CompletionState
42
+ NORMAL = :normal
43
+ COMPLETION = :completion
44
+ MENU = :menu
45
+ JOURNEY = :journey
46
+ MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
47
+ PERFECT_MATCH = :perfect_match
48
+ end
49
+
50
+ CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
51
+ MenuInfo = Struct.new('MenuInfo', :target, :list)
52
+
53
+ PROMPT_LIST_CACHE_TIMEOUT = 0.5
54
+
55
+ def initialize(config, encoding)
56
+ @config = config
57
+ @completion_append_character = ''
58
+ reset_variables(encoding: encoding)
59
+ end
60
+
61
+ def set_pasting_state(in_pasting)
62
+ @in_pasting = in_pasting
63
+ end
64
+
65
+ def simplified_rendering?
66
+ if finished?
67
+ false
68
+ elsif @just_cursor_moving and not @rerender_all
69
+ true
70
+ else
71
+ not @rerender_all and not finished? and @in_pasting
72
+ end
73
+ end
74
+
75
+ private def check_mode_string
76
+ mode_string = nil
77
+ if @config.show_mode_in_prompt
78
+ if @config.editing_mode_is?(:vi_command)
79
+ mode_string = @config.vi_cmd_mode_string
80
+ elsif @config.editing_mode_is?(:vi_insert)
81
+ mode_string = @config.vi_ins_mode_string
82
+ elsif @config.editing_mode_is?(:emacs)
83
+ mode_string = @config.emacs_mode_string
84
+ else
85
+ mode_string = '?'
86
+ end
87
+ end
88
+ if mode_string != @prev_mode_string
89
+ @rerender_all = true
90
+ end
91
+ @prev_mode_string = mode_string
92
+ mode_string
93
+ end
94
+
95
+ private def check_multiline_prompt(buffer, prompt)
96
+ if @vi_arg
97
+ prompt = "(arg: #{@vi_arg}) "
98
+ @rerender_all = true
99
+ elsif @searching_prompt
100
+ prompt = @searching_prompt
101
+ @rerender_all = true
102
+ else
103
+ prompt = @prompt
104
+ end
105
+ if simplified_rendering?
106
+ mode_string = check_mode_string
107
+ prompt = mode_string + prompt if mode_string
108
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
109
+ end
110
+ if @prompt_proc
111
+ use_cached_prompt_list = false
112
+ if @cached_prompt_list
113
+ if @just_cursor_moving
114
+ use_cached_prompt_list = true
115
+ elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
116
+ use_cached_prompt_list = true
117
+ end
118
+ end
119
+ use_cached_prompt_list = false if @rerender_all
120
+ if use_cached_prompt_list
121
+ prompt_list = @cached_prompt_list
122
+ else
123
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
124
+ @prompt_cache_time = Time.now.to_f
125
+ end
126
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
127
+ prompt_list = [prompt] if prompt_list.empty?
128
+ mode_string = check_mode_string
129
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
130
+ prompt = prompt_list[@line_index]
131
+ prompt = prompt_list[0] if prompt.nil?
132
+ prompt = prompt_list.last if prompt.nil?
133
+ if buffer.size > prompt_list.size
134
+ (buffer.size - prompt_list.size).times do
135
+ prompt_list << prompt_list.last
136
+ end
137
+ end
138
+ prompt_width = calculate_width(prompt, true)
139
+ [prompt, prompt_width, prompt_list]
140
+ else
141
+ mode_string = check_mode_string
142
+ prompt = mode_string + prompt if mode_string
143
+ prompt_width = calculate_width(prompt, true)
144
+ [prompt, prompt_width, nil]
145
+ end
146
+ end
147
+
148
+ def reset(prompt = '', encoding:)
149
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
150
+ @screen_size = Reline::IOGate.get_screen_size
151
+ @screen_height = @screen_size.first
152
+ reset_variables(prompt, encoding: encoding)
153
+ @old_trap = Signal.trap(:INT) {
154
+ clear_dialog
155
+ if @scroll_partial_screen
156
+ move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
157
+ else
158
+ move_cursor_down(@highest_in_all - @line_index - 1)
159
+ end
160
+ Reline::IOGate.move_cursor_column(0)
161
+ scroll_down(1)
162
+ case @old_trap
163
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
164
+ raise Interrupt
165
+ when 'IGNORE'
166
+ # Do nothing
167
+ when 'EXIT'
168
+ exit
169
+ else
170
+ @old_trap.call
171
+ end
172
+ }
173
+ Reline::IOGate.set_winch_handler do
174
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
175
+ old_screen_size = @screen_size
176
+ @screen_size = Reline::IOGate.get_screen_size
177
+ @screen_height = @screen_size.first
178
+ if old_screen_size.last < @screen_size.last # columns increase
179
+ @rerender_all = true
180
+ rerender
181
+ else
182
+ back = 0
183
+ new_buffer = whole_lines
184
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
185
+ new_buffer.each_with_index do |line, index|
186
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
187
+ width = prompt_width + calculate_width(line)
188
+ height = calculate_height_by_width(width)
189
+ back += height
190
+ end
191
+ @highest_in_all = back
192
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
193
+ @first_line_started_from =
194
+ if @line_index.zero?
195
+ 0
196
+ else
197
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
198
+ end
199
+ if @prompt_proc
200
+ prompt = prompt_list[@line_index]
201
+ prompt_width = calculate_width(prompt, true)
202
+ end
203
+ calculate_nearest_cursor
204
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
205
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
206
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
207
+ @rerender_all = true
208
+ end
209
+ end
210
+ end
211
+
212
+ def finalize
213
+ Signal.trap('SIGINT', @old_trap)
214
+ end
215
+
216
+ def eof?
217
+ @eof
218
+ end
219
+
220
+ def reset_variables(prompt = '', encoding:)
221
+ @prompt = prompt
222
+ @mark_pointer = nil
223
+ @encoding = encoding
224
+ @is_multiline = false
225
+ @finished = false
226
+ @cleared = false
227
+ @rerender_all = false
228
+ @history_pointer = nil
229
+ @kill_ring ||= Reline::KillRing.new
230
+ @vi_clipboard = ''
231
+ @vi_arg = nil
232
+ @waiting_proc = nil
233
+ @waiting_operator_proc = nil
234
+ @waiting_operator_vi_arg = nil
235
+ @completion_journey_data = nil
236
+ @completion_state = CompletionState::NORMAL
237
+ @perfect_matched = nil
238
+ @menu_info = nil
239
+ @first_prompt = true
240
+ @searching_prompt = nil
241
+ @first_char = true
242
+ @add_newline_to_end_of_buffer = false
243
+ @just_cursor_moving = nil
244
+ @cached_prompt_list = nil
245
+ @prompt_cache_time = nil
246
+ @eof = false
247
+ @continuous_insertion_buffer = String.new(encoding: @encoding)
248
+ @scroll_partial_screen = nil
249
+ @prev_mode_string = nil
250
+ @drop_terminate_spaces = false
251
+ @in_pasting = false
252
+ @auto_indent_proc = nil
253
+ @dialogs = []
254
+ reset_line
255
+ end
256
+
257
+ def reset_line
258
+ @cursor = 0
259
+ @cursor_max = 0
260
+ @byte_pointer = 0
261
+ @buffer_of_lines = [String.new(encoding: @encoding)]
262
+ @line_index = 0
263
+ @previous_line_index = nil
264
+ @line = @buffer_of_lines[0]
265
+ @first_line_started_from = 0
266
+ @move_up = 0
267
+ @started_from = 0
268
+ @highest_in_this = 1
269
+ @highest_in_all = 1
270
+ @line_backup_in_history = nil
271
+ @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
272
+ @check_new_auto_indent = false
273
+ end
274
+
275
+ def multiline_on
276
+ @is_multiline = true
277
+ end
278
+
279
+ def multiline_off
280
+ @is_multiline = false
281
+ end
282
+
283
+ private def calculate_height_by_lines(lines, prompt)
284
+ result = 0
285
+ prompt_list = prompt.is_a?(Array) ? prompt : nil
286
+ lines.each_with_index { |line, i|
287
+ prompt = prompt_list[i] if prompt_list and prompt_list[i]
288
+ result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
289
+ }
290
+ result
291
+ end
292
+
293
+ private def insert_new_line(cursor_line, next_line)
294
+ @line = cursor_line
295
+ @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
296
+ @previous_line_index = @line_index
297
+ @line_index += 1
298
+ @just_cursor_moving = false
299
+ end
300
+
301
+ private def calculate_height_by_width(width)
302
+ width.div(@screen_size.last) + 1
303
+ end
304
+
305
+ private def split_by_width(str, max_width)
306
+ Reline::Unicode.split_by_width(str, max_width, @encoding)
307
+ end
308
+
309
+ private def scroll_down(val)
310
+ if val <= @rest_height
311
+ Reline::IOGate.move_cursor_down(val)
312
+ @rest_height -= val
313
+ else
314
+ Reline::IOGate.move_cursor_down(@rest_height)
315
+ Reline::IOGate.scroll_down(val - @rest_height)
316
+ @rest_height = 0
317
+ end
318
+ end
319
+
320
+ private def move_cursor_up(val)
321
+ if val > 0
322
+ Reline::IOGate.move_cursor_up(val)
323
+ @rest_height += val
324
+ elsif val < 0
325
+ move_cursor_down(-val)
326
+ end
327
+ end
328
+
329
+ private def move_cursor_down(val)
330
+ if val > 0
331
+ Reline::IOGate.move_cursor_down(val)
332
+ @rest_height -= val
333
+ @rest_height = 0 if @rest_height < 0
334
+ elsif val < 0
335
+ move_cursor_up(-val)
336
+ end
337
+ end
338
+
339
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
340
+ new_cursor_max = calculate_width(line_to_calc)
341
+ new_cursor = 0
342
+ new_byte_pointer = 0
343
+ height = 1
344
+ max_width = @screen_size.last
345
+ if @config.editing_mode_is?(:vi_command)
346
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
347
+ if last_byte_size > 0
348
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
349
+ last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
350
+ end_of_line_cursor = new_cursor_max - last_width
351
+ else
352
+ end_of_line_cursor = new_cursor_max
353
+ end
354
+ else
355
+ end_of_line_cursor = new_cursor_max
356
+ end
357
+ line_to_calc.grapheme_clusters.each do |gc|
358
+ mbchar = gc.encode(Encoding::UTF_8)
359
+ mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
360
+ now = new_cursor + mbchar_width
361
+ if now > end_of_line_cursor or now > cursor
362
+ break
363
+ end
364
+ new_cursor += mbchar_width
365
+ if new_cursor > max_width * height
366
+ height += 1
367
+ end
368
+ new_byte_pointer += gc.bytesize
369
+ end
370
+ new_started_from = height - 1
371
+ if update
372
+ @cursor = new_cursor
373
+ @cursor_max = new_cursor_max
374
+ @started_from = new_started_from
375
+ @byte_pointer = new_byte_pointer
376
+ else
377
+ [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
378
+ end
379
+ end
380
+
381
+ def rerender_all
382
+ @rerender_all = true
383
+ process_insert(force: true)
384
+ rerender
385
+ end
386
+
387
+ def rerender
388
+ return if @line.nil?
389
+ if @menu_info
390
+ scroll_down(@highest_in_all - @first_line_started_from)
391
+ @rerender_all = true
392
+ end
393
+ if @menu_info
394
+ show_menu
395
+ @menu_info = nil
396
+ end
397
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
398
+ if @cleared
399
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
400
+ @cleared = false
401
+ return
402
+ end
403
+ if @is_multiline and finished? and @scroll_partial_screen
404
+ # Re-output all code higher than the screen when finished.
405
+ Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
406
+ Reline::IOGate.move_cursor_column(0)
407
+ @scroll_partial_screen = nil
408
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
409
+ if @previous_line_index
410
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
411
+ else
412
+ new_lines = whole_lines
413
+ end
414
+ modify_lines(new_lines).each_with_index do |line, index|
415
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
416
+ Reline::IOGate.erase_after_cursor
417
+ end
418
+ @output.flush
419
+ clear_dialog
420
+ return
421
+ end
422
+ new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
423
+ rendered = false
424
+ if @add_newline_to_end_of_buffer
425
+ rerender_added_newline(prompt, prompt_width)
426
+ @add_newline_to_end_of_buffer = false
427
+ else
428
+ if @just_cursor_moving and not @rerender_all
429
+ rendered = just_move_cursor
430
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
431
+ @just_cursor_moving = false
432
+ return
433
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
434
+ rerender_changed_current_line
435
+ @previous_line_index = nil
436
+ rendered = true
437
+ elsif @rerender_all
438
+ rerender_all_lines
439
+ @rerender_all = false
440
+ rendered = true
441
+ else
442
+ end
443
+ end
444
+ if @is_multiline
445
+ if finished?
446
+ # Always rerender on finish because output_modifier_proc may return a different output.
447
+ if @previous_line_index
448
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
449
+ else
450
+ new_lines = whole_lines
451
+ end
452
+ line = modify_lines(new_lines)[@line_index]
453
+ clear_dialog
454
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
455
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
456
+ move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
457
+ scroll_down(1)
458
+ Reline::IOGate.move_cursor_column(0)
459
+ Reline::IOGate.erase_after_cursor
460
+ else
461
+ if not rendered and not @in_pasting
462
+ line = modify_lines(whole_lines)[@line_index]
463
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
464
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
465
+ end
466
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
467
+ end
468
+ @buffer_of_lines[@line_index] = @line
469
+ @rest_height = 0 if @scroll_partial_screen
470
+ else
471
+ line = modify_lines(whole_lines)[@line_index]
472
+ render_partial(prompt, prompt_width, line, 0)
473
+ if finished?
474
+ scroll_down(1)
475
+ Reline::IOGate.move_cursor_column(0)
476
+ Reline::IOGate.erase_after_cursor
477
+ end
478
+ end
479
+ end
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, :contents, :width
542
+ attr_accessor :scroll_top, :column, :vertical_offset, :lines_backup
543
+
544
+ def initialize(name, proc_scope)
545
+ @name = name
546
+ @proc_scope = proc_scope
547
+ @width = nil
548
+ @scroll_top = 0
549
+ end
550
+
551
+ def set_cursor_pos(col, row)
552
+ @proc_scope.set_cursor_pos(col, row)
553
+ end
554
+
555
+ def width=(v)
556
+ @width = v
557
+ end
558
+
559
+ def contents=(contents)
560
+ @contents = contents
561
+ if contents and @width.nil?
562
+ @width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max
563
+ end
564
+ end
565
+
566
+ def call
567
+ @proc_scope.set_dialog(self)
568
+ @proc_scope.call
569
+ end
570
+ end
571
+
572
+ def add_dialog_proc(name, p, context = nil)
573
+ return if @dialogs.any? { |d| d.name == name }
574
+ @dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
575
+ end
576
+
577
+ DIALOG_HEIGHT = 20
578
+ private def render_dialog(cursor_column)
579
+ @dialogs.each do |dialog|
580
+ render_each_dialog(dialog, cursor_column)
581
+ end
582
+ end
583
+
584
+ private def padding_space_with_escape_sequences(str, width)
585
+ str + (' ' * (width - calculate_width(str, true)))
586
+ end
587
+
588
+ private def render_each_dialog(dialog, cursor_column)
589
+ if @in_pasting
590
+ dialog.contents = nil
591
+ return
592
+ end
593
+ dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
594
+ dialog_render_info = dialog.call
595
+ if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
596
+ dialog.lines_backup = {
597
+ lines: modify_lines(whole_lines),
598
+ line_index: @line_index,
599
+ first_line_started_from: @first_line_started_from,
600
+ started_from: @started_from,
601
+ byte_pointer: @byte_pointer
602
+ }
603
+ clear_each_dialog(dialog)
604
+ dialog.contents = nil
605
+ return
606
+ end
607
+ old_dialog = dialog.clone
608
+ dialog.width = dialog_render_info.width if dialog_render_info.width
609
+ height = dialog_render_info.height || DIALOG_HEIGHT
610
+ pointer = dialog_render_info.pointer
611
+ dialog.contents = dialog_render_info.contents
612
+ height = dialog.contents.size if dialog.contents.size < height
613
+ if dialog.contents.size > height
614
+ if dialog_render_info.pointer
615
+ if dialog_render_info.pointer < 0
616
+ dialog.scroll_top = 0
617
+ elsif (dialog_render_info.pointer - dialog.scroll_top) >= (height - 1)
618
+ dialog.scroll_top = dialog_render_info.pointer - (height - 1)
619
+ elsif (dialog_render_info.pointer - dialog.scroll_top) < 0
620
+ dialog.scroll_top = dialog_render_info.pointer
621
+ end
622
+ pointer = dialog_render_info.pointer - dialog.scroll_top
623
+ end
624
+ dialog.contents = dialog.contents[dialog.scroll_top, height]
625
+ end
626
+ upper_space = @first_line_started_from - @started_from
627
+ lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
628
+ dialog.column = dialog_render_info.pos.x
629
+ diff = (dialog.column + dialog.width) - (@screen_size.last - 1)
630
+ if diff > 0
631
+ dialog.column -= diff
632
+ end
633
+ if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
634
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
635
+ elsif upper_space >= height
636
+ dialog.vertical_offset = dialog_render_info.pos.y - height
637
+ else
638
+ if (lower_space + @rest_height - dialog_render_info.pos.y) < height
639
+ scroll_down(height + dialog_render_info.pos.y)
640
+ move_cursor_up(height + dialog_render_info.pos.y)
641
+ end
642
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
643
+ end
644
+ Reline::IOGate.hide_cursor
645
+ reset_dialog(dialog, old_dialog)
646
+ move_cursor_down(dialog.vertical_offset)
647
+ Reline::IOGate.move_cursor_column(dialog.column)
648
+ dialog.contents.each_with_index do |item, i|
649
+ if i == pointer
650
+ bg_color = '45'
651
+ else
652
+ if dialog_render_info.bg_color
653
+ bg_color = dialog_render_info.bg_color
654
+ else
655
+ bg_color = '46'
656
+ end
657
+ end
658
+ str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, dialog.width), dialog.width)
659
+ @output.write "\e[#{bg_color}m#{str}\e[49m"
660
+ Reline::IOGate.move_cursor_column(dialog.column)
661
+ move_cursor_down(1) if i < (dialog.contents.size - 1)
662
+ end
663
+ Reline::IOGate.move_cursor_column(cursor_column)
664
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
665
+ Reline::IOGate.show_cursor
666
+ dialog.lines_backup = {
667
+ lines: modify_lines(whole_lines),
668
+ line_index: @line_index,
669
+ first_line_started_from: @first_line_started_from,
670
+ started_from: @started_from,
671
+ byte_pointer: @byte_pointer
672
+ }
673
+ end
674
+
675
+ private def reset_dialog(dialog, old_dialog)
676
+ return if dialog.lines_backup.nil? or old_dialog.contents.nil?
677
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
678
+ visual_lines = []
679
+ visual_start = nil
680
+ dialog.lines_backup[:lines].each_with_index { |l, i|
681
+ pr = prompt_list ? prompt_list[i] : prompt
682
+ vl, _ = split_by_width(pr + l, @screen_size.last)
683
+ vl.compact!
684
+ if i == dialog.lines_backup[:line_index]
685
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from]
686
+ end
687
+ visual_lines.concat(vl)
688
+ }
689
+ old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
690
+ y = @first_line_started_from + @started_from
691
+ y_diff = y - old_y
692
+ if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
693
+ # rerender top
694
+ move_cursor_down(old_dialog.vertical_offset - y_diff)
695
+ start = visual_start + old_dialog.vertical_offset
696
+ line_num = dialog.vertical_offset - old_dialog.vertical_offset
697
+ line_num.times do |i|
698
+ Reline::IOGate.move_cursor_column(old_dialog.column)
699
+ if visual_lines[start + i].nil?
700
+ s = ' ' * dialog.width
701
+ else
702
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
703
+ s = padding_space_with_escape_sequences(s, dialog.width)
704
+ end
705
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
706
+ move_cursor_down(1) if i < (line_num - 1)
707
+ end
708
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
709
+ end
710
+ if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
711
+ # rerender bottom
712
+ move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
713
+ start = visual_start + dialog.vertical_offset + dialog.contents.size
714
+ line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
715
+ line_num.times do |i|
716
+ Reline::IOGate.move_cursor_column(old_dialog.column)
717
+ if visual_lines[start + i].nil?
718
+ s = ' ' * dialog.width
719
+ else
720
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
721
+ s = padding_space_with_escape_sequences(s, dialog.width)
722
+ end
723
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
724
+ move_cursor_down(1) if i < (line_num - 1)
725
+ end
726
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
727
+ end
728
+ if old_dialog.column < dialog.column
729
+ # rerender left
730
+ move_cursor_down(old_dialog.vertical_offset - y_diff)
731
+ width = dialog.column - old_dialog.column
732
+ start = visual_start + old_dialog.vertical_offset
733
+ line_num = old_dialog.contents.size
734
+ line_num.times do |i|
735
+ Reline::IOGate.move_cursor_column(old_dialog.column)
736
+ if visual_lines[start + i].nil?
737
+ s = ' ' * width
738
+ else
739
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
740
+ s = padding_space_with_escape_sequences(s, dialog.width)
741
+ end
742
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
743
+ move_cursor_down(1) if i < (line_num - 1)
744
+ end
745
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
746
+ end
747
+ if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
748
+ # rerender right
749
+ move_cursor_down(old_dialog.vertical_offset + y_diff)
750
+ width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
751
+ start = visual_start + old_dialog.vertical_offset
752
+ line_num = old_dialog.contents.size
753
+ line_num.times do |i|
754
+ Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
755
+ if visual_lines[start + i].nil?
756
+ s = ' ' * width
757
+ else
758
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
759
+ s = padding_space_with_escape_sequences(s, dialog.width)
760
+ end
761
+ Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
762
+ @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
763
+ move_cursor_down(1) if i < (line_num - 1)
764
+ end
765
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
766
+ end
767
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
768
+ end
769
+
770
+ private def clear_dialog
771
+ @dialogs.each do |dialog|
772
+ clear_each_dialog(dialog)
773
+ end
774
+ end
775
+
776
+ private def clear_each_dialog(dialog)
777
+ return unless dialog.contents
778
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
779
+ visual_lines = []
780
+ visual_lines_under_dialog = []
781
+ visual_start = nil
782
+ dialog.lines_backup[:lines].each_with_index { |l, i|
783
+ pr = prompt_list ? prompt_list[i] : prompt
784
+ vl, _ = split_by_width(pr + l, @screen_size.last)
785
+ vl.compact!
786
+ if i == dialog.lines_backup[:line_index]
787
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
788
+ end
789
+ visual_lines.concat(vl)
790
+ }
791
+ visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
792
+ visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
793
+ Reline::IOGate.hide_cursor
794
+ move_cursor_down(dialog.vertical_offset)
795
+ dialog_vertical_size = dialog.contents.size
796
+ dialog_vertical_size.times do |i|
797
+ if i < visual_lines_under_dialog.size
798
+ Reline::IOGate.move_cursor_column(dialog.column)
799
+ str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
800
+ str = padding_space_with_escape_sequences(str, dialog.width)
801
+ @output.write "\e[39m\e[49m#{str}\e[39m\e[49m"
802
+ else
803
+ Reline::IOGate.move_cursor_column(dialog.column)
804
+ @output.write "\e[39m\e[49m#{' ' * dialog.width}\e[39m\e[49m"
805
+ end
806
+ move_cursor_down(1) if i < (dialog_vertical_size - 1)
807
+ end
808
+ move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
809
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
810
+ Reline::IOGate.show_cursor
811
+ end
812
+
813
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
814
+ if @screen_height < highest_in_all
815
+ old_scroll_partial_screen = @scroll_partial_screen
816
+ if cursor_y == 0
817
+ @scroll_partial_screen = 0
818
+ elsif cursor_y == (highest_in_all - 1)
819
+ @scroll_partial_screen = highest_in_all - @screen_height
820
+ else
821
+ if @scroll_partial_screen
822
+ if cursor_y <= @scroll_partial_screen
823
+ @scroll_partial_screen = cursor_y
824
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
825
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
826
+ end
827
+ else
828
+ if cursor_y > (@screen_height - 1)
829
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
830
+ else
831
+ @scroll_partial_screen = 0
832
+ end
833
+ end
834
+ end
835
+ if @scroll_partial_screen != old_scroll_partial_screen
836
+ @rerender_all = true
837
+ end
838
+ else
839
+ if @scroll_partial_screen
840
+ @rerender_all = true
841
+ end
842
+ @scroll_partial_screen = nil
843
+ end
844
+ end
845
+
846
+ private def rerender_added_newline(prompt, prompt_width)
847
+ scroll_down(1)
848
+ @buffer_of_lines[@previous_line_index] = @line
849
+ @line = @buffer_of_lines[@line_index]
850
+ unless @in_pasting
851
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
852
+ end
853
+ @cursor = @cursor_max = calculate_width(@line)
854
+ @byte_pointer = @line.bytesize
855
+ @highest_in_all += @highest_in_this
856
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
857
+ @first_line_started_from += @started_from + 1
858
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
859
+ @previous_line_index = nil
860
+ end
861
+
862
+ def just_move_cursor
863
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
864
+ move_cursor_up(@started_from)
865
+ new_first_line_started_from =
866
+ if @line_index.zero?
867
+ 0
868
+ else
869
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
870
+ end
871
+ first_line_diff = new_first_line_started_from - @first_line_started_from
872
+ new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
873
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
874
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
875
+ @previous_line_index = nil
876
+ if @rerender_all
877
+ @line = @buffer_of_lines[@line_index]
878
+ rerender_all_lines
879
+ @rerender_all = false
880
+ true
881
+ else
882
+ @line = @buffer_of_lines[@line_index]
883
+ @first_line_started_from = new_first_line_started_from
884
+ @started_from = new_started_from
885
+ @cursor = new_cursor
886
+ @cursor_max = new_cursor_max
887
+ @byte_pointer = new_byte_pointer
888
+ move_cursor_down(first_line_diff + @started_from)
889
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
890
+ false
891
+ end
892
+ end
893
+
894
+ private def rerender_changed_current_line
895
+ if @previous_line_index
896
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
897
+ else
898
+ new_lines = whole_lines
899
+ end
900
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
901
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
902
+ diff = all_height - @highest_in_all
903
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
904
+ if diff > 0
905
+ scroll_down(diff)
906
+ move_cursor_up(all_height - 1)
907
+ elsif diff < 0
908
+ (-diff).times do
909
+ Reline::IOGate.move_cursor_column(0)
910
+ Reline::IOGate.erase_after_cursor
911
+ move_cursor_up(1)
912
+ end
913
+ move_cursor_up(all_height - 1)
914
+ else
915
+ move_cursor_up(all_height - 1)
916
+ end
917
+ @highest_in_all = all_height
918
+ back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
919
+ move_cursor_up(back)
920
+ if @previous_line_index
921
+ @buffer_of_lines[@previous_line_index] = @line
922
+ @line = @buffer_of_lines[@line_index]
923
+ end
924
+ @first_line_started_from =
925
+ if @line_index.zero?
926
+ 0
927
+ else
928
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
929
+ end
930
+ if @prompt_proc
931
+ prompt = prompt_list[@line_index]
932
+ prompt_width = calculate_width(prompt, true)
933
+ end
934
+ move_cursor_down(@first_line_started_from)
935
+ calculate_nearest_cursor
936
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
937
+ move_cursor_down(@started_from)
938
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
939
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
940
+ end
941
+
942
+ private def rerender_all_lines
943
+ move_cursor_up(@first_line_started_from + @started_from)
944
+ Reline::IOGate.move_cursor_column(0)
945
+ back = 0
946
+ new_buffer = whole_lines
947
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
948
+ new_buffer.each_with_index do |line, index|
949
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
950
+ width = prompt_width + calculate_width(line)
951
+ height = calculate_height_by_width(width)
952
+ back += height
953
+ end
954
+ old_highest_in_all = @highest_in_all
955
+ if @line_index.zero?
956
+ new_first_line_started_from = 0
957
+ else
958
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
959
+ end
960
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
961
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
962
+ if @scroll_partial_screen
963
+ move_cursor_up(@first_line_started_from + @started_from)
964
+ scroll_down(@screen_height - 1)
965
+ move_cursor_up(@screen_height)
966
+ Reline::IOGate.move_cursor_column(0)
967
+ elsif back > old_highest_in_all
968
+ scroll_down(back - 1)
969
+ move_cursor_up(back - 1)
970
+ elsif back < old_highest_in_all
971
+ scroll_down(back)
972
+ Reline::IOGate.erase_after_cursor
973
+ (old_highest_in_all - back - 1).times do
974
+ scroll_down(1)
975
+ Reline::IOGate.erase_after_cursor
976
+ end
977
+ move_cursor_up(old_highest_in_all - 1)
978
+ end
979
+ render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
980
+ if @prompt_proc
981
+ prompt = prompt_list[@line_index]
982
+ prompt_width = calculate_width(prompt, true)
983
+ end
984
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
985
+ @highest_in_all = back
986
+ @first_line_started_from = new_first_line_started_from
987
+ @started_from = new_started_from
988
+ if @scroll_partial_screen
989
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
990
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
991
+ else
992
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
993
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
994
+ end
995
+ end
996
+
997
+ private def render_whole_lines(lines, prompt, prompt_width)
998
+ rendered_height = 0
999
+ modify_lines(lines).each_with_index do |line, index|
1000
+ if prompt.is_a?(Array)
1001
+ line_prompt = prompt[index]
1002
+ prompt_width = calculate_width(line_prompt, true)
1003
+ else
1004
+ line_prompt = prompt
1005
+ end
1006
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
1007
+ if index < (lines.size - 1)
1008
+ if @scroll_partial_screen
1009
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
1010
+ move_cursor_down(1)
1011
+ end
1012
+ else
1013
+ scroll_down(1)
1014
+ end
1015
+ rendered_height += height
1016
+ else
1017
+ rendered_height += height - 1
1018
+ end
1019
+ end
1020
+ rendered_height
1021
+ end
1022
+
1023
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
1024
+ visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
1025
+ cursor_up_from_last_line = 0
1026
+ if @scroll_partial_screen
1027
+ last_visual_line = this_started_from + (height - 1)
1028
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
1029
+ if (@scroll_partial_screen - this_started_from) >= height
1030
+ # Render nothing because this line is before the screen.
1031
+ visual_lines = []
1032
+ elsif this_started_from > last_screen_line
1033
+ # Render nothing because this line is after the screen.
1034
+ visual_lines = []
1035
+ else
1036
+ deleted_lines_before_screen = []
1037
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
1038
+ # A part of visual lines are before the screen.
1039
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
1040
+ deleted_lines_before_screen.compact!
1041
+ end
1042
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
1043
+ # A part of visual lines are after the screen.
1044
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
1045
+ end
1046
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
1047
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
1048
+ end
1049
+ end
1050
+ if with_control
1051
+ if height > @highest_in_this
1052
+ diff = height - @highest_in_this
1053
+ scroll_down(diff)
1054
+ @highest_in_all += diff
1055
+ @highest_in_this = height
1056
+ move_cursor_up(diff)
1057
+ elsif height < @highest_in_this
1058
+ diff = @highest_in_this - height
1059
+ @highest_in_all -= diff
1060
+ @highest_in_this = height
1061
+ end
1062
+ move_cursor_up(@started_from)
1063
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1064
+ cursor_up_from_last_line = height - 1 - @started_from
1065
+ end
1066
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
1067
+ @output.write "\e[0m" # clear character decorations
1068
+ end
1069
+ visual_lines.each_with_index do |line, index|
1070
+ Reline::IOGate.move_cursor_column(0)
1071
+ if line.nil?
1072
+ if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
1073
+ # reaches the end of line
1074
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
1075
+ # A newline is automatically inserted if a character is rendered at
1076
+ # eol on command prompt.
1077
+ else
1078
+ # When the cursor is at the end of the line and erases characters
1079
+ # after the cursor, some terminals delete the character at the
1080
+ # cursor position.
1081
+ move_cursor_down(1)
1082
+ Reline::IOGate.move_cursor_column(0)
1083
+ end
1084
+ else
1085
+ Reline::IOGate.erase_after_cursor
1086
+ move_cursor_down(1)
1087
+ Reline::IOGate.move_cursor_column(0)
1088
+ end
1089
+ next
1090
+ end
1091
+ @output.write line
1092
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
1093
+ # A newline is automatically inserted if a character is rendered at eol on command prompt.
1094
+ @rest_height -= 1 if @rest_height > 0
1095
+ end
1096
+ @output.flush
1097
+ if @first_prompt
1098
+ @first_prompt = false
1099
+ @pre_input_hook&.call
1100
+ end
1101
+ end
1102
+ unless visual_lines.empty?
1103
+ Reline::IOGate.erase_after_cursor
1104
+ Reline::IOGate.move_cursor_column(0)
1105
+ end
1106
+ if with_control
1107
+ # Just after rendring, so the cursor is on the last line.
1108
+ if finished?
1109
+ Reline::IOGate.move_cursor_column(0)
1110
+ else
1111
+ # Moves up from bottom of lines to the cursor position.
1112
+ move_cursor_up(cursor_up_from_last_line)
1113
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
1114
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1115
+ end
1116
+ end
1117
+ height
1118
+ end
1119
+
1120
+ private def modify_lines(before)
1121
+ return before if before.nil? || before.empty? || simplified_rendering?
1122
+
1123
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
1124
+ after.lines("\n").map { |l| l.chomp('') }
1125
+ else
1126
+ before
1127
+ end
1128
+ end
1129
+
1130
+ private def show_menu
1131
+ scroll_down(@highest_in_all - @first_line_started_from)
1132
+ @rerender_all = true
1133
+ @menu_info.list.sort!.each do |item|
1134
+ Reline::IOGate.move_cursor_column(0)
1135
+ @output.write item
1136
+ @output.flush
1137
+ scroll_down(1)
1138
+ end
1139
+ scroll_down(@highest_in_all - 1)
1140
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
1141
+ end
1142
+
1143
+ private def clear_screen_buffer(prompt, prompt_list, prompt_width)
1144
+ Reline::IOGate.clear_screen
1145
+ back = 0
1146
+ modify_lines(whole_lines).each_with_index do |line, index|
1147
+ if @prompt_proc
1148
+ pr = prompt_list[index]
1149
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
1150
+ else
1151
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
1152
+ end
1153
+ if index < (@buffer_of_lines.size - 1)
1154
+ move_cursor_down(height)
1155
+ back += height
1156
+ end
1157
+ end
1158
+ move_cursor_up(back)
1159
+ move_cursor_down(@first_line_started_from + @started_from)
1160
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
1161
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1162
+ end
1163
+
1164
+ def editing_mode
1165
+ @config.editing_mode
1166
+ end
1167
+
1168
+ private def menu(target, list)
1169
+ @menu_info = MenuInfo.new(target, list)
1170
+ end
1171
+
1172
+ private def complete_internal_proc(list, is_menu)
1173
+ preposing, target, postposing = retrieve_completion_block
1174
+ list = list.select { |i|
1175
+ if i and not Encoding.compatible?(target.encoding, i.encoding)
1176
+ raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
1177
+ end
1178
+ if @config.completion_ignore_case
1179
+ i&.downcase&.start_with?(target.downcase)
1180
+ else
1181
+ i&.start_with?(target)
1182
+ end
1183
+ }.uniq
1184
+ if is_menu
1185
+ menu(target, list)
1186
+ return nil
1187
+ end
1188
+ completed = list.inject { |memo, item|
1189
+ begin
1190
+ memo_mbchars = memo.unicode_normalize.grapheme_clusters
1191
+ item_mbchars = item.unicode_normalize.grapheme_clusters
1192
+ rescue Encoding::CompatibilityError
1193
+ memo_mbchars = memo.grapheme_clusters
1194
+ item_mbchars = item.grapheme_clusters
1195
+ end
1196
+ size = [memo_mbchars.size, item_mbchars.size].min
1197
+ result = ''
1198
+ size.times do |i|
1199
+ if @config.completion_ignore_case
1200
+ if memo_mbchars[i].casecmp?(item_mbchars[i])
1201
+ result << memo_mbchars[i]
1202
+ else
1203
+ break
1204
+ end
1205
+ else
1206
+ if memo_mbchars[i] == item_mbchars[i]
1207
+ result << memo_mbchars[i]
1208
+ else
1209
+ break
1210
+ end
1211
+ end
1212
+ end
1213
+ result
1214
+ }
1215
+ [target, preposing, completed, postposing]
1216
+ end
1217
+
1218
+ private def complete(list, just_show_list = false)
1219
+ case @completion_state
1220
+ when CompletionState::NORMAL, CompletionState::JOURNEY
1221
+ @completion_state = CompletionState::COMPLETION
1222
+ when CompletionState::PERFECT_MATCH
1223
+ @dig_perfect_match_proc&.(@perfect_matched)
1224
+ end
1225
+ if just_show_list
1226
+ is_menu = true
1227
+ elsif @completion_state == CompletionState::MENU
1228
+ is_menu = true
1229
+ elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
1230
+ is_menu = true
1231
+ else
1232
+ is_menu = false
1233
+ end
1234
+ result = complete_internal_proc(list, is_menu)
1235
+ if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
1236
+ @completion_state = CompletionState::PERFECT_MATCH
1237
+ end
1238
+ return if result.nil?
1239
+ target, preposing, completed, postposing = result
1240
+ return if completed.nil?
1241
+ if target <= completed and (@completion_state == CompletionState::COMPLETION)
1242
+ if list.include?(completed)
1243
+ if list.one?
1244
+ @completion_state = CompletionState::PERFECT_MATCH
1245
+ else
1246
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
1247
+ end
1248
+ @perfect_matched = completed
1249
+ else
1250
+ @completion_state = CompletionState::MENU
1251
+ end
1252
+ if not just_show_list and target < completed
1253
+ @line = preposing + completed + completion_append_character.to_s + postposing
1254
+ line_to_pointer = preposing + completed + completion_append_character.to_s
1255
+ @cursor_max = calculate_width(@line)
1256
+ @cursor = calculate_width(line_to_pointer)
1257
+ @byte_pointer = line_to_pointer.bytesize
1258
+ end
1259
+ end
1260
+ end
1261
+
1262
+ private def move_completed_list(list, direction)
1263
+ case @completion_state
1264
+ when CompletionState::NORMAL, CompletionState::COMPLETION,
1265
+ CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
1266
+ @completion_state = CompletionState::JOURNEY
1267
+ result = retrieve_completion_block
1268
+ return if result.nil?
1269
+ preposing, target, postposing = result
1270
+ @completion_journey_data = CompletionJourneyData.new(
1271
+ preposing, postposing,
1272
+ [target] + list.select{ |item| item.start_with?(target) }, 0)
1273
+ if @completion_journey_data.list.size == 1
1274
+ @completion_journey_data.pointer = 0
1275
+ else
1276
+ case direction
1277
+ when :up
1278
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1279
+ when :down
1280
+ @completion_journey_data.pointer = 1
1281
+ end
1282
+ end
1283
+ @completion_state = CompletionState::JOURNEY
1284
+ else
1285
+ case direction
1286
+ when :up
1287
+ @completion_journey_data.pointer -= 1
1288
+ if @completion_journey_data.pointer < 0
1289
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1290
+ end
1291
+ when :down
1292
+ @completion_journey_data.pointer += 1
1293
+ if @completion_journey_data.pointer >= @completion_journey_data.list.size
1294
+ @completion_journey_data.pointer = 0
1295
+ end
1296
+ end
1297
+ end
1298
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
1299
+ new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
1300
+ @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
1301
+ line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
1302
+ line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
1303
+ @cursor_max = calculate_width(@line)
1304
+ @cursor = calculate_width(line_to_pointer)
1305
+ @byte_pointer = line_to_pointer.bytesize
1306
+ end
1307
+
1308
+ private def run_for_operators(key, method_symbol, &block)
1309
+ if @waiting_operator_proc
1310
+ if VI_MOTIONS.include?(method_symbol)
1311
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
1312
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
1313
+ block.(true)
1314
+ unless @waiting_proc
1315
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
1316
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
1317
+ @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
1318
+ else
1319
+ old_waiting_proc = @waiting_proc
1320
+ old_waiting_operator_proc = @waiting_operator_proc
1321
+ current_waiting_operator_proc = @waiting_operator_proc
1322
+ @waiting_proc = proc { |k|
1323
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
1324
+ old_waiting_proc.(k)
1325
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
1326
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
1327
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
1328
+ @waiting_operator_proc = old_waiting_operator_proc
1329
+ }
1330
+ end
1331
+ else
1332
+ # Ignores operator when not motion is given.
1333
+ block.(false)
1334
+ end
1335
+ @waiting_operator_proc = nil
1336
+ @waiting_operator_vi_arg = nil
1337
+ @vi_arg = nil
1338
+ else
1339
+ block.(false)
1340
+ end
1341
+ end
1342
+
1343
+ private def argumentable?(method_obj)
1344
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
1345
+ end
1346
+
1347
+ private def inclusive?(method_obj)
1348
+ # If a motion method with the keyword argument "inclusive" follows the
1349
+ # operator, it must contain the character at the cursor position.
1350
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
1351
+ end
1352
+
1353
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
1354
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
1355
+ not_insertion = method_symbol != :ed_insert
1356
+ process_insert(force: not_insertion)
1357
+ end
1358
+ if @vi_arg and argumentable?(method_obj)
1359
+ if with_operator and inclusive?(method_obj)
1360
+ method_obj.(key, arg: @vi_arg, inclusive: true)
1361
+ else
1362
+ method_obj.(key, arg: @vi_arg)
1363
+ end
1364
+ else
1365
+ if with_operator and inclusive?(method_obj)
1366
+ method_obj.(key, inclusive: true)
1367
+ else
1368
+ method_obj.(key)
1369
+ end
1370
+ end
1371
+ end
1372
+
1373
+ private def process_key(key, method_symbol)
1374
+ if method_symbol and respond_to?(method_symbol, true)
1375
+ method_obj = method(method_symbol)
1376
+ else
1377
+ method_obj = nil
1378
+ end
1379
+ if method_symbol and key.is_a?(Symbol)
1380
+ if @vi_arg and argumentable?(method_obj)
1381
+ run_for_operators(key, method_symbol) do |with_operator|
1382
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
1383
+ end
1384
+ else
1385
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
1386
+ end
1387
+ @kill_ring.process
1388
+ @vi_arg = nil
1389
+ elsif @vi_arg
1390
+ if key.chr =~ /[0-9]/
1391
+ ed_argument_digit(key)
1392
+ else
1393
+ if argumentable?(method_obj)
1394
+ run_for_operators(key, method_symbol) do |with_operator|
1395
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
1396
+ end
1397
+ elsif @waiting_proc
1398
+ @waiting_proc.(key)
1399
+ elsif method_obj
1400
+ wrap_method_call(method_symbol, method_obj, key)
1401
+ else
1402
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1403
+ end
1404
+ @kill_ring.process
1405
+ @vi_arg = nil
1406
+ end
1407
+ elsif @waiting_proc
1408
+ @waiting_proc.(key)
1409
+ @kill_ring.process
1410
+ elsif method_obj
1411
+ if method_symbol == :ed_argument_digit
1412
+ wrap_method_call(method_symbol, method_obj, key)
1413
+ else
1414
+ run_for_operators(key, method_symbol) do |with_operator|
1415
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
1416
+ end
1417
+ end
1418
+ @kill_ring.process
1419
+ else
1420
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1421
+ end
1422
+ end
1423
+
1424
+ private def normal_char(key)
1425
+ method_symbol = method_obj = nil
1426
+ if key.combined_char.is_a?(Symbol)
1427
+ process_key(key.combined_char, key.combined_char)
1428
+ return
1429
+ end
1430
+ @multibyte_buffer << key.combined_char
1431
+ if @multibyte_buffer.size > 1
1432
+ if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
1433
+ process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
1434
+ @multibyte_buffer.clear
1435
+ else
1436
+ # invalid
1437
+ return
1438
+ end
1439
+ else # single byte
1440
+ return if key.char >= 128 # maybe, first byte of multi byte
1441
+ method_symbol = @config.editing_mode.get_method(key.combined_char)
1442
+ if key.with_meta and method_symbol == :ed_unassigned
1443
+ # split ESC + key
1444
+ method_symbol = @config.editing_mode.get_method("\e".ord)
1445
+ process_key("\e".ord, method_symbol)
1446
+ method_symbol = @config.editing_mode.get_method(key.char)
1447
+ process_key(key.char, method_symbol)
1448
+ else
1449
+ process_key(key.combined_char, method_symbol)
1450
+ end
1451
+ @multibyte_buffer.clear
1452
+ end
1453
+ if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
1454
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1455
+ @byte_pointer -= byte_size
1456
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1457
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1458
+ @cursor -= width
1459
+ end
1460
+ end
1461
+
1462
+ def input_key(key)
1463
+ @just_cursor_moving = nil
1464
+ if key.char.nil?
1465
+ if @first_char
1466
+ @line = nil
1467
+ end
1468
+ finish
1469
+ return
1470
+ end
1471
+ old_line = @line.dup
1472
+ @first_char = false
1473
+ completion_occurs = false
1474
+ if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1475
+ unless @config.disable_completion
1476
+ result = call_completion_proc
1477
+ if result.is_a?(Array)
1478
+ completion_occurs = true
1479
+ process_insert
1480
+ if @config.autocompletion
1481
+ move_completed_list(result, :down)
1482
+ else
1483
+ complete(result)
1484
+ end
1485
+ end
1486
+ end
1487
+ elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
1488
+ if not @config.disable_completion and @config.autocompletion
1489
+ result = call_completion_proc
1490
+ if result.is_a?(Array)
1491
+ completion_occurs = true
1492
+ process_insert
1493
+ move_completed_list(result, :up)
1494
+ end
1495
+ end
1496
+ elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1497
+ unless @config.disable_completion
1498
+ result = call_completion_proc
1499
+ if result.is_a?(Array)
1500
+ completion_occurs = true
1501
+ process_insert
1502
+ move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
1503
+ end
1504
+ end
1505
+ elsif Symbol === key.char and respond_to?(key.char, true)
1506
+ process_key(key.char, key.char)
1507
+ else
1508
+ normal_char(key)
1509
+ end
1510
+ unless completion_occurs
1511
+ @completion_state = CompletionState::NORMAL
1512
+ @completion_journey_data = nil
1513
+ end
1514
+ if not @in_pasting and @just_cursor_moving.nil?
1515
+ if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1516
+ @just_cursor_moving = true
1517
+ elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1518
+ @just_cursor_moving = true
1519
+ else
1520
+ @just_cursor_moving = false
1521
+ end
1522
+ else
1523
+ @just_cursor_moving = false
1524
+ end
1525
+ if @is_multiline and @auto_indent_proc and not simplified_rendering?
1526
+ process_auto_indent
1527
+ end
1528
+ end
1529
+
1530
+ def call_completion_proc
1531
+ result = retrieve_completion_block(true)
1532
+ pre, target, post = result
1533
+ result = call_completion_proc_with_checking_args(pre, target, post)
1534
+ Reline.core.instance_variable_set(:@completion_quote_character, nil)
1535
+ result
1536
+ end
1537
+
1538
+ def call_completion_proc_with_checking_args(pre, target, post)
1539
+ if @completion_proc and target
1540
+ argnum = @completion_proc.parameters.inject(0) { |result, item|
1541
+ case item.first
1542
+ when :req, :opt
1543
+ result + 1
1544
+ when :rest
1545
+ break 3
1546
+ end
1547
+ }
1548
+ case argnum
1549
+ when 1
1550
+ result = @completion_proc.(target)
1551
+ when 2
1552
+ result = @completion_proc.(target, pre)
1553
+ when 3..Float::INFINITY
1554
+ result = @completion_proc.(target, pre, post)
1555
+ end
1556
+ end
1557
+ result
1558
+ end
1559
+
1560
+ private def process_auto_indent
1561
+ return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
1562
+ if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
1563
+ # Fix indent of a line when a newline is inserted to the next
1564
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
1565
+ new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
1566
+ md = @line.match(/\A */)
1567
+ prev_indent = md[0].count(' ')
1568
+ @line = ' ' * new_indent + @line.lstrip
1569
+
1570
+ new_indent = nil
1571
+ result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
1572
+ if result
1573
+ new_indent = result
1574
+ end
1575
+ if new_indent&.>= 0
1576
+ @line = ' ' * new_indent + @line.lstrip
1577
+ end
1578
+ end
1579
+ if @previous_line_index
1580
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
1581
+ else
1582
+ new_lines = whole_lines
1583
+ end
1584
+ new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1585
+ new_indent = @cursor_max if new_indent&.> @cursor_max
1586
+ if new_indent&.>= 0
1587
+ md = new_lines[@line_index].match(/\A */)
1588
+ prev_indent = md[0].count(' ')
1589
+ if @check_new_auto_indent
1590
+ @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1591
+ @cursor = new_indent
1592
+ @byte_pointer = new_indent
1593
+ else
1594
+ @line = ' ' * new_indent + @line.lstrip
1595
+ @cursor += new_indent - prev_indent
1596
+ @byte_pointer += new_indent - prev_indent
1597
+ end
1598
+ end
1599
+ @check_new_auto_indent = false
1600
+ end
1601
+
1602
+ def retrieve_completion_block(set_completion_quote_character = false)
1603
+ if Reline.completer_word_break_characters.empty?
1604
+ word_break_regexp = nil
1605
+ else
1606
+ word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1607
+ end
1608
+ if Reline.completer_quote_characters.empty?
1609
+ quote_characters_regexp = nil
1610
+ else
1611
+ quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1612
+ end
1613
+ before = @line.byteslice(0, @byte_pointer)
1614
+ rest = nil
1615
+ break_pointer = nil
1616
+ quote = nil
1617
+ closing_quote = nil
1618
+ escaped_quote = nil
1619
+ i = 0
1620
+ while i < @byte_pointer do
1621
+ slice = @line.byteslice(i, @byte_pointer - i)
1622
+ unless slice.valid_encoding?
1623
+ i += 1
1624
+ next
1625
+ end
1626
+ if quote and slice.start_with?(closing_quote)
1627
+ quote = nil
1628
+ i += 1
1629
+ rest = nil
1630
+ elsif quote and slice.start_with?(escaped_quote)
1631
+ # skip
1632
+ i += 2
1633
+ elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1634
+ rest = $'
1635
+ quote = $&
1636
+ closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1637
+ escaped_quote = /\\#{Regexp.escape(quote)}/
1638
+ i += 1
1639
+ break_pointer = i - 1
1640
+ elsif word_break_regexp and not quote and slice =~ word_break_regexp
1641
+ rest = $'
1642
+ i += 1
1643
+ before = @line.byteslice(i, @byte_pointer - i)
1644
+ break_pointer = i
1645
+ else
1646
+ i += 1
1647
+ end
1648
+ end
1649
+ postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1650
+ if rest
1651
+ preposing = @line.byteslice(0, break_pointer)
1652
+ target = rest
1653
+ if set_completion_quote_character and quote
1654
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
1655
+ if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
1656
+ insert_text(quote)
1657
+ end
1658
+ end
1659
+ else
1660
+ preposing = ''
1661
+ if break_pointer
1662
+ preposing = @line.byteslice(0, break_pointer)
1663
+ else
1664
+ preposing = ''
1665
+ end
1666
+ target = before
1667
+ end
1668
+ if @is_multiline
1669
+ if @previous_line_index
1670
+ lines = whole_lines(index: @previous_line_index, line: @line)
1671
+ else
1672
+ lines = whole_lines
1673
+ end
1674
+ if @line_index > 0
1675
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1676
+ end
1677
+ if (lines.size - 1) > @line_index
1678
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1679
+ end
1680
+ end
1681
+ [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1682
+ end
1683
+
1684
+ def confirm_multiline_termination
1685
+ temp_buffer = @buffer_of_lines.dup
1686
+ if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
1687
+ temp_buffer[@previous_line_index] = @line
1688
+ else
1689
+ temp_buffer[@line_index] = @line
1690
+ end
1691
+ @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1692
+ end
1693
+
1694
+ def insert_text(text)
1695
+ width = calculate_width(text)
1696
+ if @cursor == @cursor_max
1697
+ @line += text
1698
+ else
1699
+ @line = byteinsert(@line, @byte_pointer, text)
1700
+ end
1701
+ @byte_pointer += text.bytesize
1702
+ @cursor += width
1703
+ @cursor_max += width
1704
+ end
1705
+
1706
+ def delete_text(start = nil, length = nil)
1707
+ if start.nil? and length.nil?
1708
+ if @is_multiline
1709
+ if @buffer_of_lines.size == 1
1710
+ @line&.clear
1711
+ @byte_pointer = 0
1712
+ @cursor = 0
1713
+ @cursor_max = 0
1714
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1715
+ @buffer_of_lines.pop
1716
+ @line_index -= 1
1717
+ @line = @buffer_of_lines[@line_index]
1718
+ @byte_pointer = 0
1719
+ @cursor = 0
1720
+ @cursor_max = calculate_width(@line)
1721
+ elsif @line_index < (@buffer_of_lines.size - 1)
1722
+ @buffer_of_lines.delete_at(@line_index)
1723
+ @line = @buffer_of_lines[@line_index]
1724
+ @byte_pointer = 0
1725
+ @cursor = 0
1726
+ @cursor_max = calculate_width(@line)
1727
+ end
1728
+ else
1729
+ @line&.clear
1730
+ @byte_pointer = 0
1731
+ @cursor = 0
1732
+ @cursor_max = 0
1733
+ end
1734
+ elsif not start.nil? and not length.nil?
1735
+ if @line
1736
+ before = @line.byteslice(0, start)
1737
+ after = @line.byteslice(start + length, @line.bytesize)
1738
+ @line = before + after
1739
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1740
+ str = @line.byteslice(0, @byte_pointer)
1741
+ @cursor = calculate_width(str)
1742
+ @cursor_max = calculate_width(@line)
1743
+ end
1744
+ elsif start.is_a?(Range)
1745
+ range = start
1746
+ first = range.first
1747
+ last = range.last
1748
+ last = @line.bytesize - 1 if last > @line.bytesize
1749
+ last += @line.bytesize if last < 0
1750
+ first += @line.bytesize if first < 0
1751
+ range = range.exclude_end? ? first...last : first..last
1752
+ @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1753
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1754
+ str = @line.byteslice(0, @byte_pointer)
1755
+ @cursor = calculate_width(str)
1756
+ @cursor_max = calculate_width(@line)
1757
+ else
1758
+ @line = @line.byteslice(0, start)
1759
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1760
+ str = @line.byteslice(0, @byte_pointer)
1761
+ @cursor = calculate_width(str)
1762
+ @cursor_max = calculate_width(@line)
1763
+ end
1764
+ end
1765
+
1766
+ def byte_pointer=(val)
1767
+ @byte_pointer = val
1768
+ str = @line.byteslice(0, @byte_pointer)
1769
+ @cursor = calculate_width(str)
1770
+ @cursor_max = calculate_width(@line)
1771
+ end
1772
+
1773
+ def whole_lines(index: @line_index, line: @line)
1774
+ temp_lines = @buffer_of_lines.dup
1775
+ temp_lines[index] = line
1776
+ temp_lines
1777
+ end
1778
+
1779
+ def whole_buffer
1780
+ if @buffer_of_lines.size == 1 and @line.nil?
1781
+ nil
1782
+ else
1783
+ if @previous_line_index
1784
+ whole_lines(index: @previous_line_index, line: @line).join("\n")
1785
+ else
1786
+ whole_lines.join("\n")
1787
+ end
1788
+ end
1789
+ end
1790
+
1791
+ def finished?
1792
+ @finished
1793
+ end
1794
+
1795
+ def finish
1796
+ @finished = true
1797
+ @rerender_all = true
1798
+ @config.reset
1799
+ end
1800
+
1801
+ private def byteslice!(str, byte_pointer, size)
1802
+ new_str = str.byteslice(0, byte_pointer)
1803
+ new_str << str.byteslice(byte_pointer + size, str.bytesize)
1804
+ [new_str, str.byteslice(byte_pointer, size)]
1805
+ end
1806
+
1807
+ private def byteinsert(str, byte_pointer, other)
1808
+ new_str = str.byteslice(0, byte_pointer)
1809
+ new_str << other
1810
+ new_str << str.byteslice(byte_pointer, str.bytesize)
1811
+ new_str
1812
+ end
1813
+
1814
+ private def calculate_width(str, allow_escape_code = false)
1815
+ Reline::Unicode.calculate_width(str, allow_escape_code)
1816
+ end
1817
+
1818
+ private def key_delete(key)
1819
+ if @config.editing_mode_is?(:vi_insert, :emacs)
1820
+ ed_delete_next_char(key)
1821
+ end
1822
+ end
1823
+
1824
+ private def key_newline(key)
1825
+ if @is_multiline
1826
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1827
+ @add_newline_to_end_of_buffer = true
1828
+ end
1829
+ next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1830
+ cursor_line = @line.byteslice(0, @byte_pointer)
1831
+ insert_new_line(cursor_line, next_line)
1832
+ @cursor = 0
1833
+ @check_new_auto_indent = true unless @in_pasting
1834
+ end
1835
+ end
1836
+
1837
+ private def ed_unassigned(key) end # do nothing
1838
+
1839
+ private def process_insert(force: false)
1840
+ return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1841
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1842
+ bytesize = @continuous_insertion_buffer.bytesize
1843
+ if @cursor == @cursor_max
1844
+ @line += @continuous_insertion_buffer
1845
+ else
1846
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1847
+ end
1848
+ @byte_pointer += bytesize
1849
+ @cursor += width
1850
+ @cursor_max += width
1851
+ @continuous_insertion_buffer.clear
1852
+ end
1853
+
1854
+ private def ed_insert(key)
1855
+ str = nil
1856
+ width = nil
1857
+ bytesize = nil
1858
+ if key.instance_of?(String)
1859
+ begin
1860
+ key.encode(Encoding::UTF_8)
1861
+ rescue Encoding::UndefinedConversionError
1862
+ return
1863
+ end
1864
+ str = key
1865
+ bytesize = key.bytesize
1866
+ else
1867
+ begin
1868
+ key.chr.encode(Encoding::UTF_8)
1869
+ rescue Encoding::UndefinedConversionError
1870
+ return
1871
+ end
1872
+ str = key.chr
1873
+ bytesize = 1
1874
+ end
1875
+ if @in_pasting
1876
+ @continuous_insertion_buffer << str
1877
+ return
1878
+ elsif not @continuous_insertion_buffer.empty?
1879
+ process_insert
1880
+ end
1881
+ width = Reline::Unicode.get_mbchar_width(str)
1882
+ if @cursor == @cursor_max
1883
+ @line += str
1884
+ else
1885
+ @line = byteinsert(@line, @byte_pointer, str)
1886
+ end
1887
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1888
+ @byte_pointer += bytesize
1889
+ last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1890
+ if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1891
+ width = 0
1892
+ end
1893
+ @cursor += width
1894
+ @cursor_max += width
1895
+ end
1896
+ alias_method :ed_digit, :ed_insert
1897
+ alias_method :self_insert, :ed_insert
1898
+
1899
+ private def ed_quoted_insert(str, arg: 1)
1900
+ @waiting_proc = proc { |key|
1901
+ arg.times do
1902
+ if key == "\C-j".ord or key == "\C-m".ord
1903
+ key_newline(key)
1904
+ else
1905
+ ed_insert(key)
1906
+ end
1907
+ end
1908
+ @waiting_proc = nil
1909
+ }
1910
+ end
1911
+ alias_method :quoted_insert, :ed_quoted_insert
1912
+
1913
+ private def ed_next_char(key, arg: 1)
1914
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1915
+ if (@byte_pointer < @line.bytesize)
1916
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1917
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1918
+ @cursor += width if width
1919
+ @byte_pointer += byte_size
1920
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
1921
+ next_line = @buffer_of_lines[@line_index + 1]
1922
+ @cursor = 0
1923
+ @byte_pointer = 0
1924
+ @cursor_max = calculate_width(next_line)
1925
+ @previous_line_index = @line_index
1926
+ @line_index += 1
1927
+ end
1928
+ arg -= 1
1929
+ ed_next_char(key, arg: arg) if arg > 0
1930
+ end
1931
+ alias_method :forward_char, :ed_next_char
1932
+
1933
+ private def ed_prev_char(key, arg: 1)
1934
+ if @cursor > 0
1935
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1936
+ @byte_pointer -= byte_size
1937
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1938
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1939
+ @cursor -= width
1940
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1941
+ prev_line = @buffer_of_lines[@line_index - 1]
1942
+ @cursor = calculate_width(prev_line)
1943
+ @byte_pointer = prev_line.bytesize
1944
+ @cursor_max = calculate_width(prev_line)
1945
+ @previous_line_index = @line_index
1946
+ @line_index -= 1
1947
+ end
1948
+ arg -= 1
1949
+ ed_prev_char(key, arg: arg) if arg > 0
1950
+ end
1951
+ alias_method :backward_char, :ed_prev_char
1952
+
1953
+ private def vi_first_print(key)
1954
+ @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
1955
+ end
1956
+
1957
+ private def ed_move_to_beg(key)
1958
+ @byte_pointer = @cursor = 0
1959
+ end
1960
+ alias_method :beginning_of_line, :ed_move_to_beg
1961
+
1962
+ private def ed_move_to_end(key)
1963
+ @byte_pointer = 0
1964
+ @cursor = 0
1965
+ byte_size = 0
1966
+ while @byte_pointer < @line.bytesize
1967
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1968
+ if byte_size > 0
1969
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1970
+ @cursor += Reline::Unicode.get_mbchar_width(mbchar)
1971
+ end
1972
+ @byte_pointer += byte_size
1973
+ end
1974
+ end
1975
+ alias_method :end_of_line, :ed_move_to_end
1976
+
1977
+ private def generate_searcher
1978
+ Fiber.new do |first_key|
1979
+ prev_search_key = first_key
1980
+ search_word = String.new(encoding: @encoding)
1981
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1982
+ last_hit = nil
1983
+ case first_key
1984
+ when "\C-r".ord
1985
+ prompt_name = 'reverse-i-search'
1986
+ when "\C-s".ord
1987
+ prompt_name = 'i-search'
1988
+ end
1989
+ loop do
1990
+ key = Fiber.yield(search_word)
1991
+ search_again = false
1992
+ case key
1993
+ when -1 # determined
1994
+ Reline.last_incremental_search = search_word
1995
+ break
1996
+ when "\C-h".ord, "\C-?".ord
1997
+ grapheme_clusters = search_word.grapheme_clusters
1998
+ if grapheme_clusters.size > 0
1999
+ grapheme_clusters.pop
2000
+ search_word = grapheme_clusters.join
2001
+ end
2002
+ when "\C-r".ord, "\C-s".ord
2003
+ search_again = true if prev_search_key == key
2004
+ prev_search_key = key
2005
+ else
2006
+ multibyte_buf << key
2007
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
2008
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
2009
+ multibyte_buf.clear
2010
+ end
2011
+ end
2012
+ hit = nil
2013
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
2014
+ @history_pointer = nil
2015
+ hit = @line_backup_in_history
2016
+ else
2017
+ if search_again
2018
+ if search_word.empty? and Reline.last_incremental_search
2019
+ search_word = Reline.last_incremental_search
2020
+ end
2021
+ if @history_pointer
2022
+ case prev_search_key
2023
+ when "\C-r".ord
2024
+ history_pointer_base = 0
2025
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
2026
+ when "\C-s".ord
2027
+ history_pointer_base = @history_pointer + 1
2028
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
2029
+ end
2030
+ else
2031
+ history_pointer_base = 0
2032
+ history = Reline::HISTORY
2033
+ end
2034
+ elsif @history_pointer
2035
+ case prev_search_key
2036
+ when "\C-r".ord
2037
+ history_pointer_base = 0
2038
+ history = Reline::HISTORY[0..@history_pointer]
2039
+ when "\C-s".ord
2040
+ history_pointer_base = @history_pointer
2041
+ history = Reline::HISTORY[@history_pointer..-1]
2042
+ end
2043
+ else
2044
+ history_pointer_base = 0
2045
+ history = Reline::HISTORY
2046
+ end
2047
+ case prev_search_key
2048
+ when "\C-r".ord
2049
+ hit_index = history.rindex { |item|
2050
+ item.include?(search_word)
2051
+ }
2052
+ when "\C-s".ord
2053
+ hit_index = history.index { |item|
2054
+ item.include?(search_word)
2055
+ }
2056
+ end
2057
+ if hit_index
2058
+ @history_pointer = history_pointer_base + hit_index
2059
+ hit = Reline::HISTORY[@history_pointer]
2060
+ end
2061
+ end
2062
+ case prev_search_key
2063
+ when "\C-r".ord
2064
+ prompt_name = 'reverse-i-search'
2065
+ when "\C-s".ord
2066
+ prompt_name = 'i-search'
2067
+ end
2068
+ if hit
2069
+ if @is_multiline
2070
+ @buffer_of_lines = hit.split("\n")
2071
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2072
+ @line_index = @buffer_of_lines.size - 1
2073
+ @line = @buffer_of_lines.last
2074
+ @rerender_all = true
2075
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
2076
+ else
2077
+ @line = hit
2078
+ @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
2079
+ end
2080
+ last_hit = hit
2081
+ else
2082
+ if @is_multiline
2083
+ @rerender_all = true
2084
+ @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
2085
+ else
2086
+ @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
2087
+ end
2088
+ end
2089
+ end
2090
+ end
2091
+ end
2092
+
2093
+ private def incremental_search_history(key)
2094
+ unless @history_pointer
2095
+ if @is_multiline
2096
+ @line_backup_in_history = whole_buffer
2097
+ else
2098
+ @line_backup_in_history = @line
2099
+ end
2100
+ end
2101
+ searcher = generate_searcher
2102
+ searcher.resume(key)
2103
+ @searching_prompt = "(reverse-i-search)`': "
2104
+ termination_keys = ["\C-j".ord]
2105
+ termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
2106
+ @waiting_proc = ->(k) {
2107
+ case k
2108
+ when *termination_keys
2109
+ if @history_pointer
2110
+ buffer = Reline::HISTORY[@history_pointer]
2111
+ else
2112
+ buffer = @line_backup_in_history
2113
+ end
2114
+ if @is_multiline
2115
+ @buffer_of_lines = buffer.split("\n")
2116
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2117
+ @line_index = @buffer_of_lines.size - 1
2118
+ @line = @buffer_of_lines.last
2119
+ @rerender_all = true
2120
+ else
2121
+ @line = buffer
2122
+ end
2123
+ @searching_prompt = nil
2124
+ @waiting_proc = nil
2125
+ @cursor_max = calculate_width(@line)
2126
+ @cursor = @byte_pointer = 0
2127
+ @rerender_all = true
2128
+ @cached_prompt_list = nil
2129
+ searcher.resume(-1)
2130
+ when "\C-g".ord
2131
+ if @is_multiline
2132
+ @buffer_of_lines = @line_backup_in_history.split("\n")
2133
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2134
+ @line_index = @buffer_of_lines.size - 1
2135
+ @line = @buffer_of_lines.last
2136
+ @rerender_all = true
2137
+ else
2138
+ @line = @line_backup_in_history
2139
+ end
2140
+ @history_pointer = nil
2141
+ @searching_prompt = nil
2142
+ @waiting_proc = nil
2143
+ @line_backup_in_history = nil
2144
+ @cursor_max = calculate_width(@line)
2145
+ @cursor = @byte_pointer = 0
2146
+ @rerender_all = true
2147
+ else
2148
+ chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
2149
+ if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
2150
+ searcher.resume(k)
2151
+ else
2152
+ if @history_pointer
2153
+ line = Reline::HISTORY[@history_pointer]
2154
+ else
2155
+ line = @line_backup_in_history
2156
+ end
2157
+ if @is_multiline
2158
+ @line_backup_in_history = whole_buffer
2159
+ @buffer_of_lines = line.split("\n")
2160
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2161
+ @line_index = @buffer_of_lines.size - 1
2162
+ @line = @buffer_of_lines.last
2163
+ @rerender_all = true
2164
+ else
2165
+ @line_backup_in_history = @line
2166
+ @line = line
2167
+ end
2168
+ @searching_prompt = nil
2169
+ @waiting_proc = nil
2170
+ @cursor_max = calculate_width(@line)
2171
+ @cursor = @byte_pointer = 0
2172
+ @rerender_all = true
2173
+ @cached_prompt_list = nil
2174
+ searcher.resume(-1)
2175
+ end
2176
+ end
2177
+ }
2178
+ end
2179
+
2180
+ private def vi_search_prev(key)
2181
+ incremental_search_history(key)
2182
+ end
2183
+ alias_method :reverse_search_history, :vi_search_prev
2184
+
2185
+ private def vi_search_next(key)
2186
+ incremental_search_history(key)
2187
+ end
2188
+ alias_method :forward_search_history, :vi_search_next
2189
+
2190
+ private def ed_search_prev_history(key, arg: 1)
2191
+ history = nil
2192
+ h_pointer = nil
2193
+ line_no = nil
2194
+ substr = @line.slice(0, @byte_pointer)
2195
+ if @history_pointer.nil?
2196
+ return if not @line.empty? and substr.empty?
2197
+ history = Reline::HISTORY
2198
+ elsif @history_pointer.zero?
2199
+ history = nil
2200
+ h_pointer = nil
2201
+ else
2202
+ history = Reline::HISTORY.slice(0, @history_pointer)
2203
+ end
2204
+ return if history.nil?
2205
+ if @is_multiline
2206
+ h_pointer = history.rindex { |h|
2207
+ h.split("\n").each_with_index { |l, i|
2208
+ if l.start_with?(substr)
2209
+ line_no = i
2210
+ break
2211
+ end
2212
+ }
2213
+ not line_no.nil?
2214
+ }
2215
+ else
2216
+ h_pointer = history.rindex { |l|
2217
+ l.start_with?(substr)
2218
+ }
2219
+ end
2220
+ return if h_pointer.nil?
2221
+ @history_pointer = h_pointer
2222
+ if @is_multiline
2223
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2224
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2225
+ @line_index = line_no
2226
+ @line = @buffer_of_lines[@line_index]
2227
+ @rerender_all = true
2228
+ else
2229
+ @line = Reline::HISTORY[@history_pointer]
2230
+ end
2231
+ @cursor_max = calculate_width(@line)
2232
+ arg -= 1
2233
+ ed_search_prev_history(key, arg: arg) if arg > 0
2234
+ end
2235
+ alias_method :history_search_backward, :ed_search_prev_history
2236
+
2237
+ private def ed_search_next_history(key, arg: 1)
2238
+ substr = @line.slice(0, @byte_pointer)
2239
+ if @history_pointer.nil?
2240
+ return
2241
+ elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
2242
+ return
2243
+ end
2244
+ history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
2245
+ h_pointer = nil
2246
+ line_no = nil
2247
+ if @is_multiline
2248
+ h_pointer = history.index { |h|
2249
+ h.split("\n").each_with_index { |l, i|
2250
+ if l.start_with?(substr)
2251
+ line_no = i
2252
+ break
2253
+ end
2254
+ }
2255
+ not line_no.nil?
2256
+ }
2257
+ else
2258
+ h_pointer = history.index { |l|
2259
+ l.start_with?(substr)
2260
+ }
2261
+ end
2262
+ h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
2263
+ return if h_pointer.nil? and not substr.empty?
2264
+ @history_pointer = h_pointer
2265
+ if @is_multiline
2266
+ if @history_pointer.nil? and substr.empty?
2267
+ @buffer_of_lines = []
2268
+ @line_index = 0
2269
+ else
2270
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2271
+ @line_index = line_no
2272
+ end
2273
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2274
+ @line = @buffer_of_lines[@line_index]
2275
+ @rerender_all = true
2276
+ else
2277
+ if @history_pointer.nil? and substr.empty?
2278
+ @line = ''
2279
+ else
2280
+ @line = Reline::HISTORY[@history_pointer]
2281
+ end
2282
+ end
2283
+ @cursor_max = calculate_width(@line)
2284
+ arg -= 1
2285
+ ed_search_next_history(key, arg: arg) if arg > 0
2286
+ end
2287
+ alias_method :history_search_forward, :ed_search_next_history
2288
+
2289
+ private def ed_prev_history(key, arg: 1)
2290
+ if @is_multiline and @line_index > 0
2291
+ @previous_line_index = @line_index
2292
+ @line_index -= 1
2293
+ return
2294
+ end
2295
+ if Reline::HISTORY.empty?
2296
+ return
2297
+ end
2298
+ if @history_pointer.nil?
2299
+ @history_pointer = Reline::HISTORY.size - 1
2300
+ if @is_multiline
2301
+ @line_backup_in_history = whole_buffer
2302
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2303
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2304
+ @line_index = @buffer_of_lines.size - 1
2305
+ @line = @buffer_of_lines.last
2306
+ @rerender_all = true
2307
+ else
2308
+ @line_backup_in_history = @line
2309
+ @line = Reline::HISTORY[@history_pointer]
2310
+ end
2311
+ elsif @history_pointer.zero?
2312
+ return
2313
+ else
2314
+ if @is_multiline
2315
+ Reline::HISTORY[@history_pointer] = whole_buffer
2316
+ @history_pointer -= 1
2317
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2318
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2319
+ @line_index = @buffer_of_lines.size - 1
2320
+ @line = @buffer_of_lines.last
2321
+ @rerender_all = true
2322
+ else
2323
+ Reline::HISTORY[@history_pointer] = @line
2324
+ @history_pointer -= 1
2325
+ @line = Reline::HISTORY[@history_pointer]
2326
+ end
2327
+ end
2328
+ if @config.editing_mode_is?(:emacs, :vi_insert)
2329
+ @cursor_max = @cursor = calculate_width(@line)
2330
+ @byte_pointer = @line.bytesize
2331
+ elsif @config.editing_mode_is?(:vi_command)
2332
+ @byte_pointer = @cursor = 0
2333
+ @cursor_max = calculate_width(@line)
2334
+ end
2335
+ arg -= 1
2336
+ ed_prev_history(key, arg: arg) if arg > 0
2337
+ end
2338
+
2339
+ private def ed_next_history(key, arg: 1)
2340
+ if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
2341
+ @previous_line_index = @line_index
2342
+ @line_index += 1
2343
+ return
2344
+ end
2345
+ if @history_pointer.nil?
2346
+ return
2347
+ elsif @history_pointer == (Reline::HISTORY.size - 1)
2348
+ if @is_multiline
2349
+ @history_pointer = nil
2350
+ @buffer_of_lines = @line_backup_in_history.split("\n")
2351
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2352
+ @line_index = 0
2353
+ @line = @buffer_of_lines.first
2354
+ @rerender_all = true
2355
+ else
2356
+ @history_pointer = nil
2357
+ @line = @line_backup_in_history
2358
+ end
2359
+ else
2360
+ if @is_multiline
2361
+ Reline::HISTORY[@history_pointer] = whole_buffer
2362
+ @history_pointer += 1
2363
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2364
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2365
+ @line_index = 0
2366
+ @line = @buffer_of_lines.first
2367
+ @rerender_all = true
2368
+ else
2369
+ Reline::HISTORY[@history_pointer] = @line
2370
+ @history_pointer += 1
2371
+ @line = Reline::HISTORY[@history_pointer]
2372
+ end
2373
+ end
2374
+ @line = '' unless @line
2375
+ if @config.editing_mode_is?(:emacs, :vi_insert)
2376
+ @cursor_max = @cursor = calculate_width(@line)
2377
+ @byte_pointer = @line.bytesize
2378
+ elsif @config.editing_mode_is?(:vi_command)
2379
+ @byte_pointer = @cursor = 0
2380
+ @cursor_max = calculate_width(@line)
2381
+ end
2382
+ arg -= 1
2383
+ ed_next_history(key, arg: arg) if arg > 0
2384
+ end
2385
+
2386
+ private def ed_newline(key)
2387
+ process_insert(force: true)
2388
+ if @is_multiline
2389
+ if @config.editing_mode_is?(:vi_command)
2390
+ if @line_index < (@buffer_of_lines.size - 1)
2391
+ ed_next_history(key) # means cursor down
2392
+ else
2393
+ # should check confirm_multiline_termination to finish?
2394
+ finish
2395
+ end
2396
+ else
2397
+ if @line_index == (@buffer_of_lines.size - 1)
2398
+ if confirm_multiline_termination
2399
+ finish
2400
+ else
2401
+ key_newline(key)
2402
+ end
2403
+ else
2404
+ # should check confirm_multiline_termination to finish?
2405
+ @previous_line_index = @line_index
2406
+ @line_index = @buffer_of_lines.size - 1
2407
+ finish
2408
+ end
2409
+ end
2410
+ else
2411
+ if @history_pointer
2412
+ Reline::HISTORY[@history_pointer] = @line
2413
+ @history_pointer = nil
2414
+ end
2415
+ finish
2416
+ end
2417
+ end
2418
+
2419
+ private def em_delete_prev_char(key)
2420
+ if @is_multiline and @cursor == 0 and @line_index > 0
2421
+ @buffer_of_lines[@line_index] = @line
2422
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2423
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2424
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2425
+ @line_index -= 1
2426
+ @line = @buffer_of_lines[@line_index]
2427
+ @cursor_max = calculate_width(@line)
2428
+ @rerender_all = true
2429
+ elsif @cursor > 0
2430
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2431
+ @byte_pointer -= byte_size
2432
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2433
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2434
+ @cursor -= width
2435
+ @cursor_max -= width
2436
+ end
2437
+ end
2438
+ alias_method :backward_delete_char, :em_delete_prev_char
2439
+
2440
+ private def ed_kill_line(key)
2441
+ if @line.bytesize > @byte_pointer
2442
+ @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
2443
+ @byte_pointer = @line.bytesize
2444
+ @cursor = @cursor_max = calculate_width(@line)
2445
+ @kill_ring.append(deleted)
2446
+ elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2447
+ @cursor = calculate_width(@line)
2448
+ @byte_pointer = @line.bytesize
2449
+ @line += @buffer_of_lines.delete_at(@line_index + 1)
2450
+ @cursor_max = calculate_width(@line)
2451
+ @buffer_of_lines[@line_index] = @line
2452
+ @rerender_all = true
2453
+ @rest_height += 1
2454
+ end
2455
+ end
2456
+
2457
+ private def em_kill_line(key)
2458
+ if @byte_pointer > 0
2459
+ @line, deleted = byteslice!(@line, 0, @byte_pointer)
2460
+ @byte_pointer = 0
2461
+ @kill_ring.append(deleted, true)
2462
+ @cursor_max = calculate_width(@line)
2463
+ @cursor = 0
2464
+ end
2465
+ end
2466
+ alias_method :kill_line, :em_kill_line
2467
+
2468
+ private def em_delete(key)
2469
+ if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2470
+ @line = nil
2471
+ if @buffer_of_lines.size > 1
2472
+ scroll_down(@highest_in_all - @first_line_started_from)
2473
+ end
2474
+ Reline::IOGate.move_cursor_column(0)
2475
+ @eof = true
2476
+ finish
2477
+ elsif @byte_pointer < @line.bytesize
2478
+ splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
2479
+ mbchar = splitted_last.grapheme_clusters.first
2480
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2481
+ @cursor_max -= width
2482
+ @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
2483
+ elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2484
+ @cursor = calculate_width(@line)
2485
+ @byte_pointer = @line.bytesize
2486
+ @line += @buffer_of_lines.delete_at(@line_index + 1)
2487
+ @cursor_max = calculate_width(@line)
2488
+ @buffer_of_lines[@line_index] = @line
2489
+ @rerender_all = true
2490
+ @rest_height += 1
2491
+ end
2492
+ end
2493
+ alias_method :delete_char, :em_delete
2494
+
2495
+ private def em_delete_or_list(key)
2496
+ if @line.empty? or @byte_pointer < @line.bytesize
2497
+ em_delete(key)
2498
+ else # show completed list
2499
+ result = call_completion_proc
2500
+ if result.is_a?(Array)
2501
+ complete(result, true)
2502
+ end
2503
+ end
2504
+ end
2505
+ alias_method :delete_char_or_list, :em_delete_or_list
2506
+
2507
+ private def em_yank(key)
2508
+ yanked = @kill_ring.yank
2509
+ if yanked
2510
+ @line = byteinsert(@line, @byte_pointer, yanked)
2511
+ yanked_width = calculate_width(yanked)
2512
+ @cursor += yanked_width
2513
+ @cursor_max += yanked_width
2514
+ @byte_pointer += yanked.bytesize
2515
+ end
2516
+ end
2517
+ alias_method :yank, :em_yank
2518
+
2519
+ private def em_yank_pop(key)
2520
+ yanked, prev_yank = @kill_ring.yank_pop
2521
+ if yanked
2522
+ prev_yank_width = calculate_width(prev_yank)
2523
+ @cursor -= prev_yank_width
2524
+ @cursor_max -= prev_yank_width
2525
+ @byte_pointer -= prev_yank.bytesize
2526
+ @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
2527
+ @line = byteinsert(@line, @byte_pointer, yanked)
2528
+ yanked_width = calculate_width(yanked)
2529
+ @cursor += yanked_width
2530
+ @cursor_max += yanked_width
2531
+ @byte_pointer += yanked.bytesize
2532
+ end
2533
+ end
2534
+ alias_method :yank_pop, :em_yank_pop
2535
+
2536
+ private def ed_clear_screen(key)
2537
+ @cleared = true
2538
+ end
2539
+ alias_method :clear_screen, :ed_clear_screen
2540
+
2541
+ private def em_next_word(key)
2542
+ if @line.bytesize > @byte_pointer
2543
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2544
+ @byte_pointer += byte_size
2545
+ @cursor += width
2546
+ end
2547
+ end
2548
+ alias_method :forward_word, :em_next_word
2549
+
2550
+ private def ed_prev_word(key)
2551
+ if @byte_pointer > 0
2552
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2553
+ @byte_pointer -= byte_size
2554
+ @cursor -= width
2555
+ end
2556
+ end
2557
+ alias_method :backward_word, :ed_prev_word
2558
+
2559
+ private def em_delete_next_word(key)
2560
+ if @line.bytesize > @byte_pointer
2561
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2562
+ @line, word = byteslice!(@line, @byte_pointer, byte_size)
2563
+ @kill_ring.append(word)
2564
+ @cursor_max -= width
2565
+ end
2566
+ end
2567
+
2568
+ private def ed_delete_prev_word(key)
2569
+ if @byte_pointer > 0
2570
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2571
+ @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2572
+ @kill_ring.append(word, true)
2573
+ @byte_pointer -= byte_size
2574
+ @cursor -= width
2575
+ @cursor_max -= width
2576
+ end
2577
+ end
2578
+
2579
+ private def ed_transpose_chars(key)
2580
+ if @byte_pointer > 0
2581
+ if @cursor_max > @cursor
2582
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2583
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
2584
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2585
+ @cursor += width
2586
+ @byte_pointer += byte_size
2587
+ end
2588
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2589
+ if (@byte_pointer - back1_byte_size) > 0
2590
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
2591
+ back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
2592
+ @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
2593
+ @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
2594
+ end
2595
+ end
2596
+ end
2597
+ alias_method :transpose_chars, :ed_transpose_chars
2598
+
2599
+ private def ed_transpose_words(key)
2600
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
2601
+ before = @line.byteslice(0, left_word_start)
2602
+ left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
2603
+ middle = @line.byteslice(middle_start, right_word_start - middle_start)
2604
+ right_word = @line.byteslice(right_word_start, after_start - right_word_start)
2605
+ after = @line.byteslice(after_start, @line.bytesize - after_start)
2606
+ return if left_word.empty? or right_word.empty?
2607
+ @line = before + right_word + middle + left_word + after
2608
+ from_head_to_left_word = before + right_word + middle + left_word
2609
+ @byte_pointer = from_head_to_left_word.bytesize
2610
+ @cursor = calculate_width(from_head_to_left_word)
2611
+ end
2612
+ alias_method :transpose_words, :ed_transpose_words
2613
+
2614
+ private def em_capitol_case(key)
2615
+ if @line.bytesize > @byte_pointer
2616
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
2617
+ before = @line.byteslice(0, @byte_pointer)
2618
+ after = @line.byteslice((@byte_pointer + byte_size)..-1)
2619
+ @line = before + new_str + after
2620
+ @byte_pointer += new_str.bytesize
2621
+ @cursor += calculate_width(new_str)
2622
+ end
2623
+ end
2624
+ alias_method :capitalize_word, :em_capitol_case
2625
+
2626
+ private def em_lower_case(key)
2627
+ if @line.bytesize > @byte_pointer
2628
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2629
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2630
+ mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2631
+ }.join
2632
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2633
+ @line = @line.byteslice(0, @byte_pointer) + part
2634
+ @byte_pointer = @line.bytesize
2635
+ @cursor = calculate_width(@line)
2636
+ @cursor_max = @cursor + calculate_width(rest)
2637
+ @line += rest
2638
+ end
2639
+ end
2640
+ alias_method :downcase_word, :em_lower_case
2641
+
2642
+ private def em_upper_case(key)
2643
+ if @line.bytesize > @byte_pointer
2644
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2645
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2646
+ mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2647
+ }.join
2648
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2649
+ @line = @line.byteslice(0, @byte_pointer) + part
2650
+ @byte_pointer = @line.bytesize
2651
+ @cursor = calculate_width(@line)
2652
+ @cursor_max = @cursor + calculate_width(rest)
2653
+ @line += rest
2654
+ end
2655
+ end
2656
+ alias_method :upcase_word, :em_upper_case
2657
+
2658
+ private def em_kill_region(key)
2659
+ if @byte_pointer > 0
2660
+ byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
2661
+ @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2662
+ @byte_pointer -= byte_size
2663
+ @cursor -= width
2664
+ @cursor_max -= width
2665
+ @kill_ring.append(deleted, true)
2666
+ end
2667
+ end
2668
+ alias_method :unix_word_rubout, :em_kill_region
2669
+
2670
+ private def copy_for_vi(text)
2671
+ if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
2672
+ @vi_clipboard = text
2673
+ end
2674
+ end
2675
+
2676
+ private def vi_insert(key)
2677
+ @config.editing_mode = :vi_insert
2678
+ end
2679
+
2680
+ private def vi_add(key)
2681
+ @config.editing_mode = :vi_insert
2682
+ ed_next_char(key)
2683
+ end
2684
+
2685
+ private def vi_command_mode(key)
2686
+ ed_prev_char(key)
2687
+ @config.editing_mode = :vi_command
2688
+ end
2689
+ alias_method :vi_movement_mode, :vi_command_mode
2690
+
2691
+ private def vi_next_word(key, arg: 1)
2692
+ if @line.bytesize > @byte_pointer
2693
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
2694
+ @byte_pointer += byte_size
2695
+ @cursor += width
2696
+ end
2697
+ arg -= 1
2698
+ vi_next_word(key, arg: arg) if arg > 0
2699
+ end
2700
+
2701
+ private def vi_prev_word(key, arg: 1)
2702
+ if @byte_pointer > 0
2703
+ byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
2704
+ @byte_pointer -= byte_size
2705
+ @cursor -= width
2706
+ end
2707
+ arg -= 1
2708
+ vi_prev_word(key, arg: arg) if arg > 0
2709
+ end
2710
+
2711
+ private def vi_end_word(key, arg: 1, inclusive: false)
2712
+ if @line.bytesize > @byte_pointer
2713
+ byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
2714
+ @byte_pointer += byte_size
2715
+ @cursor += width
2716
+ end
2717
+ arg -= 1
2718
+ if inclusive and arg.zero?
2719
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2720
+ if byte_size > 0
2721
+ c = @line.byteslice(@byte_pointer, byte_size)
2722
+ width = Reline::Unicode.get_mbchar_width(c)
2723
+ @byte_pointer += byte_size
2724
+ @cursor += width
2725
+ end
2726
+ end
2727
+ vi_end_word(key, arg: arg) if arg > 0
2728
+ end
2729
+
2730
+ private def vi_next_big_word(key, arg: 1)
2731
+ if @line.bytesize > @byte_pointer
2732
+ byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
2733
+ @byte_pointer += byte_size
2734
+ @cursor += width
2735
+ end
2736
+ arg -= 1
2737
+ vi_next_big_word(key, arg: arg) if arg > 0
2738
+ end
2739
+
2740
+ private def vi_prev_big_word(key, arg: 1)
2741
+ if @byte_pointer > 0
2742
+ byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
2743
+ @byte_pointer -= byte_size
2744
+ @cursor -= width
2745
+ end
2746
+ arg -= 1
2747
+ vi_prev_big_word(key, arg: arg) if arg > 0
2748
+ end
2749
+
2750
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
2751
+ if @line.bytesize > @byte_pointer
2752
+ byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
2753
+ @byte_pointer += byte_size
2754
+ @cursor += width
2755
+ end
2756
+ arg -= 1
2757
+ if inclusive and arg.zero?
2758
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2759
+ if byte_size > 0
2760
+ c = @line.byteslice(@byte_pointer, byte_size)
2761
+ width = Reline::Unicode.get_mbchar_width(c)
2762
+ @byte_pointer += byte_size
2763
+ @cursor += width
2764
+ end
2765
+ end
2766
+ vi_end_big_word(key, arg: arg) if arg > 0
2767
+ end
2768
+
2769
+ private def vi_delete_prev_char(key)
2770
+ if @is_multiline and @cursor == 0 and @line_index > 0
2771
+ @buffer_of_lines[@line_index] = @line
2772
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2773
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2774
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2775
+ @line_index -= 1
2776
+ @line = @buffer_of_lines[@line_index]
2777
+ @cursor_max = calculate_width(@line)
2778
+ @rerender_all = true
2779
+ elsif @cursor > 0
2780
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2781
+ @byte_pointer -= byte_size
2782
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2783
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2784
+ @cursor -= width
2785
+ @cursor_max -= width
2786
+ end
2787
+ end
2788
+
2789
+ private def vi_insert_at_bol(key)
2790
+ ed_move_to_beg(key)
2791
+ @config.editing_mode = :vi_insert
2792
+ end
2793
+
2794
+ private def vi_add_at_eol(key)
2795
+ ed_move_to_end(key)
2796
+ @config.editing_mode = :vi_insert
2797
+ end
2798
+
2799
+ private def ed_delete_prev_char(key, arg: 1)
2800
+ deleted = ''
2801
+ arg.times do
2802
+ if @cursor > 0
2803
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2804
+ @byte_pointer -= byte_size
2805
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2806
+ deleted.prepend(mbchar)
2807
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2808
+ @cursor -= width
2809
+ @cursor_max -= width
2810
+ end
2811
+ end
2812
+ copy_for_vi(deleted)
2813
+ end
2814
+
2815
+ private def vi_zero(key)
2816
+ @byte_pointer = 0
2817
+ @cursor = 0
2818
+ end
2819
+
2820
+ private def vi_change_meta(key, arg: 1)
2821
+ @drop_terminate_spaces = true
2822
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2823
+ if byte_pointer_diff > 0
2824
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2825
+ elsif byte_pointer_diff < 0
2826
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2827
+ end
2828
+ copy_for_vi(cut)
2829
+ @cursor += cursor_diff if cursor_diff < 0
2830
+ @cursor_max -= cursor_diff.abs
2831
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2832
+ @config.editing_mode = :vi_insert
2833
+ @drop_terminate_spaces = false
2834
+ }
2835
+ @waiting_operator_vi_arg = arg
2836
+ end
2837
+
2838
+ private def vi_delete_meta(key, arg: 1)
2839
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2840
+ if byte_pointer_diff > 0
2841
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2842
+ elsif byte_pointer_diff < 0
2843
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2844
+ end
2845
+ copy_for_vi(cut)
2846
+ @cursor += cursor_diff if cursor_diff < 0
2847
+ @cursor_max -= cursor_diff.abs
2848
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2849
+ }
2850
+ @waiting_operator_vi_arg = arg
2851
+ end
2852
+
2853
+ private def vi_yank(key, arg: 1)
2854
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2855
+ if byte_pointer_diff > 0
2856
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2857
+ elsif byte_pointer_diff < 0
2858
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2859
+ end
2860
+ copy_for_vi(cut)
2861
+ }
2862
+ @waiting_operator_vi_arg = arg
2863
+ end
2864
+
2865
+ private def vi_list_or_eof(key)
2866
+ if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2867
+ @line = nil
2868
+ if @buffer_of_lines.size > 1
2869
+ scroll_down(@highest_in_all - @first_line_started_from)
2870
+ end
2871
+ Reline::IOGate.move_cursor_column(0)
2872
+ @eof = true
2873
+ finish
2874
+ else
2875
+ ed_newline(key)
2876
+ end
2877
+ end
2878
+ alias_method :vi_end_of_transmission, :vi_list_or_eof
2879
+ alias_method :vi_eof_maybe, :vi_list_or_eof
2880
+
2881
+ private def ed_delete_next_char(key, arg: 1)
2882
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2883
+ unless @line.empty? || byte_size == 0
2884
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2885
+ copy_for_vi(mbchar)
2886
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2887
+ @cursor_max -= width
2888
+ if @cursor > 0 and @cursor >= @cursor_max
2889
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2890
+ mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
2891
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2892
+ @byte_pointer -= byte_size
2893
+ @cursor -= width
2894
+ end
2895
+ end
2896
+ arg -= 1
2897
+ ed_delete_next_char(key, arg: arg) if arg > 0
2898
+ end
2899
+
2900
+ private def vi_to_history_line(key)
2901
+ if Reline::HISTORY.empty?
2902
+ return
2903
+ end
2904
+ if @history_pointer.nil?
2905
+ @history_pointer = 0
2906
+ @line_backup_in_history = @line
2907
+ @line = Reline::HISTORY[@history_pointer]
2908
+ @cursor_max = calculate_width(@line)
2909
+ @cursor = 0
2910
+ @byte_pointer = 0
2911
+ elsif @history_pointer.zero?
2912
+ return
2913
+ else
2914
+ Reline::HISTORY[@history_pointer] = @line
2915
+ @history_pointer = 0
2916
+ @line = Reline::HISTORY[@history_pointer]
2917
+ @cursor_max = calculate_width(@line)
2918
+ @cursor = 0
2919
+ @byte_pointer = 0
2920
+ end
2921
+ end
2922
+
2923
+ private def vi_histedit(key)
2924
+ path = Tempfile.open { |fp|
2925
+ if @is_multiline
2926
+ fp.write whole_lines.join("\n")
2927
+ else
2928
+ fp.write @line
2929
+ end
2930
+ fp.path
2931
+ }
2932
+ system("#{ENV['EDITOR']} #{path}")
2933
+ if @is_multiline
2934
+ @buffer_of_lines = File.read(path).split("\n")
2935
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2936
+ @line_index = 0
2937
+ @line = @buffer_of_lines[@line_index]
2938
+ @rerender_all = true
2939
+ else
2940
+ @line = File.read(path)
2941
+ end
2942
+ finish
2943
+ end
2944
+
2945
+ private def vi_paste_prev(key, arg: 1)
2946
+ if @vi_clipboard.size > 0
2947
+ @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
2948
+ @cursor_max += calculate_width(@vi_clipboard)
2949
+ cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
2950
+ @cursor += calculate_width(cursor_point)
2951
+ @byte_pointer += cursor_point.bytesize
2952
+ end
2953
+ arg -= 1
2954
+ vi_paste_prev(key, arg: arg) if arg > 0
2955
+ end
2956
+
2957
+ private def vi_paste_next(key, arg: 1)
2958
+ if @vi_clipboard.size > 0
2959
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2960
+ @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
2961
+ @cursor_max += calculate_width(@vi_clipboard)
2962
+ @cursor += calculate_width(@vi_clipboard)
2963
+ @byte_pointer += @vi_clipboard.bytesize
2964
+ end
2965
+ arg -= 1
2966
+ vi_paste_next(key, arg: arg) if arg > 0
2967
+ end
2968
+
2969
+ private def ed_argument_digit(key)
2970
+ if @vi_arg.nil?
2971
+ unless key.chr.to_i.zero?
2972
+ @vi_arg = key.chr.to_i
2973
+ end
2974
+ else
2975
+ @vi_arg = @vi_arg * 10 + key.chr.to_i
2976
+ end
2977
+ end
2978
+
2979
+ private def vi_to_column(key, arg: 0)
2980
+ @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
2981
+ # total has [byte_size, cursor]
2982
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
2983
+ if (total.last + mbchar_width) >= arg
2984
+ break total
2985
+ elsif (total.last + mbchar_width) >= @cursor_max
2986
+ break total
2987
+ else
2988
+ total = [total.first + gc.bytesize, total.last + mbchar_width]
2989
+ total
2990
+ end
2991
+ }
2992
+ end
2993
+
2994
+ private def vi_replace_char(key, arg: 1)
2995
+ @waiting_proc = ->(k) {
2996
+ if arg == 1
2997
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2998
+ before = @line.byteslice(0, @byte_pointer)
2999
+ remaining_point = @byte_pointer + byte_size
3000
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
3001
+ @line = before + k.chr + after
3002
+ @cursor_max = calculate_width(@line)
3003
+ @waiting_proc = nil
3004
+ elsif arg > 1
3005
+ byte_size = 0
3006
+ arg.times do
3007
+ byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
3008
+ end
3009
+ before = @line.byteslice(0, @byte_pointer)
3010
+ remaining_point = @byte_pointer + byte_size
3011
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
3012
+ replaced = k.chr * arg
3013
+ @line = before + replaced + after
3014
+ @byte_pointer += replaced.bytesize
3015
+ @cursor += calculate_width(replaced)
3016
+ @cursor_max = calculate_width(@line)
3017
+ @waiting_proc = nil
3018
+ end
3019
+ }
3020
+ end
3021
+
3022
+ private def vi_next_char(key, arg: 1, inclusive: false)
3023
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
3024
+ end
3025
+
3026
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
3027
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
3028
+ end
3029
+
3030
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
3031
+ if key.instance_of?(String)
3032
+ inputed_char = key
3033
+ else
3034
+ inputed_char = key.chr
3035
+ end
3036
+ prev_total = nil
3037
+ total = nil
3038
+ found = false
3039
+ @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
3040
+ # total has [byte_size, cursor]
3041
+ unless total
3042
+ # skip cursor point
3043
+ width = Reline::Unicode.get_mbchar_width(mbchar)
3044
+ total = [mbchar.bytesize, width]
3045
+ else
3046
+ if inputed_char == mbchar
3047
+ arg -= 1
3048
+ if arg.zero?
3049
+ found = true
3050
+ break
3051
+ end
3052
+ end
3053
+ width = Reline::Unicode.get_mbchar_width(mbchar)
3054
+ prev_total = total
3055
+ total = [total.first + mbchar.bytesize, total.last + width]
3056
+ end
3057
+ end
3058
+ if not need_prev_char and found and total
3059
+ byte_size, width = total
3060
+ @byte_pointer += byte_size
3061
+ @cursor += width
3062
+ elsif need_prev_char and found and prev_total
3063
+ byte_size, width = prev_total
3064
+ @byte_pointer += byte_size
3065
+ @cursor += width
3066
+ end
3067
+ if inclusive
3068
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3069
+ if byte_size > 0
3070
+ c = @line.byteslice(@byte_pointer, byte_size)
3071
+ width = Reline::Unicode.get_mbchar_width(c)
3072
+ @byte_pointer += byte_size
3073
+ @cursor += width
3074
+ end
3075
+ end
3076
+ @waiting_proc = nil
3077
+ end
3078
+
3079
+ private def vi_prev_char(key, arg: 1)
3080
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
3081
+ end
3082
+
3083
+ private def vi_to_prev_char(key, arg: 1)
3084
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
3085
+ end
3086
+
3087
+ private def search_prev_char(key, arg, need_next_char = false)
3088
+ if key.instance_of?(String)
3089
+ inputed_char = key
3090
+ else
3091
+ inputed_char = key.chr
3092
+ end
3093
+ prev_total = nil
3094
+ total = nil
3095
+ found = false
3096
+ @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
3097
+ # total has [byte_size, cursor]
3098
+ unless total
3099
+ # skip cursor point
3100
+ width = Reline::Unicode.get_mbchar_width(mbchar)
3101
+ total = [mbchar.bytesize, width]
3102
+ else
3103
+ if inputed_char == mbchar
3104
+ arg -= 1
3105
+ if arg.zero?
3106
+ found = true
3107
+ break
3108
+ end
3109
+ end
3110
+ width = Reline::Unicode.get_mbchar_width(mbchar)
3111
+ prev_total = total
3112
+ total = [total.first + mbchar.bytesize, total.last + width]
3113
+ end
3114
+ end
3115
+ if not need_next_char and found and total
3116
+ byte_size, width = total
3117
+ @byte_pointer -= byte_size
3118
+ @cursor -= width
3119
+ elsif need_next_char and found and prev_total
3120
+ byte_size, width = prev_total
3121
+ @byte_pointer -= byte_size
3122
+ @cursor -= width
3123
+ end
3124
+ @waiting_proc = nil
3125
+ end
3126
+
3127
+ private def vi_join_lines(key, arg: 1)
3128
+ if @is_multiline and @buffer_of_lines.size > @line_index + 1
3129
+ @cursor = calculate_width(@line)
3130
+ @byte_pointer = @line.bytesize
3131
+ @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
3132
+ @cursor_max = calculate_width(@line)
3133
+ @buffer_of_lines[@line_index] = @line
3134
+ @rerender_all = true
3135
+ @rest_height += 1
3136
+ end
3137
+ arg -= 1
3138
+ vi_join_lines(key, arg: arg) if arg > 0
3139
+ end
3140
+
3141
+ private def em_set_mark(key)
3142
+ @mark_pointer = [@byte_pointer, @line_index]
3143
+ end
3144
+ alias_method :set_mark, :em_set_mark
3145
+
3146
+ private def em_exchange_mark(key)
3147
+ return unless @mark_pointer
3148
+ new_pointer = [@byte_pointer, @line_index]
3149
+ @previous_line_index = @line_index
3150
+ @byte_pointer, @line_index = @mark_pointer
3151
+ @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
3152
+ @cursor_max = calculate_width(@line)
3153
+ @mark_pointer = new_pointer
3154
+ end
3155
+ alias_method :exchange_point_and_mark, :em_exchange_mark
3156
+ end