reline 0.2.0 → 0.2.5

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: 2a20f90b83534916dd8575a842ef2cbe968879cc4b2d1826b5374137f554249a
4
- data.tar.gz: add41e0e8f89fa39b8064c9d45bc0f17ac1b374578ae25872e24c50cb2e0e761
3
+ metadata.gz: 1da0f8ec80f16c720589413fbc14c02ac199dcfc0b0344270a61edbad4aec966
4
+ data.tar.gz: d214e02ad3500323e98d97d57677b7bbc94ea4c09df0b529d5b21ea66374a6a8
5
5
  SHA512:
6
- metadata.gz: ebd4286538633a6bb87dd2007bdd6bf389ef87aa5dd7406288ae8f52cacc1e0d7f87c4d3ae4bbac3fa125004d5f65b5740c6b8d2ec014e130160d7bd22c6ec1e
7
- data.tar.gz: 3a418c2aa202ac4710bb7d90d0017870c066e17ece72c644f90e4d37b9faadb02636150d332c00845c5ca60a421abd1394255f77e95be7626cfce046705b7ed0
6
+ metadata.gz: 0d218a92032ad8b02218e44bda7fe7d7404845f6cc9cceb9aade01d3c49bc33a0396b02857de417ecee59b5d65c0cb787eecec8dea60180ae3b5457df6c89ebd
7
+ data.tar.gz: 12c4b8d00608e1f9f16a1c5e530ae86c7530f39ddb1c0f6100b1d4ae87a6dbd53def15d9f387941062aafdb57360fd499dc5850a5bd60d07776daf60e057acfe
data/README.md CHANGED
@@ -11,3 +11,7 @@ Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and
11
11
  ## License
12
12
 
13
13
  The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
14
+
15
+ ## Acknowledgments for [rb-readline](https://github.com/ConnorAtherton/rb-readline)
16
+
17
+ In developing Reline, we have used some of the rb-readline implementation, so this library includes [copyright notice, list of conditions and the disclaimer](license_of_rb-readline) under the 3-Clause BSD License. Reline would never have been developed without rb-readline. Thank you for the tremendous accomplishments.
data/lib/reline.rb CHANGED
@@ -243,6 +243,7 @@ module Reline
243
243
  loop do
244
244
  prev_pasting_state = Reline::IOGate.in_pasting?
245
245
  read_io(config.keyseq_timeout) { |inputs|
246
+ line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
246
247
  inputs.each { |c|
247
248
  line_editor.input_key(c)
248
249
  line_editor.rerender
@@ -253,6 +254,7 @@ module Reline
253
254
  end
254
255
  }
255
256
  if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
257
+ line_editor.set_pasting_state(false)
256
258
  prev_pasting_state = false
257
259
  line_editor.rerender_all
258
260
  end
@@ -444,6 +446,10 @@ module Reline
444
446
  }
445
447
  end
446
448
 
449
+ def self.ungetc(c)
450
+ Reline::IOGate.ungetc(c)
451
+ end
452
+
447
453
  def self.line_editor
448
454
  core.line_editor
449
455
  end
data/lib/reline/config.rb CHANGED
@@ -34,8 +34,8 @@ class Reline::Config
34
34
  show-all-if-unmodified
35
35
  visible-stats
36
36
  show-mode-in-prompt
37
- vi-cmd-mode-icon
38
- vi-ins-mode-icon
37
+ vi-cmd-mode-string
38
+ vi-ins-mode-string
39
39
  emacs-mode-string
40
40
  enable-bracketed-paste
41
41
  isearch-terminators
@@ -56,8 +56,8 @@ class Reline::Config
56
56
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
57
57
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
58
58
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
59
- @vi_cmd_mode_icon = '(cmd)'
60
- @vi_ins_mode_icon = '(ins)'
59
+ @vi_cmd_mode_string = '(cmd)'
60
+ @vi_ins_mode_string = '(ins)'
61
61
  @emacs_mode_string = '@'
62
62
  # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
63
63
  @history_size = -1 # unlimited
@@ -270,9 +270,9 @@ class Reline::Config
270
270
  @show_mode_in_prompt = false
271
271
  end
272
272
  when 'vi-cmd-mode-string'
