reline 0.4.3 → 0.5.0.pre.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.
@@ -6,7 +6,6 @@ require 'tempfile'
6
6
  class Reline::LineEditor
7
7
  # TODO: undo
8
8
  # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
9
- attr_reader :line
10
9
  attr_reader :byte_pointer
11
10
  attr_accessor :confirm_multiline_termination_proc
12
11
  attr_accessor :completion_proc
@@ -14,7 +13,6 @@ class Reline::LineEditor
14
13
  attr_accessor :output_modifier_proc
15
14
  attr_accessor :prompt_proc
16
15
  attr_accessor :auto_indent_proc
17
- attr_accessor :pre_input_hook
18
16
  attr_accessor :dig_perfect_match_proc
19
17
  attr_writer :output
20
18
 
@@ -57,6 +55,8 @@ class Reline::LineEditor
57
55
  def initialize(config, encoding)
58
56
  @config = config
59
57
  @completion_append_character = ''
58
+ @cursor_base_y = 0
59
+ @screen_size = Reline::IOGate.get_screen_size
60
60
  reset_variables(encoding: encoding)
61
61
  end
62
62
 
@@ -68,67 +68,30 @@ class Reline::LineEditor
68
68
  @in_pasting = in_pasting
69
69
  end
70
70
 
71
- def simplified_rendering?
72
- if finished?
73
- false
74
- elsif @just_cursor_moving and not @rerender_all
75
- true
76
- else
77
- not @rerender_all and not finished? and @in_pasting
78
- end
79
- end
80
-
81
71
  private def check_mode_string
82
- mode_string = nil
83
72
  if @config.show_mode_in_prompt
84
73
  if @config.editing_mode_is?(:vi_command)
85
- mode_string = @config.vi_cmd_mode_string
74
+ @config.vi_cmd_mode_string
86
75
  elsif @config.editing_mode_is?(:vi_insert)
87
- mode_string = @config.vi_ins_mode_string
76
+ @config.vi_ins_mode_string
88
77
  elsif @config.editing_mode_is?(:emacs)
89
- mode_string = @config.emacs_mode_string
78
+ @config.emacs_mode_string
90
79
  else
91
- mode_string = '?'
80
+ '?'
92
81
  end
93
82
  end
94
- if mode_string != @prev_mode_string
95
- @rerender_all = true
96
- end
97
- @prev_mode_string = mode_string
98
- mode_string
99
83
  end
100
84
 
101
- private def check_multiline_prompt(buffer, force_recalc: false)
85
+ private def check_multiline_prompt(buffer)
102
86
  if @vi_arg
103
87
  prompt = "(arg: #{@vi_arg}) "
104
- @rerender_all = true
105
88
  elsif @searching_prompt
106
89
  prompt = @searching_prompt
107
- @rerender_all = true
108
90
  else
109
91
  prompt = @prompt
110
92
  end
111
- if simplified_rendering? && !force_recalc
112
- mode_string = check_mode_string
113
- prompt = mode_string + prompt if mode_string
114
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
115
- end
116
93
  if @prompt_proc
117
- use_cached_prompt_list = false
118
- if @cached_prompt_list
119
- if @just_cursor_moving
120
- use_cached_prompt_list = true
121
- elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
122
- use_cached_prompt_list = true
123
- end
124
- end
125
- use_cached_prompt_list = false if @rerender_all
126
- if use_cached_prompt_list
127
- prompt_list = @cached_prompt_list
128
- else
129
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
130
- @prompt_cache_time = Time.now.to_f
131
- end
94
+ prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
132
95
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
133
96
  prompt_list = [prompt] if prompt_list.empty?
134
97
  mode_string = check_mode_string
@@ -141,20 +104,17 @@ class Reline::LineEditor
141
104
  prompt_list << prompt_list.last
142
105
  end
143
106
  end
144
- prompt_width = calculate_width(prompt, true)
145
- [prompt, prompt_width, prompt_list]
107
+ prompt_list
146
108
  else
147
109
  mode_string = check_mode_string
148
110
  prompt = mode_string + prompt if mode_string
149
- prompt_width = calculate_width(prompt, true)
150
- [prompt, prompt_width, nil]
111
+ [prompt] * buffer.size
151
112
  end
152
113
  end
153
114
 
154
115
  def reset(prompt = '', encoding:)
155
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
116
+ @cursor_base_y = Reline::IOGate.cursor_pos.y
156
117
  @screen_size = Reline::IOGate.get_screen_size
157
- @screen_height = @screen_size.first
158
118
  reset_variables(prompt, encoding: encoding)
159
119
  Reline::IOGate.set_winch_handler do
160
120
  @resized = true
@@ -184,54 +144,28 @@ class Reline::LineEditor
184
144
 
185
145
  def resize
186
146
  return unless @resized
187
- @resized = false
188
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
189
- old_screen_size = @screen_size
147
+
148
+ Reline::IOGate.hide_cursor
190
149
  @screen_size = Reline::IOGate.get_screen_size
191
- @screen_height = @screen_size.first
192
- if old_screen_size.last < @screen_size.last # columns increase
193
- @rerender_all = true
194
- rerender
195
- else
196
- back = 0
197
- new_buffer = whole_lines
198
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
199
- new_buffer.each_with_index do |line, index|
200
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
201
- width = prompt_width + calculate_width(line)
202
- height = calculate_height_by_width(width)
203
- back += height
204
- end
205
- @highest_in_all = back
206
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
207
- @first_line_started_from =
208
- if @line_index.zero?
209
- 0
210
- else
211
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
212
- end
213
- if @prompt_proc
214
- prompt = prompt_list[@line_index]
215
- prompt_width = calculate_width(prompt, true)
216
- end
217
- calculate_nearest_cursor
218
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
219
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
220
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
221
- @rerender_all = true
222
- end
150
+ @resized = false
151
+ scroll_into_view
152
+ Reline::IOGate.move_cursor_up @cursor_y
153
+ @cursor_base_y = Reline::IOGate.cursor_pos.y
154
+ @cursor_y = 0
155
+ @rendered_screen_cache = nil
156
+ render_differential
157
+ Reline::IOGate.show_cursor
223
158
  end
224
159
 
225
160
  def set_signal_handlers
