reline 0.0.5 → 0.1.2

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