reline 0.0.5 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05acbc99c317aaa7f46d205bf73a13cc482bcfba9f6f05562f189f91082b4fd7
4
- data.tar.gz: d785c8da605eba1b1440110ccbf197b1c5c654cce78dd18b17192e6686aa62f6
3
+ metadata.gz: 7612855f90bdd7b5602c7d9e7b2b4de200d6986c45ff64b5a356380c1211b83e
4
+ data.tar.gz: 58406022787d706f400e828e31fde79b3e90c09a106b82a84218e5b748725db4
5
5
  SHA512:
6
- metadata.gz: 4d964d8b085973dd17da51e8359c93377c5356b01db720258b3a0a27c3b03b34e77c099d290f2f2fa41d2f8f6da1dd5cdcdcca4f7a18e1bbb199e9a755f6caca
7
- data.tar.gz: 8c93e1e261d29ff6e27f0544bf6ddfe3eb41ac573111c59cc68869f508363a109880909c2893ebdc9b281b50d78fe795925667f63a7b4e5e1e26a91f93019adf
6
+ metadata.gz: dcd02ec0e09c6dafa6cf3e9e1dbbeab9befabe033a1ae581628ff01fb4435fce3bb4cbe4cccb5890761e1a58a6637379a5f154ed67f28edaf46a71ba6a00d5c1
7
+ data.tar.gz: 55a61bcab1c41d35002462c80f11d4406426a815bc84ddb680f73c023139fea95e89bc5cc3d1a9e25f1b77ed0986923e5152cfdd489b9e484bd2e459a6602256
@@ -16,12 +16,6 @@ module Reline
16
16
  CursorPos = Struct.new(:x, :y)
17
17
 
18
18
  class Core
19
- if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
20
- IS_WINDOWS = true
21
- else
22
- IS_WINDOWS = false
23
- end
24
-
25
19
  ATTR_READER_NAMES = %i(
26
20
  completion_append_character
27
21
  basic_word_break_characters
@@ -38,19 +32,17 @@ module Reline
38
32
  dig_perfect_match_proc
39
33
  ).each(&method(:attr_reader))
40
34
 
41
- ATTR_ACCESSOR_NAMES = %i(
42
- completion_case_fold
43
- ).each(&method(:attr_accessor))
44
-
45
35
  attr_accessor :config
46
36
  attr_accessor :key_stroke
47
37
  attr_accessor :line_editor
48
38
  attr_accessor :ambiguous_width
39
+ attr_accessor :last_incremental_search
49
40
  attr_reader :output
50
41
 
51
42
  def initialize
52
43
  self.output = STDOUT
53
44
  yield self
45
+ @completion_quote_character = nil
54
46
  end
55
47
 
56
48
  def completion_append_character=(val)
@@ -89,23 +81,35 @@ module Reline
89
81
  @special_prefixes = v.encode(Encoding::default_external)
90
82
  end
91
83
 
84
+ def completion_case_fold=(v)
85
+ @config.completion_ignore_case = v
86
+ end
87
+
88
+ def completion_case_fold
89
+ @config.completion_ignore_case
90
+ end
91
+
92
+ def completion_quote_character
93
+ @completion_quote_character
94
+ end
95
+
92
96
  def completion_proc=(p)
93
- raise ArgumentError unless p.is_a?(Proc)
97
+ raise ArgumentError unless p.respond_to?(:call)
94
98
  @completion_proc = p
95
99
  end
96
100
 
97
101
  def output_modifier_proc=(p)
98
- raise ArgumentError unless p.is_a?(Proc)
102
+ raise ArgumentError unless p.respond_to?(:call)
99
103
  @output_modifier_proc = p
100
104
  end
101
105
 
102
106
  def prompt_proc=(p)
103
- raise ArgumentError unless p.is_a?(Proc)
107
+ raise ArgumentError unless p.respond_to?(:call)
104
108
  @prompt_proc = p
105
109
  end
106
110
 
107
111
  def auto_indent_proc=(p)
108
- raise ArgumentError unless p.is_a?(Proc)
112
+ raise ArgumentError unless p.respond_to?(:call)
109
113
  @auto_indent_proc = p
110
114
  end
111
115
 
@@ -114,7 +118,7 @@ module Reline
114
118
  end
115
119
 
