reline 0.0.4 → 0.1.1

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: 5871c5fe137b41dce9b91a8c3507c871c00440393af055c1d546a17e2613fc1b
4
- data.tar.gz: d6add41c6538d3cd07b813eab3b8998f57759209f821bf7950bf1a69d1f33966
3
+ metadata.gz: bb3a343a202a2c043257a432f50eb05eeb58b5638a88c24eb095c96a40b03fea
4
+ data.tar.gz: e9c8aec5f70fe3897fb8f835d0d5b228a93a5444b0a7ce804290c19ae44c6c55
5
5
  SHA512:
6
- metadata.gz: 105ed33c513bc997c46f6167da553f098f372272fe026b24afd2b472776dd840484afc0f284cd9d973b3db35940d84d99b4110b3d135d16944269c306ba33003
7
- data.tar.gz: d28e6fe88b8a16022febeba31ead5a4e533f2a50fa65ed93328f35e5c0b2634b01b0e0b35efa278f6bbfd96b4fd42adee2d8e15e3f9d3092de5da2b61b1ef550
6
+ metadata.gz: a0e2a92e1e007d23542313783108250c66ad81b90389c306a9f413b8426ce14a39e14731f974aa8fcabcddaeceae529a70388e6e1a3db6ec7a511278b0c568d7
7
+ data.tar.gz: 0f86f497938921154a475f4b63059a4cda560b44b5b6f9fb63b3b4afcae9de3ea351c1226640eb074546a765b2f51ff5a62bd1af20142a6211c8e74a06f16a57
@@ -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
 
@@ -166,7 +170,7 @@ module Reline
166
170
  inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
167
171
 
168
172
  whole_buffer = line_editor.whole_buffer.dup
169
- whole_buffer.taint
173
+ whole_buffer.taint if RUBY_VERSION < '2.7'
170
174
  if add_hist and whole_buffer and whole_buffer.chomp.size > 0
171
175
  Reline::HISTORY << whole_buffer
172
176
  end
@@ -179,7 +183,7 @@ module Reline
179
183
  inner_readline(prompt, add_hist, false)
180
184
 
181
185
  line = line_editor.line.dup
182
- line.taint
186
+ line.taint if RUBY_VERSION < '2.7'
183
187
  if add_hist and line and line.chomp.size > 0
184
188
  Reline::HISTORY << line.chomp
185
189
  end
@@ -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
@@ -260,7 +265,10 @@ module Reline
260
265
  result = key_stroke.match_status(buffer)
261
266
  case result
262
267
  when :matched
263
- block.(key_stroke.expand(buffer).map{ |c| Reline::Key.new(c, c, false) })
268
+ expanded = key_stroke.expand(buffer).map{ |expanded_c|
269
+ Reline::Key.new(expanded_c, expanded_c, false)
270
+ }
271
+ block.(expanded)
264
272
  break
265
273
  when :matching
266
274
  if buffer.size == 1
@@ -289,7 +297,10 @@ module Reline
289
297
  if buffer.size == 1 and c == "\e".ord
290
298
  read_escaped_key(keyseq_timeout, c, block)
291
299
  else
292
- block.(buffer.map{ |c| Reline::Key.new(c, c, false) })
300
+ expanded = buffer.map{ |expanded_c|
301
+ Reline::Key.new(expanded_c, expanded_c, false)
302
+ }
303
+ block.(expanded)
293
304
  end
294
305
  break
295
306
  end
@@ -319,7 +330,7 @@ module Reline
319
330
 
320
331
  private def may_req_ambiguous_char_width
321
332
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
322
- return if @ambiguous_width
333
+ return if ambiguous_width
323
334
  Reline::IOGate.move_cursor_column(0)
324
335
  print "\u{25bd}"
325
336
  @ambiguous_width = Reline::IOGate.cursor_pos.x
@@ -335,13 +346,16 @@ module Reline
335
346
  # Documented API
