reline 0.4.1 → 0.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)
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
512
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)
890
+ @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
1316
891
  line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n").last || String.new(encoding: @encoding)
1317
- @cursor_max = calculate_width(@line)
1318
- @cursor = calculate_width(line_to_pointer)
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").last
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
1168
+ end
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
1604
1175
  end
1605
- if @is_multiline and @auto_indent_proc and not simplified_rendering? and @line
1606
- process_auto_indent
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,19 +1983,12 @@ 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
@@ -2570,11 +2000,9 @@ class Reline::LineEditor
2570
2000
  # to the beginning of the current line.
2571
2001
  private def vi_kill_line_prev(key)
2572
2002
  if @byte_pointer > 0
2573
- @line, deleted = byteslice!(@line, 0, @byte_pointer)
2574
- @byte_pointer = 0
2003
+ line, deleted = byteslice!(current_line, 0, @byte_pointer)
2004
+ set_current_line(line, 0)
2575
2005
  @kill_ring.append(deleted, true)
2576
- @cursor_max = calculate_width(@line)
2577
- @cursor = 0
2578
2006
  end
2579
2007
  end
2580
2008
  alias_method :unix_line_discard, :vi_kill_line_prev
@@ -2584,45 +2012,30 @@ class Reline::LineEditor
2584
2012
  # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
2585
2013
  # current line, no matter where point is.
2586
2014
  private def em_kill_line(key)
2587
- if @line.size > 0
2588
- @kill_ring.append(@line.dup, true)
2589
- @line.clear
2590
- @byte_pointer = 0
2591
- @cursor_max = 0
2592
- @cursor = 0
2015
+ if current_line.size > 0
2016
+ @kill_ring.append(current_line.dup, true)
2017
+ set_current_line('', 0)
2593
2018
  end
2594
2019
  end
2595
2020
  alias_method :kill_whole_line, :em_kill_line
2596
2021
 
2597
2022
  private def em_delete(key)
2598
- if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
2599
- @line = nil
2600
- if @buffer_of_lines.size > 1
2601
- scroll_down(@highest_in_all - @first_line_started_from)
2602
- end
2603
- 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
2604
2024
  @eof = true
2605
2025
  finish
2606
- elsif @byte_pointer < @line.bytesize
2607
- 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)
2608
2028
  mbchar = splitted_last.grapheme_clusters.first
2609
- width = Reline::Unicode.get_mbchar_width(mbchar)
2610
- @cursor_max -= width
2611
- @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
2612
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2613
- @cursor = calculate_width(@line)
2614
- @byte_pointer = @line.bytesize
2615
- @line += @buffer_of_lines.delete_at(@line_index + 1)
2616
- @cursor_max = calculate_width(@line)
2617
- @buffer_of_lines[@line_index] = @line
2618
- @rerender_all = true
2619
- @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)
2620
2033
  end
2621
2034
  end
2622
2035
  alias_method :delete_char, :em_delete
2623
2036
 
2624
2037
  private def em_delete_or_list(key)
2625
- if @line.empty? or @byte_pointer < @line.bytesize
2038
+ if current_line.empty? or @byte_pointer < current_line.bytesize
2626
2039
  em_delete(key)
2627
2040
  else # show completed list
2628
2041
  result = call_completion_proc
@@ -2635,29 +2048,16 @@ class Reline::LineEditor
2635
2048
 
2636
2049
  private def em_yank(key)
2637
2050
  yanked = @kill_ring.yank
2638
- if yanked
2639
- @line = byteinsert(@line, @byte_pointer, yanked)
2640
- yanked_width = calculate_width(yanked)
2641
- @cursor += yanked_width
2642
- @cursor_max += yanked_width
2643
- @byte_pointer += yanked.bytesize
2644
- end
2051
+ insert_text(yanked) if yanked
2645
2052
  end
2646
2053
  alias_method :yank, :em_yank
2647
2054
 
2648
2055
  private def em_yank_pop(key)
2649
2056
  yanked, prev_yank = @kill_ring.yank_pop
2650
2057
  if yanked
2651
- prev_yank_width = calculate_width(prev_yank)
2652
- @cursor -= prev_yank_width
2653
- @cursor_max -= prev_yank_width
2654
- @byte_pointer -= prev_yank.bytesize
2655
- @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
2656
- @line = byteinsert(@line, @byte_pointer, yanked)
2657
- yanked_width = calculate_width(yanked)
2658
- @cursor += yanked_width
2659
- @cursor_max += yanked_width
2660
- @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)
2661
2061
  end