226
161
  @old_trap = Signal.trap('INT') {
227
- clear_dialog(0)
228
- if @scroll_partial_screen
229
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
230
- else
231
- move_cursor_down(@highest_in_all - @line_index - 1)
232
- end
233
- Reline::IOGate.move_cursor_column(0)
234
- scroll_down(1)
162
+ Reline::IOGate.hide_cursor
163
+ clear_dialogs
164
+ scrolldown = render_differential
165
+ Reline::IOGate.scroll_down scrolldown
166
+ Reline::IOGate.move_cursor_column 0
167
+ @rendered_screen_cache = nil
168
+ Reline::IOGate.show_cursor
235
169
  case @old_trap
236
170
  when 'DEFAULT', 'SYSTEM_DEFAULT'
237
171
  raise Interrupt
@@ -260,7 +194,6 @@ class Reline::LineEditor
260
194
  @is_multiline = false
261
195
  @finished = false
262
196
  @cleared = false
263
- @rerender_all = false
264
197
  @history_pointer = nil
265
198
  @kill_ring ||= Reline::KillRing.new
266
199
  @vi_clipboard = ''
@@ -275,40 +208,27 @@ class Reline::LineEditor
275
208
  @first_prompt = true
276
209
  @searching_prompt = nil
277
210
  @first_char = true
278
- @add_newline_to_end_of_buffer = false
279
- @just_cursor_moving = nil
280
- @cached_prompt_list = nil
281
- @prompt_cache_time = nil
282
211
  @eof = false
283
212
  @continuous_insertion_buffer = String.new(encoding: @encoding)
284
- @scroll_partial_screen = nil
285
- @prev_mode_string = nil
213
+ @scroll_partial_screen = 0
286
214
  @drop_terminate_spaces = false
287
215
  @in_pasting = false
288
216
  @auto_indent_proc = nil
289
217
  @dialogs = []
290
- @previous_rendered_dialog_y = 0
291
- @last_key = nil
292
218
  @resized = false
219
+ @cursor_y = 0
220
+ @cache = {}
221
+ @rendered_screen_cache = nil
293
222
  reset_line
294
223
  end
295
224
 
296
225
  def reset_line
297
- @cursor = 0
298
- @cursor_max = 0
299
226
  @byte_pointer = 0
300
227
  @buffer_of_lines = [String.new(encoding: @encoding)]
301
228
  @line_index = 0
302
- @previous_line_index = nil
303
- @line = @buffer_of_lines[0]
304
- @first_line_started_from = 0
305
- @move_up = 0
306
- @started_from = 0
307
- @highest_in_this = 1
308
- @highest_in_all = 1
229
+ @cache.clear
309
230
  @line_backup_in_history = nil
310
231
  @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
311
- @check_new_auto_indent = false
312
232
  end
313
233
 
314
234
  def multiline_on
@@ -319,26 +239,27 @@ class Reline::LineEditor
319
239
  @is_multiline = false
320
240
  end
321
241
 
322
- private def calculate_height_by_lines(lines, prompt)
323
- result = 0
324
- prompt_list = prompt.is_a?(Array) ? prompt : nil
325
- lines.each_with_index { |line, i|
326
- prompt = prompt_list[i] if prompt_list and prompt_list[i]
327
- result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
328
- }
329
- result
330
- end
331
-
332
242
  private def insert_new_line(cursor_line, next_line)
333
- @line = cursor_line
334
243
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
335
- @previous_line_index = @line_index
244
+ @buffer_of_lines[@line_index] = cursor_line
336
245
  @line_index += 1
337
- @just_cursor_moving = false
338
- end
339
-
340
- private def calculate_height_by_width(width)
341
- width.div(@screen_size.last) + 1
246
+ @byte_pointer = 0
247
+ if @auto_indent_proc && !@in_pasting
248
+ if next_line.empty?
249
+ (
250
+ # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
251
+ indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
252
+ indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
253
+ indent = indent2 || indent1
254
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
255
+ )
256
+ process_auto_indent @line_index, add_newline: true
257
+ else
258
+ process_auto_indent @line_index - 1, cursor_dependent: false
259
+ process_auto_indent @line_index, add_newline: true # Need for compatibility
260
+ process_auto_indent @line_index, cursor_dependent: false
261
+ end
262
+ end
342
263
  end
343
264
 
344
265
  private def split_by_width(str, max_width)
@@ -346,41 +267,30 @@ class Reline::LineEditor
346
267
  end
347
268
 
348
269
  private def scroll_down(val)
349
- if val <= @rest_height
270
+ if @cursor_base_y + @cursor_y + val < screen_height
350
271
  Reline::IOGate.move_cursor_down(val)
351
- @rest_height -= val
272
+ @cursor_y += val
352
273
  else
353
- Reline::IOGate.move_cursor_down(@rest_height)
354
- Reline::IOGate.scroll_down(val - @rest_height)
355
- @rest_height = 0
356
- end
357
- end
358
-
359
- private def move_cursor_up(val)
360
- if val > 0
361
- Reline::IOGate.move_cursor_up(val)
362
- @rest_height += val
363
- elsif val < 0
364
- move_cursor_down(-val)
274
+ move = screen_height - @cursor_base_y - @cursor_y - 1
275
+ scroll = val - move
276
+ Reline::IOGate.scroll_down(move)
277
+ Reline::IOGate.scroll_down(scroll)
278
+ @cursor_y += move
279
+ @cursor_base_y = [@cursor_base_y - scroll, 0].max
365
280
  end
366
281
  end
367
282
 
368
- private def move_cursor_down(val)
369
- if val > 0
370
- Reline::IOGate.move_cursor_down(val)
371
- @rest_height -= val
372
- @rest_height = 0 if @rest_height < 0
373
- elsif val < 0
374
- move_cursor_up(-val)
375
- end
283
+ def current_byte_pointer_cursor
284
+ calculate_width(current_line.byteslice(0, @byte_pointer))
376
285
  end
377
286
 
378
- private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
287
+ private def calculate_nearest_cursor(cursor)
288
+ line_to_calc = current_line
379
289
  new_cursor_max = calculate_width(line_to_calc)
380
290
  new_cursor = 0
381
291
  new_byte_pointer = 0
382
292
  height = 1
383
- max_width = @screen_size.last
293
+ max_width = screen_width
384
294
  if @config.editing_mode_is?(:vi_command)
385
295
  last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
386
296
  if last_byte_size > 0
@@ -406,110 +316,246 @@ class Reline::LineEditor
406
316
  end
407
317
  new_byte_pointer += gc.bytesize
408
318
  end
409
- new_started_from = height - 1
410
- if update
411
- @cursor = new_cursor
412
- @cursor_max = new_cursor_max
413
- @started_from = new_started_from
414
- @byte_pointer = new_byte_pointer
415
- else
416
- [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
417
- end
319
+ @byte_pointer = new_byte_pointer
418
320
  end
419
321
 
420
- def rerender_all
421
- @rerender_all = true
422
- process_insert(force: true)
423
- rerender
322
+ def with_cache(key, *deps)
323
+ cached_deps, value = @cache[key]
324
+ if cached_deps != deps
325
+ @cache[key] = [deps, value = yield(*deps, cached_deps, value)]
326
+ end
327
+ value
424
328
  end
425
329
 
426
- def rerender
427
- return if @line.nil?
428
- if @menu_info
429
- scroll_down(@highest_in_all - @first_line_started_from)
430
- @rerender_all = true
330
+ def modified_lines
331
+ with_cache(__method__, whole_lines, finished?) do |whole, complete|
332
+ modify_lines(whole, complete)
431
333
  end
432
- if @menu_info
433
- show_menu
434
- @menu_info = nil
435
- end
436
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
437
- cursor_column = (prompt_width + @cursor) % @screen_size.last
438
- if @cleared
439
- clear_screen_buffer(prompt, prompt_list, prompt_width)
440
- @cleared = false
441
- return
334
+ end
335
+
336
+ def prompt_list
337
+ with_cache(__method__, whole_lines, @vi_arg, @searching_prompt) do |lines|
338
+ check_multiline_prompt(lines)
442
339
  end
443
- if @is_multiline and finished? and @scroll_partial_screen
444
- # Re-output all code higher than the screen when finished.
445
- Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
446
- Reline::IOGate.move_cursor_column(0)
447
- @scroll_partial_screen = nil
448
- new_lines = whole_lines
449
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
450
- modify_lines(new_lines).each_with_index do |line, index|
451
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
452
- Reline::IOGate.erase_after_cursor
340
+ end
341
+
342
+ def screen_height
343
+ @screen_size.first
344
+ end
345
+
346
+ def screen_width
347
+ @screen_size.last
348
+ end
349
+
350
+ def screen_scroll_top
351
+ @scroll_partial_screen
352
+ end
353
+
354
+ def wrapped_lines
355
+ with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
356
+ prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
357
+ cached_wraps = {}
358
+ if prev_width == width
359
+ prev_n.times do |i|
360
+ cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
361
+ end
362
+ end
363
+
364
+ n.times.map do |i|
365
+ prompt = prompts[i]
366
+ line = lines[i]
367
+ cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact
453
368
  end
454
- @output.flush
455
- clear_dialog(cursor_column)
456
- return
457
369
  end
458
- new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
459
- rendered = false
460
- if @add_newline_to_end_of_buffer
461
- clear_dialog_with_trap_key(cursor_column)
462
- rerender_added_newline(prompt, prompt_width, prompt_list)
463
- @add_newline_to_end_of_buffer = false
464
- else
465
- if @just_cursor_moving and not @rerender_all
466
- clear_dialog_with_trap_key(cursor_column)
467
- rendered = just_move_cursor
468
- @just_cursor_moving = false
469
- return
470
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
471
- clear_dialog_with_trap_key(cursor_column)
472
- rerender_changed_current_line
473
- @previous_line_index = nil
474
- rendered = true
475
- elsif @rerender_all
476
- rerender_all_lines
477
- @rerender_all = false
478
- rendered = true
370
+ end
371
+
372
+ def calculate_overlay_levels(overlay_levels)
373
+ levels = []
374
+ overlay_levels.each do |x, w, l|
375
+ levels.fill(l, x, w)
376
+ end
377
+ levels
378
+ end
379
+
380
+ def render_line_differential(old_items, new_items)
381
+ old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact)
382
+ new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
383
+ base_x = 0
384
+ new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
385
+ width = chunk.size
386
+ if level == :skip
387
+ # do nothing
388
+ elsif level == :blank
389
+ Reline::IOGate.move_cursor_column base_x
390
+ @output.write "\e[0m#{' ' * width}"
479
391
  else
392
+ x, w, content = new_items[level]
393
+ content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
394
+ Reline::IOGate.move_cursor_column base_x
395
+ @output.write "\e[0m#{content}\e[0m"
480
396
  end
397
+ base_x += width
481
398
  end
482
- if @is_multiline
483
- if finished?
484
- # Always rerender on finish because output_modifier_proc may return a different output.
485
- new_lines = whole_lines
486
- line = modify_lines(new_lines)[@line_index]
487
- clear_dialog(cursor_column)
488
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
489
- render_partial(prompt, prompt_width, line, @first_line_started_from)
490
- move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
491
- scroll_down(1)
492
- Reline::IOGate.move_cursor_column(0)
493
- Reline::IOGate.erase_after_cursor
494
- else
495
- if not rendered and not @in_pasting
496
- line = modify_lines(whole_lines)[@line_index]
497
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
498
- render_partial(prompt, prompt_width, line, @first_line_started_from)
499
- end
500
- render_dialog(cursor_column)
399
+ if old_levels.size > new_levels.size
400
+ Reline::IOGate.move_cursor_column new_levels.size
401
+ Reline::IOGate.erase_after_cursor
402
+ end
403
+ end
404
+
405
+ def editor_cursor_position
406
+ line = ' ' * calculate_width(prompt_list[@line_index], true) + whole_lines[@line_index].byteslice(0, @byte_pointer)
407
+ wrapped_line_before_cursor = split_by_width(line, screen_width).first.compact
408
+ editor_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
409
+ editor_cursor_x = calculate_width(wrapped_line_before_cursor.last)
410
+ [editor_cursor_x, editor_cursor_y]
411
+ end
412
+
413
+ def clear_dialogs
414
+ @dialogs.each do |dialog|
415
+ dialog.contents = nil
416
+ dialog.trap_key = nil
417
+ end
418
+ end
419
+
420
+ def update_dialogs(key = nil)
421
+ @dialog_initialzed = true
422
+ editor_cursor_x, editor_cursor_y = editor_cursor_position
423
+ @dialogs.each do |dialog|
424
+ dialog.trap_key = nil
425
+ update_each_dialog(dialog, editor_cursor_x, editor_cursor_y - screen_scroll_top, key)
426
+ end
427
+ end
428
+
429
+ def clear_rendered_lines
430
+ Reline::IOGate.move_cursor_up @cursor_y
431
+ Reline::IOGate.move_cursor_column 0
432
+
433
+ num_lines = @rendered_screen_cache&.size
434
+ return unless num_lines && num_lines >= 1
435
+
436
+ Reline::IOGate.move_cursor_down num_lines - 1
437
+ (num_lines - 1).times do
438
+ Reline::IOGate.erase_after_cursor
439
+ Reline::IOGate.move_cursor_up 1
440
+ end
441
+ Reline::IOGate.erase_after_cursor
442
+ @rendered_screen_cache = nil
443
+ end
444
+
445
+ def render_full_content
446
+ lines = @buffer_of_lines.size.times.map do |i|
447
+ line = prompt_list[i] + modified_lines[i]
448
+ wrapped_lines, = split_by_width(line, screen_width)
449
+ wrapped_lines.last.empty? ? "#{line} " : line
450
+ end
451
+ @output.puts lines.map { |l| "#{l}\r\n" }.join
452
+ end
453
+
454
+ def print_nomultiline_prompt(prompt)
455
+ return unless prompt && !@is_multiline
456
+
457
+ # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
458
+ @rendered_screen_cache = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
459
+ @output.write prompt
460
+ end
461
+
462
+ def render_differential
463
+ unless @dialog_initialzed
464
+ update_dialogs
465
+ end
466
+
467
+ editor_cursor_x, editor_cursor_y = editor_cursor_position
468
+
469
+ rendered_lines = @rendered_screen_cache || []
470
+ new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
471
+ [[0, Reline::Unicode.calculate_width(l, true), l]]
472
+ end
473
+ if @menu_info
474
+ @menu_info.list.sort!.each do |item|
475
+ new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
501
476
  end
502
- @buffer_of_lines[@line_index] = @line
503
- @rest_height = 0 if @scroll_partial_screen
504
- else
505
- line = modify_lines(whole_lines)[@line_index]
506
- render_partial(prompt, prompt_width, line, 0)
507
- if finished?
508
- scroll_down(1)
509
- Reline::IOGate.move_cursor_column(0)
477
+ @menu_info = nil # TODO: do not change state here
478
+ end
479
+
480
+ @dialogs.each_with_index do |dialog, index|
481
+ next unless dialog.contents
482
+
483
+ x_range, y_range = dialog_range dialog, editor_cursor_y - screen_scroll_top
484
+ y_range.each do |row|
485
+ next if row < 0 || row >= screen_height
486
+ dialog_rows = new_lines[row] ||= []
487
+ dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
488
+ end
489
+ end
490
+
491
+ num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
492
+ scroll_down(num_lines - 1 - @cursor_y) if (num_lines - 1 - @cursor_y) > 0
493
+ @cursor_y = num_lines - 1
494
+ num_lines.times do |i|
495
+ rendered_line = rendered_lines[i] || []
496
+ line_to_render = new_lines[i] || []
497
+ next if rendered_line == line_to_render
498
+
499
+ Reline::IOGate.move_cursor_down i - @cursor_y
500
+ @cursor_y = i
501
+ unless rendered_lines[i]
502
+ Reline::IOGate.move_cursor_column 0
510
503
  Reline::IOGate.erase_after_cursor
511
504
  end
505
+ render_line_differential(rendered_line, line_to_render)
512
506
  end
507
+ @rendered_screen_cache = new_lines
508
+ y = editor_cursor_y - screen_scroll_top
509
+ Reline::IOGate.move_cursor_column editor_cursor_x
510
+ Reline::IOGate.move_cursor_down y - @cursor_y
511
+ @cursor_y = y
512
+ new_lines.size - @cursor_y
513
+ end
514
+
515
+ def current_row
516
+ wrapped_lines.flatten[editor_cursor_y]
517
+ end
518
+
519
+ def upper_space_height
520
+ @cursor_y - 1
521
+ end
522
+
523
+ def rest_height
524
+ screen_height - @cursor_y - @cursor_base_y - 1
525
+ end
526
+
527
+ def rerender_all
528
+ Reline::IOGate.hide_cursor
529
+ process_insert(force: true)
530
+ handle_cleared
531
+ render_differential unless @in_pasting
532
+ Reline::IOGate.show_cursor
533
+ end
534
+
535
+ def handle_cleared
536
+ return unless @cleared
537
+
538
+ @cleared = false
539
+ @rendered_screen_cache = nil
540
+ Reline::IOGate.clear_screen
541
+ @screen_size = Reline::IOGate.get_screen_size
542
+ @cursor_base_y = 0
543
+ @cursor_y = 0
544
+ scroll_into_view
545
+ render_differential
546
+ end
547
+
548
+ def rerender
549
+ Reline::IOGate.hide_cursor
550
+ finished = finished?
551
+ handle_cleared
552
+ if finished
553
+ clear_rendered_lines
554
+ render_full_content
555
+ elsif !@in_pasting
556
+ render_differential
557
+ end
558
+ Reline::IOGate.show_cursor
513
559
  end
514
560
 
515
561
  class DialogProcScope
@@ -563,17 +609,16 @@ class Reline::LineEditor
563
609
  end
564
610
 
565
611
  def screen_width
566
- @line_editor.instance_variable_get(:@screen_size).last
612
+ @line_editor.screen_width
567
613
  end
568
614
 
569
615
  def screen_height
570
- @line_editor.instance_variable_get(:@screen_size).first
616
+ @line_editor.screen_height
571
617
  end
572
618
 
573
619
  def preferred_dialog_height
574
- rest_height = @line_editor.instance_variable_get(:@rest_height)
575
- scroll_partial_screen = @line_editor.instance_variable_get(:@scroll_partial_screen) || 0
576
- [cursor_pos.y - scroll_partial_screen, rest_height, (screen_height + 6) / 5].max
620
+ _editor_cursor_x, editor_cursor_y = @line_editor.editor_cursor_position
621
+ [editor_cursor_y - @line_editor.screen_scroll_top, @line_editor.rest_height, (screen_height + 6) / 5].max
577
622
  end
578
623
 
579
624
  def completion_journey_data
@@ -646,14 +691,6 @@ class Reline::LineEditor
646
691
  end
647
692
 
648
693
  DIALOG_DEFAULT_HEIGHT = 20
649
- private def render_dialog(cursor_column)
650
- changes = @dialogs.map do |dialog|
651
- old_dialog = dialog.dup
652
- update_each_dialog(dialog, cursor_column)
653
- [old_dialog, dialog]
654
- end
655
- render_dialog_changes(changes, cursor_column)
656
- end
657
694
 
658
695
  private def padding_space_with_escape_sequences(str, width)
659
696
  padding_width = width - calculate_width(str, true)
@@ -662,118 +699,15 @@ class Reline::LineEditor
662
699
  str + (' ' * padding_width)
663
700
  end
664
701
 
665
- private def range_subtract(base_ranges, subtract_ranges)
666
- indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a)
667
- chunks = indices.chunk_while { |a, b| a + 1 == b }
668
- chunks.map { |a| a.first...a.last + 1 }
669
- end
670
-
671
702
  private def dialog_range(dialog, dialog_y)
672
703
  x_range = dialog.column...dialog.column + dialog.width
673
704
  y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
674
705
  [x_range, y_range]
675
706
  end
676
707
 
