reline 0.0.1 → 0.0.6

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.
@@ -7,7 +7,11 @@ class Reline::ANSI
7
7
  [27, 91, 51, 126] => :key_delete, # Del
8
8
  [27, 91, 49, 126] => :ed_move_to_beg, # Home
9
9
  [27, 91, 52, 126] => :ed_move_to_end, # End
10
- }.each_key(&:freeze).freeze
10
+ [27, 91, 72] => :ed_move_to_beg, # Home
11
+ [27, 91, 70] => :ed_move_to_end, # End
12
+ [27, 32] => :em_set_mark, # M-<space>
13
+ [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
14
+ }
11
15
 
12
16
  @@input = STDIN
13
17
  def self.input=(val)
@@ -24,20 +28,22 @@ class Reline::ANSI
24
28
  unless @@buf.empty?
25
29
  return @@buf.shift
26
30
  end
27
- c = nil
28
- loop do
29
- result = select([@@input], [], [], 0.1)
30
- next if result.nil?
31
- c = @@input.read(1)
32
- break
33
- end
34
- c&.ord
31
+ @@input.getbyte
35
32
  end
36
33
 
37
34
  def self.ungetc(c)
38
35
  @@buf.unshift(c)
39
36
  end
40
37
 
38
+ def self.retrieve_keybuffer
39
+ result = select([@@input], [], [], 0.001)
40
+ return if result.nil?
41
+ str = @@input.read_nonblock(1024)
42
+ str.bytes.each do |c|
43
+ @@buf.push(c)
44
+ end
45
+ end
46
+
41
47
  def self.get_screen_size
42
48
  @@input.winsize
43
49
  rescue Errno::ENOTTY
@@ -106,13 +112,23 @@ class Reline::ANSI
106
112
  print "\e[1;1H"
107
113
  end
108
114
 
115
+ @@old_winch_handler = nil
116
+ def self.set_winch_handler(&handler)
117
+ @@old_winch_handler = Signal.trap('WINCH', &handler)
118
+ end
119
+
109
120
  def self.prep
121
+ retrieve_keybuffer
110
122
  int_handle = Signal.trap('INT', 'IGNORE')
111
123
  otio = `stty -g`.chomp
112
124
  setting = ' -echo -icrnl cbreak'
113
- if /-parenb\b/ =~ `stty -a`
125
+ stty = `stty -a`
126
+ if /-parenb\b/ =~ stty
114
127
  setting << ' pass8'
115
128
  end
129
+ if /\bdsusp *=/ =~ stty
130
+ setting << ' dsusp undef'
131
+ end
116
132
  setting << ' -ixoff'
117
133
  `stty #{setting}`
118
134
  Signal.trap('INT', int_handle)
@@ -121,7 +137,8 @@ class Reline::ANSI
121
137
 
122
138
  def self.deprep(otio)
123
139
  int_handle = Signal.trap('INT', 'IGNORE')
124
- `stty #{otio}`
140
+ system("stty #{otio}", err: File::NULL)
125
141
  Signal.trap('INT', int_handle)
142
+ Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
126
143
  end
127
144
  end
@@ -126,20 +126,19 @@ class Reline::Config
126
126
  no += 1
127
127
 
128
128
  line = line.chomp.lstrip
129
- if line[0, 1] == '$'
129
+ if line.start_with?('$')
130
130
  handle_directive(line[1..-1], file, no)
131
131
  next
132
132
  end
133
133
 
134
134
  next if @skip_section
135
135
 
136
- if line.match(/^set +([^ ]+) +([^ ]+)/i)
136
+ case line
137
+ when /^set +([^ ]+) +([^ ]+)/i
137
138
  var, value = $1.downcase, $2.downcase
138
139
  bind_variable(var, value)
139
140
  next
140
- end
141
-
142
- if line =~ /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/
141
+ when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
143
142
  key, func_name = $1, $2
144
143
  keystroke, func = bind_key(key, func_name)
145
144
  next unless keystroke