2662
2062
  end
2663
2063
  alias_method :yank_pop, :em_yank_pop
@@ -2668,131 +2068,112 @@ class Reline::LineEditor
2668
2068
  alias_method :clear_screen, :ed_clear_screen
2669
2069
 
2670
2070
  private def em_next_word(key)
2671
- if @line.bytesize > @byte_pointer
2672
- 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)
2673
2073
  @byte_pointer += byte_size
2674
- @cursor += width
2675
2074
  end
2676
2075
  end
2677
2076
  alias_method :forward_word, :em_next_word
2678
2077
 
2679
2078
  private def ed_prev_word(key)
2680
2079
  if @byte_pointer > 0
2681
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2080
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2682
2081
  @byte_pointer -= byte_size
2683
- @cursor -= width
2684
2082
  end
2685
2083
  end
2686
2084
  alias_method :backward_word, :ed_prev_word
2687
2085
 
2688
2086
  private def em_delete_next_word(key)
2689
- if @line.bytesize > @byte_pointer
2690
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2691
- @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)
2692
2091
  @kill_ring.append(word)
2693
- @cursor_max -= width
2694
2092
  end
2695
2093
  end
2696
2094
  alias_method :kill_word, :em_delete_next_word
2697
2095
 
2698
2096
  private def ed_delete_prev_word(key)
2699
2097
  if @byte_pointer > 0
2700
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2701
- @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)
2702
2101
  @kill_ring.append(word, true)
2703
- @byte_pointer -= byte_size
2704
- @cursor -= width
2705
- @cursor_max -= width
2706
2102
  end
2707
2103
  end
2708
2104
  alias_method :backward_kill_word, :ed_delete_prev_word
2709
2105
 
2710
2106
  private def ed_transpose_chars(key)
2711
2107
  if @byte_pointer > 0
2712
- if @cursor_max > @cursor
2713
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2714
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2715
- width = Reline::Unicode.get_mbchar_width(mbchar)
2716
- @cursor += width
2108
+ if @byte_pointer < current_line.bytesize
2109
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2717
2110
  @byte_pointer += byte_size
2718
2111
  end
2719
- 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)
2720
2113
  if (@byte_pointer - back1_byte_size) > 0
2721
- 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)
2722
2115
  back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
2723
- @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
2724
- @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))
2725
2118
  end
2726
2119
  end
2727
2120
  end
2728
2121
  alias_method :transpose_chars, :ed_transpose_chars
2729
2122
 
2730
2123
  private def ed_transpose_words(key)
2731
- left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
2732
- before = @line.byteslice(0, left_word_start)
2733
- left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
2734
- middle = @line.byteslice(middle_start, right_word_start - middle_start)
2735
- right_word = @line.byteslice(right_word_start, after_start - right_word_start)
2736
- 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)
2737
2130
  return if left_word.empty? or right_word.empty?
2738
- @line = before + right_word + middle + left_word + after
2739
2131
  from_head_to_left_word = before + right_word + middle + left_word
2740
- @byte_pointer = from_head_to_left_word.bytesize
2741
- @cursor = calculate_width(from_head_to_left_word)
2132
+ set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
2742
2133
  end
2743
2134
  alias_method :transpose_words, :ed_transpose_words
2744
2135
 
2745
2136
  private def em_capitol_case(key)
2746
- if @line.bytesize > @byte_pointer
2747
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
2748
- before = @line.byteslice(0, @byte_pointer)
2749
- after = @line.byteslice((@byte_pointer + byte_size)..-1)
2750
- @line = before + new_str + after
2751
- @byte_pointer += new_str.bytesize
2752
- @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)
2753
2142
  end
2754
2143
  end
2755
2144
  alias_method :capitalize_word, :em_capitol_case
2756
2145
 
2757
2146
  private def em_lower_case(key)
2758
- if @line.bytesize > @byte_pointer
2759
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2760
- 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|
2761
2150
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2762
2151
  }.join
2763
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2764
- @line = @line.byteslice(0, @byte_pointer) + part
2765
- @byte_pointer = @line.bytesize
2766
- @cursor = calculate_width(@line)
2767
- @cursor_max = @cursor + calculate_width(rest)
2768
- @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)
2769
2155
  end
