reline 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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