@@ -1,7 +1,7 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Reline::GeneralIO
4
- RAW_KEYSTROKE_CONFIG = {}.freeze
4
+ RAW_KEYSTROKE_CONFIG = {}
5
5
 
6
6
  @@buf = []
7
7
 
@@ -56,6 +56,9 @@ class Reline::GeneralIO
56
56
  def self.set_screen_size(rows, columns)
57
57
  end
58
58
 
59
+ def self.set_winch_handler(&handler)
60
+ end
61
+
59
62
  def self.prep
60
63
  end
61
64
 
@@ -55,7 +55,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
55
55
  # 26 ^Z
56
56
  :ed_unassigned,
57
57
  # 27 ^[
58
- :em_meta_next,
58
+ :ed_unassigned,
59
59
  # 28 ^\
60
60
  :ed_ignore,
61
61
  # 29 ^]
@@ -37,7 +37,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
37
37
  # 17 ^Q
38
38
  :ed_ignore,
39
39
  # 18 ^R
40
- :ed_insert,
40
+ :ed_search_prev_history,
41
41
  # 19 ^S
42
42
  :ed_ignore,
43
43
  # 20 ^T
@@ -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
 
@@ -60,15 +60,72 @@ class Reline::LineEditor
60
60
  reset_variables
61
61
  end
62
62
 
63
+ private def check_multiline_prompt(buffer, prompt)
64
+ if @vi_arg
65
+ prompt = "(arg: #{@vi_arg}) "
66
+ @rerender_all = true
67
+ elsif @searching_prompt
68
+ prompt = @searching_prompt
69
+ @rerender_all = true
70
+ else
71
+ prompt = @prompt
72
+ end
73
+ if @prompt_proc
74
+ prompt_list = @prompt_proc.(buffer)
75
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
76
+ prompt = prompt_list[@line_index]
77
+ prompt_width = calculate_width(prompt, true)
78
+ [prompt, prompt_width, prompt_list]
79
+ else
80
+ prompt_width = calculate_width(prompt, true)
81
+ [prompt, prompt_width, nil]
82
+ end
83
+ end
84
+
63
85
  def reset(prompt = '', encoding = Encoding.default_external)
64
86
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
65
87
  @screen_size = Reline::IOGate.get_screen_size
66
88
  reset_variables(prompt, encoding)
67
89
  @old_trap = Signal.trap('SIGINT') {
68
- scroll_down(@highest_in_all - @first_line_started_from)
69
- Reline::IOGate.move_cursor_column(0)
70
90
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
91
+ raise Interrupt
71
92
  }
93
+ Reline::IOGate.set_winch_handler do
94
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
95
+ old_screen_size = @screen_size
96
+ @screen_size = Reline::IOGate.get_screen_size
97
+ if old_screen_size.last < @screen_size.last # columns increase
98
+ @rerender_all = true
99
+ rerender
100
+ else
101
+ back = 0
102
+ new_buffer = whole_lines
103
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
104
+ new_buffer.each_with_index do |line, index|
105
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
106
+ width = prompt_width + calculate_width(line)
107
+ height = calculate_height_by_width(width)
108
+ back += height
109
+ end
110
+ @highest_in_all = back
111
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
112
+ @first_line_started_from =
113
+ if @line_index.zero?
114
+ 0
115
+ else
116
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
117
+ end
118
+ if @prompt_proc
119
+ prompt = prompt_list[@line_index]
120
+ prompt_width = calculate_width(prompt, true)
121
+ end
122
+ calculate_nearest_cursor
123
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
124
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
125
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
126
+ @rerender_all = true
127
+ end
128
+ end
72
129
  end
73
130
 
74
131
  def finalize
@@ -81,6 +138,7 @@ class Reline::LineEditor
81
138
 
82
139
  def reset_variables(prompt = '', encoding = Encoding.default_external)
83
140
  @prompt = prompt
141
+ @mark_pointer = nil
84
142
  @encoding = encoding
85
143
  @is_multiline = false
86
144
  @finished = false
