reline 0.4.0 → 0.5.0.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,6 @@ require 'tempfile'
6
6
  class Reline::LineEditor
7
7
  # TODO: undo
8
8
  # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
9
- attr_reader :line
10
9
  attr_reader :byte_pointer
11
10
  attr_accessor :confirm_multiline_termination_proc
12
11
  attr_accessor :completion_proc
@@ -14,7 +13,6 @@ class Reline::LineEditor
14
13
  attr_accessor :output_modifier_proc
15
14
  attr_accessor :prompt_proc
16
15
  attr_accessor :auto_indent_proc
17
- attr_accessor :pre_input_hook
18
16
  attr_accessor :dig_perfect_match_proc
19
17
  attr_writer :output
20
18
 
@@ -57,6 +55,8 @@ class Reline::LineEditor
57
55
  def initialize(config, encoding)
58
56
  @config = config
59
57
  @completion_append_character = ''
58
+ @cursor_base_y = 0
59
+ @screen_size = Reline::IOGate.get_screen_size
60
60
  reset_variables(encoding: encoding)
61
61
  end
62
62
 
@@ -68,67 +68,30 @@ class Reline::LineEditor
68
68
  @in_pasting = in_pasting
69
69
  end
70
70
 
71
- def simplified_rendering?
72
- if finished?
73
- false
74
- elsif @just_cursor_moving and not @rerender_all
75
- true
76
- else
77
- not @rerender_all and not finished? and @in_pasting
78
- end
79
- end
80
-
81
71
  private def check_mode_string
82
- mode_string = nil
83
72
  if @config.show_mode_in_prompt
84
73
  if @config.editing_mode_is?(:vi_command)
85
- mode_string = @config.vi_cmd_mode_string
74
+ @config.vi_cmd_mode_string
86
75
  elsif @config.editing_mode_is?(:vi_insert)
87
- mode_string = @config.vi_ins_mode_string
76
+ @config.vi_ins_mode_string
88
77
  elsif @config.editing_mode_is?(:emacs)
89
- mode_string = @config.emacs_mode_string
78
+ @config.emacs_mode_string
90
79
  else
91
- mode_string = '?'
80
+ '?'
92
81
  end
93
82
  end
94
- if mode_string != @prev_mode_string
95
- @rerender_all = true
96
- end
97
- @prev_mode_string = mode_string
98
- mode_string
99
83
  end
100
84
 
101
- private def check_multiline_prompt(buffer, force_recalc: false)
85
+ private def check_multiline_prompt(buffer)
102
86
  if @vi_arg
103
87
  prompt = "(arg: #{@vi_arg}) "
104
- @rerender_all = true
105
88
  elsif @searching_prompt
106
89
  prompt = @searching_prompt
107
- @rerender_all = true
108
90
  else
109
91
  prompt = @prompt
110
92
  end
111
- if simplified_rendering? && !force_recalc
112
- mode_string = check_mode_string
113
- prompt = mode_string + prompt if mode_string
114
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
115
- end
116
93
  if @prompt_proc
117
- use_cached_prompt_list = false
118
- if @cached_prompt_list
119
- if @just_cursor_moving
120
- use_cached_prompt_list = true
121
- elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
122
- use_cached_prompt_list = true
123
- end
124
- end
125
- use_cached_prompt_list = false if @rerender_all
126
- if use_cached_prompt_list
127
- prompt_list = @cached_prompt_list
128
- else
129
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
130
- @prompt_cache_time = Time.now.to_f
131
- end
94
+ prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
132
95
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
133
96
  prompt_list = [prompt] if prompt_list.empty?
134
97
  mode_string = check_mode_string
@@ -141,20 +104,17 @@ class Reline::LineEditor
141
104
  prompt_list << prompt_list.last
142
105
  end
143
106
  end
144
- prompt_width = calculate_width(prompt, true)
145
- [prompt, prompt_width, prompt_list]
107
+ prompt_list
146
108
  else
147
109
  mode_string = check_mode_string
148
110
  prompt = mode_string + prompt if mode_string
149
- prompt_width = calculate_width(prompt, true)
150
- [prompt, prompt_width, nil]
111
+ [prompt] * buffer.size
151
112
  end
152
113
  end
153
114
 
154
115
  def reset(prompt = '', encoding:)
155
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
116
+ @cursor_base_y = Reline::IOGate.cursor_pos.y
156
117
  @screen_size = Reline::IOGate.get_screen_size
157
- @screen_height = @screen_size.first
158
118
  reset_variables(prompt, encoding: encoding)