273
- @vi_cmd_mode_icon = retrieve_string(value)
273
+ @vi_cmd_mode_string = retrieve_string(value)
274
274
  when 'vi-ins-mode-string'
275
- @vi_ins_mode_icon = retrieve_string(value)
275
+ @vi_ins_mode_string = retrieve_string(value)
276
276
  when 'emacs-mode-string'
277
277
  @emacs_mode_string = retrieve_string(value)
278
278
  when *VARIABLE_NAMES then
@@ -58,34 +58,38 @@ class Reline::LineEditor
58
58
  reset_variables(encoding: encoding)
59
59
  end
60
60
 
61
+ def set_pasting_state(in_pasting)
62
+ @in_pasting = in_pasting
63
+ end
64
+
61
65
  def simplified_rendering?
62
66
  if finished?
63
67
  false
64
68
  elsif @just_cursor_moving and not @rerender_all
65
69
  true
66
70
  else
67
- not @rerender_all and not finished? and Reline::IOGate.in_pasting?
71
+ not @rerender_all and not finished? and @in_pasting
68
72
  end
69
73
  end
70
74
 
71
- private def check_mode_icon
72
- mode_icon = nil
75
+ private def check_mode_string
76
+ mode_string = nil
73
77
  if @config.show_mode_in_prompt
74
78
  if @config.editing_mode_is?(:vi_command)
75
- mode_icon = @config.vi_cmd_mode_icon
79
+ mode_string = @config.vi_cmd_mode_string
76
80
  elsif @config.editing_mode_is?(:vi_insert)
77
- mode_icon = @config.vi_ins_mode_icon
81
+ mode_string = @config.vi_ins_mode_string
78
82
  elsif @config.editing_mode_is?(:emacs)
79
- mode_icon = @config.emacs_mode_string
83
+ mode_string = @config.emacs_mode_string
80
84
  else
81
- mode_icon = '?'
85
+ mode_string = '?'
82
86
  end
83
87
  end
84
- if mode_icon != @prev_mode_icon
88
+ if mode_string != @prev_mode_string
85
89
  @rerender_all = true
86
90
  end
87
- @prev_mode_icon = mode_icon
88
- mode_icon
91
+ @prev_mode_string = mode_string
92
+ mode_string
89
93
  end
90
94
 
91
95
  private def check_multiline_prompt(buffer, prompt)
@@ -99,8 +103,8 @@ class Reline::LineEditor
99
103
  prompt = @prompt
100
104
  end
101
105
  if simplified_rendering?
102
- mode_icon = check_mode_icon
103
- prompt = mode_icon + prompt if mode_icon
106
+ mode_string = check_mode_string
107
+ prompt = mode_string + prompt if mode_string
104
108
  return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
105
109
  end
106
110
  if @prompt_proc
@@ -112,6 +116,7 @@ class Reline::LineEditor
112
116
  use_cached_prompt_list = true
113
117
  end
114
118
  end
119
+ use_cached_prompt_list = false if @rerender_all
115
120
  if use_cached_prompt_list
116
121
  prompt_list = @cached_prompt_list
117
122
  else
@@ -119,15 +124,22 @@ class Reline::LineEditor
119
124
  @prompt_cache_time = Time.now.to_f
120
125
  end
121
126
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
122
- mode_icon = check_mode_icon
123
- prompt_list = prompt_list.map{ |pr| mode_icon + pr } if mode_icon
127
+ prompt_list = [prompt] if prompt_list.empty?
128
+ mode_string = check_mode_string
129
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
124
130
  prompt = prompt_list[@line_index]
125
131
  prompt = prompt_list[0] if prompt.nil?
132
+ prompt = prompt_list.last if prompt.nil?
133
+ if buffer.size > prompt_list.size
134
+ (buffer.size - prompt_list.size).times do
135
+ prompt_list << prompt_list.last
136
+ end
137
+ end
126
138
  prompt_width = calculate_width(prompt, true)
127
139
  [prompt, prompt_width, prompt_list]
128
140
  else
129
- mode_icon = check_mode_icon
130
- prompt = mode_icon + prompt if mode_icon
141
+ mode_string = check_mode_string
142
+ prompt = mode_string + prompt if mode_string
131
143
  prompt_width = calculate_width(prompt, true)