116
120
  def dig_perfect_match_proc=(p)
117
- raise ArgumentError unless p.is_a?(Proc)
121
+ raise ArgumentError unless p.respond_to?(:call)
118
122
  @dig_perfect_match_proc = p
119
123
  end
120
124
 
@@ -208,6 +212,7 @@ module Reline
208
212
  end
209
213
  line_editor.output = output
210
214
  line_editor.completion_proc = completion_proc
215
+ line_editor.completion_append_character = completion_append_character
211
216
  line_editor.output_modifier_proc = output_modifier_proc
212
217
  line_editor.prompt_proc = prompt_proc
213
218
  line_editor.auto_indent_proc = auto_indent_proc
@@ -341,12 +346,14 @@ module Reline
341
346
  # Documented API
342
347
  #--------------------------------------------------------
343
348
 
344
- (Core::ATTR_READER_NAMES + Core::ATTR_ACCESSOR_NAMES).each { |name|
349
+ (Core::ATTR_READER_NAMES).each { |name|
345
350
  def_single_delegators :core, "#{name}", "#{name}="
346
351
  }
347
352
  def_single_delegators :core, :input=, :output=
348
353
  def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
349
354
  def_single_delegators :core, :readline
355
+ def_single_delegators :core, :completion_case_fold, :completion_case_fold=
356
+ def_single_delegators :core, :completion_quote_character
350
357
  def_instance_delegators self, :readline
351
358
  private :readline
352
359
 
@@ -373,6 +380,8 @@ module Reline
373
380
  def_single_delegator :line_editor, :rerender, :redisplay
374
381
  def_single_delegators :core, :vi_editing_mode?, :emacs_editing_mode?
375
382
  def_single_delegators :core, :ambiguous_width
383
+ def_single_delegators :core, :last_incremental_search
384
+ def_single_delegators :core, :last_incremental_search=
376
385
 
377
386
  def_single_delegators :core, :readmultiline
378
387
  def_instance_delegators self, :readmultiline
@@ -400,9 +409,15 @@ module Reline
400
409
  HISTORY = History.new(core.config)
401
410
  end
402
411
 
403
- if Reline::Core::IS_WINDOWS
412
+ if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
404
413
  require 'reline/windows'
405
- Reline::IOGate = Reline::Windows
414
+ if Reline::Windows.get_screen_size == [0, 0]
415
+ # Maybe Mintty on Cygwin
416
+ require 'reline/ansi'
417
+ Reline::IOGate = Reline::ANSI
418
+ else
419
+ Reline::IOGate = Reline::Windows
420
+ end
406
421
  else
407
422
  require 'reline/ansi'
408
423
  Reline::IOGate = Reline::ANSI
@@ -1,3 +1,5 @@
1
+ require 'io/console'
2
+
1
3
  class Reline::ANSI
2
4
  RAW_KEYSTROKE_CONFIG = {
3
5
  [27, 91, 65] => :ed_prev_history, # ↑
@@ -7,8 +9,12 @@ class Reline::ANSI
7
9
  [27, 91, 51, 126] => :key_delete, # Del
8
10
  [27, 91, 49, 126] => :ed_move_to_beg, # Home
9
11
  [27, 91, 52, 126] => :ed_move_to_end, # End
12
+ [27, 91, 72] => :ed_move_to_beg, # Home
13
+ [27, 91, 70] => :ed_move_to_end, # End
10
14
  [27, 32] => :em_set_mark, # M-<space>
11
15
  [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
16
+ [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
17
+ [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←
12
18
  }
13
19
 
14
20
  @@input = STDIN
@@ -26,7 +32,8 @@ class Reline::ANSI
26
32
  unless @@buf.empty?
27
33
  return @@buf.shift
28
34
  end
29
- @@input.getbyte
35
+ c = @@input.raw(intr: true, &:getbyte)
36
+ (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
30
37
  end
31
38
 
32
39
  def self.ungetc(c)
@@ -58,14 +65,18 @@ class Reline::ANSI
58
65
  def self.cursor_pos
59
66
  begin
60
67
  res = ''
68
+ m = nil
61
69
  @@input.raw do |stdin|
62
70
  @@output << "\e[6n"
63
71
  @@output.flush
64
72
  while (c = stdin.getc) != 'R'
65
73
  res << c if c
66
74
  end
75
+ m = res.match(/\e\[(?<row>\d+);(?<column>\d+)/)
76
+ (m.pre_match + m.post_match).chars.reverse_each do |ch|
77
+ stdin.ungetc ch
78
+ end
67
79
  end
68
- m = res.match(/(?<row>\d+);(?<column>\d+)/)
69
80
  column = m[:column].to_i - 1
70
81
  row = m[:row].to_i - 1
71
82
  rescue Errno::ENOTTY
@@ -118,24 +129,12 @@ class Reline::ANSI
118
129
  def self.prep
119
130
  retrieve_keybuffer
120
131
  int_handle = Signal.trap('INT', 'IGNORE')
121
- otio = `stty -g`.chomp
122
- setting = ' -echo -icrnl cbreak'
123
- stty = `stty -a`
124
- if /-parenb\b/ =~ stty
125
- setting << ' pass8'
126
- end
127
- if /\bdsusp *=/ =~ stty
128
- setting << ' dsusp undef'
129
- end
130
- setting << ' -ixoff'
131
- `stty #{setting}`
132
132
  Signal.trap('INT', int_handle)
133
- otio
133
+ nil
134
134
  end
135
135
 
136
136
  def self.deprep(otio)
137
137
  int_handle = Signal.trap('INT', 'IGNORE')
138
- `stty #{otio}`
139
138
  Signal.trap('INT', int_handle)
140
139
  Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
141
140
  end
@@ -157,7 +157,7 @@ class Reline::Config
157
157
  case directive
158
158
  when 'if'
159
159
  condition = false
160
- case args # TODO: variables
160
+ case args
161
161
  when 'mode'
162
162
  when 'term'
163
163
  when 'version'
@@ -184,7 +184,7 @@ class Reline::Config
184
184
 
185
185
  def bind_variable(name, value)
186
186
  case name
187
- when VARIABLE_NAMES then
187
+ when *VARIABLE_NAMES then
188
188
  variable_name = :"@#{name.tr(?-, ?_)}"
189
189
  instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
190
190
  when 'bell-style'
@@ -13,7 +13,7 @@ class Reline::History < Array
13
13
  end
14
14
 
15
15
  def [](index)
16
- index = check_index(index)
16
+ index = check_index(index) unless index.is_a?(Range)
17
17
  super(index)
18
18
  end
19
19
 
@@ -9,7 +9,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
9
9
  # 3 ^C
10
10
  :ed_ignore,
11
11
  # 4 ^D
12
- :em_delete_or_list,
12
+ :em_delete,
13
13
  # 5 ^E
14
14
  :ed_move_to_end,
15
15
  # 6 ^F
@@ -39,7 +39,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
39
39
  # 18 ^R
40
40
  :ed_search_prev_history,
41
41
  # 19 ^S
42
- :ed_ignore,
42
+ :ed_search_next_history,
43
43
  # 20 ^T
44
44
  :ed_transpose_chars,
45
45
  # 21 ^U
@@ -37,7 +37,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
37
37
  # 17 ^Q
38
38
  :ed_ignore,
39
39
  # 18 ^R
40
- :ed_redisplay,
40
+ :ed_search_prev_history,
41
41
  # 19 ^S
42
42
  :ed_ignore,
43
43
  # 20 ^T
@@ -39,7 +39,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
39
39
  # 18 ^R
40
40
  :ed_search_prev_history,
41
41
  # 19 ^S
42
- :ed_ignore,
42
+ :ed_search_next_history,
43
43
  # 20 ^T
44
44
  :ed_insert,
45
45
  # 21 ^U
@@ -10,6 +10,7 @@ class Reline::LineEditor
10
10
  attr_reader :byte_pointer
11
11
  attr_accessor :confirm_multiline_termination_proc
12
12
  attr_accessor :completion_proc
13
+ attr_accessor :completion_append_character
13
14
  attr_accessor :output_modifier_proc
14
15
  attr_accessor :prompt_proc
15
16
  attr_accessor :auto_indent_proc
@@ -43,6 +44,7 @@ class Reline::LineEditor
43
44
  COMPLETION = :completion
44
45
  MENU = :menu
45
46
  JOURNEY = :journey
47
+ MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
46
48
  PERFECT_MATCH = :perfect_match
47
49
  end
48
50
 
@@ -57,6 +59,7 @@ class Reline::LineEditor
57
59
 
58
60
  def initialize(config)
59
61
  @config = config
62
+ @completion_append_character = ''
60
63
  reset_variables
61
64
  end
62
65
 
@@ -192,7 +195,7 @@ class Reline::LineEditor
192
195
  lines.each_with_index { |line, i|
193
196
  prompt = ''
194
197
  prompt = prompt_list[i] if prompt_list and prompt_list[i]
195
- result += calculate_height_by_width(calculate_width(prompt + line))
198
+ result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
196
199
  }
197
200
  result
198
201
  end
@@ -549,10 +552,14 @@ class Reline::LineEditor
549
552
  private def complete_internal_proc(list, is_menu)
550
553
  preposing, target, postposing = retrieve_completion_block
551
554
  list = list.select { |i|
552
- if i and i.encoding != Encoding::US_ASCII and i.encoding != @encoding
553
- raise Encoding::CompatibilityError
555
+ if i and not Encoding.compatible?(target.encoding, i.encoding)
556
+ raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
557
+ end
558
+ if @config.completion_ignore_case
559
+ i&.downcase&.start_with?(target.downcase)
560
+ else
561
+ i&.start_with?(target)
554
562
  end
555
- i&.start_with?(target)
556
563
  }
557
564
  if is_menu
558
565
  menu(target, list)
@@ -569,10 +576,18 @@ class Reline::LineEditor
569
576
  size = [memo_mbchars.size, item_mbchars.size].min
570
577
  result = ''
571
578
  size.times do |i|
572
- if memo_mbchars[i] == item_mbchars[i]
573
- result << memo_mbchars[i]
579
+ if @config.completion_ignore_case
580
+ if memo_mbchars[i].casecmp?(item_mbchars[i])
581
+ result << memo_mbchars[i]
582
+ else
583
+ break
584
+ end
574
585
  else
575
- break
586
+ if memo_mbchars[i] == item_mbchars[i]
587
+ result << memo_mbchars[i]
588
+ else
589
+ break
590
+ end
576
591
  end
577
592
  end
578
593
  result
@@ -580,27 +595,43 @@ class Reline::LineEditor
580
595
  [target, preposing, completed, postposing]
581
596
  end
582
597
 
583
- private def complete(list)
598
+ private def complete(list, just_show_list = false)
584
599
  case @completion_state
585
600
  when CompletionState::NORMAL, CompletionState::JOURNEY
586
601
  @completion_state = CompletionState::COMPLETION
587
602
  when CompletionState::PERFECT_MATCH
588
603
  @dig_perfect_match_proc&.(@perfect_matched)
589
604
  end
590
- is_menu = (@completion_state == CompletionState::MENU)
605
+ if just_show_list
606
+ is_menu = true
607
+ elsif @completion_state == CompletionState::MENU
608
+ is_menu = true
609
+ elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
610
+ is_menu = true
611
+ else
612
+ is_menu = false
613
+ end
591
614
  result = complete_internal_proc(list, is_menu)
615
+ if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
616
+ @completion_state = CompletionState::PERFECT_MATCH
617
+ end
592
618
  return if result.nil?
593
619
  target, preposing, completed, postposing = result
594
620
  return if completed.nil?
595
- if target <= completed and (@completion_state == CompletionState::COMPLETION or @completion_state == CompletionState::PERFECT_MATCH)
596
- @completion_state = CompletionState::MENU
621
+ if target <= completed and (@completion_state == CompletionState::COMPLETION)
597
622
  if list.include?(completed)
598
- @completion_state = CompletionState::PERFECT_MATCH
623
+ if list.one?
624
+ @completion_state = CompletionState::PERFECT_MATCH
625
+ else
626
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
627
+ end
599
628
  @perfect_matched = completed
629
+ else
630
+ @completion_state = CompletionState::MENU
600
631
  end
601
- if target < completed
602
- @line = preposing + completed + postposing
603
- line_to_pointer = preposing + completed
632
+ if not just_show_list and target < completed
633
+ @line = preposing + completed + completion_append_character.to_s + postposing
634
+ line_to_pointer = preposing + completed + completion_append_character.to_s
604
635
  @cursor_max = calculate_width(@line)
605
636
  @cursor = calculate_width(line_to_pointer)
606
637
  @byte_pointer = line_to_pointer.bytesize
@@ -610,7 +641,8 @@ class Reline::LineEditor
610
641
 
611
642
  private def move_completed_list(list, direction)
612
643
  case @completion_state
613
- when CompletionState::NORMAL, CompletionState::COMPLETION, CompletionState::MENU
644
+ when CompletionState::NORMAL, CompletionState::COMPLETION,
645
+ CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
614
646
  @completion_state = CompletionState::JOURNEY
615
647
  result = retrieve_completion_block
616
648
  return if result.nil?
@@ -766,7 +798,7 @@ class Reline::LineEditor
766
798
  end
767
799
 
768
800
  def input_key(key)
769
- if key.nil? or key.char.nil?
801
+ if key.char.nil?
770
802
  if @first_char
771
803
  @line = nil
772
804
  end
@@ -776,20 +808,20 @@ class Reline::LineEditor
776
808
  @first_char = false
777
809
  completion_occurs = false
778
810
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
779
- result = retrieve_completion_block
780
- slice = result[1]
781
- result = @completion_proc.(slice) if @completion_proc and slice
782
- if result.is_a?(Array)
783
- completion_occurs = true
784
- complete(result)
811
+ unless @config.disable_completion
812
+ result = call_completion_proc
813
+ if result.is_a?(Array)
814
+ completion_occurs = true
815
+ complete(result)
816
+ end
785
817
  end
786
- elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
787
- result = retrieve_completion_block
788
- slice = result[1]
789
- result = @completion_proc.(slice) if @completion_proc and slice
790
- if result.is_a?(Array)
791
- completion_occurs = true
792
- move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
818
+ elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
819
+ unless @config.disable_completion
820
+ result = call_completion_proc
821
+ if result.is_a?(Array)
822
+ completion_occurs = true
823
+ move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
824
+ end
793
825
  end
794
826
  elsif Symbol === key.char and respond_to?(key.char, true)
795
827
  process_key(key.char, key.char)
@@ -804,8 +836,33 @@ class Reline::LineEditor
804
836
  end
805
837
  end
806
838
 
839
+ def call_completion_proc
840
+ result = retrieve_completion_block(true)
841
+ slice = result[1]
842
+ result = @completion_proc.(slice) if @completion_proc and slice
843
+ Reline.core.instance_variable_set(:@completion_quote_character, nil)
844
+ result
845
+ end
846
+
807
847
  private def process_auto_indent
808
848
  return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
849
+ if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
850
+ # Fix indent of a line when a newline is inserted to the next
851
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
852
+ new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
853
+ md = @line.match(/\A */)
854
+ prev_indent = md[0].count(' ')
855
+ @line = ' ' * new_indent + @line.lstrip
856
+
857
+ new_indent = nil
858
+ result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
859
+ if result
860
+ new_indent = result
861
+ end
862
+ if new_indent&.>= 0
863
+ @line = ' ' * new_indent + @line.lstrip
864
+ end
865
+ end
809
866
  if @previous_line_index
810
867
  new_lines = whole_lines(index: @previous_line_index, line: @line)
811
868
  else
@@ -828,7 +885,7 @@ class Reline::LineEditor
828
885
  @check_new_auto_indent = false
829
886
  end
830
887
 
831
- def retrieve_completion_block
888
+ def retrieve_completion_block(set_completion_quote_character = false)
832
889
  word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
833
890
  quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
834
891
  before = @line.byteslice(0, @byte_pointer)
@@ -847,31 +904,42 @@ class Reline::LineEditor
847
904
  if quote and slice.start_with?(closing_quote)
848
905
  quote = nil
849
906
  i += 1
907
+ rest = nil
908
+ break_pointer = nil
850
909
  elsif quote and slice.start_with?(escaped_quote)
851
910
  # skip
852
911
  i += 2
853
912
  elsif slice =~ quote_characters_regexp # find new "
913
+ rest = $'
854
914
  quote = $&
855
915
  closing_quote = /(?!\\)#{Regexp.escape(quote)}/
856
916
  escaped_quote = /\\#{Regexp.escape(quote)}/
857
917
  i += 1
918
+ break_pointer = i
858
919
  elsif not quote and slice =~ word_break_regexp
859
920
  rest = $'
860
921
  i += 1
922
+ before = @line.byteslice(i, @byte_pointer - i)
861
923
  break_pointer = i
862
924
  else
863
925
  i += 1
864
926
  end
865
927
  end
928
+ postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
866
929
  if rest
867
930
  preposing = @line.byteslice(0, break_pointer)
868
931
  target = rest
932
+ if set_completion_quote_character and quote
933
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
934
+ if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
935
+ insert_text(quote)
936
+ end
937
+ end
869
938
  else
870
939
  preposing = ''
871
940
  target = before
872
941
  end
873
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
874
- [preposing, target, postposing]
942
+ [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
875
943
  end
876
944
 
877
945
  def confirm_multiline_termination
@@ -1124,25 +1192,34 @@ class Reline::LineEditor
1124
1192
  end
1125
1193
  alias_method :end_of_line, :ed_move_to_end
1126
1194
 
1127
- private def ed_search_prev_history(key)
1128
- if @is_multiline
1129
- @line_backup_in_history = whole_buffer
1130
- else
1131
- @line_backup_in_history = @line
1132
- end
1133
- searcher = Fiber.new do
1195
+ private def generate_searcher
1196
+ Fiber.new do |first_key|
1197
+ prev_search_key = first_key
1134
1198
  search_word = String.new(encoding: @encoding)
1135
1199
  multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1136
1200
  last_hit = nil
1201
+ case first_key
1202
+ when "\C-r".ord
1203
+ prompt_name = 'reverse-i-search'
1204
+ when "\C-s".ord
1205
+ prompt_name = 'i-search'
1206
+ end
1137
1207
  loop do
1138
1208
  key = Fiber.yield(search_word)
1209
+ search_again = false
1139
1210
  case key
1140
- when "\C-h".ord, 127
1211
+ when -1 # determined
1212
+ Reline.last_incremental_search = search_word
1213
+ break
1214
+ when "\C-h".ord, "\C-?".ord
1141
1215
  grapheme_clusters = search_word.grapheme_clusters
1142
1216
  if grapheme_clusters.size > 0
1143
1217
  grapheme_clusters.pop
1144
1218
  search_word = grapheme_clusters.join
1145
1219
  end
1220
+ when "\C-r".ord, "\C-s".ord
1221
+ search_again = true if prev_search_key == key
1222
+ prev_search_key = key
1146
1223
  else
1147
1224
  multibyte_buf << key
1148
1225
  if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
@@ -1151,18 +1228,61 @@ class Reline::LineEditor
1151
1228
  end
1152
1229
  end
1153
1230
  hit = nil
1154
- if @line_backup_in_history.include?(search_word)
1231
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1155
1232
  @history_pointer = nil
1156
1233
  hit = @line_backup_in_history
1157
1234
  else
1158
- hit_index = Reline::HISTORY.rindex { |item|
1159
- item.include?(search_word)
1160
- }
1235
+ if search_again
1236
+ if search_word.empty? and Reline.last_incremental_search
1237
+ search_word = Reline.last_incremental_search
1238
+ end
1239
+ if @history_pointer # TODO
1240
+ case prev_search_key
1241
+ when "\C-r".ord
1242
+ history_pointer_base = 0
1243
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1244
+ when "\C-s".ord
1245
+ history_pointer_base = @history_pointer + 1
1246
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1247
+ end
1248
+ else
1249
+ history_pointer_base = 0
1250
+ history = Reline::HISTORY
1251
+ end
1252
+ elsif @history_pointer
1253
+ case prev_search_key
1254
+ when "\C-r".ord
1255
+ history_pointer_base = 0
1256
+ history = Reline::HISTORY[0..@history_pointer]
1257
+ when "\C-s".ord
1258
+ history_pointer_base = @history_pointer
1259
+ history = Reline::HISTORY[@history_pointer..-1]
1260
+ end
1261
+ else
1262
+ history_pointer_base = 0
1263
+ history = Reline::HISTORY
1264
+ end
1265
+ case prev_search_key
1266
+ when "\C-r".ord
1267
+ hit_index = history.rindex { |item|
1268
+ item.include?(search_word)
1269
+ }
1270
+ when "\C-s".ord
1271
+ hit_index = history.index { |item|
1272
+ item.include?(search_word)
1273
+ }
1274
+ end
1161
1275
  if hit_index
1162
- @history_pointer = hit_index
1276
+ @history_pointer = history_pointer_base + hit_index
1163
1277
  hit = Reline::HISTORY[@history_pointer]
1164
1278
  end
1165
1279
  end
1280
+ case prev_search_key
1281
+ when "\C-r".ord
1282
+ prompt_name = 'reverse-i-search'
1283
+ when "\C-s".ord
1284
+ prompt_name = 'i-search'
1285
+ end
1166
1286
  if hit
1167
1287
  if @is_multiline
1168
1288
  @buffer_of_lines = hit.split("\n")
@@ -1170,47 +1290,77 @@ class Reline::LineEditor
1170
1290
  @line_index = @buffer_of_lines.size - 1
1171
1291
  @line = @buffer_of_lines.last
1172
1292
  @rerender_all = true
1173
- @searching_prompt = "(reverse-i-search)`%s'" % [search_word]
1293
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1174
1294
  else
1175
1295
  @line = hit
1176
- @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
1296
+ @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1177
1297
  end
1178
1298
  last_hit = hit
1179
1299
  else
1180
1300
  if @is_multiline
1181
1301
  @rerender_all = true
1182
- @searching_prompt = "(failed reverse-i-search)`%s'" % [search_word]
1302
+ @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1183
1303
  else
1184
- @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
1304
+ @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1185
1305
  end
1186
1306
  end
1187
1307
  end
1188
1308
  end
1189
- searcher.resume
1309
+ end
1310
+
1311
+ private def search_history(key)
1312
+ unless @history_pointer
1313
+ if @is_multiline
1314
+ @line_backup_in_history = whole_buffer
1315
+ else
1316
+ @line_backup_in_history = @line
1317
+ end
1318
+ end
1319
+ searcher = generate_searcher
1320
+ searcher.resume(key)
1190
1321
  @searching_prompt = "(reverse-i-search)`': "
1191
1322
  @waiting_proc = ->(k) {
1192
1323
  case k
1193
- when "\C-j".ord, "\C-?".ord
1324
+ when "\C-j".ord
1194
1325
  if @history_pointer
1195
- @line = Reline::HISTORY[@history_pointer]
1326
+ buffer = Reline::HISTORY[@history_pointer]
1196
1327
  else
1197
- @line = @line_backup_in_history
1328
+ buffer = @line_backup_in_history
1329
+ end
1330
+ if @is_multiline
1331
+ @buffer_of_lines = buffer.split("\n")
1332
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1333
+ @line_index = @buffer_of_lines.size - 1
1334
+ @line = @buffer_of_lines.last
1335
+ @rerender_all = true
1336
+ else
1337
+ @line = buffer
1198
1338
  end
1199
1339
  @searching_prompt = nil
1200
1340
  @waiting_proc = nil
1201
1341
  @cursor_max = calculate_width(@line)
1202
1342
  @cursor = @byte_pointer = 0
1343
+ searcher.resume(-1)
1203
1344
  when "\C-g".ord
1204
- @line = @line_backup_in_history
1345
+ if @is_multiline
1346
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1347
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1348
+ @line_index = @buffer_of_lines.size - 1
1349
+ @line = @buffer_of_lines.last
1350
+ @rerender_all = true
1351
+ else
1352
+ @line = @line_backup_in_history
1353
+ end
1205
1354
  @history_pointer = nil
1206
1355
  @searching_prompt = nil
1207
1356
  @waiting_proc = nil
1208
1357
  @line_backup_in_history = nil
1209
1358
  @cursor_max = calculate_width(@line)
1210
1359
  @cursor = @byte_pointer = 0
1360
+ @rerender_all = true
1211
1361
  else
1212
1362
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1213
- if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == 127
1363
+ if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1214
1364
  searcher.resume(k)
1215
1365
  else
1216
1366
  if @history_pointer
@@ -1233,13 +1383,21 @@ class Reline::LineEditor
1233
1383
  @waiting_proc = nil
1234
1384
  @cursor_max = calculate_width(@line)
1235
1385
  @cursor = @byte_pointer = 0
1386
+ searcher.resume(-1)
1236
1387
  end
1237
1388
  end
1238
1389
  }
1239
1390
  end
1240
1391
 
1392
+ private def ed_search_prev_history(key)
1393
+ search_history(key)
1394
+ end
1395
+ alias_method :reverse_search_history, :ed_search_prev_history
1396
+
1241
1397
  private def ed_search_next_history(key)
1398
+ search_history(key)
1242
1399
  end
1400
+ alias_method :forward_search_history, :ed_search_next_history
1243
1401
 
1244
1402
  private def ed_prev_history(key, arg: 1)
1245
1403
  if @is_multiline and @line_index > 0
@@ -1397,6 +1555,14 @@ class Reline::LineEditor
1397
1555
  @byte_pointer = @line.bytesize
1398
1556
  @cursor = @cursor_max = calculate_width(@line)
1399
1557
  @kill_ring.append(deleted)
1558
+ elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
1559
+ @cursor = calculate_width(@line)
1560
+ @byte_pointer = @line.bytesize
1561
+ @line += @buffer_of_lines.delete_at(@line_index + 1)
1562
+ @cursor_max = calculate_width(@line)
1563
+ @buffer_of_lines[@line_index] = @line
1564
+ @rerender_all = true
1565
+ @rest_height += 1
1400
1566
  end
1401
1567
  end
1402
1568
 
@@ -1410,7 +1576,7 @@ class Reline::LineEditor
1410
1576
  end
1411
1577
  end
1412
1578
 
1413
- private def em_delete_or_list(key)
1579
+ private def em_delete(key)
1414
1580
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
1415
1581
  @line = nil
1416
1582
  if @buffer_of_lines.size > 1
@@ -1435,7 +1601,19 @@ class Reline::LineEditor
1435
1601
  @rest_height += 1
1436
1602
  end
1437
1603
  end
1438
- alias_method :delete_char, :em_delete_or_list
1604
+ alias_method :delete_char, :em_delete
1605
+
1606
+ private def em_delete_or_list(key)
1607
+ if @line.empty? or @byte_pointer < @line.bytesize
1608
+ em_delete(key)
1609
+ else # show completed list
1610
+ result = call_completion_proc
1611
+ if result.is_a?(Array)
1612
+ complete(result, true)
1613
+ end
1614
+ end
1615
+ end
1616
+ alias_method :delete_char_or_list, :em_delete_or_list
1439
1617
 
1440
1618
  private def em_yank(key)
1441
1619
  yanked = @kill_ring.yank
@@ -1739,18 +1917,6 @@ class Reline::LineEditor
1739
1917
  private def vi_yank(key)
1740
1918
  end
1741
1919
 
1742
- private def vi_end_of_transmission(key)
1743
- if @line.empty?
1744
- @line = nil
1745
- if @buffer_of_lines.size > 1
1746
- scroll_down(@highest_in_all - @first_line_started_from)
1747
- end
1748
- Reline::IOGate.move_cursor_column(0)
1749
- @eof = true
1750
- finish
1751
- end
1752
- end
1753
-
1754
1920
  private def vi_list_or_eof(key)
1755
1921
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
1756
1922
  @line = nil
@@ -1761,9 +1927,11 @@ class Reline::LineEditor
1761
1927
  @eof = true
1762
1928
  finish
1763
1929
  else
1764
- # TODO: list
1930
+ ed_newline(key)
1765
1931
  end
1766
1932
  end
1933
+ alias_method :vi_end_of_transmission, :vi_list_or_eof
1934
+ alias_method :vi_eof_maybe, :vi_list_or_eof
1767
1935
 
1768
1936
  private def ed_delete_next_char(key, arg: 1)
1769
1937
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.0.5'
2
+ VERSION = '0.1.2'
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-17 00:00:00.000000000 Z
11
+ date: 2019-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: io-console
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.5'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -91,14 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
105
  requirements:
92
106
  - - ">="
93
107
  - !ruby/object:Gem::Version
94
- version: '0'
108
+ version: '2.5'
95
109
  required_rubygems_version: !ruby/object:Gem::Requirement
96
110
  requirements:
97
111
  - - ">="
98
112
  - !ruby/object:Gem::Version
99
113
  version: '0'
100
114
  requirements: []
101
- rubygems_version: 3.0.3
115
+ rubygems_version: 3.1.2
102
116
  signing_key:
103
117
  specification_version: 4
104
118
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.