@@ -129,6 +187,16 @@ class Reline::LineEditor
129
187
  @is_multiline = false
130
188
  end
131
189
 
190
+ private def calculate_height_by_lines(lines, prompt_list)
191
+ result = 0
192
+ lines.each_with_index { |line, i|
193
+ prompt = ''
194
+ prompt = prompt_list[i] if prompt_list and prompt_list[i]
195
+ result += calculate_height_by_width(calculate_width(prompt + line))
196
+ }
197
+ result
198
+ end
199
+
132
200
  private def insert_new_line(cursor_line, next_line)
133
201
  @line = cursor_line
134
202
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
@@ -241,7 +309,7 @@ class Reline::LineEditor
241
309
  @byte_pointer = new_byte_pointer
242
310
  end
243
311
 
244
- def rerender # TODO: support physical and logical lines
312
+ def rerender
245
313
  return if @line.nil?
246
314
  if @menu_info
247
315
  scroll_down(@highest_in_all - @first_line_started_from)
@@ -249,32 +317,18 @@ class Reline::LineEditor
249
317
  @menu_info.list.each do |item|
250
318
  Reline::IOGate.move_cursor_column(0)
251
319
  @output.print item
320
+ @output.flush
252
321
  scroll_down(1)
253
322
  end
254
323
  scroll_down(@highest_in_all - 1)
255
324
  move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
256
325
  @menu_info = nil
257
326
  end
258
- if @vi_arg
259
- prompt = "(arg: #{@vi_arg}) "
260
- prompt_width = calculate_width(prompt)
261
- elsif @searching_prompt
262
- prompt = @searching_prompt
263
- prompt_width = calculate_width(prompt)
264
- else
265
- prompt = @prompt
266
- prompt_width = calculate_width(prompt, true)
267
- end
327
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
268
328
  if @cleared
269
329
  Reline::IOGate.clear_screen
270
330
  @cleared = false
271
331
  back = 0
272
- prompt_list = nil
273
- if @prompt_proc
274
- prompt_list = @prompt_proc.(whole_lines)
275
- prompt = prompt_list[@line_index]
276
- prompt_width = calculate_width(prompt, true)
277
- end
278
332
  modify_lines(whole_lines).each_with_index do |line, index|
279
333
  if @prompt_proc
280
334
  pr = prompt_list[index]
@@ -300,15 +354,8 @@ class Reline::LineEditor
300
354
  else
301
355
  new_lines = whole_lines
302
356
  end
303
- prompt_list = nil
304
- if @prompt_proc
305
- prompt_list = @prompt_proc.(new_lines)
306
- prompt = prompt_list[@line_index]
307
- prompt_width = calculate_width(prompt, true)
308
- end
309
- all_height = new_lines.inject(0) { |result, line|
310
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
311
- }
357
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
358
+ all_height = calculate_height_by_lines(new_lines, prompt_list)
312
359
  diff = all_height - @highest_in_all
313
360
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
314
361
  if diff > 0
@@ -348,9 +395,7 @@ class Reline::LineEditor
348
395
  if @line_index.zero?
349
396
  0
350
397
  else
351
- @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
352
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
353
- }
398
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
354
399
  end
355
400
  if @prompt_proc
356
401
  prompt = prompt_list[@line_index]
@@ -369,12 +414,7 @@ class Reline::LineEditor
369
414
  Reline::IOGate.move_cursor_column(0)
370
415
  back = 0
371
416
  new_buffer = whole_lines
372
- prompt_list = nil
373
- if @prompt_proc
374
- prompt_list = @prompt_proc.(new_buffer)
375
- prompt = prompt_list[@line_index]
376
- prompt_width = calculate_width(prompt, true)
377
- end
417
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
378
418
  new_buffer.each_with_index do |line, index|
379
419
  prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
380
420
  width = prompt_width + calculate_width(line)
@@ -414,9 +454,7 @@ class Reline::LineEditor
414
454
  if @line_index.zero?
415
455
  0
416
456
  else