159
119
  Reline::IOGate.set_winch_handler do
160
120
  @resized = true
@@ -184,54 +144,28 @@ class Reline::LineEditor
184
144
 
185
145
  def resize
186
146
  return unless @resized
187
- @resized = false
188
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
189
- old_screen_size = @screen_size
147
+
148
+ Reline::IOGate.hide_cursor
190
149
  @screen_size = Reline::IOGate.get_screen_size
191
- @screen_height = @screen_size.first
192
- if old_screen_size.last < @screen_size.last # columns increase
193
- @rerender_all = true
194
- rerender
195
- else
196
- back = 0
197
- new_buffer = whole_lines
198
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
199
- new_buffer.each_with_index do |line, index|
200
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
201
- width = prompt_width + calculate_width(line)
202
- height = calculate_height_by_width(width)
203
- back += height
204
- end
205
- @highest_in_all = back
206
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
207
- @first_line_started_from =
208
- if @line_index.zero?
209
- 0
210
- else
211
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
212
- end
213
- if @prompt_proc
214
- prompt = prompt_list[@line_index]
215
- prompt_width = calculate_width(prompt, true)
216
- end
217
- calculate_nearest_cursor
218
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
219
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
220
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
221
- @rerender_all = true
222
- end
150
+ @resized = false
151
+ scroll_into_view
152
+ Reline::IOGate.move_cursor_up @cursor_y
153
+ @cursor_base_y = Reline::IOGate.cursor_pos.y
154
+ @cursor_y = 0
155
+ @rendered_screen_cache = nil
156
+ render_differential
157
+ Reline::IOGate.show_cursor
223
158
  end
224
159
 
225
160
  def set_signal_handlers