336
347
  #--------------------------------------------------------
337
348
 
338
- (Core::ATTR_READER_NAMES + Core::ATTR_ACCESSOR_NAMES).each { |name|
349
+ (Core::ATTR_READER_NAMES).each { |name|
339
350
  def_single_delegators :core, "#{name}", "#{name}="
340
351
  }
341
352
  def_single_delegators :core, :input=, :output=
342
353
  def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
343
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
344
357
  def_instance_delegators self, :readline
358
+ private :readline
345
359
 
346
360
 
347
361
  #--------------------------------------------------------
@@ -366,9 +380,12 @@ module Reline
366
380
  def_single_delegator :line_editor, :rerender, :redisplay
367
381
  def_single_delegators :core, :vi_editing_mode?, :emacs_editing_mode?
368
382
  def_single_delegators :core, :ambiguous_width
383
+ def_single_delegators :core, :last_incremental_search
384
+ def_single_delegators :core, :last_incremental_search=
369
385
 
370
386
  def_single_delegators :core, :readmultiline
371
387
  def_instance_delegators self, :readmultiline
388
+ private :readmultiline
372
389
 
373
390
  def self.core
374
391
  @core ||= Core.new { |core|
@@ -392,9 +409,15 @@ module Reline
392
409
  HISTORY = History.new(core.config)
393
410
  end
394
411
 
395
- if Reline::Core::IS_WINDOWS
412
+ if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
396
413
  require 'reline/windows'
397
- 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
398
421
  else
399
422
  require 'reline/ansi'
400
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,6 +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
14
+ [27, 32] => :em_set_mark, # M-<space>
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+←
10
18
  }
11
19
 
12
20
  @@input = STDIN
@@ -24,7 +32,8 @@ class Reline::ANSI
24
32
  unless @@buf.empty?
25
33
  return @@buf.shift
26
34
  end
27
- @@input.getbyte
35
+ c = @@input.raw(intr: true, &:getbyte)
36
+ (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
28
37
  end
29
38
 
30
39
  def self.ungetc(c)
@@ -56,14 +65,18 @@ class Reline::ANSI
56
65
  def self.cursor_pos
57
66
  begin
58
67
  res = ''
68
+ m = nil
59
69
  @@input.raw do |stdin|
60
70
  @@output << "\e[6n"
61
71
  @@output.flush
62
72
  while (c = stdin.getc) != 'R'
63
73
  res << c if c
64
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
65
79
  end
66
- m = res.match(/(?<row>\d+);(?<column>\d+)/)
67
80
  column = m[:column].to_i - 1
68
81
  row = m[:row].to_i - 1
69
82
  rescue Errno::ENOTTY
@@ -116,24 +129,12 @@ class Reline::ANSI
116
129
  def self.prep
117
130
  retrieve_keybuffer
118
131
  int_handle = Signal.trap('INT', 'IGNORE')
119
- otio = `stty -g`.chomp
120
- setting = ' -echo -icrnl cbreak'
121
- stty = `stty -a`
122
- if /-parenb\b/ =~ stty
123
- setting << ' pass8'
124
- end
125
- if /\bdsusp *=/ =~ stty
126
- setting << ' dsusp undef'
127
- end
128
- setting << ' -ixoff'
129
- `stty #{setting}`
130
132
  Signal.trap('INT', int_handle)
131
- otio
133
+ nil
132
134
  end
133
135
 
134
136
  def self.deprep(otio)
135
137
  int_handle = Signal.trap('INT', 'IGNORE')
136
- `stty #{otio}`
137
138
  Signal.trap('INT', int_handle)
138
139
  Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
139
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
@@ -32,7 +32,7 @@ class Reline::KeyStroke
32
32
  end
33
33
 
34
34
  def expand(input)
35
- lhs = key_mapping.keys.select { |lhs| input.start_with? lhs }.sort_by(&:size).reverse.first
35
+ lhs = key_mapping.keys.select { |item| input.start_with? item }.sort_by(&:size).reverse.first
36
36
  return input unless lhs
37
37
  rhs = key_mapping[lhs]
38
38
 
@@ -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
 
@@ -113,9 +116,7 @@ class Reline::LineEditor
113
116
  if @line_index.zero?
114
117
  0
115
118
  else
116
- @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
117
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
118
- }
119
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
119
120
  end
120
121
  if @prompt_proc
121
122
  prompt = prompt_list[@line_index]
@@ -140,6 +141,7 @@ class Reline::LineEditor
140
141
 
141
142
  def reset_variables(prompt = '', encoding = Encoding.default_external)
142
143
  @prompt = prompt
144
+ @mark_pointer = nil
143
145
  @encoding = encoding
144
146
  @is_multiline = false
145
147
  @finished = false
@@ -188,6 +190,16 @@ class Reline::LineEditor
188
190
  @is_multiline = false
189
191
  end
190
192
 
193
+ private def calculate_height_by_lines(lines, prompt_list)
194
+ result = 0
195
+ lines.each_with_index { |line, i|
196
+ prompt = ''
197
+ prompt = prompt_list[i] if prompt_list and prompt_list[i]
198
+ result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
199
+ }
200
+ result
201
+ end
202
+
191
203
  private def insert_new_line(cursor_line, next_line)
192
204
  @line = cursor_line
193
205
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
@@ -346,9 +358,7 @@ class Reline::LineEditor
346
358
  new_lines = whole_lines
347
359
  end
348
360
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
349
- all_height = new_lines.inject(0) { |result, line|
350
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
351
- }
361
+ all_height = calculate_height_by_lines(new_lines, prompt_list)
352
362
  diff = all_height - @highest_in_all
353
363
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
354
364
  if diff > 0
@@ -388,9 +398,7 @@ class Reline::LineEditor
388
398
  if @line_index.zero?
389
399
  0
390
400
  else
391
- @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
392
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
393
- }
401
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
394
402
  end