417
- new_buffer[0..(@line_index - 1)].inject(0) { |result, line|
418
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
419
- }
457
+ calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
420
458
  end
421
459
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
422
460
  move_cursor_down(@first_line_started_from + @started_from)
@@ -426,12 +464,7 @@ class Reline::LineEditor
426
464
  end
427
465
  line = modify_lines(whole_lines)[@line_index]
428
466
  if @is_multiline
429
- prompt_list = nil
430
- if @prompt_proc
431
- prompt_list = @prompt_proc.(whole_lines)
432
- prompt = prompt_list[@line_index]
433
- prompt_width = calculate_width(prompt, true)
434
- end
467
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
435
468
  if finished?
436
469
  # Always rerender on finish because output_modifier_proc may return a different output.
437
470
  render_partial(prompt, prompt_width, line)
@@ -477,6 +510,7 @@ class Reline::LineEditor
477
510
  next
478
511
  end
479
512
  @output.print line
513
+ @output.flush
480
514
  if @first_prompt
481
515
  @first_prompt = false
482
516
  @pre_input_hook&.call
@@ -619,9 +653,9 @@ class Reline::LineEditor
619
653
  else
620
654
  old_waiting_proc = @waiting_proc
621
655
  old_waiting_operator_proc = @waiting_operator_proc