677
- private def render_dialog_changes(changes, cursor_column)
678
- # Collect x-coordinate range and content of previous and current dialogs for each line
679
- old_dialog_ranges = {}
680
- new_dialog_ranges = {}
681
- new_dialog_contents = {}
682
- changes.each do |old_dialog, new_dialog|
683
- if old_dialog.contents
684
- x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y)
685
- y_range.each do |y|
686
- (old_dialog_ranges[y] ||= []) << x_range
687
- end
688
- end
689
- if new_dialog.contents
690
- x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from)
691
- y_range.each do |y|
692
- (new_dialog_ranges[y] ||= []) << x_range
693
- (new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]]
694
- end
695
- end
696
- end
697
- return if old_dialog_ranges.empty? && new_dialog_ranges.empty?
698
-
699
- # Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line
700
- ranges_to_restore = {}
701
- subtract_cache = {}
702
- old_dialog_ranges.each do |y, old_x_ranges|
703
- new_x_ranges = new_dialog_ranges[y] || []
704
- ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges)
705
- ranges_to_restore[y] = ranges if ranges.any?
706
- end
707
-
708
- # Create visual_lines for restoring text hidden behind dialogs
709
- if ranges_to_restore.any?
710
- lines = whole_lines
711
- prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true)
712
- modified_lines = modify_lines(lines, force_recalc: true)
713
- visual_lines = []
714
- modified_lines.each_with_index { |l, i|
715
- pr = prompt_list ? prompt_list[i] : prompt
716
- vl, = split_by_width(pr + l, @screen_size.last)
717
- vl.compact!
718
- visual_lines.concat(vl)
719
- }
720
- end
721
-
722
- # Clear and rerender all dialogs line by line
723
- Reline::IOGate.hide_cursor
724
- ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax
725
- scroll_partial_screen = @scroll_partial_screen || 0
726
- screen_y_range = scroll_partial_screen..(scroll_partial_screen + @screen_height - 1)
727
- ymin = ymin.clamp(screen_y_range.begin, screen_y_range.end)
728
- ymax = ymax.clamp(screen_y_range.begin, screen_y_range.end)
729
- dialog_y = @first_line_started_from + @started_from
730
- cursor_y = dialog_y
731
- if @highest_in_all <= ymax
732
- scroll_down(ymax - cursor_y)
733
- move_cursor_up(ymax - cursor_y)
734
- end
735
- (ymin..ymax).each do |y|
736
- move_cursor_down(y - cursor_y)
737
- cursor_y = y
738
- new_x_ranges = new_dialog_ranges[y]
739
- restore_ranges = ranges_to_restore[y]
740
- # Restore text that was hidden behind dialogs
741
- if restore_ranges
742
- line = visual_lines[y] || ''
743
- restore_ranges.each do |range|
744
- col = range.begin
745
- width = range.end - range.begin
746
- s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width)
747
- Reline::IOGate.move_cursor_column(col)
748
- @output.write "\e[0m#{s}\e[0m"
749
- end
750
- max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max
751
- if max_column < restore_ranges.map(&:end).max
752
- Reline::IOGate.move_cursor_column(max_column)
753
- Reline::IOGate.erase_after_cursor
754
- end
755
- end
756
- # Render dialog contents
757
- new_dialog_contents[y]&.each do |x_range, content|
758
- Reline::IOGate.move_cursor_column(x_range.begin)
759
- @output.write "\e[0m#{content}\e[0m"
760
- end
761
- end
762
- move_cursor_up(cursor_y - dialog_y)
763
- Reline::IOGate.move_cursor_column(cursor_column)
764
- Reline::IOGate.show_cursor
765
-
766
- @previous_rendered_dialog_y = dialog_y
767
- end
768
-
769
- private def update_each_dialog(dialog, cursor_column)
770
- if @in_pasting
771
- dialog.contents = nil
772
- dialog.trap_key = nil
773
- return
774
- end
775
- dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
776
- dialog_render_info = dialog.call(@last_key)
708
+ private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil)
709
+ dialog.set_cursor_pos(cursor_column, cursor_row)
710
+ dialog_render_info = dialog.call(key)
777
711
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
778
712
  dialog.contents = nil
779
713
  dialog.trap_key = nil
@@ -813,14 +747,14 @@ class Reline::LineEditor
813
747
  else
814
748
  scrollbar_pos = nil
815
749
  end
816
- upper_space = @first_line_started_from - @started_from
750
+ upper_space = upper_space_height
817
751
  dialog.column = dialog_render_info.pos.x
818
752
  dialog.width += @block_elem_width if scrollbar_pos
819
- diff = (dialog.column + dialog.width) - (@screen_size.last)
753
+ diff = (dialog.column + dialog.width) - screen_width
820
754
  if diff > 0
821
755
  dialog.column -= diff
822
756
  end
823
- if (@rest_height - dialog_render_info.pos.y) >= height
757
+ if (rest_height - dialog_render_info.pos.y) >= height
824
758
  dialog.vertical_offset = dialog_render_info.pos.y + 1
825
759
  elsif upper_space >= height
826
760
  dialog.vertical_offset = dialog_render_info.pos.y - height
@@ -829,7 +763,7 @@ class Reline::LineEditor
829
763
  end
830
764
  if dialog.column < 0
831
765
  dialog.column = 0
832
- dialog.width = @screen_size.last
766
+ dialog.width = screen_width
833
767
  end
834
768
  face = Reline::Face[dialog_render_info.face || :default]
835
769
  scrollbar_sgr = face[:scrollbar]
@@ -856,373 +790,14 @@ class Reline::LineEditor
856
790
  end
857
791
  end
858
792
 
859
- private def clear_dialog(cursor_column)
860
- changes = @dialogs.map do |dialog|
861
- old_dialog = dialog.dup
862
- dialog.contents = nil
863
- [old_dialog, dialog]
864
- end
865
- render_dialog_changes(changes, cursor_column)
866
- end
867
-
868
- private def clear_dialog_with_trap_key(cursor_column)
869
- clear_dialog(cursor_column)
870
- @dialogs.each do |dialog|
871
- dialog.trap_key = nil
872
- end
873
- end
874
-
875
- private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
876
- if @screen_height < highest_in_all
877
- old_scroll_partial_screen = @scroll_partial_screen
878
- if cursor_y == 0
879
- @scroll_partial_screen = 0
880
- elsif cursor_y == (highest_in_all - 1)
881
- @scroll_partial_screen = highest_in_all - @screen_height
882
- else
883
- if @scroll_partial_screen
884
- if cursor_y <= @scroll_partial_screen
885
- @scroll_partial_screen = cursor_y
886
- elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
887
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
888
- end
889
- else
890
- if cursor_y > (@screen_height - 1)
891
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
892
- else
893
- @scroll_partial_screen = 0
894
- end
895
- end
896
- end
897
- if @scroll_partial_screen != old_scroll_partial_screen
898
- @rerender_all = true
899
- end
900
- else
901
- if @scroll_partial_screen
902
- @rerender_all = true
903
- end
904
- @scroll_partial_screen = nil
905
- end
906
- end
907
-
908
- private def rerender_added_newline(prompt, prompt_width, prompt_list)
909
- @buffer_of_lines[@previous_line_index] = @line
910
- @line = @buffer_of_lines[@line_index]
911
- @previous_line_index = nil
912
- if @in_pasting
913
- scroll_down(1)
914
- else
915
- lines = whole_lines
916
- prev_line_prompt = @prompt_proc ? prompt_list[@line_index - 1] : prompt
917
- prev_line_prompt_width = @prompt_proc ? calculate_width(prev_line_prompt, true) : prompt_width
918
- prev_line = modify_lines(lines)[@line_index - 1]
919
- move_cursor_up(@started_from)
920
- render_partial(prev_line_prompt, prev_line_prompt_width, prev_line, @first_line_started_from + @started_from, with_control: false)
921
- scroll_down(1)
922
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
923
- end
924
- @cursor = @cursor_max = calculate_width(@line)
925
- @byte_pointer = @line.bytesize
926
- @highest_in_all += @highest_in_this
927
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
928
- @first_line_started_from += @started_from + 1
929
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
930
- end
931
-
932
- def just_move_cursor
933
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
934
- move_cursor_up(@started_from)
935
- new_first_line_started_from =
936
- if @line_index.zero?
937
- 0
938
- else
939
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
940
- end
941
- first_line_diff = new_first_line_started_from - @first_line_started_from
942
- @cursor, @cursor_max, _, @byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
943
- new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
944
- calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
945
- @previous_line_index = nil
946
- @line = @buffer_of_lines[@line_index]
947
- if @rerender_all
948
- rerender_all_lines
949
- @rerender_all = false
950
- true
951
- else
952
- @first_line_started_from = new_first_line_started_from
953
- @started_from = new_started_from
954
- move_cursor_down(first_line_diff + @started_from)
955
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
956
- false
957
- end
958
- end
959
-
960
- private def rerender_changed_current_line
961
- new_lines = whole_lines
962
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
963
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
964
- diff = all_height - @highest_in_all
965
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
966
- if diff > 0
967
- scroll_down(diff)
968
- move_cursor_up(all_height - 1)
969
- elsif diff < 0
970
- (-diff).times do
971
- Reline::IOGate.move_cursor_column(0)
972
- Reline::IOGate.erase_after_cursor
973
- move_cursor_up(1)
974
- end
975
- move_cursor_up(all_height - 1)
976
- else
977
- move_cursor_up(all_height - 1)
978
- end
979
- @highest_in_all = all_height
980
- back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
981
- move_cursor_up(back)
982
- if @previous_line_index
983
- @buffer_of_lines[@previous_line_index] = @line
984
- @line = @buffer_of_lines[@line_index]
985
- end
986
- @first_line_started_from =
987
- if @line_index.zero?
988
- 0
989
- else
990
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
991
- end
992
- if @prompt_proc
993
- prompt = prompt_list[@line_index]
994
- prompt_width = calculate_width(prompt, true)
995
- end
996
- move_cursor_down(@first_line_started_from)
997
- calculate_nearest_cursor
998
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
999
- move_cursor_down(@started_from)
1000
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1001
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
1002
- end
1003
-
1004
- private def rerender_all_lines
1005
- move_cursor_up(@first_line_started_from + @started_from)
1006
- Reline::IOGate.move_cursor_column(0)
1007
- back = 0
1008
- new_buffer = whole_lines
1009
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
1010
- new_buffer.each_with_index do |line, index|
1011
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
1012
- width = prompt_width + calculate_width(line)
1013
- height = calculate_height_by_width(width)
1014
- back += height
1015
- end
1016
- old_highest_in_all = @highest_in_all
1017
- if @line_index.zero?
1018
- new_first_line_started_from = 0
1019
- else
1020
- new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
1021
- end
1022
- new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1023
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
1024
- if @scroll_partial_screen
1025
- move_cursor_up(@first_line_started_from + @started_from)
1026
- scroll_down(@screen_height - 1)
1027
- move_cursor_up(@screen_height)
1028
- Reline::IOGate.move_cursor_column(0)
1029
- elsif back > old_highest_in_all
1030
- scroll_down(back - 1)
1031
- move_cursor_up(back - 1)
1032
- elsif back < old_highest_in_all
1033
- scroll_down(back)
1034
- Reline::IOGate.erase_after_cursor
1035
- (old_highest_in_all - back - 1).times do
1036
- scroll_down(1)
1037
- Reline::IOGate.erase_after_cursor
1038
- end
1039
- move_cursor_up(old_highest_in_all - 1)
1040
- end
1041
- render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
1042
- if @prompt_proc
1043
- prompt = prompt_list[@line_index]
1044
- prompt_width = calculate_width(prompt, true)
1045
- end
1046
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
1047
- @highest_in_all = back
1048
- @first_line_started_from = new_first_line_started_from
1049
- @started_from = new_started_from
1050
- if @scroll_partial_screen
1051
- Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
1052
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1053
- else
1054
- move_cursor_down(@first_line_started_from + @started_from - back + 1)
1055
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1056
- end
1057
- end
1058
-
1059
- private def render_whole_lines(lines, prompt, prompt_width)
1060
- rendered_height = 0
1061
- modify_lines(lines).each_with_index do |line, index|
1062
- if prompt.is_a?(Array)
1063
- line_prompt = prompt[index]
1064
- prompt_width = calculate_width(line_prompt, true)
1065
- else
1066
- line_prompt = prompt
1067
- end
1068
- height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
1069
- if index < (lines.size - 1)
1070
- if @scroll_partial_screen
1071
- if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
1072
- move_cursor_down(1)
1073
- end
1074
- else
1075
- scroll_down(1)
1076
- end
1077
- rendered_height += height
1078
- else
1079
- rendered_height += height - 1
1080
- end
1081
- end
1082
- rendered_height
1083
- end
1084
-
1085
- private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
1086
- visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
1087
- cursor_up_from_last_line = 0
1088
- if @scroll_partial_screen
1089
- last_visual_line = this_started_from + (height - 1)
1090
- last_screen_line = @scroll_partial_screen + (@screen_height - 1)
1091
- if (@scroll_partial_screen - this_started_from) >= height
1092
- # Render nothing because this line is before the screen.
1093
- visual_lines = []
1094
- elsif this_started_from > last_screen_line
1095
- # Render nothing because this line is after the screen.
1096
- visual_lines = []
1097
- else
1098
- deleted_lines_before_screen = []
1099
- if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
1100
- # A part of visual lines are before the screen.
1101
- deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
1102
- deleted_lines_before_screen.compact!
1103
- end
1104
- if this_started_from <= last_screen_line and last_screen_line < last_visual_line
1105
- # A part of visual lines are after the screen.
1106
- visual_lines.pop((last_visual_line - last_screen_line) * 2)
1107
- end
1108
- move_cursor_up(deleted_lines_before_screen.size - @started_from)
1109
- cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
1110
- end
1111
- end
1112
- if with_control
1113
- if height > @highest_in_this
1114
- diff = height - @highest_in_this
1115
- scroll_down(diff)
1116
- @highest_in_all += diff
1117
- @highest_in_this = height
1118
- move_cursor_up(diff)
1119
- elsif height < @highest_in_this
1120
- diff = @highest_in_this - height
1121
- @highest_in_all -= diff
1122
- @highest_in_this = height
1123
- end
1124
- move_cursor_up(@started_from)
1125
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1126
- cursor_up_from_last_line = height - 1 - @started_from
1127
- end
1128
- if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
1129
- @output.write "\e[0m" # clear character decorations
1130
- end
1131
- visual_lines.each_with_index do |line, index|
1132
- Reline::IOGate.move_cursor_column(0)
1133
- if line.nil?
1134
- if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
1135
- # reaches the end of line
1136
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
1137
- # A newline is automatically inserted if a character is rendered at
1138
- # eol on command prompt.
1139
- else
1140
- # When the cursor is at the end of the line and erases characters
1141
- # after the cursor, some terminals delete the character at the
1142
- # cursor position.
1143
- move_cursor_down(1)
1144
- Reline::IOGate.move_cursor_column(0)
1145
- end
1146
- else
1147
- Reline::IOGate.erase_after_cursor
1148
- move_cursor_down(1)
1149
- Reline::IOGate.move_cursor_column(0)
1150
- end
1151
- next
1152
- end
1153
- @output.write line
1154
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
1155
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
1156
- @rest_height -= 1 if @rest_height > 0
1157
- end
1158
- @output.flush
1159
- if @first_prompt
1160
- @first_prompt = false
1161
- @pre_input_hook&.call
1162
- end
1163
- end
1164
- unless visual_lines.empty?
1165
- Reline::IOGate.erase_after_cursor
1166
- Reline::IOGate.move_cursor_column(0)
1167
- end
1168
- if with_control
1169
- # Just after rendring, so the cursor is on the last line.
1170
- if finished?
1171
- Reline::IOGate.move_cursor_column(0)
1172
- else
1173
- # Moves up from bottom of lines to the cursor position.
1174
- move_cursor_up(cursor_up_from_last_line)
1175
- # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
1176
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1177
- end
1178
- end
1179
- height
1180
- end
1181
-
1182
- private def modify_lines(before, force_recalc: false)
1183
- return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?)
1184
-
1185
- if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
793
+ private def modify_lines(before, complete)
794
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
1186
795
  after.lines("\n").map { |l| l.chomp('') }