395
403
  if @prompt_proc
396
404
  prompt = prompt_list[@line_index]
@@ -449,9 +457,7 @@ class Reline::LineEditor
449
457
  if @line_index.zero?
450
458
  0
451
459
  else
452
- new_buffer[0..(@line_index - 1)].inject(0) { |result, line|
453
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
454
- }
460
+ calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
455
461
  end
456
462
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
457
463
  move_cursor_down(@first_line_started_from + @started_from)
@@ -546,10 +552,14 @@ class Reline::LineEditor
546
552
  private def complete_internal_proc(list, is_menu)
547
553
  preposing, target, postposing = retrieve_completion_block
548
554
  list = list.select { |i|
549
- if i and i.encoding != Encoding::US_ASCII and i.encoding != @encoding
550
- 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)
551
562
  end
552
- i&.start_with?(target)
553
563
  }
554
564
  if is_menu
555
565
  menu(target, list)
@@ -566,10 +576,18 @@ class Reline::LineEditor
566
576
  size = [memo_mbchars.size, item_mbchars.size].min
567
577
  result = ''
568
578
  size.times do |i|
569
- if memo_mbchars[i] == item_mbchars[i]
570
- 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
571
585
  else
572
- break
586
+ if memo_mbchars[i] == item_mbchars[i]
587
+ result << memo_mbchars[i]
588
+ else
589
+ break
590
+ end
573
591
  end
574
592
  end
575
593
  result
@@ -577,27 +595,43 @@ class Reline::LineEditor
577
595
  [target, preposing, completed, postposing]
578
596
  end
579
597
 
580
- private def complete(list)
598
+ private def complete(list, just_show_list = false)
581
599
  case @completion_state
582
600
  when CompletionState::NORMAL, CompletionState::JOURNEY
583
601
  @completion_state = CompletionState::COMPLETION
584
602
  when CompletionState::PERFECT_MATCH
585
603
  @dig_perfect_match_proc&.(@perfect_matched)