2770
2156
  end
2771
2157
  alias_method :downcase_word, :em_lower_case
2772
2158
 
2773
2159
  private def em_upper_case(key)
2774
- if @line.bytesize > @byte_pointer
2775
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2776
- 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|
2777
2163
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2778
2164
  }.join
2779
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2780
- @line = @line.byteslice(0, @byte_pointer) + part
2781
- @byte_pointer = @line.bytesize
2782
- @cursor = calculate_width(@line)
2783
- @cursor_max = @cursor + calculate_width(rest)
2784
- @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)
2785
2168
  end
2786
2169
  end
2787
2170
  alias_method :upcase_word, :em_upper_case
2788
2171
 
2789
2172
  private def em_kill_region(key)
2790
2173
  if @byte_pointer > 0
2791
- byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
2792
- @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2793
- @byte_pointer -= byte_size
2794
- @cursor -= width
2795
- @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)
2796
2177
  @kill_ring.append(deleted, true)
2797
2178
  end
2798
2179
  end
@@ -2820,10 +2201,9 @@ class Reline::LineEditor
2820
2201
  alias_method :vi_movement_mode, :vi_command_mode
2821
2202
 
2822
2203
  private def vi_next_word(key, arg: 1)
2823
- if @line.bytesize > @byte_pointer
2824
- 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)
2825
2206
  @byte_pointer += byte_size
2826
- @cursor += width
2827
2207
  end
2828
2208
  arg -= 1
2829
2209
  vi_next_word(key, arg: arg) if arg > 0
@@ -2831,38 +2211,32 @@ class Reline::LineEditor
2831
2211
 
2832
2212
  private def vi_prev_word(key, arg: 1)
2833
2213
  if @byte_pointer > 0
2834
- byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
2214
+ byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2835
2215
  @byte_pointer -= byte_size
2836
- @cursor -= width
2837
2216
  end
2838
2217
  arg -= 1
2839
2218
  vi_prev_word(key, arg: arg) if arg > 0
2840
2219
  end
2841
2220
 
2842
2221
  private def vi_end_word(key, arg: 1, inclusive: false)
2843
- if @line.bytesize > @byte_pointer
2844
- 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)
2845
2224
  @byte_pointer += byte_size
2846
- @cursor += width
2847
2225
  end
2848
2226
  arg -= 1
2849
2227
  if inclusive and arg.zero?
2850
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2228
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2851
2229
  if byte_size > 0
2852
- c = @line.byteslice(@byte_pointer, byte_size)
2853
- width = Reline::Unicode.get_mbchar_width(c)
2854
2230
  @byte_pointer += byte_size
2855
- @cursor += width
2856
2231
  end
2857
2232
  end
2858
2233
  vi_end_word(key, arg: arg) if arg > 0
2859
2234
  end
2860
2235
 
2861
2236
  private def vi_next_big_word(key, arg: 1)
2862
- if @line.bytesize > @byte_pointer
2863
- 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)
2864
2239
  @byte_pointer += byte_size
2865
- @cursor += width
2866
2240
  end
2867
2241
  arg -= 1
2868
2242
  vi_next_big_word(key, arg: arg) if arg > 0
@@ -2870,50 +2244,39 @@ class Reline::LineEditor
2870
2244
 
2871
2245
  private def vi_prev_big_word(key, arg: 1)
2872
2246
  if @byte_pointer > 0
2873
- 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)
2874
2248
  @byte_pointer -= byte_size
2875
- @cursor -= width
2876
2249
  end
2877
2250
  arg -= 1
2878
2251
  vi_prev_big_word(key, arg: arg) if arg > 0
2879
2252
  end
2880
2253
 
2881
2254
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2882
- if @line.bytesize > @byte_pointer
2883
- 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)
2884
2257
  @byte_pointer += byte_size
2885
- @cursor += width
2886
2258
  end
2887
2259
  arg -= 1
2888
2260
  if inclusive and arg.zero?
2889
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2261
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2890
2262
  if byte_size > 0
2891
- c = @line.byteslice(@byte_pointer, byte_size)
2892
- width = Reline::Unicode.get_mbchar_width(c)
2893
2263
  @byte_pointer += byte_size
2894
- @cursor += width
2895
2264
  end
2896
2265
  end
2897
2266
  vi_end_big_word(key, arg: arg) if arg > 0