1187
796
  else
1188
797
  before
1189
798
  end
1190
799
  end
1191
800
 
1192
- private def show_menu
1193
- scroll_down(@highest_in_all - @first_line_started_from)
1194
- @rerender_all = true
1195
- @menu_info.list.sort!.each do |item|
1196
- Reline::IOGate.move_cursor_column(0)
1197
- @output.write item
1198
- @output.flush
1199
- scroll_down(1)
1200
- end
1201
- scroll_down(@highest_in_all - 1)
1202
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
1203
- end
1204
-
1205
- private def clear_screen_buffer(prompt, prompt_list, prompt_width)
1206
- Reline::IOGate.clear_screen
1207
- back = 0
1208
- modify_lines(whole_lines).each_with_index do |line, index|
1209
- if @prompt_proc
1210
- pr = prompt_list[index]
1211
- height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
1212
- else
1213
- height = render_partial(prompt, prompt_width, line, back, with_control: false)
1214
- end
1215
- if index < (@buffer_of_lines.size - 1)
1216
- move_cursor_down(1)
1217
- back += height
1218
- end
1219
- end
1220
- move_cursor_up(back)
1221
- move_cursor_down(@first_line_started_from + @started_from)
1222
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
1223
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1224
- end
1225
-
1226
801
  def editing_mode
1227
802
  @config.editing_mode
1228
803
  end
@@ -1312,10 +887,8 @@ class Reline::LineEditor
1312
887
  @completion_state = CompletionState::MENU
1313
888
  end
1314
889
  if not just_show_list and target < completed
1315
- @line = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
1316
- line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
1317
- @cursor_max = calculate_width(@line)
1318
- @cursor = calculate_width(line_to_pointer)
890
+ @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
891
+ line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n").last || String.new(encoding: @encoding)
1319
892
  @byte_pointer = line_to_pointer.bytesize
1320
893
  end
1321
894
  end
@@ -1358,35 +931,31 @@ class Reline::LineEditor
1358
931
  end
1359
932
  end
1360
933
  completed = @completion_journey_data.list[@completion_journey_data.pointer]
1361
- new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
1362
- @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
1363
- line_to_pointer = (@completion_journey_data.preposing + completed).split("\n")[@line_index]
1364
- line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
1365
- @cursor_max = calculate_width(@line)
1366
- @cursor = calculate_width(line_to_pointer)
1367
- @byte_pointer = line_to_pointer.bytesize
934
+ line_to_pointer = (@completion_journey_data.preposing + completed).split("\n")[@line_index] || String.new(encoding: @encoding)
935
+ new_line = line_to_pointer + (@completion_journey_data.postposing.split("\n").first || '')
936
+ set_current_line(new_line, line_to_pointer.bytesize)
1368
937
  end
1369
938
 
1370
939
  private def run_for_operators(key, method_symbol, &block)
1371
940
  if @waiting_operator_proc
1372
941
  if VI_MOTIONS.include?(method_symbol)
1373
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
942
+ old_byte_pointer = @byte_pointer
1374
943
  @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
1375
944
  block.(true)
1376
945
  unless @waiting_proc
1377
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
1378
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
1379
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
946
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
947
+ @byte_pointer = old_byte_pointer
948
+ @waiting_operator_proc.(byte_pointer_diff)
1380
949
  else
1381
950
  old_waiting_proc = @waiting_proc
1382
951
  old_waiting_operator_proc = @waiting_operator_proc
1383
952
  current_waiting_operator_proc = @waiting_operator_proc
1384
953
  @waiting_proc = proc { |k|
1385
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
954
+ old_byte_pointer = @byte_pointer
1386
955
  old_waiting_proc.(k)
1387
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
1388
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
1389
- current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
956
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
957
+ @byte_pointer = old_byte_pointer
958
+ current_waiting_operator_proc.(byte_pointer_diff)
1390
959
  @waiting_operator_proc = old_waiting_operator_proc
1391
960
  }
1392
961
  end
@@ -1397,7 +966,6 @@ class Reline::LineEditor
1397
966
  @waiting_operator_proc = nil
1398
967
  @waiting_operator_vi_arg = nil
1399
968
  if @vi_arg
1400
- @rerender_all = true
1401
969
  @vi_arg = nil
1402
970
  end
1403
971
  else
@@ -1451,7 +1019,6 @@ class Reline::LineEditor
1451
1019
  end
1452
1020
  @kill_ring.process
1453
1021
  if @vi_arg
1454
- @rerender_al = true
1455
1022
  @vi_arg = nil
1456
1023
  end
1457
1024
  elsif @vi_arg
@@ -1471,7 +1038,6 @@ class Reline::LineEditor
1471
1038
  end
1472
1039
  @kill_ring.process
1473
1040
  if @vi_arg
1474
- @rerender_all = true
1475
1041
  @vi_arg = nil
1476
1042
  end
1477
1043
  end
@@ -1493,7 +1059,6 @@ class Reline::LineEditor
1493
1059
  end
1494
1060
 
1495
1061
  private def normal_char(key)
1496
- method_symbol = method_obj = nil
1497
1062
  if key.combined_char.is_a?(Symbol)
1498
1063
  process_key(key.combined_char, key.combined_char)
1499
1064
  return
@@ -1523,32 +1088,37 @@ class Reline::LineEditor
1523
1088
  end
1524
1089
  @multibyte_buffer.clear
1525
1090
  end
1526
- if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
1527
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1091
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1092
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1528
1093
  @byte_pointer -= byte_size
1529
- mbchar = @line.byteslice(@byte_pointer, byte_size)
1530
- width = Reline::Unicode.get_mbchar_width(mbchar)
1531
- @cursor -= width
1094
+ end
1095
+ end
1096
+
1097
+ def update(key)
1098
+ modified = input_key(key)
1099
+ unless @in_pasting
1100
+ scroll_into_view
1101
+ @just_cursor_moving = !modified
1102
+ update_dialogs(key)
1103
+ @just_cursor_moving = false
1532
1104
  end
1533
1105
  end
1534
1106
 
1535
1107
  def input_key(key)
1536
- @last_key = key
1537
1108
  @config.reset_oneshot_key_bindings
1538
1109
  @dialogs.each do |dialog|
1539
1110
  if key.char.instance_of?(Symbol) and key.char == dialog.name
1540
1111
  return
1541
1112
  end
1542
1113
  end
1543
- @just_cursor_moving = nil
1544
1114
  if key.char.nil?
1545
1115
  if @first_char
1546
- @line = nil
1116
+ @eof = true
1547
1117
  end
1548
1118
  finish
1549
1119
  return
1550
1120
  end
1551
- old_line = @line.dup
1121
+ old_lines = @buffer_of_lines.dup
1552
1122
  @first_char = false
1553
1123
  completion_occurs = false
1554
1124
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -1591,19 +1161,20 @@ class Reline::LineEditor
1591
1161
  @completion_state = CompletionState::NORMAL
1592
1162
  @completion_journey_data = nil
1593
1163
  end
1594
- if not @in_pasting and @just_cursor_moving.nil?
1595
- if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1596
- @just_cursor_moving = true
1597
- elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1598
- @just_cursor_moving = true
1599
- else
1600
- @just_cursor_moving = false
1601
- end
1164
+ if @in_pasting
1165
+ clear_dialogs
1602
1166
  else
1603
- @just_cursor_moving = false
1167
+ return old_lines != @buffer_of_lines
1604
1168
  end
1605
- if @is_multiline and @auto_indent_proc and not simplified_rendering? and @line
1606
- process_auto_indent
1169
+ end
1170
+
1171
+ def scroll_into_view
1172
+ _editor_cursor_x, editor_cursor_y = editor_cursor_position
1173
+ if editor_cursor_y < screen_scroll_top
1174
+ @scroll_partial_screen = editor_cursor_y
1175
+ end
1176
+ if editor_cursor_y >= screen_scroll_top + screen_height
1177
+ @scroll_partial_screen = editor_cursor_y - screen_height + 1
1607
1178
  end
1608
1179
  end
1609
1180
 
@@ -1637,43 +1208,40 @@ class Reline::LineEditor
1637
1208
  result
1638
1209
  end
1639
1210
 
1640
- private def process_auto_indent
1641
- return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
1642
- if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
1643
- # Fix indent of a line when a newline is inserted to the next
1644
- new_lines = whole_lines
1645
- new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
1646
- md = @line.match(/\A */)
1647
- prev_indent = md[0].count(' ')
1648
- @line = ' ' * new_indent + @line.lstrip
1649
-
1650
- new_indent = nil
1651
- result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[@line_index - 1].bytesize + 1), false)
1652
- if result
1653
- new_indent = result
1654
- end
1655
- if new_indent&.>= 0
1656
- @line = ' ' * new_indent + @line.lstrip
1657
- end
1658
- end
1659
- new_lines = whole_lines
1660
- new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1661
- if new_indent&.>= 0
1662
- md = new_lines[@line_index].match(/\A */)
1663
- prev_indent = md[0].count(' ')
1664
- if @check_new_auto_indent
1665
- line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1666
- @cursor = new_indent
1667
- @cursor_max = calculate_width(line)
1668
- @byte_pointer = new_indent
1669
- else
1670
- @line = ' ' * new_indent + @line.lstrip
1671
- @cursor += new_indent - prev_indent
1672
- @cursor_max = calculate_width(@line)
1673
- @byte_pointer += new_indent - prev_indent
1674
- end
1211
+ private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false)
1212
+ return if @in_pasting
1213
+ return unless @auto_indent_proc
1214
+
1215
+ line = @buffer_of_lines[line_index]
1216
+ byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize
1217
+ new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
1218
+ return unless new_indent
1219
+
1220
+ @buffer_of_lines[line_index] = ' ' * new_indent + line.lstrip
1221
+ if @line_index == line_index
1222
+ old_indent = line[/\A */].size
1223
+ @byte_pointer = [@byte_pointer + new_indent - old_indent, 0].max
1675
1224
  end
1676
- @check_new_auto_indent = false
1225
+ end
1226
+
1227
+ def line()
1228
+ current_line unless eof?
1229
+ end
1230
+
1231
+ def current_line
1232
+ @buffer_of_lines[@line_index]
1233
+ end
1234
+
1235
+ def set_current_line(line, byte_pointer = nil)
1236
+ @modified = true
1237
+ cursor = current_byte_pointer_cursor
1238
+ @buffer_of_lines[@line_index] = line
1239
+ if byte_pointer
1240
+ @byte_pointer = byte_pointer
1241
+ else
1242
+ calculate_nearest_cursor(cursor)
1243
+ end
1244
+ process_auto_indent
1677
1245
  end
1678
1246
 
1679
1247
  def retrieve_completion_block(set_completion_quote_character = false)
@@ -1687,7 +1255,7 @@ class Reline::LineEditor
1687
1255
  else
1688
1256
  quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1689
1257
  end
1690
- before = @line.byteslice(0, @byte_pointer)
1258
+ before = current_line.byteslice(0, @byte_pointer)
1691
1259
  rest = nil
1692
1260
  break_pointer = nil
1693
1261
  quote = nil
@@ -1695,7 +1263,7 @@ class Reline::LineEditor
1695
1263
  escaped_quote = nil
1696
1264
  i = 0
1697
1265
  while i < @byte_pointer do
1698
- slice = @line.byteslice(i, @byte_pointer - i)
1266
+ slice = current_line.byteslice(i, @byte_pointer - i)
1699
1267
  unless slice.valid_encoding?
1700
1268
  i += 1
1701
1269
  next
@@ -1717,15 +1285,15 @@ class Reline::LineEditor
1717
1285
  elsif word_break_regexp and not quote and slice =~ word_break_regexp
1718
1286
  rest = $'
1719
1287
  i += 1
1720
- before = @line.byteslice(i, @byte_pointer - i)
1288
+ before = current_line.byteslice(i, @byte_pointer - i)
1721
1289
  break_pointer = i
1722
1290
  else
1723
1291
  i += 1
1724
1292
  end
1725
1293
  end
1726
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1294
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1727
1295
  if rest
1728
- preposing = @line.byteslice(0, break_pointer)
1296
+ preposing = current_line.byteslice(0, break_pointer)
1729
1297
  target = rest
1730
1298
  if set_completion_quote_character and quote
1731
1299
  Reline.core.instance_variable_set(:@completion_quote_character, quote)
@@ -1736,7 +1304,7 @@ class Reline::LineEditor
1736
1304
  else
1737
1305
  preposing = ''
1738
1306
  if break_pointer
1739
- preposing = @line.byteslice(0, break_pointer)
1307
+ preposing = current_line.byteslice(0, break_pointer)
1740
1308
  else