586
604
  end
587
- 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
588
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
589
618
  return if result.nil?
590
619
  target, preposing, completed, postposing = result
591
620
  return if completed.nil?
592
- if target <= completed and (@completion_state == CompletionState::COMPLETION or @completion_state == CompletionState::PERFECT_MATCH)
593
- @completion_state = CompletionState::MENU
621
+ if target <= completed and (@completion_state == CompletionState::COMPLETION)
594
622
  if list.include?(completed)
595
- @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
596
628
  @perfect_matched = completed
629
+ else
630
+ @completion_state = CompletionState::MENU
597
631
  end
598
- if target < completed
599
- @line = preposing + completed + postposing
600
- 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
601
635
  @cursor_max = calculate_width(@line)
602
636
  @cursor = calculate_width(line_to_pointer)
603
637
  @byte_pointer = line_to_pointer.bytesize
@@ -607,7 +641,8 @@ class Reline::LineEditor
607
641
 
608
642
  private def move_completed_list(list, direction)
609
643
  case @completion_state
610
- when CompletionState::NORMAL, CompletionState::COMPLETION, CompletionState::MENU
644
+ when CompletionState::NORMAL, CompletionState::COMPLETION,
645
+ CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
611
646
  @completion_state = CompletionState::JOURNEY
612
647
  result = retrieve_completion_block
613
648
  return if result.nil?
@@ -650,9 +685,9 @@ class Reline::LineEditor
650
685
  else
651
686
  old_waiting_proc = @waiting_proc
652
687
  old_waiting_operator_proc = @waiting_operator_proc