2898
2267
  end
2899
2268
 
2900
2269
  private def vi_delete_prev_char(key)
2901
- if @is_multiline and @cursor == 0 and @line_index > 0
2902
- @buffer_of_lines[@line_index] = @line
2903
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2270
+ if @is_multiline and @byte_pointer == 0 and @line_index > 0
2904
2271
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2905
2272
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2906
2273
  @line_index -= 1
2907
- @line = @buffer_of_lines[@line_index]
2908
- @cursor_max = calculate_width(@line)
2909
- @rerender_all = true
2910
- elsif @cursor > 0
2911
- 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)
2912
2277
  @byte_pointer -= byte_size
2913
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2914
- width = Reline::Unicode.get_mbchar_width(mbchar)
2915
- @cursor -= width
2916
- @cursor_max -= width
2278
+ line, _ = byteslice!(current_line, @byte_pointer, byte_size)
2279
+ set_current_line(line)
2917
2280
  end
2918
2281
  end
2919
2282
 
@@ -2930,14 +2293,12 @@ class Reline::LineEditor
2930
2293
  private def ed_delete_prev_char(key, arg: 1)
2931
2294
  deleted = ''
2932
2295
  arg.times do
2933
- if @cursor > 0
2934
- 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)
2935
2298
  @byte_pointer -= byte_size
2936
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2299
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
2300
+ set_current_line(line)
2937
2301
  deleted.prepend(mbchar)
2938
- width = Reline::Unicode.get_mbchar_width(mbchar)
2939
- @cursor -= width
2940
- @cursor_max -= width
2941
2302
  end
2942
2303
  end
2943
2304
  copy_for_vi(deleted)
@@ -2945,20 +2306,18 @@ class Reline::LineEditor
2945
2306
 
2946
2307
  private def vi_zero(key)
2947
2308
  @byte_pointer = 0
2948
- @cursor = 0
2949
2309
  end
2950
2310
 
2951
2311
  private def vi_change_meta(key, arg: 1)
2952
2312
  @drop_terminate_spaces = true
2953
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2313
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2954
2314
  if byte_pointer_diff > 0
2955
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2315
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2956
2316
  elsif byte_pointer_diff < 0
2957
- @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)
2958
2318
  end
2319
+ set_current_line(line)
2959
2320
  copy_for_vi(cut)
2960
- @cursor += cursor_diff if cursor_diff < 0
2961
- @cursor_max -= cursor_diff.abs
2962
2321
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2963
2322
  @config.editing_mode = :vi_insert
2964
2323
  @drop_terminate_spaces = false
@@ -2967,26 +2326,24 @@ class Reline::LineEditor
2967
2326
  end
2968
2327
 
2969
2328
  private def vi_delete_meta(key, arg: 1)
2970
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2329
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2971
2330
  if byte_pointer_diff > 0
2972
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2331
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2973
2332
  elsif byte_pointer_diff < 0
2974
- @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)
2975
2334
  end
2976
2335
  copy_for_vi(cut)
2977
- @cursor += cursor_diff if cursor_diff < 0
2978
- @cursor_max -= cursor_diff.abs
2979
- @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))
2980
2337
  }
2981
2338
  @waiting_operator_vi_arg = arg
2982
2339
  end
2983
2340
 
2984
2341
  private def vi_yank(key, arg: 1)
2985
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2342
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2986
2343
  if byte_pointer_diff > 0
2987
- cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2344
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2988
2345
  elsif byte_pointer_diff < 0
2989
- cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2346
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2990
2347
  end
2991
2348
  copy_for_vi(cut)
2992
2349
  }
@@ -2994,12 +2351,8 @@ class Reline::LineEditor
2994
2351
  end
2995
2352
 
2996
2353
  private def vi_list_or_eof(key)
2997
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2998
- @line = nil
2999
- if @buffer_of_lines.size > 1
3000
- scroll_down(@highest_in_all - @first_line_started_from)
3001
- end
3002
- 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)
3003
2356
  @eof = true
3004
2357
  finish
3005
2358
  else
@@ -3010,18 +2363,15 @@ class Reline::LineEditor
3010
2363
  alias_method :vi_eof_maybe, :vi_list_or_eof
3011
2364
 
3012
2365
  private def ed_delete_next_char(key, arg: 1)
3013
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3014
- unless @line.empty? || byte_size == 0
3015
- @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)
3016
2369
  copy_for_vi(mbchar)