1741
1309
  preposing = ''
1742
1310
  end
@@ -1756,106 +1324,67 @@ class Reline::LineEditor
1756
1324
 
1757
1325
  def confirm_multiline_termination
1758
1326
  temp_buffer = @buffer_of_lines.dup
1759
- if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
1760
- temp_buffer[@previous_line_index] = @line
1761
- else
1762
- temp_buffer[@line_index] = @line
1763
- end
1764
1327
  @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1765
1328
  end
1766
1329
 
1767
1330
  def insert_text(text)
1768
- width = calculate_width(text)
1769
- if @cursor == @cursor_max
1770
- @line += text
1331
+ if @buffer_of_lines[@line_index].bytesize == @byte_pointer
1332
+ @buffer_of_lines[@line_index] += text
1771
1333
  else
1772
- @line = byteinsert(@line, @byte_pointer, text)
1334
+ @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text)
1773
1335
  end
1774
1336
  @byte_pointer += text.bytesize
1775
- @cursor += width
1776
- @cursor_max += width
1337
+ process_auto_indent
1777
1338
  end
1778
1339
 
1779
1340
  def delete_text(start = nil, length = nil)
1780
1341
  if start.nil? and length.nil?
1781
1342
  if @is_multiline
1782
1343
  if @buffer_of_lines.size == 1
1783
- @line&.clear
1344
+ @buffer_of_lines[@line_index] = ''
1784
1345
  @byte_pointer = 0
1785
- @cursor = 0
1786
- @cursor_max = 0
1787
1346
  elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1788
1347
  @buffer_of_lines.pop
1789
1348
  @line_index -= 1
1790
- @line = @buffer_of_lines[@line_index]
1791
1349
  @byte_pointer = 0
1792
- @cursor = 0
1793
- @cursor_max = calculate_width(@line)
1794
1350
  elsif @line_index < (@buffer_of_lines.size - 1)
1795
1351
  @buffer_of_lines.delete_at(@line_index)
1796
- @line = @buffer_of_lines[@line_index]
1797
1352
  @byte_pointer = 0
1798
- @cursor = 0
1799
- @cursor_max = calculate_width(@line)
1800
1353
  end
1801
1354
  else
1802
- @line&.clear
1803
- @byte_pointer = 0
1804
- @cursor = 0
1805
- @cursor_max = 0
1355
+ set_current_line('', 0)
1806
1356
  end
1807
1357
  elsif not start.nil? and not length.nil?
1808
- if @line
1809
- before = @line.byteslice(0, start)
1810
- after = @line.byteslice(start + length, @line.bytesize)
1811
- @line = before + after
1812
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1813
- str = @line.byteslice(0, @byte_pointer)
1814
- @cursor = calculate_width(str)
1815
- @cursor_max = calculate_width(@line)
1358
+ if current_line
1359
+ before = current_line.byteslice(0, start)
1360
+ after = current_line.byteslice(start + length, current_line.bytesize)
1361
+ set_current_line(before + after)
1816
1362
  end
1817
1363
  elsif start.is_a?(Range)
1818
1364
  range = start
1819
1365
  first = range.first
1820
1366
  last = range.last
1821
- last = @line.bytesize - 1 if last > @line.bytesize
1822
- last += @line.bytesize if last < 0
1823
- first += @line.bytesize if first < 0
1367
+ last = current_line.bytesize - 1 if last > current_line.bytesize
1368
+ last += current_line.bytesize if last < 0
1369
+ first += current_line.bytesize if first < 0
1824
1370
  range = range.exclude_end? ? first...last : first..last
1825
- @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1826
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1827
- str = @line.byteslice(0, @byte_pointer)
1828
- @cursor = calculate_width(str)
1829
- @cursor_max = calculate_width(@line)
1371
+ line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1372
+ set_current_line(line)
1830
1373
  else
1831
- @line = @line.byteslice(0, start)
1832
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1833
- str = @line.byteslice(0, @byte_pointer)
1834
- @cursor = calculate_width(str)
1835
- @cursor_max = calculate_width(@line)
1374
+ set_current_line(current_line.byteslice(0, start))
1836
1375
  end
1837
1376
  end
1838
1377
 
1839
1378
  def byte_pointer=(val)
1840
1379
  @byte_pointer = val
1841
- str = @line.byteslice(0, @byte_pointer)
1842
- @cursor = calculate_width(str)
1843
- @cursor_max = calculate_width(@line)
1844
1380
  end
1845
1381
 
1846
1382
  def whole_lines
1847
- index = @previous_line_index || @line_index
1848
- temp_lines = @buffer_of_lines.dup
1849
- temp_lines[index] = @line
1850
- temp_lines
1383
+ @buffer_of_lines.dup
1851
1384
  end
1852
1385
 
1853
1386
  def whole_buffer
1854
- if @buffer_of_lines.size == 1 and @line.nil?
1855
- nil
1856
- else
1857
- whole_lines.join("\n")
1858
- end
1387
+ whole_lines.join("\n")
1859
1388
  end
1860
1389
 
1861
1390
  def finished?
@@ -1864,7 +1393,6 @@ class Reline::LineEditor
1864
1393
 
1865
1394
  def finish
1866
1395
  @finished = true
1867
- @rerender_all = true
1868
1396
  @config.reset
1869
1397
  end
1870
1398
 
@@ -1895,14 +1423,9 @@ class Reline::LineEditor
1895
1423
 
1896
1424
  private def key_newline(key)
1897
1425
  if @is_multiline
1898
- if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1899
- @add_newline_to_end_of_buffer = true
1900
- end
1901
- next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1902
- cursor_line = @line.byteslice(0, @byte_pointer)
1426
+ next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1427
+ cursor_line = current_line.byteslice(0, @byte_pointer)
1903
1428
  insert_new_line(cursor_line, next_line)
1904
- @cursor = 0
1905
- @check_new_auto_indent = true unless @in_pasting
1906
1429
  end
1907
1430
  end
1908
1431
 
@@ -1912,16 +1435,7 @@ class Reline::LineEditor
1912
1435
 
1913
1436
  private def process_insert(force: false)
1914
1437
  return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1915
- width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1916
- bytesize = @continuous_insertion_buffer.bytesize
1917
- if @cursor == @cursor_max
1918
- @line += @continuous_insertion_buffer
1919
- else
1920
- @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1921
- end
1922
- @byte_pointer += bytesize
1923
- @cursor += width
1924
- @cursor_max += width
1438
+ insert_text(@continuous_insertion_buffer)
1925
1439
  @continuous_insertion_buffer.clear
1926
1440
  end
1927
1441
 
@@ -1939,9 +1453,6 @@ class Reline::LineEditor
1939
1453
  # million.
1940
1454
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1941
1455
  private def ed_insert(key)
1942
- str = nil
1943
- width = nil
1944
- bytesize = nil
1945
1456
  if key.instance_of?(String)
1946
1457
  begin
1947
1458
  key.encode(Encoding::UTF_8)
@@ -1949,7 +1460,6 @@ class Reline::LineEditor
1949
1460
  return
1950
1461
  end
1951
1462
  str = key
1952
- bytesize = key.bytesize
1953
1463
  else
1954
1464
  begin
1955
1465
  key.chr.encode(Encoding::UTF_8)
@@ -1957,7 +1467,6 @@ class Reline::LineEditor
1957
1467
  return
1958
1468
  end
1959
1469
  str = key.chr
1960
- bytesize = 1
1961
1470
  end
1962
1471
  if @in_pasting
1963
1472
  @continuous_insertion_buffer << str
@@ -1965,28 +1474,8 @@ class Reline::LineEditor
1965
1474
  elsif not @continuous_insertion_buffer.empty?
1966
1475
  process_insert
1967
1476
  end
1968
- width = Reline::Unicode.get_mbchar_width(str)
1969
- if @cursor == @cursor_max
1970
- @line += str
1971
- else
1972
- @line = byteinsert(@line, @byte_pointer, str)
1973
- end
1974
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1975
- @byte_pointer += bytesize
1976
- last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1977
- combined_char = last_mbchar + str
1978
- if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
1979
- # combined char
1980
- last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
1981
- combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
1982
- if combined_char_width > last_mbchar_width
1983
- width = combined_char_width - last_mbchar_width
1984
- else
1985
- width = 0
1986
- end
1987
- end
1988
- @cursor += width
1989
- @cursor_max += width
1477
+
1478
+ insert_text(str)
1990
1479
  end
1991
1480
  alias_method :ed_digit, :ed_insert
1992
1481
  alias_method :self_insert, :ed_insert
@@ -2008,18 +1497,11 @@ class Reline::LineEditor
2008
1497
  alias_method :quoted_insert, :ed_quoted_insert
2009
1498
 
2010
1499
  private def ed_next_char(key, arg: 1)
2011
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2012
- if (@byte_pointer < @line.bytesize)
2013
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2014
- width = Reline::Unicode.get_mbchar_width(mbchar)
2015
- @cursor += width if width
1500
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1501
+ if (@byte_pointer < current_line.bytesize)
2016
1502
  @byte_pointer += byte_size
2017
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
2018
- next_line = @buffer_of_lines[@line_index + 1]
2019
- @cursor = 0
1503
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
2020
1504
  @byte_pointer = 0
2021
- @cursor_max = calculate_width(next_line)
2022
- @previous_line_index = @line_index
2023
1505
  @line_index += 1
2024
1506
  end
2025
1507
  arg -= 1
@@ -2028,19 +1510,12 @@ class Reline::LineEditor
2028
1510
  alias_method :forward_char, :ed_next_char
2029
1511
 
2030
1512
  private def ed_prev_char(key, arg: 1)
2031
- if @cursor > 0
2032
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1513
+ if @byte_pointer > 0
1514
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2033
1515
  @byte_pointer -= byte_size
2034
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2035
- width = Reline::Unicode.get_mbchar_width(mbchar)
2036
- @cursor -= width
2037
1516
  elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
2038
- prev_line = @buffer_of_lines[@line_index - 1]
2039
- @cursor = calculate_width(prev_line)
2040
- @byte_pointer = prev_line.bytesize
2041
- @cursor_max = calculate_width(prev_line)
2042
- @previous_line_index = @line_index
2043
1517
  @line_index -= 1
1518
+ @byte_pointer = current_line.bytesize
2044
1519
  end
2045
1520
  arg -= 1
2046
1521
  ed_prev_char(key, arg: arg) if arg > 0
@@ -2048,24 +1523,18 @@ class Reline::LineEditor
2048
1523
  alias_method :backward_char, :ed_prev_char
2049
1524
 
2050
1525
  private def vi_first_print(key)
2051
- @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
1526
+ @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
2052
1527
  end
2053
1528
 
2054
1529
  private def ed_move_to_beg(key)
2055
- @byte_pointer = @cursor = 0
1530
+ @byte_pointer = 0
2056
1531
  end
2057
1532
  alias_method :beginning_of_line, :ed_move_to_beg
2058
1533
 
2059
1534
  private def ed_move_to_end(key)
2060
1535
  @byte_pointer = 0
2061
- @cursor = 0
2062
- byte_size = 0
2063
- while @byte_pointer < @line.bytesize
2064
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2065
- if byte_size > 0
2066
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2067
- @cursor += Reline::Unicode.get_mbchar_width(mbchar)
2068
- end
1536
+ while @byte_pointer < current_line.bytesize
1537
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2069
1538
  @byte_pointer += byte_size
2070
1539
  end
2071
1540
  end
@@ -2167,19 +1636,16 @@ class Reline::LineEditor
2167
1636
  @buffer_of_lines = hit.split("\n")
2168
1637
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2169
1638
  @line_index = @buffer_of_lines.size - 1
2170
- @line = @buffer_of_lines.last
2171
- @byte_pointer = @line.bytesize
2172
- @cursor = @cursor_max = calculate_width(@line)
2173
- @rerender_all = true
1639
+ @byte_pointer = current_line.bytesize
2174
1640
  @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
2175
1641
  else
2176
- @line = hit
1642
+ @buffer_of_lines = [hit]
1643
+ @byte_pointer = hit.bytesize
2177
1644
  @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
2178
1645
  end
2179
1646
  last_hit = hit
2180
1647
  else
2181
1648
  if @is_multiline
2182
- @rerender_all = true
2183
1649
  @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
2184
1650
  else
2185
1651
  @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
@@ -2194,7 +1660,7 @@ class Reline::LineEditor
2194
1660
  if @is_multiline
2195
1661
  @line_backup_in_history = whole_buffer
2196
1662
  else
2197
- @line_backup_in_history = @line
1663
+ @line_backup_in_history = current_line
2198
1664
  end
2199
1665
  end
2200
1666
  searcher = generate_searcher
@@ -2214,35 +1680,26 @@ class Reline::LineEditor
2214
1680
  @buffer_of_lines = buffer.split("\n")
2215
1681
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2216
1682
  @line_index = @buffer_of_lines.size - 1
2217
- @line = @buffer_of_lines.last
2218
- @rerender_all = true
2219
1683
  else
2220
- @line = buffer
1684
+ @buffer_of_lines = [buffer]
2221
1685
  end
2222
1686
  @searching_prompt = nil
2223
1687
  @waiting_proc = nil
2224
- @cursor_max = calculate_width(@line)
2225
- @cursor = @byte_pointer = 0
2226
- @rerender_all = true
2227
- @cached_prompt_list = nil
1688
+ @byte_pointer = 0
2228
1689
  searcher.resume(-1)
2229
1690
  when "\C-g".ord
2230
1691
  if @is_multiline
2231
1692
  @buffer_of_lines = @line_backup_in_history.split("\n")
2232
1693
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2233
1694
  @line_index = @buffer_of_lines.size - 1
2234
- @line = @buffer_of_lines.last
2235
- @rerender_all = true
2236
1695
  else