132
144
  [prompt, prompt_width, nil]
133
145
  end
@@ -139,6 +151,13 @@ class Reline::LineEditor
139
151
  @screen_height = @screen_size.first
140
152
  reset_variables(prompt, encoding: encoding)
141
153
  @old_trap = Signal.trap('SIGINT') {
154
+ if @scroll_partial_screen
155
+ move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
156
+ else
157
+ move_cursor_down(@highest_in_all - @line_index - 1)
158
+ end
159
+ Reline::IOGate.move_cursor_column(0)
160
+ scroll_down(1)
142
161
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
143
162
  raise Interrupt
144
163
  }
@@ -218,8 +237,10 @@ class Reline::LineEditor
218
237
  @eof = false
219
238
  @continuous_insertion_buffer = String.new(encoding: @encoding)
220
239
  @scroll_partial_screen = nil
221
- @prev_mode_icon = nil
240
+ @prev_mode_string = nil
222
241
  @drop_terminate_spaces = false
242
+ @in_pasting = false
243
+ @auto_indent_proc = nil
223
244
  reset_line
224
245
  end
225
246
 
@@ -323,8 +344,9 @@ class Reline::LineEditor
323
344
  else
324
345
  end_of_line_cursor = new_cursor_max
325
346
  end
326
- line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
327
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
347
+ line_to_calc.grapheme_clusters.each do |gc|
348
+ mbchar = gc.encode(Encoding::UTF_8)
349
+ mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
328
350
  now = new_cursor + mbchar_width
329
351
  if now > end_of_line_cursor or now > cursor
330
352
  break
@@ -368,10 +390,29 @@ class Reline::LineEditor
368
390
  @cleared = false
369
391
  return
370
392
  end
393
+ if @is_multiline and finished? and @scroll_partial_screen
394
+ # Re-output all code higher than the screen when finished.
395
+ Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
396
+ Reline::IOGate.move_cursor_column(0)
397
+ @scroll_partial_screen = nil
398
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
399
+ if @previous_line_index
400
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
401
+ else
402
+ new_lines = whole_lines
403
+ end
404
+ modify_lines(new_lines).each_with_index do |line, index|
405
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
406
+ Reline::IOGate.erase_after_cursor
407
+ end
408
+ @output.flush
409
+ return
410
+ end
371
411
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
372
412
  # FIXME: end of logical line sometimes breaks
413
+ rendered = false
373
414
  if @add_newline_to_end_of_buffer
374
- rerender_added_newline
415
+ rerender_added_newline(prompt, prompt_width)
375
416
  @add_newline_to_end_of_buffer = false
376
417
  else
377
418
  if @just_cursor_moving and not @rerender_all
@@ -389,20 +430,32 @@ class Reline::LineEditor
389
430
  else
390
431
  end
391
432
  end
392
- line = modify_lines(whole_lines)[@line_index]
393
433
  if @is_multiline
394
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
395
434
  if finished?
396
435
  # Always rerender on finish because output_modifier_proc may return a different output.
436
+ if @previous_line_index
437
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
438
+ else
439
+ new_lines = whole_lines
440
+ end
441
+ line = modify_lines(new_lines)[@line_index]
442
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
397
443
  render_partial(prompt, prompt_width, line, @first_line_started_from)
444
+ move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
398
445
  scroll_down(1)
399
446
  Reline::IOGate.move_cursor_column(0)
400
447
  Reline::IOGate.erase_after_cursor
401
448
  elsif not rendered
402
- render_partial(prompt, prompt_width, line, @first_line_started_from)
449
+ unless @in_pasting
450
+ line = modify_lines(whole_lines)[@line_index]
451
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
452
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
453
+ end
403
454
  end
404
455
  @buffer_of_lines[@line_index] = @line
456
+ @rest_height = 0 if @scroll_partial_screen
405
457
  else
458
+ line = modify_lines(whole_lines)[@line_index]
406
459
  render_partial(prompt, prompt_width, line, 0)
407
460
  if finished?
408
461
  scroll_down(1)
@@ -445,13 +498,13 @@ class Reline::LineEditor
445
498
  end
446
499
  end
447
500
 
448
- private def rerender_added_newline
501
+ private def rerender_added_newline(prompt, prompt_width)
449
502
  scroll_down(1)