3017
- width = Reline::Unicode.get_mbchar_width(mbchar)
3018
- @cursor_max -= width
3019
- if @cursor > 0 and @cursor >= @cursor_max
3020
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
3021
- mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
3022
- width = Reline::Unicode.get_mbchar_width(mbchar)
3023
- @byte_pointer -= byte_size
3024
- @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)
3025
2375
  end
3026
2376
  end
3027
2377
  arg -= 1
@@ -3034,20 +2384,14 @@ class Reline::LineEditor
3034
2384
  end
3035
2385
  if @history_pointer.nil?
3036
2386
  @history_pointer = 0
3037
- @line_backup_in_history = @line
3038
- @line = Reline::HISTORY[@history_pointer]
3039
- @cursor_max = calculate_width(@line)
3040
- @cursor = 0
3041
- @byte_pointer = 0
2387
+ @line_backup_in_history = current_line
2388
+ set_current_line(Reline::HISTORY[@history_pointer], 0)
3042
2389
  elsif @history_pointer.zero?
3043
2390
  return
3044
2391
  else
3045
- Reline::HISTORY[@history_pointer] = @line
2392
+ Reline::HISTORY[@history_pointer] = current_line
3046
2393
  @history_pointer = 0
3047
- @line = Reline::HISTORY[@history_pointer]
3048
- @cursor_max = calculate_width(@line)
3049
- @cursor = 0
3050
- @byte_pointer = 0
2394
+ set_current_line(Reline::HISTORY[@history_pointer], 0)
3051
2395
  end
3052
2396
  end
3053
2397
 
@@ -3056,7 +2400,7 @@ class Reline::LineEditor
3056
2400
  if @is_multiline
3057
2401
  fp.write whole_lines.join("\n")
3058
2402
  else
3059
- fp.write @line
2403
+ fp.write current_line
3060
2404
  end
3061
2405
  fp.path
3062
2406
  }
@@ -3065,21 +2409,16 @@ class Reline::LineEditor
3065
2409
  @buffer_of_lines = File.read(path).split("\n")
3066
2410
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
3067
2411
  @line_index = 0
3068
- @line = @buffer_of_lines[@line_index]
3069
- @rerender_all = true
3070
2412
  else
3071
- @line = File.read(path)
2413
+ @buffer_of_lines = File.read(path).split("\n")
3072
2414
  end
3073
2415
  finish
3074
2416
  end
3075
2417
 
3076
2418
  private def vi_paste_prev(key, arg: 1)
3077
2419
  if @vi_clipboard.size > 0
3078
- @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
3079
- @cursor_max += calculate_width(@vi_clipboard)
3080
2420
  cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
3081
- @cursor += calculate_width(cursor_point)
3082
- @byte_pointer += cursor_point.bytesize
2421
+ set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
3083
2422
  end
3084
2423
  arg -= 1
3085
2424
  vi_paste_prev(key, arg: arg) if arg > 0
@@ -3087,11 +2426,9 @@ class Reline::LineEditor
3087
2426
 
3088
2427
  private def vi_paste_next(key, arg: 1)
3089
2428
  if @vi_clipboard.size > 0
3090
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3091
- @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
3092
- @cursor_max += calculate_width(@vi_clipboard)
3093
- @cursor += calculate_width(@vi_clipboard)
3094
- @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)
3095
2432
  end
3096
2433
  arg -= 1
3097
2434
  vi_paste_next(key, arg: arg) if arg > 0
@@ -3115,12 +2452,13 @@ class Reline::LineEditor
3115
2452
  end
3116
2453
 
3117
2454
  private def vi_to_column(key, arg: 0)
3118
- @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|
3119
2457
  # total has [byte_size, cursor]
3120
2458
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
3121
2459
  if (total.last + mbchar_width) >= arg
3122
2460
  break total
3123
- elsif (total.last + mbchar_width) >= @cursor_max
2461
+ elsif (total.last + mbchar_width) >= current_row_width
3124
2462
  break total
3125
2463
  else
3126
2464
  total = [total.first + gc.bytesize, total.last + mbchar_width]
@@ -3132,26 +2470,22 @@ class Reline::LineEditor
3132
2470
  private def vi_replace_char(key, arg: 1)