622
- @waiting_proc = proc { |key|
656
+ @waiting_proc = proc { |k|
623
657
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
624
- old_waiting_proc.(key)
658
+ old_waiting_proc.(k)
625
659
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
626
660
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
627
661
  @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
@@ -732,7 +766,7 @@ class Reline::LineEditor
732
766
  end
733
767
 
734
768
  def input_key(key)
735
- if key.nil? or key.char.nil?
769
+ if key.char.nil?
736
770
  if @first_char
737
771
  @line = nil
738
772
  end
@@ -772,6 +806,26 @@ class Reline::LineEditor
772
806
 
773
807
  private def process_auto_indent
774
808
  return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
809
+ if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
810
+ # Fix indent of a line when a newline is inserted to the next
811
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
812
+ new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
813
+ md = @line.match(/\A */)
814
+ prev_indent = md[0].count(' ')
815
+ @line = ' ' * new_indent + @line.lstrip
816
+
817
+ new_indent = nil
818
+ (new_lines[-2].size + 1).times do |n|
819
+ result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, n, false)
820
+ if result
821
+ new_indent = result
822
+ break
823
+ end
824
+ end
825
+ if new_indent&.>= 0
826
+ @line = ' ' * new_indent + @line.lstrip
827
+ end
828
+ end
775
829
  if @previous_line_index
776
830
  new_lines = whole_lines(index: @previous_line_index, line: @line)
777
831
  else
@@ -801,17 +855,25 @@ class Reline::LineEditor
801
855
  rest = nil
802
856
  break_pointer = nil
803
857
  quote = nil
858
+ closing_quote = nil
859
+ escaped_quote = nil
804
860
  i = 0
805
861
  while i < @byte_pointer do
806
862
  slice = @line.byteslice(i, @byte_pointer - i)
807
- if quote and slice.start_with?(/(?!\\)#{Regexp.escape(quote)}/) # closing "
863
+ unless slice.valid_encoding?
864
+ i += 1
865
+ next
866
+ end
867
+ if quote and slice.start_with?(closing_quote)
808
868
  quote = nil
809
869
  i += 1
810
- elsif quote and slice.start_with?(/\\#{Regexp.escape(quote)}/) # escaped \"
870
+ elsif quote and slice.start_with?(escaped_quote)
811
871
  # skip
812
872
  i += 2
813
873
  elsif slice =~ quote_characters_regexp # find new "
814
874
  quote = $&
875
+ closing_quote = /(?!\\)#{Regexp.escape(quote)}/
876
+ escaped_quote = /\\#{Regexp.escape(quote)}/
815
877
  i += 1
816
878
  elsif not quote and slice =~ word_break_regexp
817
879
  rest = $'
@@ -839,11 +901,7 @@ class Reline::LineEditor
839
901
  else
840
902
  temp_buffer[@line_index] = @line
841
903
  end
842
- if temp_buffer.any?{ |l| l.chomp != '' }
843
- @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
844
- else
845
- false
846
- end
904
+ @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
847
905
  end
848
906
 
849
907
  def insert_text(text)
@@ -959,8 +1017,8 @@ class Reline::LineEditor
959
1017
  end
960
1018
  width
961
1019
  else
962
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
963
- width + Reline::Unicode.get_mbchar_width(gc)
1020
+ str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
1021
+ w + Reline::Unicode.get_mbchar_width(gc)
964
1022
  }
965
1023
  end
966
1024
  end
@@ -981,6 +1039,8 @@ class Reline::LineEditor
981
1039
  end
982
1040
  end
983
1041
 
1042
+ private def ed_unassigned(key) end # do nothing
1043
+
984
1044
  private def ed_insert(key)
985
1045
  if key.instance_of?(String)
986
1046
  width = Reline::Unicode.get_mbchar_width(key)
@@ -1085,7 +1145,11 @@ class Reline::LineEditor
1085
1145
  alias_method :end_of_line, :ed_move_to_end
1086
1146
 
1087
1147
  private def ed_search_prev_history(key)
1088
- @line_backup_in_history = @line
1148
+ if @is_multiline
1149
+ @line_backup_in_history = whole_buffer
1150
+ else
1151
+ @line_backup_in_history = @line
1152
+ end
1089
1153
  searcher = Fiber.new do
1090
1154
  search_word = String.new(encoding: @encoding)
1091
1155
  multibyte_buf = String.new(encoding: 'ASCII-8BIT')
@@ -1120,18 +1184,32 @@ class Reline::LineEditor
1120
1184
  end
1121
1185
  end
1122
1186
  if hit
1123
- @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
1124
- @line = hit
1187
+ if @is_multiline
1188
+ @buffer_of_lines = hit.split("\n")
1189
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1190
+ @line_index = @buffer_of_lines.size - 1
1191
+ @line = @buffer_of_lines.last
1192
+ @rerender_all = true
1193
+ @searching_prompt = "(reverse-i-search)`%s'" % [search_word]
1194
+ else
1195
+ @line = hit
1196
+ @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
1197
+ end
1125
1198
  last_hit = hit
1126
1199
  else
1127
- @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
1200
+ if @is_multiline
1201
+ @rerender_all = true
1202
+ @searching_prompt = "(failed reverse-i-search)`%s'" % [search_word]
1203
+ else
1204
+ @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
1205
+ end
1128
1206
  end
1129
1207
  end
1130
1208
  end
1131
1209
  searcher.resume
1132
1210
  @searching_prompt = "(reverse-i-search)`': "
1133
- @waiting_proc = ->(key) {
1134
- case key
1211
+ @waiting_proc = ->(k) {
1212
+ case k
1135
1213
  when "\C-j".ord, "\C-?".ord
1136
1214
  if @history_pointer
1137
1215
  @line = Reline::HISTORY[@history_pointer]
@@ -1151,14 +1229,25 @@ class Reline::LineEditor
1151
1229
  @cursor_max = calculate_width(@line)
1152
1230
  @cursor = @byte_pointer = 0
1153
1231
  else
1154
- chr = key.is_a?(String) ? key : key.chr(Encoding::ASCII_8BIT)
1155
- if chr.match?(/[[:print:]]/)
1156
- searcher.resume(key)
1232
+ chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1233
+ if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == 127
1234
+ searcher.resume(k)
1157
1235
  else
1158
1236
  if @history_pointer
1159
- @line = Reline::HISTORY[@history_pointer]
1237
+ line = Reline::HISTORY[@history_pointer]
1238
+ else
1239
+ line = @line_backup_in_history
1240
+ end
1241
+ if @is_multiline
1242
+ @line_backup_in_history = whole_buffer
1243
+ @buffer_of_lines = line.split("\n")
1244
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1245
+ @line_index = @buffer_of_lines.size - 1
1246
+ @line = @buffer_of_lines.last
1247
+ @rerender_all = true
1160
1248
  else
1161
- @line = @line_backup_in_history
1249
+ @line_backup_in_history = @line
1250
+ @line = line
1162
1251
  end
1163
1252
  @searching_prompt = nil
1164
1253
  @waiting_proc = nil
@@ -1211,7 +1300,7 @@ class Reline::LineEditor
1211
1300
  @line = Reline::HISTORY[@history_pointer]
1212
1301
  end
1213
1302
  end
1214
- if @config.editing_mode_is?(:emacs)
1303
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1215
1304
  @cursor_max = @cursor = calculate_width(@line)
1216
1305
  @byte_pointer = @line.bytesize
1217
1306
  elsif @config.editing_mode_is?(:vi_command)
@@ -1258,7 +1347,7 @@ class Reline::LineEditor
1258
1347
  end
1259
1348
  end
1260
1349
  @line = '' unless @line
1261
- if @config.editing_mode_is?(:emacs)
1350
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1262
1351
  @cursor_max = @cursor = calculate_width(@line)
1263
1352
  @byte_pointer = @line.bytesize
1264
1353
  elsif @config.editing_mode_is?(:vi_command)
@@ -1697,8 +1786,8 @@ class Reline::LineEditor
1697
1786
  end
1698
1787
 
1699
1788
  private def ed_delete_next_char(key, arg: 1)
1700
- unless @line.empty?
1701
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1789
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1790
+ unless @line.empty? || byte_size == 0
1702
1791
  @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1703
1792
  copy_for_vi(mbchar)
1704
1793
  width = Reline::Unicode.get_mbchar_width(mbchar)
@@ -1795,13 +1884,13 @@ class Reline::LineEditor
1795
1884
  end
1796
1885
 
1797
1886
  private def vi_replace_char(key, arg: 1)
1798
- @waiting_proc = ->(key) {
1887
+ @waiting_proc = ->(k) {
1799
1888
  if arg == 1
1800
1889
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1801
1890
  before = @line.byteslice(0, @byte_pointer)
1802
1891
  remaining_point = @byte_pointer + byte_size
1803
1892
  after = @line.byteslice(remaining_point, @line.size - remaining_point)
1804
- @line = before + key.chr + after
1893
+ @line = before + k.chr + after
1805
1894
  @cursor_max = calculate_width(@line)
1806
1895
  @waiting_proc = nil
1807
1896
  elsif arg > 1
@@ -1812,7 +1901,7 @@ class Reline::LineEditor
1812
1901
  before = @line.byteslice(0, @byte_pointer)
1813
1902
  remaining_point = @byte_pointer + byte_size
1814
1903
  after = @line.byteslice(remaining_point, @line.size - remaining_point)
1815
- replaced = key.chr * arg
1904
+ replaced = k.chr * arg
1816
1905
  @line = before + replaced + after
1817
1906
  @byte_pointer += replaced.bytesize
1818
1907
  @cursor += calculate_width(replaced)
@@ -1873,4 +1962,20 @@ class Reline::LineEditor
1873
1962
  arg -= 1
1874
1963
  vi_join_lines(key, arg: arg) if arg > 0
1875
1964
  end
1965
+
1966
+ private def em_set_mark(key)
1967
+ @mark_pointer = [@byte_pointer, @line_index]
1968
+ end
1969
+ alias_method :set_mark, :em_set_mark
1970
+
1971
+ private def em_exchange_mark(key)
1972
+ new_pointer = [@byte_pointer, @line_index]
1973
+ @previous_line_index = @line_index
1974
+ @byte_pointer, @line_index = @mark_pointer
1975
+ @byte_pointer, @line_index = @mark_pointer
1976
+ @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
1977
+ @cursor_max = calculate_width(@line)
1978
+ @mark_pointer = new_pointer
1979
+ end
1980
+ alias_method :exchange_point_and_mark, :em_exchange_mark
1876
1981
  end