450
- new_lines = whole_lines(index: @previous_line_index, line: @line)
451
- prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
452
503
  @buffer_of_lines[@previous_line_index] = @line
453
504
  @line = @buffer_of_lines[@line_index]
454
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
505
+ unless @in_pasting
506
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
507
+ end
455
508
  @cursor = @cursor_max = calculate_width(@line)
456
509
  @byte_pointer = @line.bytesize
457
510
  @highest_in_all += @highest_in_this
@@ -471,7 +524,7 @@ class Reline::LineEditor
471
524
  calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
472
525
  end
473
526
  first_line_diff = new_first_line_started_from - @first_line_started_from
474
- new_cursor, _, new_started_from, _ = calculate_nearest_cursor(@line, @cursor, @started_from, @byte_pointer, false)
527
+ new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
475
528
  new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
476
529
  calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
477
530
  @previous_line_index = nil
@@ -485,6 +538,8 @@ class Reline::LineEditor
485
538
  @first_line_started_from = new_first_line_started_from
486
539
  @started_from = new_started_from
487
540
  @cursor = new_cursor
541
+ @cursor_max = new_cursor_max
542
+ @byte_pointer = new_byte_pointer
488
543
  move_cursor_down(first_line_diff + @started_from)
489
544
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
490
545
  false
@@ -558,7 +613,13 @@ class Reline::LineEditor
558
613
  new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
559
614
  end
560
615
  new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
561
- if back > old_highest_in_all
616
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
617
+ if @scroll_partial_screen
618
+ move_cursor_up(@first_line_started_from + @started_from)
619
+ scroll_down(@screen_height - 1)
620
+ move_cursor_up(@screen_height)
621
+ Reline::IOGate.move_cursor_column(0)
622
+ elsif back > old_highest_in_all
562
623
  scroll_down(back - 1)
563
624
  move_cursor_up(back - 1)
564
625
  elsif back < old_highest_in_all
@@ -570,7 +631,6 @@ class Reline::LineEditor
570
631
  end
571
632
  move_cursor_up(old_highest_in_all - 1)
572
633
  end
573
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
574
634
  render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
575
635
  if @prompt_proc
576
636
  prompt = prompt_list[@line_index]
@@ -656,8 +716,8 @@ class Reline::LineEditor
656
716
  @highest_in_this = height
657
717
  end
658
718
  move_cursor_up(@started_from)
659
- cursor_up_from_last_line = height - 1 - @started_from
660
719
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
720
+ cursor_up_from_last_line = height - 1 - @started_from
661
721
  end
662
722
  if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
663
723
  @output.write "\e[0m" # clear character decorations
@@ -667,7 +727,7 @@ class Reline::LineEditor
667
727
  if line.nil?
668
728
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
669
729
  # reaches the end of line
670
- if Reline::IOGate.win?
730
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
671
731
  # A newline is automatically inserted if a character is rendered at
672
732
  # eol on command prompt.
673
733
  else
@@ -685,7 +745,7 @@ class Reline::LineEditor
685
745
  next
686
746
  end
687
747
  @output.write line
688
- if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
748
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
689
749
  # A newline is automatically inserted if a character is rendered at eol on command prompt.
690
750
  @rest_height -= 1 if @rest_height > 0
691
751
  end
@@ -753,6 +813,7 @@ class Reline::LineEditor
753
813
  end
754
814
  move_cursor_up(back)
755
815
  move_cursor_down(@first_line_started_from + @started_from)
816
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
756
817
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
757
818
  end
758
819
 
@@ -1080,7 +1141,7 @@ class Reline::LineEditor
1080
1141
  unless completion_occurs
1081
1142
  @completion_state = CompletionState::NORMAL
1082
1143
  end
1083
- if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1144
+ if not @in_pasting and @just_cursor_moving.nil?
1084
1145
  if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1085
1146
  @just_cursor_moving = true
1086
1147
  elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
@@ -1098,8 +1159,25 @@ class Reline::LineEditor
1098
1159
 
1099
1160
  def call_completion_proc
1100
1161
  result = retrieve_completion_block(true)