2237
- @line = @line_backup_in_history
1696
+ @buffer_of_lines = [@line_backup_in_history]
2238
1697
  end
2239
1698
  @history_pointer = nil
2240
1699
  @searching_prompt = nil
2241
1700
  @waiting_proc = nil
2242
1701
  @line_backup_in_history = nil
2243
- @cursor_max = calculate_width(@line)
2244
- @cursor = @byte_pointer = 0
2245
- @rerender_all = true
1702
+ @byte_pointer = 0
2246
1703
  else
2247
1704
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
2248
1705
  if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
@@ -2258,18 +1715,13 @@ class Reline::LineEditor
2258
1715
  @buffer_of_lines = line.split("\n")
2259
1716
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2260
1717
  @line_index = @buffer_of_lines.size - 1
2261
- @line = @buffer_of_lines.last
2262
- @rerender_all = true
2263
1718
  else
2264
- @line_backup_in_history = @line
2265
- @line = line
1719
+ @line_backup_in_history = current_line
1720
+ @buffer_of_lines = [line]
2266
1721
  end
2267
1722
  @searching_prompt = nil
2268
1723
  @waiting_proc = nil
2269
- @cursor_max = calculate_width(@line)
2270
- @cursor = @byte_pointer = 0
2271
- @rerender_all = true
2272
- @cached_prompt_list = nil
1724
+ @byte_pointer = 0
2273
1725
  searcher.resume(-1)
2274
1726
  end
2275
1727
  end
@@ -2290,9 +1742,9 @@ class Reline::LineEditor
2290
1742
  history = nil
2291
1743
  h_pointer = nil
2292
1744
  line_no = nil
2293
- substr = @line.slice(0, @byte_pointer)
1745
+ substr = current_line.slice(0, @byte_pointer)
2294
1746
  if @history_pointer.nil?
2295
- return if not @line.empty? and substr.empty?
1747
+ return if not current_line.empty? and substr.empty?
2296
1748
  history = Reline::HISTORY
2297
1749
  elsif @history_pointer.zero?
2298
1750
  history = nil
@@ -2318,23 +1770,23 @@ class Reline::LineEditor
2318
1770
  end
2319
1771
  return if h_pointer.nil?
2320
1772
  @history_pointer = h_pointer
1773
+ cursor = current_byte_pointer_cursor
2321
1774
  if @is_multiline
2322
1775
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2323
1776
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2324
1777
  @line_index = line_no
2325
- @line = @buffer_of_lines[@line_index]
2326
- @rerender_all = true
1778
+ calculate_nearest_cursor(cursor)
2327
1779
  else
2328
- @line = Reline::HISTORY[@history_pointer]
1780
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1781
+ calculate_nearest_cursor(cursor)
2329
1782
  end
2330
- @cursor_max = calculate_width(@line)
2331
1783
  arg -= 1
2332
1784
  ed_search_prev_history(key, arg: arg) if arg > 0
2333
1785
  end
2334
1786
  alias_method :history_search_backward, :ed_search_prev_history
2335
1787
 
2336
1788
  private def ed_search_next_history(key, arg: 1)
2337
- substr = @line.slice(0, @byte_pointer)
1789
+ substr = current_line.slice(0, @byte_pointer)
2338
1790
  if @history_pointer.nil?
2339
1791
  return
2340
1792
  elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
@@ -2365,21 +1817,21 @@ class Reline::LineEditor
2365
1817
  if @history_pointer.nil? and substr.empty?
2366
1818
  @buffer_of_lines = []
2367
1819
  @line_index = 0
1820
+ @byte_pointer = 0
2368
1821
  else
1822
+ cursor = current_byte_pointer_cursor
2369
1823
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2370
1824
  @line_index = line_no
1825
+ calculate_nearest_cursor(cursor)
2371
1826
  end
2372
1827
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2373
- @line = @buffer_of_lines[@line_index]
2374
- @rerender_all = true
2375
1828
  else
2376
1829
  if @history_pointer.nil? and substr.empty?
2377
- @line = ''
1830
+ set_current_line('', 0)
2378
1831
  else
2379
- @line = Reline::HISTORY[@history_pointer]
1832
+ set_current_line(Reline::HISTORY[@history_pointer])
2380
1833
  end
2381
1834
  end
2382
- @cursor_max = calculate_width(@line)
2383
1835
  arg -= 1
2384
1836
  ed_search_next_history(key, arg: arg) if arg > 0
2385
1837
  end
@@ -2387,8 +1839,9 @@ class Reline::LineEditor
2387
1839
 
2388
1840
  private def ed_prev_history(key, arg: 1)
2389
1841
  if @is_multiline and @line_index > 0
2390
- @previous_line_index = @line_index
1842
+ cursor = current_byte_pointer_cursor
2391
1843
  @line_index -= 1
1844
+ calculate_nearest_cursor(cursor)
2392
1845
  return
2393
1846
  end
2394
1847
  if Reline::HISTORY.empty?
@@ -2396,16 +1849,17 @@ class Reline::LineEditor
2396
1849
  end
2397
1850
  if @history_pointer.nil?
2398
1851
  @history_pointer = Reline::HISTORY.size - 1
1852
+ cursor = current_byte_pointer_cursor
2399
1853
  if @is_multiline
2400
1854
  @line_backup_in_history = whole_buffer
2401
1855
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2402
1856
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2403
1857
  @line_index = @buffer_of_lines.size - 1
2404
- @line = @buffer_of_lines.last
2405
- @rerender_all = true
1858
+ calculate_nearest_cursor(cursor)
2406
1859
  else
2407
- @line_backup_in_history = @line
2408
- @line = Reline::HISTORY[@history_pointer]
1860
+ @line_backup_in_history = whole_buffer
1861
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1862
+ calculate_nearest_cursor(cursor)
2409
1863
  end
2410
1864
  elsif @history_pointer.zero?
2411
1865
  return
@@ -2416,20 +1870,16 @@ class Reline::LineEditor
2416
1870
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2417
1871
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2418
1872
  @line_index = @buffer_of_lines.size - 1
2419
- @line = @buffer_of_lines.last
2420
- @rerender_all = true
2421
1873
  else
2422
- Reline::HISTORY[@history_pointer] = @line
1874
+ Reline::HISTORY[@history_pointer] = whole_buffer
2423
1875
  @history_pointer -= 1
2424
- @line = Reline::HISTORY[@history_pointer]
1876
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
2425
1877
  end
2426
1878
  end
2427
1879
  if @config.editing_mode_is?(:emacs, :vi_insert)
2428
- @cursor_max = @cursor = calculate_width(@line)
2429
- @byte_pointer = @line.bytesize
1880
+ @byte_pointer = current_line.bytesize
2430
1881
  elsif @config.editing_mode_is?(:vi_command)
2431
- @byte_pointer = @cursor = 0
2432
- @cursor_max = calculate_width(@line)
1882
+ @byte_pointer = 0
2433
1883
  end
2434
1884
  arg -= 1
2435
1885
  ed_prev_history(key, arg: arg) if arg > 0
@@ -2438,8 +1888,9 @@ class Reline::LineEditor
2438
1888
 
2439
1889
  private def ed_next_history(key, arg: 1)
2440
1890
  if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
2441
- @previous_line_index = @line_index
1891
+ cursor = current_byte_pointer_cursor
2442
1892
  @line_index += 1
1893
+ calculate_nearest_cursor(cursor)
2443
1894
  return
2444
1895
  end
2445
1896
  if @history_pointer.nil?
@@ -2450,11 +1901,9 @@ class Reline::LineEditor
2450
1901
  @buffer_of_lines = @line_backup_in_history.split("\n")
2451
1902
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2452
1903
  @line_index = 0
2453
- @line = @buffer_of_lines.first
2454
- @rerender_all = true
2455
1904
  else
2456
1905
  @history_pointer = nil
2457
- @line = @line_backup_in_history
1906
+ @buffer_of_lines = [@line_backup_in_history]
2458
1907
  end
2459
1908
  else
2460
1909
  if @is_multiline
@@ -2463,21 +1912,16 @@ class Reline::LineEditor
2463
1912
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2464
1913
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2465
1914
  @line_index = 0
2466
- @line = @buffer_of_lines.first
2467
- @rerender_all = true
2468
1915
  else
2469
- Reline::HISTORY[@history_pointer] = @line
1916
+ Reline::HISTORY[@history_pointer] = whole_buffer
2470
1917
  @history_pointer += 1
2471
- @line = Reline::HISTORY[@history_pointer]
1918
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
2472
1919
  end
2473
1920
  end
2474
- @line = '' unless @line
2475
1921
  if @config.editing_mode_is?(:emacs, :vi_insert)
2476
- @cursor_max = @cursor = calculate_width(@line)
2477
- @byte_pointer = @line.bytesize
1922
+ @byte_pointer = current_line.bytesize
2478
1923
  elsif @config.editing_mode_is?(:vi_command)
2479
- @byte_pointer = @cursor = 0
2480
- @cursor_max = calculate_width(@line)
1924
+ @byte_pointer = 0
2481
1925
  end
2482
1926
  arg -= 1
2483
1927
  ed_next_history(key, arg: arg) if arg > 0
@@ -2503,14 +1947,14 @@ class Reline::LineEditor
2503
1947
  end
2504
1948
  else
2505
1949
  # should check confirm_multiline_termination to finish?
2506
- @previous_line_index = @line_index
2507
1950
  @line_index = @buffer_of_lines.size - 1
1951
+ @byte_pointer = current_line.bytesize
2508
1952
  finish
2509
1953
  end
2510
1954
  end
2511
1955
  else
2512
1956
  if @history_pointer
2513
- Reline::HISTORY[@history_pointer] = @line
1957
+ Reline::HISTORY[@history_pointer] = whole_buffer
2514
1958
  @history_pointer = nil
2515
1959
  end
2516
1960
  finish
@@ -2518,25 +1962,18 @@ class Reline::LineEditor
2518
1962
  end
2519
1963
 
2520
1964
  private def em_delete_prev_char(key, arg: 1)
2521
- if @is_multiline and @cursor == 0 and @line_index > 0
2522
- @buffer_of_lines[@line_index] = @line
2523
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2524
- @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2525
- @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2526
- @line_index -= 1
2527
- @line = @buffer_of_lines[@line_index]
2528
- @cursor_max = calculate_width(@line)
2529
- @rerender_all = true
2530
- elsif @cursor > 0
2531
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2532
- @byte_pointer -= byte_size
2533
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2534
- width = Reline::Unicode.get_mbchar_width(mbchar)
2535
- @cursor -= width
2536
- @cursor_max -= width
1965
+ arg.times do
1966
+ if @is_multiline and @byte_pointer == 0 and @line_index > 0
1967
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1968
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1969
+ @line_index -= 1
1970
+ elsif @byte_pointer > 0
1971
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1972
+ line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1973
+ set_current_line(line, @byte_pointer - byte_size)
1974
+ end
2537
1975
  end
2538
- arg -= 1
2539
- em_delete_prev_char(key, arg: arg) if arg > 0
1976
+ process_auto_indent
2540
1977
  end
2541
1978
  alias_method :backward_delete_char, :em_delete_prev_char
2542
1979
 
@@ -2546,30 +1983,16 @@ class Reline::LineEditor
2546
1983
  # the line. With a negative numeric argument, kill backward
2547
1984
  # from the cursor to the beginning of the current line.
2548
1985
  private def ed_kill_line(key)
2549
- if @line.bytesize > @byte_pointer
2550
- @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
2551
- @byte_pointer = @line.bytesize
2552
- @cursor = @cursor_max = calculate_width(@line)
1986
+ if current_line.bytesize > @byte_pointer
1987
+ line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1988
+ set_current_line(line, line.bytesize)
2553
1989
  @kill_ring.append(deleted)
2554
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2555
- @cursor = calculate_width(@line)
2556
- @byte_pointer = @line.bytesize
2557
- @line += @buffer_of_lines.delete_at(@line_index + 1)
2558
- @cursor_max = calculate_width(@line)
2559
- @buffer_of_lines[@line_index] = @line
2560
- @rerender_all = true
2561
- @rest_height += 1
1990
+ elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1991
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2562
1992
  end
2563
1993
  end
2564
1994
  alias_method :kill_line, :ed_kill_line
2565
1995
 
2566
- # Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line.
2567
- private def vi_change_to_eol(key)
2568
- ed_kill_line(key)
2569
-
2570
- @config.editing_mode = :vi_insert
2571
- end
2572
-
2573
1996
  # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
2574
1997
  # beginning of the edit buffer to the cursor and save it to the
2575
1998
  # cut buffer.
@@ -2577,11 +2000,9 @@ class Reline::LineEditor
2577
2000
  # to the beginning of the current line.
2578
2001
  private def vi_kill_line_prev(key)
2579
2002
  if @byte_pointer > 0
2580
- @line, deleted = byteslice!(@line, 0, @byte_pointer)
2581
- @byte_pointer = 0
2003
+ line, deleted = byteslice!(current_line, 0, @byte_pointer)
2004
+ set_current_line(line, 0)
2582
2005
  @kill_ring.append(deleted, true)
2583
- @cursor_max = calculate_width(@line)
2584
- @cursor = 0
2585
2006
  end
2586
2007
  end
2587
2008
  alias_method :unix_line_discard, :vi_kill_line_prev
@@ -2591,45 +2012,30 @@ class Reline::LineEditor
2591
2012
  # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
2592
2013
  # current line, no matter where point is.
2593
2014
  private def em_kill_line(key)
2594
- if @line.size > 0
2595
- @kill_ring.append(@line.dup, true)
2596
- @line.clear
2597
- @byte_pointer = 0
2598
- @cursor_max = 0
2599
- @cursor = 0
2015
+ if current_line.size > 0
2016
+ @kill_ring.append(current_line.dup, true)
2017
+ set_current_line('', 0)
2600
2018
  end