653
- @waiting_proc = proc { |key|
688
+ @waiting_proc = proc { |k|
654
689
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
655
- old_waiting_proc.(key)
690
+ old_waiting_proc.(k)
656
691
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
657
692
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
658
693
  @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
@@ -763,7 +798,7 @@ class Reline::LineEditor
763
798
  end
764
799
 
765
800
  def input_key(key)
766
- if key.nil? or key.char.nil?
801
+ if key.char.nil?
767
802
  if @first_char
768
803
  @line = nil
769
804
  end
@@ -773,20 +808,20 @@ class Reline::LineEditor
773
808
  @first_char = false
774
809
  completion_occurs = false
775
810
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
776
- result = retrieve_completion_block
777
- slice = result[1]
778
- result = @completion_proc.(slice) if @completion_proc and slice
779
- if result.is_a?(Array)
780
- completion_occurs = true
781
- 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
782
817
  end
783
- elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
784
- result = retrieve_completion_block
785
- slice = result[1]
786
- result = @completion_proc.(slice) if @completion_proc and slice
787
- if result.is_a?(Array)
788
- completion_occurs = true
789
- 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
790
825
  end
791
826
  elsif Symbol === key.char and respond_to?(key.char, true)
792
827
  process_key(key.char, key.char)
@@ -801,8 +836,33 @@ class Reline::LineEditor
801
836
  end
802
837
  end
803
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
+
804
847
  private def process_auto_indent
805
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
806
866
  if @previous_line_index
807
867
  new_lines = whole_lines(index: @previous_line_index, line: @line)
808
868
  else
@@ -825,7 +885,7 @@ class Reline::LineEditor
825
885
  @check_new_auto_indent = false
826
886
  end
827
887
 
828
- def retrieve_completion_block
888
+ def retrieve_completion_block(set_completion_quote_character = false)
829
889
  word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
830
890
  quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
831
891
  before = @line.byteslice(0, @byte_pointer)
@@ -844,14 +904,18 @@ class Reline::LineEditor
844
904
  if quote and slice.start_with?(closing_quote)
845
905
  quote = nil
846
906
  i += 1
907
+ rest = nil
908
+ break_pointer = nil
847
909
  elsif quote and slice.start_with?(escaped_quote)
848
910
  # skip
849
911
  i += 2
850
912
  elsif slice =~ quote_characters_regexp # find new "
913
+ rest = $'
851
914
  quote = $&
852
915
  closing_quote = /(?!\\)#{Regexp.escape(quote)}/
853
916
  escaped_quote = /\\#{Regexp.escape(quote)}/
854
917
  i += 1
918
+ break_pointer = i
855
919
  elsif not quote and slice =~ word_break_regexp
856
920
  rest = $'
857
921
  i += 1
@@ -860,15 +924,21 @@ class Reline::LineEditor
860
924
  i += 1
861
925
  end
862
926
  end
927
+ postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
863
928
  if rest
864
929
  preposing = @line.byteslice(0, break_pointer)
865
930
  target = rest
931
+ if set_completion_quote_character and quote
932
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
933
+ if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
934
+ insert_text(quote)
935
+ end
936
+ end
866
937
  else
867
938
  preposing = ''
868
939
  target = before
869
940
  end
870
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
871
- [preposing, target, postposing]
941
+ [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
872
942
  end
873
943
 
874
944
  def confirm_multiline_termination
@@ -994,8 +1064,8 @@ class Reline::LineEditor
994
1064
  end
995
1065
  width
996
1066
  else
997
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
998
- width + Reline::Unicode.get_mbchar_width(gc)
1067
+ str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
1068
+ w + Reline::Unicode.get_mbchar_width(gc)
999
1069
  }
1000
1070
  end
1001
1071
  end
@@ -1121,25 +1191,34 @@ class Reline::LineEditor
1121
1191
  end
1122
1192
  alias_method :end_of_line, :ed_move_to_end
1123
1193
 
1124
- private def ed_search_prev_history(key)
1125
- if @is_multiline
1126
- @line_backup_in_history = whole_buffer
1127
- else
1128
- @line_backup_in_history = @line
1129
- end
1130
- searcher = Fiber.new do
1194
+ private def generate_searcher
1195
+ Fiber.new do |first_key|
1196
+ prev_search_key = first_key
1131
1197
  search_word = String.new(encoding: @encoding)
1132
1198
  multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1133
1199
  last_hit = nil
1200
+ case first_key
1201
+ when "\C-r".ord
1202
+ prompt_name = 'reverse-i-search'
1203
+ when "\C-s".ord
1204
+ prompt_name = 'i-search'
1205
+ end
1134
1206
  loop do
1135
1207
  key = Fiber.yield(search_word)
1208
+ search_again = false
1136
1209
  case key
1137
- when "\C-h".ord, 127
1210
+ when -1 # determined
1211
+ Reline.last_incremental_search = search_word
1212
+ break
1213
+ when "\C-h".ord, "\C-?".ord
1138
1214
  grapheme_clusters = search_word.grapheme_clusters
1139
1215
  if grapheme_clusters.size > 0
1140
1216
  grapheme_clusters.pop
1141
1217
  search_word = grapheme_clusters.join
1142
1218
  end
1219
+ when "\C-r".ord, "\C-s".ord
1220
+ search_again = true if prev_search_key == key
1221
+ prev_search_key = key
1143
1222
  else
1144
1223
  multibyte_buf << key
1145
1224
  if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
@@ -1148,18 +1227,61 @@ class Reline::LineEditor
1148
1227
  end
1149
1228
  end
1150
1229
  hit = nil
1151
- if @line_backup_in_history.include?(search_word)
1230
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1152
1231
  @history_pointer = nil
1153
1232
  hit = @line_backup_in_history
1154
1233
  else
1155
- hit_index = Reline::HISTORY.rindex { |item|
1156
- item.include?(search_word)
1157
- }
1234
+ if search_again
1235
+ if search_word.empty? and Reline.last_incremental_search
1236
+ search_word = Reline.last_incremental_search
1237
+ end
1238
+ if @history_pointer # TODO
1239
+ case prev_search_key
1240
+ when "\C-r".ord
1241
+ history_pointer_base = 0
1242
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1243
+ when "\C-s".ord
1244
+ history_pointer_base = @history_pointer + 1
1245
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1246
+ end
1247
+ else
1248
+ history_pointer_base = 0
1249
+ history = Reline::HISTORY
1250
+ end
1251
+ elsif @history_pointer
1252
+ case prev_search_key
1253
+ when "\C-r".ord
1254
+ history_pointer_base = 0
1255
+ history = Reline::HISTORY[0..@history_pointer]
1256
+ when "\C-s".ord
1257
+ history_pointer_base = @history_pointer
1258
+ history = Reline::HISTORY[@history_pointer..-1]
1259
+ end
1260
+ else
1261
+ history_pointer_base = 0
1262
+ history = Reline::HISTORY
1263
+ end
1264
+ case prev_search_key
1265
+ when "\C-r".ord
1266
+ hit_index = history.rindex { |item|
1267
+ item.include?(search_word)
1268
+ }
1269
+ when "\C-s".ord
1270
+ hit_index = history.index { |item|
1271
+ item.include?(search_word)
1272
+ }
1273
+ end
1158
1274
  if hit_index
1159
- @history_pointer = hit_index
1275
+ @history_pointer = history_pointer_base + hit_index
1160
1276
  hit = Reline::HISTORY[@history_pointer]
1161
1277
  end
1162
1278
  end
1279
+ case prev_search_key
1280
+ when "\C-r".ord
1281
+ prompt_name = 'reverse-i-search'
1282
+ when "\C-s".ord
1283
+ prompt_name = 'i-search'
1284
+ end
1163
1285
  if hit
1164
1286
  if @is_multiline
1165
1287
  @buffer_of_lines = hit.split("\n")
@@ -1167,48 +1289,78 @@ class Reline::LineEditor
1167
1289
  @line_index = @buffer_of_lines.size - 1
1168
1290
  @line = @buffer_of_lines.last
1169
1291
  @rerender_all = true
1170
- @searching_prompt = "(reverse-i-search)`%s'" % [search_word]
1292
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1171
1293
  else
1172
1294
  @line = hit
1173
- @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
1295
+ @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1174
1296
  end
1175
1297
  last_hit = hit
1176
1298
  else
1177
1299
  if @is_multiline
1178
1300
  @rerender_all = true
1179
- @searching_prompt = "(failed reverse-i-search)`%s'" % [search_word]
1301
+ @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1180
1302
  else
1181
- @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
1303
+ @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1182
1304
  end
1183
1305
  end
1184
1306
  end
1185
1307
  end
1186
- searcher.resume
1308
+ end
1309
+
1310
+ private def search_history(key)
1311
+ unless @history_pointer
1312
+ if @is_multiline
1313
+ @line_backup_in_history = whole_buffer
1314
+ else
1315
+ @line_backup_in_history = @line
1316
+ end
1317
+ end
1318
+ searcher = generate_searcher
1319
+ searcher.resume(key)
1187
1320
  @searching_prompt = "(reverse-i-search)`': "
1188
- @waiting_proc = ->(key) {
1189
- case key
1190
- when "\C-j".ord, "\C-?".ord
1321
+ @waiting_proc = ->(k) {
1322
+ case k
1323
+ when "\C-j".ord
1191
1324
  if @history_pointer
1192
- @line = Reline::HISTORY[@history_pointer]
1325
+ buffer = Reline::HISTORY[@history_pointer]
1193
1326
  else
1194
- @line = @line_backup_in_history
1327
+ buffer = @line_backup_in_history
1328
+ end
1329
+ if @is_multiline
1330
+ @buffer_of_lines = buffer.split("\n")
1331
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1332
+ @line_index = @buffer_of_lines.size - 1
1333
+ @line = @buffer_of_lines.last
1334
+ @rerender_all = true
1335
+ else
1336
+ @line = buffer
1195
1337
  end
1196
1338
  @searching_prompt = nil
1197
1339
  @waiting_proc = nil
1198
1340
  @cursor_max = calculate_width(@line)
1199
1341
  @cursor = @byte_pointer = 0
1342
+ searcher.resume(-1)
1200
1343
  when "\C-g".ord
1201
- @line = @line_backup_in_history
1344
+ if @is_multiline
1345
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1346
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1347
+ @line_index = @buffer_of_lines.size - 1
1348
+ @line = @buffer_of_lines.last
1349
+ @rerender_all = true
1350
+ else
1351
+ @line = @line_backup_in_history
1352
+ end
1202
1353
  @history_pointer = nil
1203
1354
  @searching_prompt = nil
1204
1355
  @waiting_proc = nil
1205
1356
  @line_backup_in_history = nil
1206
1357
  @cursor_max = calculate_width(@line)
1207
1358
  @cursor = @byte_pointer = 0
1359
+ @rerender_all = true
1208
1360
  else
1209
- chr = key.is_a?(String) ? key : key.chr(Encoding::ASCII_8BIT)
1210
- if chr.match?(/[[:print:]]/) or key == "\C-h".ord or key == 127
1211
- searcher.resume(key)
1361
+ chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1362
+ if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1363
+ searcher.resume(k)
1212
1364
  else
1213
1365
  if @history_pointer
1214
1366
  line = Reline::HISTORY[@history_pointer]
@@ -1230,13 +1382,21 @@ class Reline::LineEditor
1230
1382
  @waiting_proc = nil
1231
1383
  @cursor_max = calculate_width(@line)
1232
1384
  @cursor = @byte_pointer = 0
1385
+ searcher.resume(-1)
1233
1386
  end
1234
1387
  end
1235
1388
  }
1236
1389
  end
1237
1390
 
1391
+ private def ed_search_prev_history(key)
1392
+ search_history(key)
1393
+ end
1394
+ alias_method :reverse_search_history, :ed_search_prev_history
1395
+
1238
1396
  private def ed_search_next_history(key)
1397
+ search_history(key)
1239
1398
  end
1399
+ alias_method :forward_search_history, :ed_search_next_history
1240
1400
 
1241
1401
  private def ed_prev_history(key, arg: 1)
1242
1402
  if @is_multiline and @line_index > 0
@@ -1394,6 +1554,14 @@ class Reline::LineEditor
1394
1554
  @byte_pointer = @line.bytesize
1395
1555
  @cursor = @cursor_max = calculate_width(@line)
1396
1556
  @kill_ring.append(deleted)
1557
+ elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
1558
+ @cursor = calculate_width(@line)
1559
+ @byte_pointer = @line.bytesize
1560
+ @line += @buffer_of_lines.delete_at(@line_index + 1)
1561
+ @cursor_max = calculate_width(@line)
1562
+ @buffer_of_lines[@line_index] = @line
1563
+ @rerender_all = true
1564
+ @rest_height += 1
1397
1565
  end
1398
1566
  end
1399
1567
 
@@ -1407,7 +1575,7 @@ class Reline::LineEditor
1407
1575
  end
1408
1576
  end
1409
1577
 
1410
- private def em_delete_or_list(key)
1578
+ private def em_delete(key)
1411
1579
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
1412
1580
  @line = nil
1413
1581
  if @buffer_of_lines.size > 1
@@ -1432,7 +1600,19 @@ class Reline::LineEditor
1432
1600
  @rest_height += 1
1433
1601
  end
1434
1602
  end
1435
- alias_method :delete_char, :em_delete_or_list
1603
+ alias_method :delete_char, :em_delete
1604
+
1605
+ private def em_delete_or_list(key)
1606
+ if @line.empty? or @byte_pointer < @line.bytesize
1607
+ em_delete(key)
1608
+ else # show completed list
1609
+ result = call_completion_proc
1610
+ if result.is_a?(Array)
1611
+ complete(result, true)
1612
+ end
1613
+ end
1614
+ end
1615
+ alias_method :delete_char_or_list, :em_delete_or_list
1436
1616
 
1437
1617
  private def em_yank(key)
1438
1618
  yanked = @kill_ring.yank
@@ -1736,18 +1916,6 @@ class Reline::LineEditor
1736
1916
  private def vi_yank(key)
1737
1917
  end
1738
1918
 
1739
- private def vi_end_of_transmission(key)
1740
- if @line.empty?
1741
- @line = nil
1742
- if @buffer_of_lines.size > 1
1743
- scroll_down(@highest_in_all - @first_line_started_from)
1744
- end
1745
- Reline::IOGate.move_cursor_column(0)
1746
- @eof = true
1747
- finish
1748
- end
1749
- end
1750
-
1751
1919
  private def vi_list_or_eof(key)
1752
1920
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
1753
1921
  @line = nil
@@ -1758,9 +1926,11 @@ class Reline::LineEditor
1758
1926
  @eof = true
1759
1927
  finish
1760
1928
  else
1761
- # TODO: list
1929
+ ed_newline(key)
1762
1930
  end
1763
1931
  end
1932
+ alias_method :vi_end_of_transmission, :vi_list_or_eof
1933
+ alias_method :vi_eof_maybe, :vi_list_or_eof
1764
1934
 
1765
1935
  private def ed_delete_next_char(key, arg: 1)
1766
1936
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
@@ -1861,13 +2031,13 @@ class Reline::LineEditor
1861
2031
  end
1862
2032
 
1863
2033
  private def vi_replace_char(key, arg: 1)
1864
- @waiting_proc = ->(key) {
2034
+ @waiting_proc = ->(k) {
1865
2035
  if arg == 1
1866
2036
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1867
2037
  before = @line.byteslice(0, @byte_pointer)
1868
2038
  remaining_point = @byte_pointer + byte_size
1869
2039
  after = @line.byteslice(remaining_point, @line.size - remaining_point)
1870
- @line = before + key.chr + after
2040
+ @line = before + k.chr + after
1871
2041
  @cursor_max = calculate_width(@line)
1872
2042
  @waiting_proc = nil
1873
2043
  elsif arg > 1
@@ -1878,7 +2048,7 @@ class Reline::LineEditor
1878
2048
  before = @line.byteslice(0, @byte_pointer)
1879
2049
  remaining_point = @byte_pointer + byte_size
1880
2050
  after = @line.byteslice(remaining_point, @line.size - remaining_point)
1881
- replaced = key.chr * arg
2051
+ replaced = k.chr * arg
1882
2052
  @line = before + replaced + after
1883
2053
  @byte_pointer += replaced.bytesize
1884
2054
  @cursor += calculate_width(replaced)
@@ -1939,4 +2109,20 @@ class Reline::LineEditor
1939
2109
  arg -= 1
1940
2110
  vi_join_lines(key, arg: arg) if arg > 0
1941
2111
  end
2112
+
2113
+ private def em_set_mark(key)
2114
+ @mark_pointer = [@byte_pointer, @line_index]
2115
+ end
2116
+ alias_method :set_mark, :em_set_mark
2117
+
2118
+ private def em_exchange_mark(key)
2119
+ new_pointer = [@byte_pointer, @line_index]
2120
+ @previous_line_index = @line_index
2121
+ @byte_pointer, @line_index = @mark_pointer
2122
+ @byte_pointer, @line_index = @mark_pointer
2123
+ @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
2124
+ @cursor_max = calculate_width(@line)
2125
+ @mark_pointer = new_pointer
2126
+ end
2127
+ alias_method :exchange_point_and_mark, :em_exchange_mark
1942
2128
  end
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.0.4'
2
+ VERSION = '0.1.1'
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.4
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-29 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.6
115
+ rubygems_version: 3.1.0.pre3
102
116
  signing_key:
103
117
  specification_version: 4
104
118
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.