1101
- slice = result[1]
1102
- result = @completion_proc.(slice) if @completion_proc and slice
1162
+ preposing, target, postposing = result
1163
+ if @completion_proc and target
1164
+ argnum = @completion_proc.parameters.inject(0) { |result, item|
1165
+ case item.first
1166
+ when :req, :opt
1167
+ result + 1
1168
+ when :rest
1169
+ break 3
1170
+ end
1171
+ }
1172
+ case argnum
1173
+ when 1
1174
+ result = @completion_proc.(target)
1175
+ when 2
1176
+ result = @completion_proc.(target, preposing)
1177
+ when 3..Float::INFINITY
1178
+ result = @completion_proc.(target, preposing, postposing)
1179
+ end
1180
+ end
1103
1181
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
1104
1182
  result
1105
1183
  end
@@ -1129,6 +1207,7 @@ class Reline::LineEditor
1129
1207
  new_lines = whole_lines
1130
1208
  end
1131
1209
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1210
+ new_indent = @cursor_max if new_indent&.> @cursor_max
1132
1211
  if new_indent&.>= 0
1133
1212
  md = new_lines[@line_index].match(/\A */)
1134
1213
  prev_indent = md[0].count(' ')
@@ -1146,8 +1225,16 @@ class Reline::LineEditor
1146
1225
  end
1147
1226
 
1148
1227
  def retrieve_completion_block(set_completion_quote_character = false)
1149
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1150
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1228
+ if Reline.completer_word_break_characters.empty?
1229
+ word_break_regexp = nil
1230
+ else
1231
+ word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1232
+ end
1233
+ if Reline.completer_quote_characters.empty?
1234
+ quote_characters_regexp = nil
1235
+ else
1236
+ quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1237
+ end
1151
1238
  before = @line.byteslice(0, @byte_pointer)
1152
1239
  rest = nil
1153
1240
  break_pointer = nil
@@ -1168,14 +1255,14 @@ class Reline::LineEditor
1168
1255
  elsif quote and slice.start_with?(escaped_quote)
1169
1256
  # skip
1170
1257
  i += 2
1171
- elsif slice =~ quote_characters_regexp # find new "
1258
+ elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1172
1259
  rest = $'
1173
1260
  quote = $&
1174
1261
  closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1175
1262
  escaped_quote = /\\#{Regexp.escape(quote)}/
1176
1263
  i += 1
1177
1264
  break_pointer = i - 1
1178
- elsif not quote and slice =~ word_break_regexp
1265
+ elsif word_break_regexp and not quote and slice =~ word_break_regexp
1179
1266
  rest = $'
1180
1267
  i += 1
1181
1268
  before = @line.byteslice(i, @byte_pointer - i)
@@ -1203,6 +1290,19 @@ class Reline::LineEditor
1203
1290
  end
1204
1291
  target = before
1205
1292
  end
1293
+ if @is_multiline
1294
+ if @previous_line_index
1295
+ lines = whole_lines(index: @previous_line_index, line: @line)
1296
+ else
1297
+ lines = whole_lines
1298
+ end
1299
+ if @line_index > 0
1300
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1301
+ end
1302
+ if (lines.size - 1) > @line_index
1303
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1304
+ end
1305
+ end
1206
1306
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1207
1307
  end
1208
1308
 
@@ -1230,10 +1330,32 @@ class Reline::LineEditor
1230
1330
 
1231
1331
  def delete_text(start = nil, length = nil)
1232
1332
  if start.nil? and length.nil?
1233
- @line&.clear
1234
- @byte_pointer = 0
1235
- @cursor = 0
1236
- @cursor_max = 0
1333
+ if @is_multiline
1334
+ if @buffer_of_lines.size == 1
1335
+ @line&.clear
1336
+ @byte_pointer = 0
1337
+ @cursor = 0
1338
+ @cursor_max = 0
1339
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1340
+ @buffer_of_lines.pop
1341
+ @line_index -= 1
1342
+ @line = @buffer_of_lines[@line_index]
1343
+ @byte_pointer = 0
1344
+ @cursor = 0
1345
+ @cursor_max = calculate_width(@line)
1346
+ elsif @line_index < (@buffer_of_lines.size - 1)
1347
+ @buffer_of_lines.delete_at(@line_index)
1348
+ @line = @buffer_of_lines[@line_index]
1349
+ @byte_pointer = 0
1350
+ @cursor = 0
1351
+ @cursor_max = calculate_width(@line)
1352
+ end
1353
+ else
1354
+ @line&.clear
1355
+ @byte_pointer = 0
1356
+ @cursor = 0
1357
+ @cursor_max = 0
1358
+ end
1237
1359
  elsif not start.nil? and not length.nil?