2601
2019
  end
2602
2020
  alias_method :kill_whole_line, :em_kill_line
2603
2021
 
2604
2022
  private def em_delete(key)
2605
- if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
2606
- @line = nil
2607
- if @buffer_of_lines.size > 1
2608
- scroll_down(@highest_in_all - @first_line_started_from)
2609
- end
2610
- Reline::IOGate.move_cursor_column(0)
2023
+ if current_line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
2611
2024
  @eof = true
2612
2025
  finish
2613
- elsif @byte_pointer < @line.bytesize
2614
- splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
2026
+ elsif @byte_pointer < current_line.bytesize
2027
+ splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize)
2615
2028
  mbchar = splitted_last.grapheme_clusters.first
2616
- width = Reline::Unicode.get_mbchar_width(mbchar)
2617
- @cursor_max -= width
2618
- @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
2619
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2620
- @cursor = calculate_width(@line)
2621
- @byte_pointer = @line.bytesize
2622
- @line += @buffer_of_lines.delete_at(@line_index + 1)
2623
- @cursor_max = calculate_width(@line)
2624
- @buffer_of_lines[@line_index] = @line
2625
- @rerender_all = true
2626
- @rest_height += 1
2029
+ line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
2030
+ set_current_line(line)
2031
+ elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
2032
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2627
2033
  end
2628
2034
  end
2629
2035
  alias_method :delete_char, :em_delete
2630
2036
 
2631
2037
  private def em_delete_or_list(key)
2632
- if @line.empty? or @byte_pointer < @line.bytesize
2038
+ if current_line.empty? or @byte_pointer < current_line.bytesize
2633
2039
  em_delete(key)
2634
2040
  else # show completed list
2635
2041
  result = call_completion_proc
@@ -2642,29 +2048,16 @@ class Reline::LineEditor
2642
2048
 
2643
2049
  private def em_yank(key)
2644
2050
  yanked = @kill_ring.yank
2645
- if yanked
2646
- @line = byteinsert(@line, @byte_pointer, yanked)
2647
- yanked_width = calculate_width(yanked)
2648
- @cursor += yanked_width
2649
- @cursor_max += yanked_width
2650
- @byte_pointer += yanked.bytesize
2651
- end
2051
+ insert_text(yanked) if yanked
2652
2052
  end
2653
2053
  alias_method :yank, :em_yank
2654
2054
 
2655
2055
  private def em_yank_pop(key)
2656
2056
  yanked, prev_yank = @kill_ring.yank_pop
2657
2057
  if yanked
2658
- prev_yank_width = calculate_width(prev_yank)
2659
- @cursor -= prev_yank_width
2660
- @cursor_max -= prev_yank_width
2661
- @byte_pointer -= prev_yank.bytesize
2662
- @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
2663
- @line = byteinsert(@line, @byte_pointer, yanked)
2664
- yanked_width = calculate_width(yanked)
2665
- @cursor += yanked_width
2666
- @cursor_max += yanked_width
2667
- @byte_pointer += yanked.bytesize
2058
+ line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
2059
+ set_current_line(line, @byte_pointer - prev_yank.bytesize)
2060
+ insert_text(yanked)
2668
2061
  end
2669
2062
  end
2670
2063
  alias_method :yank_pop, :em_yank_pop
@@ -2675,131 +2068,112 @@ class Reline::LineEditor
2675
2068
  alias_method :clear_screen, :ed_clear_screen
2676
2069
 
2677
2070
  private def em_next_word(key)
2678
- if @line.bytesize > @byte_pointer
2679
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2071
+ if current_line.bytesize > @byte_pointer
2072
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2680
2073
  @byte_pointer += byte_size
2681
- @cursor += width
2682
2074
  end
2683
2075
  end
2684
2076
  alias_method :forward_word, :em_next_word
2685
2077
 
2686
2078
  private def ed_prev_word(key)
2687
2079
  if @byte_pointer > 0
2688
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2080
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2689
2081
  @byte_pointer -= byte_size
2690
- @cursor -= width
2691
2082
  end
2692
2083
  end
2693
2084
  alias_method :backward_word, :ed_prev_word
2694
2085
 
2695
2086
  private def em_delete_next_word(key)
2696
- if @line.bytesize > @byte_pointer
2697
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2698
- @line, word = byteslice!(@line, @byte_pointer, byte_size)
2087
+ if current_line.bytesize > @byte_pointer
2088
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2089
+ line, word = byteslice!(current_line, @byte_pointer, byte_size)
2090
+ set_current_line(line)
2699
2091
  @kill_ring.append(word)
2700
- @cursor_max -= width
2701
2092
  end
2702
2093
  end
2703
2094
  alias_method :kill_word, :em_delete_next_word
2704
2095
 
2705
2096
  private def ed_delete_prev_word(key)
2706
2097
  if @byte_pointer > 0
2707
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2708
- @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2098
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2099
+ line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2100
+ set_current_line(line, @byte_pointer - byte_size)
2709
2101
  @kill_ring.append(word, true)
2710
- @byte_pointer -= byte_size
2711
- @cursor -= width
2712
- @cursor_max -= width
2713
2102
  end
2714
2103
  end
2715
2104
  alias_method :backward_kill_word, :ed_delete_prev_word
2716
2105
 
2717
2106
  private def ed_transpose_chars(key)
2718
2107
  if @byte_pointer > 0
2719
- if @cursor_max > @cursor
2720
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2721
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2722
- width = Reline::Unicode.get_mbchar_width(mbchar)
2723
- @cursor += width
2108
+ if @byte_pointer < current_line.bytesize
2109
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2724
2110
  @byte_pointer += byte_size
2725
2111
  end
2726
- back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2112
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2727
2113
  if (@byte_pointer - back1_byte_size) > 0
2728
- back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
2114
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size)
2729
2115
  back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
2730
- @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
2731
- @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
2116
+ line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size)
2117
+ set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar))
2732
2118
  end
2733
2119
  end
2734
2120
  end
2735
2121
  alias_method :transpose_chars, :ed_transpose_chars
2736
2122
 
2737
2123
  private def ed_transpose_words(key)
2738
- left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
2739
- before = @line.byteslice(0, left_word_start)
2740
- left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
2741
- middle = @line.byteslice(middle_start, right_word_start - middle_start)
2742
- right_word = @line.byteslice(right_word_start, after_start - right_word_start)
2743
- after = @line.byteslice(after_start, @line.bytesize - after_start)
2124
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer)
2125
+ before = current_line.byteslice(0, left_word_start)
2126
+ left_word = current_line.byteslice(left_word_start, middle_start - left_word_start)
2127
+ middle = current_line.byteslice(middle_start, right_word_start - middle_start)
2128
+ right_word = current_line.byteslice(right_word_start, after_start - right_word_start)
2129
+ after = current_line.byteslice(after_start, current_line.bytesize - after_start)
2744
2130
  return if left_word.empty? or right_word.empty?
2745
- @line = before + right_word + middle + left_word + after
2746
2131
  from_head_to_left_word = before + right_word + middle + left_word
2747
- @byte_pointer = from_head_to_left_word.bytesize
2748
- @cursor = calculate_width(from_head_to_left_word)
2132
+ set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
2749
2133
  end
2750
2134
  alias_method :transpose_words, :ed_transpose_words
2751
2135
 
2752
2136
  private def em_capitol_case(key)
2753
- if @line.bytesize > @byte_pointer
2754
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
2755
- before = @line.byteslice(0, @byte_pointer)
2756
- after = @line.byteslice((@byte_pointer + byte_size)..-1)
2757
- @line = before + new_str + after
2758
- @byte_pointer += new_str.bytesize
2759
- @cursor += calculate_width(new_str)
2137
+ if current_line.bytesize > @byte_pointer
2138
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
2139
+ before = current_line.byteslice(0, @byte_pointer)
2140
+ after = current_line.byteslice((@byte_pointer + byte_size)..-1)
2141
+ set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
2760
2142
  end
2761
2143
  end
2762
2144
  alias_method :capitalize_word, :em_capitol_case
2763
2145
 
2764
2146
  private def em_lower_case(key)
2765
- if @line.bytesize > @byte_pointer
2766
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2767
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2147
+ if current_line.bytesize > @byte_pointer
2148
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2149
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2768
2150
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2769
2151
  }.join
2770
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2771
- @line = @line.byteslice(0, @byte_pointer) + part
2772
- @byte_pointer = @line.bytesize
2773
- @cursor = calculate_width(@line)
2774
- @cursor_max = @cursor + calculate_width(rest)
2775
- @line += rest
2152
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
2153
+ line = current_line.byteslice(0, @byte_pointer) + part
2154
+ set_current_line(line + rest, line.bytesize)
2776
2155
  end
2777
2156
  end
2778
2157
  alias_method :downcase_word, :em_lower_case
2779
2158
 
2780
2159
  private def em_upper_case(key)
2781
- if @line.bytesize > @byte_pointer
2782
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2783
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2160
+ if current_line.bytesize > @byte_pointer
2161
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2162
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2784
2163
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2785
2164
  }.join
2786
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2787
- @line = @line.byteslice(0, @byte_pointer) + part
2788
- @byte_pointer = @line.bytesize
2789
- @cursor = calculate_width(@line)
2790
- @cursor_max = @cursor + calculate_width(rest)
2791
- @line += rest
2165
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
2166
+ line = current_line.byteslice(0, @byte_pointer) + part
2167
+ set_current_line(line + rest, line.bytesize)
2792
2168
  end
2793
2169
  end
2794
2170
  alias_method :upcase_word, :em_upper_case
2795
2171
 
2796
2172
  private def em_kill_region(key)
2797
2173
  if @byte_pointer > 0
2798
- byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
2799
- @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2800
- @byte_pointer -= byte_size
2801
- @cursor -= width
2802
- @cursor_max -= width
2174
+ byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
2175
+ line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2176
+ set_current_line(line, @byte_pointer - byte_size)
2803
2177
  @kill_ring.append(deleted, true)
2804
2178
  end
2805
2179
  end
@@ -2827,10 +2201,9 @@ class Reline::LineEditor
2827
2201
  alias_method :vi_movement_mode, :vi_command_mode
2828
2202
 
2829
2203
  private def vi_next_word(key, arg: 1)
2830
- if @line.bytesize > @byte_pointer
2831
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
2204
+ if current_line.bytesize > @byte_pointer
2205
+ byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
2832
2206
  @byte_pointer += byte_size
2833
- @cursor += width
2834
2207
  end
2835
2208
  arg -= 1
2836
2209
  vi_next_word(key, arg: arg) if arg > 0
@@ -2838,38 +2211,32 @@ class Reline::LineEditor
2838
2211
 
2839
2212
  private def vi_prev_word(key, arg: 1)
2840
2213
  if @byte_pointer > 0
2841
- byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
2214
+ byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2842
2215
  @byte_pointer -= byte_size
2843
- @cursor -= width
2844
2216
  end
2845
2217
  arg -= 1
2846
2218
  vi_prev_word(key, arg: arg) if arg > 0
2847
2219
  end
2848
2220
 
2849
2221
  private def vi_end_word(key, arg: 1, inclusive: false)
2850
- if @line.bytesize > @byte_pointer
2851
- byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
2222
+ if current_line.bytesize > @byte_pointer
2223
+ byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
2852
2224
  @byte_pointer += byte_size
2853
- @cursor += width
2854
2225
  end
2855
2226
  arg -= 1
2856
2227
  if inclusive and arg.zero?
2857
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2228
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2858
2229
  if byte_size > 0
2859
- c = @line.byteslice(@byte_pointer, byte_size)
2860
- width = Reline::Unicode.get_mbchar_width(c)
2861
2230
  @byte_pointer += byte_size
2862
- @cursor += width
2863
2231
  end
2864
2232
  end
2865
2233
  vi_end_word(key, arg: arg) if arg > 0
2866
2234
  end
2867
2235
 
2868
2236
  private def vi_next_big_word(key, arg: 1)
2869
- if @line.bytesize > @byte_pointer
2870
- byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
2237
+ if current_line.bytesize > @byte_pointer
2238
+ byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
2871
2239
  @byte_pointer += byte_size
2872
- @cursor += width
2873
2240
  end
2874
2241
  arg -= 1
2875
2242
  vi_next_big_word(key, arg: arg) if arg > 0
@@ -2877,50 +2244,39 @@ class Reline::LineEditor
2877
2244
 
2878
2245
  private def vi_prev_big_word(key, arg: 1)
2879
2246
  if @byte_pointer > 0
2880
- byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
2247
+ byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2881
2248
  @byte_pointer -= byte_size
2882
- @cursor -= width
2883
2249
  end
2884
2250
  arg -= 1
2885
2251
  vi_prev_big_word(key, arg: arg) if arg > 0
2886
2252
  end
2887
2253
 
2888
2254
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2889
- if @line.bytesize > @byte_pointer
2890
- byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
2255
+ if current_line.bytesize > @byte_pointer
2256
+ byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2891
2257
  @byte_pointer += byte_size
2892
- @cursor += width
2893
2258
  end
2894
2259
  arg -= 1
2895
2260
  if inclusive and arg.zero?
2896
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2261
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2897
2262
  if byte_size > 0
2898
- c = @line.byteslice(@byte_pointer, byte_size)
2899
- width = Reline::Unicode.get_mbchar_width(c)
2900
2263
  @byte_pointer += byte_size
2901
- @cursor += width
2902
2264
  end
2903
2265
  end
2904
2266
  vi_end_big_word(key, arg: arg) if arg > 0
2905
2267
  end
2906
2268
 
2907
2269
  private def vi_delete_prev_char(key)