3133
2471
  @waiting_proc = ->(k) {
3134
2472
  if arg == 1
3135
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3136
- 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)
3137
2475
  remaining_point = @byte_pointer + byte_size
3138
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
3139
- @line = before + k.chr + after
3140
- @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)
3141
2478
  @waiting_proc = nil
3142
2479
  elsif arg > 1
3143
2480
  byte_size = 0
3144
2481
  arg.times do
3145
- 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)
3146
2483
  end
3147
- before = @line.byteslice(0, @byte_pointer)
2484
+ before = current_line.byteslice(0, @byte_pointer)
3148
2485
  remaining_point = @byte_pointer + byte_size
3149
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2486
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
3150
2487
  replaced = k.chr * arg
3151
- @line = before + replaced + after
3152
- @byte_pointer += replaced.bytesize
3153
- @cursor += calculate_width(replaced)
3154
- @cursor_max = calculate_width(@line)
2488
+ set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
3155
2489
  @waiting_proc = nil
3156
2490
  end
3157
2491
  }
@@ -3174,7 +2508,7 @@ class Reline::LineEditor
3174
2508
  prev_total = nil
3175
2509
  total = nil
3176
2510
  found = false
3177
- @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
2511
+ current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
3178
2512
  # total has [byte_size, cursor]
3179
2513
  unless total
3180
2514
  # skip cursor point
@@ -3194,21 +2528,16 @@ class Reline::LineEditor
3194
2528
  end
3195
2529
  end
3196
2530
  if not need_prev_char and found and total
3197
- byte_size, width = total
2531
+ byte_size, _ = total
3198
2532
  @byte_pointer += byte_size
3199
- @cursor += width
3200
2533
  elsif need_prev_char and found and prev_total
3201
- byte_size, width = prev_total
2534
+ byte_size, _ = prev_total
3202
2535
  @byte_pointer += byte_size
3203
- @cursor += width
3204
2536
  end
3205
2537
  if inclusive
3206
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2538
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
3207
2539
  if byte_size > 0
3208
- c = @line.byteslice(@byte_pointer, byte_size)
3209
- width = Reline::Unicode.get_mbchar_width(c)
3210
2540
  @byte_pointer += byte_size
3211
- @cursor += width
3212
2541
  end
3213
2542
  end
3214
2543
  @waiting_proc = nil
@@ -3231,7 +2560,7 @@ class Reline::LineEditor
3231
2560
  prev_total = nil
3232
2561
  total = nil
3233
2562
  found = false
3234
- @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2563
+ current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
3235
2564
  # total has [byte_size, cursor]
3236
2565
  unless total
3237
2566
  # skip cursor point
@@ -3251,26 +2580,19 @@ class Reline::LineEditor
3251
2580
  end
3252
2581
  end
3253
2582
  if not need_next_char and found and total
3254
- byte_size, width = total
2583
+ byte_size, _ = total
3255
2584
  @byte_pointer -= byte_size
3256
- @cursor -= width
3257
2585
  elsif need_next_char and found and prev_total
3258
- byte_size, width = prev_total
2586
+ byte_size, _ = prev_total
3259
2587
  @byte_pointer -= byte_size
3260
- @cursor -= width
3261
2588
  end
3262
2589
  @waiting_proc = nil
3263
2590
  end
3264
2591
 
3265
2592
  private def vi_join_lines(key, arg: 1)
3266
2593
  if @is_multiline and @buffer_of_lines.size > @line_index + 1
3267
- @cursor = calculate_width(@line)
3268
- @byte_pointer = @line.bytesize
3269
- @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
3270
- @cursor_max = calculate_width(@line)
3271
- @buffer_of_lines[@line_index] = @line
3272
- @rerender_all = true
3273
- @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)
3274
2596
  end
3275
2597
  arg -= 1
3276
2598
  vi_join_lines(key, arg: arg) if arg > 0
@@ -3284,10 +2606,7 @@ class Reline::LineEditor
3284
2606
  private def em_exchange_mark(key)
3285
2607
  return unless @mark_pointer
3286
2608
  new_pointer = [@byte_pointer, @line_index]
3287
- @previous_line_index = @line_index
3288
2609
  @byte_pointer, @line_index = @mark_pointer
3289
- @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
3290
- @cursor_max = calculate_width(@line)
3291
2610
  @mark_pointer = new_pointer
3292
2611
  end
3293
2612
  alias_method :exchange_point_and_mark, :em_exchange_mark