226
161
  @old_trap = Signal.trap('INT') {
227
- clear_dialog(0)
228
- if @scroll_partial_screen
229
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
230
- else
231
- move_cursor_down(@highest_in_all - @line_index - 1)
232
- end
233
- Reline::IOGate.move_cursor_column(0)
234
- scroll_down(1)
162
+ Reline::IOGate.hide_cursor
163
+ clear_dialogs
164
+ scrolldown = render_differential
165
+ Reline::IOGate.scroll_down scrolldown
166
+ Reline::IOGate.move_cursor_column 0
167
+ @rendered_screen_cache = nil
168
+ Reline::IOGate.show_cursor
235
169
  case @old_trap
236
170
  when 'DEFAULT', 'SYSTEM_DEFAULT'
237
171
  raise Interrupt
@@ -260,7 +194,6 @@ class Reline::LineEditor
260
194
  @is_multiline = false
261
195
  @finished = false
262
196
  @cleared = false
263
- @rerender_all = false
264
197
  @history_pointer = nil
265
198
  @kill_ring ||= Reline::KillRing.new
266
199
  @vi_clipboard = ''
@@ -275,40 +208,27 @@ class Reline::LineEditor
275
208
  @first_prompt = true
276
209
  @searching_prompt = nil
277
210
  @first_char = true
278
- @add_newline_to_end_of_buffer = false
279
- @just_cursor_moving = nil
280
- @cached_prompt_list = nil
281
- @prompt_cache_time = nil
282
211
  @eof = false
283
212
  @continuous_insertion_buffer = String.new(encoding: @encoding)
284
- @scroll_partial_screen = nil
285
- @prev_mode_string = nil
213
+ @scroll_partial_screen = 0
286
214
  @drop_terminate_spaces = false
287
215
  @in_pasting = false
288
216
  @auto_indent_proc = nil
289
217
  @dialogs = []
290
- @previous_rendered_dialog_y = 0
291
- @last_key = nil
292
218
  @resized = false
219
+ @cursor_y = 0
220
+ @cache = {}
221
+ @rendered_screen_cache = nil
293
222
  reset_line
294
223
  end
295
224
 
296
225
  def reset_line
297
- @cursor = 0
298
- @cursor_max = 0
299
226
  @byte_pointer = 0
300
227
  @buffer_of_lines = [String.new(encoding: @encoding)]
301
228
  @line_index = 0
302
- @previous_line_index = nil
303
- @line = @buffer_of_lines[0]
304
- @first_line_started_from = 0
305
- @move_up = 0
306
- @started_from = 0
307
- @highest_in_this = 1
308
- @highest_in_all = 1
229
+ @cache.clear
309
230
  @line_backup_in_history = nil
310
231
  @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
311
- @check_new_auto_indent = false
312
232
  end
313
233
 
314
234
  def multiline_on
@@ -319,26 +239,27 @@ class Reline::LineEditor
319
239
  @is_multiline = false
320
240
  end
321
241
 
322
- private def calculate_height_by_lines(lines, prompt)
323
- result = 0
324
- prompt_list = prompt.is_a?(Array) ? prompt : nil
325
- lines.each_with_index { |line, i|
326
- prompt = prompt_list[i] if prompt_list and prompt_list[i]
327
- result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
328
- }
329
- result
330
- end
331
-
332
242
  private def insert_new_line(cursor_line, next_line)
333
- @line = cursor_line
334
243
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
335
- @previous_line_index = @line_index
244
+ @buffer_of_lines[@line_index] = cursor_line
336
245
  @line_index += 1
337
- @just_cursor_moving = false
338
- end
339
-
340
- private def calculate_height_by_width(width)
341
- width.div(@screen_size.last) + 1
246
+ @byte_pointer = 0
247
+ if @auto_indent_proc && !@in_pasting
248
+ if next_line.empty?
249
+ (
250
+ # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
251
+ indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
252
+ indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
253
+ indent = indent2 || indent1
254
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
255
+ )
256
+ process_auto_indent @line_index, add_newline: true
257
+ else
258
+ process_auto_indent @line_index - 1, cursor_dependent: false
259
+ process_auto_indent @line_index, add_newline: true # Need for compatibility
260
+ process_auto_indent @line_index, cursor_dependent: false
261
+ end
262
+ end
342
263
  end
343
264
 
344
265
  private def split_by_width(str, max_width)
@@ -346,41 +267,30 @@ class Reline::LineEditor
346
267
  end
347
268
 
348
269
  private def scroll_down(val)
349
- if val <= @rest_height
270
+ if @cursor_base_y + @cursor_y + val < screen_height
350
271
  Reline::IOGate.move_cursor_down(val)
351
- @rest_height -= val
272
+ @cursor_y += val
352
273
  else
353
- Reline::IOGate.move_cursor_down(@rest_height)
354
- Reline::IOGate.scroll_down(val - @rest_height)
355
- @rest_height = 0
356
- end
357
- end
358
-
359
- private def move_cursor_up(val)
360
- if val > 0
361
- Reline::IOGate.move_cursor_up(val)
362
- @rest_height += val
363
- elsif val < 0
364
- move_cursor_down(-val)
274
+ move = screen_height - @cursor_base_y - @cursor_y - 1
275
+ scroll = val - move
276
+ Reline::IOGate.scroll_down(move)
277
+ Reline::IOGate.scroll_down(scroll)
278
+ @cursor_y += move
279
+ @cursor_base_y = [@cursor_base_y - scroll, 0].max
365
280
  end
366
281
  end
367
282
 
368
- private def move_cursor_down(val)
369
- if val > 0
370
- Reline::IOGate.move_cursor_down(val)
371
- @rest_height -= val
372
- @rest_height = 0 if @rest_height < 0
373
- elsif val < 0
374
- move_cursor_up(-val)
375
- end
283
+ def current_byte_pointer_cursor
284
+ calculate_width(current_line.byteslice(0, @byte_pointer))
376
285
  end
377
286
 
378
- private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
287
+ private def calculate_nearest_cursor(cursor)
288
+ line_to_calc = current_line
379
289
  new_cursor_max = calculate_width(line_to_calc)
380
290
  new_cursor = 0
381
291
  new_byte_pointer = 0
382
292
  height = 1
383
- max_width = @screen_size.last
293
+ max_width = screen_width
384
294
  if @config.editing_mode_is?(:vi_command)
385
295
  last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
386
296
  if last_byte_size > 0
@@ -406,110 +316,246 @@ class Reline::LineEditor
406
316
  end
407
317
  new_byte_pointer += gc.bytesize
408
318
  end
409
- new_started_from = height - 1
410
- if update
411
- @cursor = new_cursor
412
- @cursor_max = new_cursor_max
413
- @started_from = new_started_from
414
- @byte_pointer = new_byte_pointer
415
- else
416
- [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
417
- end
319
+ @byte_pointer = new_byte_pointer
418
320
  end
419
321
 
420
- def rerender_all
421
- @rerender_all = true
422
- process_insert(force: true)
423
- rerender
322
+ def with_cache(key, *deps)
323
+ cached_deps, value = @cache[key]
324
+ if cached_deps != deps
325
+ @cache[key] = [deps, value = yield(*deps, cached_deps, value)]
326
+ end
327
+ value
424
328
  end
425
329
 
426
- def rerender
427
- return if @line.nil?
428
- if @menu_info
429
- scroll_down(@highest_in_all - @first_line_started_from)
430
- @rerender_all = true
330
+ def modified_lines
331
+ with_cache(__method__, whole_lines, finished?) do |whole, complete|
332
+ modify_lines(whole, complete)
431
333
  end
432
- if @menu_info
433
- show_menu
434
- @menu_info = nil
435
- end
436
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
437
- cursor_column = (prompt_width + @cursor) % @screen_size.last
438
- if @cleared
439
- clear_screen_buffer(prompt, prompt_list, prompt_width)
440
- @cleared = false
441
- return
334
+ end
335
+
336
+ def prompt_list
337
+ with_cache(__method__, whole_lines, @vi_arg, @searching_prompt) do |lines|
338
+ check_multiline_prompt(lines)
442
339
  end
443
- if @is_multiline and finished? and @scroll_partial_screen
444
- # Re-output all code higher than the screen when finished.
445
- Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
446
- Reline::IOGate.move_cursor_column(0)
447
- @scroll_partial_screen = nil
448
- new_lines = whole_lines
449
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
450
- modify_lines(new_lines).each_with_index do |line, index|
451
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
452
- Reline::IOGate.erase_after_cursor
340
+ end
341
+
342
+ def screen_height
343
+ @screen_size.first
344
+ end
345
+
346
+ def screen_width
347
+ @screen_size.last
348
+ end
349
+
350
+ def screen_scroll_top
351
+ @scroll_partial_screen
352
+ end
353
+
354
+ def wrapped_lines
355
+ with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
356
+ prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
357
+ cached_wraps = {}
358
+ if prev_width == width
359
+ prev_n.times do |i|
360
+ cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
361
+ end
362
+ end
363
+
364
+ n.times.map do |i|
365
+ prompt = prompts[i]
366
+ line = lines[i]
367
+ cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact
453
368
  end
454
- @output.flush
455
- clear_dialog(cursor_column)
456
- return
457
369
  end
458
- new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
459
- rendered = false
460
- if @add_newline_to_end_of_buffer
461
- clear_dialog_with_trap_key(cursor_column)
462
- rerender_added_newline(prompt, prompt_width, prompt_list)
463
- @add_newline_to_end_of_buffer = false
464
- else
465
- if @just_cursor_moving and not @rerender_all
466
- clear_dialog_with_trap_key(cursor_column)
467
- rendered = just_move_cursor
468
- @just_cursor_moving = false
469
- return
470
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
471
- clear_dialog_with_trap_key(cursor_column)
472
- rerender_changed_current_line
473
- @previous_line_index = nil
474
- rendered = true
475
- elsif @rerender_all
476
- rerender_all_lines
477
- @rerender_all = false
478
- rendered = true
370
+ end
371
+
372
+ def calculate_overlay_levels(overlay_levels)
373
+ levels = []
374
+ overlay_levels.each do |x, w, l|
375
+ levels.fill(l, x, w)
376
+ end
377
+ levels
378
+ end
379
+
380
+ def render_line_differential(old_items, new_items)
381
+ old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact)
382
+ new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
383
+ base_x = 0
384
+ new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
385
+ width = chunk.size
386
+ if level == :skip
387
+ # do nothing
388
+ elsif level == :blank
389
+ Reline::IOGate.move_cursor_column base_x
390
+ @output.write "\e[0m#{' ' * width}"
479
391
  else
392
+ x, w, content = new_items[level]
393
+ content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
394
+ Reline::IOGate.move_cursor_column base_x
395
+ @output.write "\e[0m#{content}\e[0m"
480
396
  end
397
+ base_x += width
481
398
  end
482
- if @is_multiline
483
- if finished?
484
- # Always rerender on finish because output_modifier_proc may return a different output.
485
- new_lines = whole_lines
486
- line = modify_lines(new_lines)[@line_index]
487
- clear_dialog(cursor_column)
488
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
489
- render_partial(prompt, prompt_width, line, @first_line_started_from)
490
- move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
491
- scroll_down(1)
492
- Reline::IOGate.move_cursor_column(0)
493
- Reline::IOGate.erase_after_cursor
494
- else
495
- if not rendered and not @in_pasting
496
- line = modify_lines(whole_lines)[@line_index]
497
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
498
- render_partial(prompt, prompt_width, line, @first_line_started_from)
499
- end
500
- render_dialog(cursor_column)
399
+ if old_levels.size > new_levels.size
400
+ Reline::IOGate.move_cursor_column new_levels.size
401
+ Reline::IOGate.erase_after_cursor
402
+ end
403
+ end
404
+
405
+ def editor_cursor_position
406
+ line = ' ' * calculate_width(prompt_list[@line_index], true) + whole_lines[@line_index].byteslice(0, @byte_pointer)
407
+ wrapped_line_before_cursor = split_by_width(line, screen_width).first.compact
408
+ editor_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
409
+ editor_cursor_x = calculate_width(wrapped_line_before_cursor.last)
410
+ [editor_cursor_x, editor_cursor_y]
411
+ end
412
+
413
+ def clear_dialogs
414
+ @dialogs.each do |dialog|
415
+ dialog.contents = nil
416
+ dialog.trap_key = nil
417
+ end
418
+ end
419
+
420
+ def update_dialogs(key = nil)
421
+ @dialog_initialzed = true
422
+ editor_cursor_x, editor_cursor_y = editor_cursor_position
423
+ @dialogs.each do |dialog|
424
+ dialog.trap_key = nil
425
+ update_each_dialog(dialog, editor_cursor_x, editor_cursor_y - screen_scroll_top, key)
426
+ end
427
+ end
428
+
429
+ def clear_rendered_lines
430
+ Reline::IOGate.move_cursor_up @cursor_y
431
+ Reline::IOGate.move_cursor_column 0
432
+
433
+ num_lines = @rendered_screen_cache&.size
434
+ return unless num_lines && num_lines >= 1
435
+
436
+ Reline::IOGate.move_cursor_down num_lines - 1
437
+ (num_lines - 1).times do
438
+ Reline::IOGate.erase_after_cursor
439
+ Reline::IOGate.move_cursor_up 1
440
+ end
441
+ Reline::IOGate.erase_after_cursor
442
+ @rendered_screen_cache = nil
443
+ end
444
+
445
+ def render_full_content
446
+ lines = @buffer_of_lines.size.times.map do |i|
447
+ line = prompt_list[i] + modified_lines[i]
448
+ wrapped_lines, = split_by_width(line, screen_width)
449
+ wrapped_lines.last.empty? ? "#{line} " : line
450
+ end
451
+ @output.puts lines.map { |l| "#{l}\r\n" }.join
452
+ end
453
+
454
+ def print_nomultiline_prompt(prompt)
455
+ return unless prompt && !@is_multiline
456
+
457
+ # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
458
+ @rendered_screen_cache = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
459
+ @output.write prompt
460
+ end
461
+
462
+ def render_differential
463
+ unless @dialog_initialzed
464
+ update_dialogs
465
+ end
466
+
467
+ editor_cursor_x, editor_cursor_y = editor_cursor_position
468
+
469
+ rendered_lines = @rendered_screen_cache || []
470
+ new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
471
+ [[0, Reline::Unicode.calculate_width(l, true), l]]
472
+ end
473
+ if @menu_info
474
+ @menu_info.list.sort!.each do |item|
475
+ new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
501
476
  end
502
- @buffer_of_lines[@line_index] = @line
503
- @rest_height = 0 if @scroll_partial_screen
504
- else
505
- line = modify_lines(whole_lines)[@line_index]
506
- render_partial(prompt, prompt_width, line, 0)
507
- if finished?
508
- scroll_down(1)
509
- Reline::IOGate.move_cursor_column(0)
477
+ @menu_info = nil # TODO: do not change state here
478
+ end
479
+
480
+ @dialogs.each_with_index do |dialog, index|
481
+ next unless dialog.contents
482
+
483
+ x_range, y_range = dialog_range dialog, editor_cursor_y - screen_scroll_top
484
+ y_range.each do |row|
485
+ next if row < 0 || row >= screen_height
486
+ dialog_rows = new_lines[row] ||= []
487
+ dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
488
+ end
489
+ end
490
+
491
+ num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
492
+ scroll_down(num_lines - 1 - @cursor_y) if (num_lines - 1 - @cursor_y) > 0
493
+ @cursor_y = num_lines - 1
494
+ num_lines.times do |i|
495
+ rendered_line = rendered_lines[i] || []
496
+ line_to_render = new_lines[i] || []
497
+ next if rendered_line == line_to_render
498
+
499
+ Reline::IOGate.move_cursor_down i - @cursor_y
500
+ @cursor_y = i
501
+ unless rendered_lines[i]
502
+ Reline::IOGate.move_cursor_column 0
510
503
  Reline::IOGate.erase_after_cursor
511
504
  end
505
+ render_line_differential(rendered_line, line_to_render)
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