2908
- if @is_multiline and @cursor == 0 and @line_index > 0
2909
- @buffer_of_lines[@line_index] = @line
2910
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2270
+ if @is_multiline and @byte_pointer == 0 and @line_index > 0
2911
2271
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2912
2272
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2913
2273
  @line_index -= 1
2914
- @line = @buffer_of_lines[@line_index]
2915
- @cursor_max = calculate_width(@line)
2916
- @rerender_all = true
2917
- elsif @cursor > 0
2918
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2274
+ process_auto_indent cursor_dependent: false
2275
+ elsif @byte_pointer > 0
2276
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2919
2277
  @byte_pointer -= byte_size
2920
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2921
- width = Reline::Unicode.get_mbchar_width(mbchar)
2922
- @cursor -= width
2923
- @cursor_max -= width
2278
+ line, _ = byteslice!(current_line, @byte_pointer, byte_size)
2279
+ set_current_line(line)
2924
2280
  end
2925
2281
  end
2926
2282
 
@@ -2937,14 +2293,12 @@ class Reline::LineEditor
2937
2293
  private def ed_delete_prev_char(key, arg: 1)
2938
2294
  deleted = ''
2939
2295
  arg.times do
2940
- if @cursor > 0
2941
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2296
+ if @byte_pointer > 0
2297
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2942
2298
  @byte_pointer -= byte_size
2943
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2299
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
2300
+ set_current_line(line)
2944
2301
  deleted.prepend(mbchar)
2945
- width = Reline::Unicode.get_mbchar_width(mbchar)
2946
- @cursor -= width
2947
- @cursor_max -= width
2948
2302
  end
2949
2303
  end
2950
2304
  copy_for_vi(deleted)
@@ -2952,20 +2306,18 @@ class Reline::LineEditor
2952
2306
 
2953
2307
  private def vi_zero(key)
2954
2308
  @byte_pointer = 0
2955
- @cursor = 0
2956
2309
  end
2957
2310
 
2958
2311
  private def vi_change_meta(key, arg: 1)
2959
2312
  @drop_terminate_spaces = true
2960
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2313
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2961
2314
  if byte_pointer_diff > 0
2962
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2315
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2963
2316
  elsif byte_pointer_diff < 0
2964
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2317
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2965
2318
  end
2319
+ set_current_line(line)
2966
2320
  copy_for_vi(cut)
2967
- @cursor += cursor_diff if cursor_diff < 0
2968
- @cursor_max -= cursor_diff.abs
2969
2321
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2970
2322
  @config.editing_mode = :vi_insert
2971
2323
  @drop_terminate_spaces = false
@@ -2974,26 +2326,24 @@ class Reline::LineEditor
2974
2326
  end
2975
2327
 
2976
2328
  private def vi_delete_meta(key, arg: 1)
2977
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2329
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2978
2330
  if byte_pointer_diff > 0
2979
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2331
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2980
2332
  elsif byte_pointer_diff < 0
2981
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2333
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2982
2334
  end
2983
2335
  copy_for_vi(cut)
2984
- @cursor += cursor_diff if cursor_diff < 0
2985
- @cursor_max -= cursor_diff.abs
2986
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2336
+ set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2987
2337
  }
2988
2338
  @waiting_operator_vi_arg = arg
2989
2339
  end
2990
2340
 
2991
2341
  private def vi_yank(key, arg: 1)
2992
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2342
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2993
2343
  if byte_pointer_diff > 0
2994
- cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2344
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2995
2345
  elsif byte_pointer_diff < 0
2996
- cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2346
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2997
2347
  end
2998
2348
  copy_for_vi(cut)
2999
2349
  }
@@ -3001,12 +2351,8 @@ class Reline::LineEditor
3001
2351
  end
3002
2352
 
3003
2353
  private def vi_list_or_eof(key)
3004
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
3005
- @line = nil
3006
- if @buffer_of_lines.size > 1
3007
- scroll_down(@highest_in_all - @first_line_started_from)
3008
- end
3009
- Reline::IOGate.move_cursor_column(0)
2354
+ if (not @is_multiline and current_line.empty?) or (@is_multiline and current_line.empty? and @buffer_of_lines.size == 1)
2355
+ set_current_line('', 0)
3010
2356
  @eof = true
3011
2357
  finish
3012
2358
  else
@@ -3017,18 +2363,15 @@ class Reline::LineEditor
3017
2363
  alias_method :vi_eof_maybe, :vi_list_or_eof
3018
2364
 
3019
2365
  private def ed_delete_next_char(key, arg: 1)
3020
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3021
- unless @line.empty? || byte_size == 0
3022
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2366
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2367
+ unless current_line.empty? || byte_size == 0
2368
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
3023
2369
  copy_for_vi(mbchar)
3024
- width = Reline::Unicode.get_mbchar_width(mbchar)
3025
- @cursor_max -= width
3026
- if @cursor > 0 and @cursor >= @cursor_max
3027
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
3028
- mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
3029
- width = Reline::Unicode.get_mbchar_width(mbchar)
3030
- @byte_pointer -= byte_size
3031
- @cursor -= width
2370
+ if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size
2371
+ byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
2372
+ set_current_line(line, @byte_pointer - byte_size)
2373
+ else
2374
+ set_current_line(line, @byte_pointer)
3032
2375
  end
3033
2376
  end
3034
2377
  arg -= 1
@@ -3041,20 +2384,14 @@ class Reline::LineEditor
3041
2384
  end
3042
2385
  if @history_pointer.nil?
3043
2386
  @history_pointer = 0
3044
- @line_backup_in_history = @line
3045
- @line = Reline::HISTORY[@history_pointer]
3046
- @cursor_max = calculate_width(@line)
3047
- @cursor = 0
3048
- @byte_pointer = 0
2387
+ @line_backup_in_history = current_line
2388
+ set_current_line(Reline::HISTORY[@history_pointer], 0)
3049
2389
  elsif @history_pointer.zero?
3050
2390
  return
3051
2391
  else
3052
- Reline::HISTORY[@history_pointer] = @line
2392
+ Reline::HISTORY[@history_pointer] = current_line
3053
2393
  @history_pointer = 0
3054
- @line = Reline::HISTORY[@history_pointer]
3055
- @cursor_max = calculate_width(@line)
3056
- @cursor = 0
3057
- @byte_pointer = 0
2394
+ set_current_line(Reline::HISTORY[@history_pointer], 0)
3058
2395
  end
3059
2396
  end
3060
2397
 
@@ -3063,7 +2400,7 @@ class Reline::LineEditor
3063
2400
  if @is_multiline
3064
2401
  fp.write whole_lines.join("\n")
3065
2402
  else
3066
- fp.write @line
2403
+ fp.write current_line
3067
2404
  end
3068
2405
  fp.path
3069
2406
  }
@@ -3072,21 +2409,16 @@ class Reline::LineEditor
3072
2409
  @buffer_of_lines = File.read(path).split("\n")
3073
2410
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
3074
2411
  @line_index = 0
3075
- @line = @buffer_of_lines[@line_index]
3076
- @rerender_all = true
3077
2412
  else
3078
- @line = File.read(path)
2413
+ @buffer_of_lines = File.read(path).split("\n")
3079
2414
  end
3080
2415
  finish
3081
2416
  end
3082
2417
 
3083
2418
  private def vi_paste_prev(key, arg: 1)
3084
2419
  if @vi_clipboard.size > 0
3085
- @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
3086
- @cursor_max += calculate_width(@vi_clipboard)
3087
2420
  cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
3088
- @cursor += calculate_width(cursor_point)
3089
- @byte_pointer += cursor_point.bytesize
2421
+ set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
3090
2422
  end
3091
2423
  arg -= 1
3092
2424
  vi_paste_prev(key, arg: arg) if arg > 0
@@ -3094,11 +2426,9 @@ class Reline::LineEditor
3094
2426
 
3095
2427
  private def vi_paste_next(key, arg: 1)
3096
2428
  if @vi_clipboard.size > 0
3097
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3098
- @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
3099
- @cursor_max += calculate_width(@vi_clipboard)
3100
- @cursor += calculate_width(@vi_clipboard)
3101
- @byte_pointer += @vi_clipboard.bytesize
2429
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2430
+ line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard)
2431
+ set_current_line(line, @byte_pointer + @vi_clipboard.bytesize)
3102
2432
  end
3103
2433
  arg -= 1
3104
2434
  vi_paste_next(key, arg: arg) if arg > 0
@@ -3122,12 +2452,13 @@ class Reline::LineEditor
3122
2452
  end
3123
2453
 
3124
2454
  private def vi_to_column(key, arg: 0)
3125
- @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
2455
+ current_row_width = calculate_width(current_row)
2456
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |total, gc|
3126
2457
  # total has [byte_size, cursor]
3127
2458
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
3128
2459
  if (total.last + mbchar_width) >= arg
3129
2460
  break total
3130
- elsif (total.last + mbchar_width) >= @cursor_max
2461
+ elsif (total.last + mbchar_width) >= current_row_width
3131
2462
  break total
3132
2463
  else
3133
2464
  total = [total.first + gc.bytesize, total.last + mbchar_width]
@@ -3139,26 +2470,22 @@ class Reline::LineEditor
3139
2470
  private def vi_replace_char(key, arg: 1)
3140
2471
  @waiting_proc = ->(k) {
3141
2472
  if arg == 1
3142
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3143
- before = @line.byteslice(0, @byte_pointer)
2473
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2474
+ before = current_line.byteslice(0, @byte_pointer)
3144
2475
  remaining_point = @byte_pointer + byte_size
3145
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
3146
- @line = before + k.chr + after
3147
- @cursor_max = calculate_width(@line)
2476
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2477
+ set_current_line(before + k.chr + after)
3148
2478
  @waiting_proc = nil
3149
2479
  elsif arg > 1
3150
2480
  byte_size = 0
3151
2481
  arg.times do
3152
- byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
2482
+ byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size)
3153
2483
  end
3154
- before = @line.byteslice(0, @byte_pointer)
2484
+ before = current_line.byteslice(0, @byte_pointer)
3155
2485
  remaining_point = @byte_pointer + byte_size
3156
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2486
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
3157
2487
  replaced = k.chr * arg
3158
- @line = before + replaced + after
3159
- @byte_pointer += replaced.bytesize
3160
- @cursor += calculate_width(replaced)
3161
- @cursor_max = calculate_width(@line)
2488
+ set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
3162
2489
  @waiting_proc = nil
3163
2490
  end
3164
2491
  }
@@ -3181,7 +2508,7 @@ class Reline::LineEditor
3181
2508
  prev_total = nil
3182
2509
  total = nil
3183
2510
  found = false
3184
- @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
2511
+ current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
3185
2512
  # total has [byte_size, cursor]
3186
2513
  unless total
3187
2514
  # skip cursor point
@@ -3201,21 +2528,16 @@ class Reline::LineEditor
3201
2528
  end
3202
2529
  end
3203
2530
  if not need_prev_char and found and total
3204
- byte_size, width = total
2531
+ byte_size, _ = total
3205
2532
  @byte_pointer += byte_size
3206
- @cursor += width
3207
2533
  elsif need_prev_char and found and prev_total
3208
- byte_size, width = prev_total
2534
+ byte_size, _ = prev_total
3209
2535
  @byte_pointer += byte_size
3210
- @cursor += width
3211
2536
  end
3212
2537
  if inclusive
3213
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2538
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
3214
2539
  if byte_size > 0
3215
- c = @line.byteslice(@byte_pointer, byte_size)
3216
- width = Reline::Unicode.get_mbchar_width(c)
3217
2540
  @byte_pointer += byte_size
3218
- @cursor += width
3219
2541
  end
3220
2542
  end
3221
2543
  @waiting_proc = nil
@@ -3238,7 +2560,7 @@ class Reline::LineEditor
3238
2560
  prev_total = nil
3239
2561
  total = nil
3240
2562
  found = false
3241
- @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2563
+ current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
3242
2564
  # total has [byte_size, cursor]
3243
2565
  unless total
3244
2566
  # skip cursor point
@@ -3258,26 +2580,19 @@ class Reline::LineEditor
3258
2580
  end
3259
2581
  end
3260
2582
  if not need_next_char and found and total
3261
- byte_size, width = total
2583
+ byte_size, _ = total
3262
2584
  @byte_pointer -= byte_size
3263
- @cursor -= width
3264
2585
  elsif need_next_char and found and prev_total
3265
- byte_size, width = prev_total
2586
+ byte_size, _ = prev_total
3266
2587
  @byte_pointer -= byte_size
3267
- @cursor -= width
3268
2588
  end
3269
2589
  @waiting_proc = nil
3270
2590
  end
3271
2591
 
3272
2592
  private def vi_join_lines(key, arg: 1)
3273
2593
  if @is_multiline and @buffer_of_lines.size > @line_index + 1
3274
- @cursor = calculate_width(@line)
3275
- @byte_pointer = @line.bytesize
3276
- @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
3277
- @cursor_max = calculate_width(@line)
3278
- @buffer_of_lines[@line_index] = @line
3279
- @rerender_all = true
3280
- @rest_height += 1
2594
+ next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2595
+ set_current_line(current_line + ' ' + next_line, current_line.bytesize)
3281
2596
  end
3282
2597
  arg -= 1
3283
2598
  vi_join_lines(key, arg: arg) if arg > 0
@@ -3291,10 +2606,7 @@ class Reline::LineEditor
3291
2606
  private def em_exchange_mark(key)
3292
2607
  return unless @mark_pointer
3293
2608
  new_pointer = [@byte_pointer, @line_index]
3294
- @previous_line_index = @line_index
3295
2609
  @byte_pointer, @line_index = @mark_pointer
3296
- @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
3297
- @cursor_max = calculate_width(@line)
3298
2610
  @mark_pointer = new_pointer
3299
2611
  end
3300
2612
  alias_method :exchange_point_and_mark, :em_exchange_mark