1238
1360
  if @line
1239
1361
  before = @line.byteslice(0, start)
@@ -1283,7 +1405,11 @@ class Reline::LineEditor
1283
1405
  if @buffer_of_lines.size == 1 and @line.nil?
1284
1406
  nil
1285
1407
  else
1286
- whole_lines.join("\n")
1408
+ if @previous_line_index
1409
+ whole_lines(index: @previous_line_index, line: @line).join("\n")
1410
+ else
1411
+ whole_lines.join("\n")
1412
+ end
1287
1413
  end
1288
1414
  end
1289
1415
 
@@ -1329,14 +1455,14 @@ class Reline::LineEditor
1329
1455
  cursor_line = @line.byteslice(0, @byte_pointer)
1330
1456
  insert_new_line(cursor_line, next_line)
1331
1457
  @cursor = 0
1332
- @check_new_auto_indent = true
1458
+ @check_new_auto_indent = true unless @in_pasting
1333
1459
  end
1334
1460
  end
1335
1461
 
1336
1462
  private def ed_unassigned(key) end # do nothing
1337
1463
 
1338
1464
  private def process_insert(force: false)
1339
- return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1465
+ return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1340
1466
  width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1341
1467
  bytesize = @continuous_insertion_buffer.bytesize
1342
1468
  if @cursor == @cursor_max
@@ -1371,7 +1497,7 @@ class Reline::LineEditor
1371
1497
  str = key.chr
1372
1498
  bytesize = 1
1373
1499
  end
1374
- if Reline::IOGate.in_pasting?
1500
+ if @in_pasting
1375
1501
  @continuous_insertion_buffer << str
1376
1502
  return
1377
1503
  elsif not @continuous_insertion_buffer.empty?
@@ -1722,7 +1848,7 @@ class Reline::LineEditor
1722
1848
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1723
1849
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1724
1850
  @line_index = line_no
1725
- @line = @buffer_of_lines.last
1851
+ @line = @buffer_of_lines[@line_index]
1726
1852
  @rerender_all = true
1727
1853
  else
1728
1854
  @line = Reline::HISTORY[@history_pointer]
@@ -1770,7 +1896,7 @@ class Reline::LineEditor
1770
1896
  @line_index = line_no
1771
1897
  end
1772
1898
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1773
- @line = @buffer_of_lines.last
1899
+ @line = @buffer_of_lines[@line_index]
1774
1900
  @rerender_all = true
1775
1901
  else
1776
1902
  if @history_pointer.nil? and substr.empty?
@@ -2385,6 +2511,9 @@ class Reline::LineEditor
2385
2511
  width = Reline::Unicode.get_mbchar_width(mbchar)
2386
2512
  @cursor_max -= width
2387
2513
  if @cursor > 0 and @cursor >= @cursor_max
2514
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2515
+ mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
2516
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2388
2517
  @byte_pointer -= byte_size
2389
2518
  @cursor -= width
2390
2519
  end
@@ -2418,11 +2547,23 @@ class Reline::LineEditor
2418
2547
 
2419
2548
  private def vi_histedit(key)
2420
2549
  path = Tempfile.open { |fp|
2421
- fp.write @line
2550
+ if @is_multiline
2551
+ fp.write whole_lines.join("\n")
2552
+ else
2553
+ fp.write @line
2554
+ end
2422
2555
  fp.path
2423
2556
  }
2424
2557
  system("#{ENV['EDITOR']} #{path}")
2425
- @line = File.read(path)
2558
+ if @is_multiline
2559
+ @buffer_of_lines = File.read(path).split("\n")
2560
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2561
+ @line_index = 0
2562
+ @line = @buffer_of_lines[@line_index]
2563
+ @rerender_all = true
2564
+ else
2565
+ @line = File.read(path)
2566
+ end
2426
2567
  finish
2427
2568
  end
2428
2569