reline 0.4.3 → 0.5.0

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
 
@@ -48,15 +46,17 @@ class Reline::LineEditor
48
46
  PERFECT_MATCH = :perfect_match
49
47
  end
50
48
 
49
+ RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
50
+
51
51
  CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
52
52
  MenuInfo = Struct.new(:target, :list)
53
53
 
54
- PROMPT_LIST_CACHE_TIMEOUT = 0.5
55
54
  MINIMUM_SCROLLBAR_HEIGHT = 1
56
55
 
57
56
  def initialize(config, encoding)
58
57
  @config = config
59
58
  @completion_append_character = ''
59
+ @screen_size = Reline::IOGate.get_screen_size
60
60
  reset_variables(encoding: encoding)
61
61
  end
62
62
 
@@ -65,73 +65,38 @@ class Reline::LineEditor
65
65
  end
66
66
 
67
67
  def set_pasting_state(in_pasting)
68
+ # While pasting, text to be inserted is stored to @continuous_insertion_buffer.
69
+ # After pasting, this buffer should be force inserted.
70
+ process_insert(force: true) if @in_pasting && !in_pasting
68
71
  @in_pasting = in_pasting
69
72
  end
70
73
 
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
74
  private def check_mode_string
82
- mode_string = nil
83
75
  if @config.show_mode_in_prompt
84
76
  if @config.editing_mode_is?(:vi_command)
85
- mode_string = @config.vi_cmd_mode_string
77
+ @config.vi_cmd_mode_string
86
78
  elsif @config.editing_mode_is?(:vi_insert)
87
- mode_string = @config.vi_ins_mode_string
79
+ @config.vi_ins_mode_string
88
80
  elsif @config.editing_mode_is?(:emacs)
89
- mode_string = @config.emacs_mode_string
81
+ @config.emacs_mode_string
90
82
  else
91
- mode_string = '?'
83
+ '?'
92
84
  end
93
85
  end
94
- if mode_string != @prev_mode_string
95
- @rerender_all = true
96
- end
97
- @prev_mode_string = mode_string
98
- mode_string
99
86
  end
100
87
 
101
- private def check_multiline_prompt(buffer, force_recalc: false)
88
+ private def check_multiline_prompt(buffer, mode_string)
102
89
  if @vi_arg
103
90
  prompt = "(arg: #{@vi_arg}) "
104
- @rerender_all = true
105
91
  elsif @searching_prompt
106
92
  prompt = @searching_prompt
107
- @rerender_all = true
108
93
  else
109
94
  prompt = @prompt
110
95
  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
96
  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
97
+ prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
132
98
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
133
99
  prompt_list = [prompt] if prompt_list.empty?
134
- mode_string = check_mode_string
135
100
  prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
136
101
  prompt = prompt_list[@line_index]
137
102
  prompt = prompt_list[0] if prompt.nil?
@@ -141,21 +106,17 @@ class Reline::LineEditor
141
106
  prompt_list << prompt_list.last
142
107
  end
143
108
  end
144
- prompt_width = calculate_width(prompt, true)
145
- [prompt, prompt_width, prompt_list]
109
+ prompt_list
146
110
  else
147
- mode_string = check_mode_string
148
111
  prompt = mode_string + prompt if mode_string
149
- prompt_width = calculate_width(prompt, true)
150
- [prompt, prompt_width, nil]
112
+ [prompt] * buffer.size
151
113
  end
152
114
  end
153
115
 
154
116
  def reset(prompt = '', encoding:)
155
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - 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)
119
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
159
120
  Reline::IOGate.set_winch_handler do
160
121
  @resized = true
161
122
  end
@@ -184,54 +145,25 @@ class Reline::LineEditor
184
145
 
185
146
  def resize
186
147
  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
148
+
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 @rendered_screen.cursor_y
153
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
154
+ @rendered_screen.lines = []
155
+ @rendered_screen.cursor_y = 0
156
+ render_differential
223
157
  end
224
158
 
225
159
  def set_signal_handlers
226
160
  @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)
161
+ clear_dialogs
162
+ scrolldown = render_differential
163
+ Reline::IOGate.scroll_down scrolldown
164
+ Reline::IOGate.move_cursor_column 0
165
+ @rendered_screen.lines = []
166
+ @rendered_screen.cursor_y = 0
235
167
  case @old_trap
236
168
  when 'DEFAULT', 'SYSTEM_DEFAULT'
237
169
  raise Interrupt
@@ -260,7 +192,6 @@ class Reline::LineEditor
260
192
  @is_multiline = false
261
193
  @finished = false
262
194
  @cleared = false
263
- @rerender_all = false
264
195
  @history_pointer = nil
265
196
  @kill_ring ||= Reline::KillRing.new
266
197
  @vi_clipboard = ''
@@ -272,43 +203,29 @@ class Reline::LineEditor
272
203
  @completion_state = CompletionState::NORMAL
273
204
  @perfect_matched = nil
274
205
  @menu_info = nil
275
- @first_prompt = true
276
206
  @searching_prompt = nil
277
207
  @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
208
+ @just_cursor_moving = false
282
209
  @eof = false
283
210
  @continuous_insertion_buffer = String.new(encoding: @encoding)
284
- @scroll_partial_screen = nil
285
- @prev_mode_string = nil
211
+ @scroll_partial_screen = 0
286
212
  @drop_terminate_spaces = false
287
213
  @in_pasting = false
288
214
  @auto_indent_proc = nil
289
215
  @dialogs = []
290
- @previous_rendered_dialog_y = 0
291
- @last_key = nil
292
216
  @resized = false
217
+ @cache = {}
218
+ @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
293
219
  reset_line
294
220
  end
295
221
 
296
222
  def reset_line
297
- @cursor = 0
298
- @cursor_max = 0
299
223
  @byte_pointer = 0
300
224
  @buffer_of_lines = [String.new(encoding: @encoding)]
301
225
  @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
226
+ @cache.clear
309
227
  @line_backup_in_history = nil
310
228
  @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
311
- @check_new_auto_indent = false
312
229
  end
313
230
 
314
231
  def multiline_on
@@ -319,68 +236,44 @@ class Reline::LineEditor
319
236
  @is_multiline = false
320
237
  end
321
238
 
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
239
  private def insert_new_line(cursor_line, next_line)
333
- @line = cursor_line
334
240
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
335
- @previous_line_index = @line_index
241
+ @buffer_of_lines[@line_index] = cursor_line
336
242
  @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
243
+ @byte_pointer = 0
244
+ if @auto_indent_proc && !@in_pasting
245
+ if next_line.empty?
246
+ (
247
+ # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
248
+ indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
249
+ indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
250
+ indent = indent2 || indent1
251
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
252
+ )
253
+ process_auto_indent @line_index, add_newline: true
254
+ else
255
+ process_auto_indent @line_index - 1, cursor_dependent: false
256
+ process_auto_indent @line_index, add_newline: true # Need for compatibility
257
+ process_auto_indent @line_index, cursor_dependent: false
258
+ end
259
+ end
342
260
  end
343
261
 
344
262
  private def split_by_width(str, max_width)
345
263
  Reline::Unicode.split_by_width(str, max_width, @encoding)
346
264
  end
347
265
 
348
- private def scroll_down(val)
349
- if val <= @rest_height
350
- Reline::IOGate.move_cursor_down(val)
351
- @rest_height -= val
352
- 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)
365
- end
366
- end
367
-
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
266
+ def current_byte_pointer_cursor
267
+ calculate_width(current_line.byteslice(0, @byte_pointer))
376
268
  end
377
269
 
378
- private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
270
+ private def calculate_nearest_cursor(cursor)
271
+ line_to_calc = current_line
379
272
  new_cursor_max = calculate_width(line_to_calc)
380
273
  new_cursor = 0
381
274
  new_byte_pointer = 0
382
275
  height = 1
383
- max_width = @screen_size.last
276
+ max_width = screen_width
384
277
  if @config.editing_mode_is?(:vi_command)
385
278
  last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
386
279
  if last_byte_size > 0
@@ -406,110 +299,241 @@ class Reline::LineEditor
406
299
  end
407
300
  new_byte_pointer += gc.bytesize
408
301
  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]
302
+ @byte_pointer = new_byte_pointer
303
+ end
304
+
305
+ def with_cache(key, *deps)
306
+ cached_deps, value = @cache[key]
307
+ if cached_deps != deps
308
+ @cache[key] = [deps, value = yield(*deps, cached_deps, value)]
417
309
  end
310
+ value
418
311
  end
419
312
 
420
- def rerender_all
421
- @rerender_all = true
422
- process_insert(force: true)
423
- rerender
313
+ def modified_lines
314
+ with_cache(__method__, whole_lines, finished?) do |whole, complete|
315
+ modify_lines(whole, complete)
316
+ end
424
317
  end
425
318
 
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
319
+ def prompt_list
320
+ with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string|
321
+ check_multiline_prompt(lines, mode_string)
431
322
  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
323
+ end
324
+
325
+ def screen_height
326
+ @screen_size.first
327
+ end
328
+
329
+ def screen_width
330
+ @screen_size.last
331
+ end
332
+
333
+ def screen_scroll_top
334
+ @scroll_partial_screen
335
+ end
336
+
337
+ def wrapped_lines
338
+ with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
339
+ prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
340
+ cached_wraps = {}
341
+ if prev_width == width
342
+ prev_n.times do |i|
343
+ cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
344
+ end
345
+ end
346
+
347
+ n.times.map do |i|
348
+ prompt = prompts[i]
349
+ line = lines[i]
350
+ cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact
351
+ end
442
352
  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
453
- end
454
- @output.flush
455
- clear_dialog(cursor_column)
456
- return
353
+ end
354
+
355
+ def calculate_overlay_levels(overlay_levels)
356
+ levels = []
357
+ overlay_levels.each do |x, w, l|
358
+ levels.fill(l, x, w)
457
359
  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
360
+ levels
361
+ end
362
+
363
+ def render_line_differential(old_items, new_items)
364
+ 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)
365
+ new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
366
+ base_x = 0
367
+ new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
368
+ width = chunk.size
369
+ if level == :skip
370
+ # do nothing
371
+ elsif level == :blank
372
+ Reline::IOGate.move_cursor_column base_x
373
+ @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
479
374
  else
375
+ x, w, content = new_items[level]
376
+ content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
377
+ Reline::IOGate.move_cursor_column base_x
378
+ @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
480
379
  end
380
+ base_x += width
481
381
  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)
382
+ if old_levels.size > new_levels.size
383
+ Reline::IOGate.move_cursor_column new_levels.size
384
+ Reline::IOGate.erase_after_cursor
385
+ end
386
+ end
387
+
388
+ # Calculate cursor position in word wrapped content.
389
+ def wrapped_cursor_position
390
+ prompt_width = calculate_width(prompt_list[@line_index], true)
391
+ line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
392
+ wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
393
+ wrapped_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
394
+ wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
395
+ [wrapped_cursor_x, wrapped_cursor_y]
396
+ end
397
+
398
+ def clear_dialogs
399
+ @dialogs.each do |dialog|
400
+ dialog.contents = nil
401
+ dialog.trap_key = nil
402
+ end
403
+ end
404
+
405
+ def update_dialogs(key = nil)
406
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
407
+ @dialogs.each do |dialog|
408
+ dialog.trap_key = nil
409
+ update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key)
410
+ end
411
+ end
412
+
413
+ def render_finished
414
+ clear_rendered_lines
415
+ render_full_content
416
+ end
417
+
418
+ def clear_rendered_lines
419
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
420
+ Reline::IOGate.move_cursor_column 0
421
+
422
+ num_lines = @rendered_screen.lines.size
423
+ return unless num_lines && num_lines >= 1
424
+
425
+ Reline::IOGate.move_cursor_down num_lines - 1
426
+ (num_lines - 1).times do
427
+ Reline::IOGate.erase_after_cursor
428
+ Reline::IOGate.move_cursor_up 1
429
+ end
430
+ Reline::IOGate.erase_after_cursor
431
+ @rendered_screen.lines = []
432
+ @rendered_screen.cursor_y = 0
433
+ end
434
+
435
+ def render_full_content
436
+ lines = @buffer_of_lines.size.times.map do |i|
437
+ line = prompt_list[i] + modified_lines[i]
438
+ wrapped_lines, = split_by_width(line, screen_width)
439
+ wrapped_lines.last.empty? ? "#{line} " : line
440
+ end
441
+ @output.puts lines.map { |l| "#{l}\r\n" }.join
442
+ end
443
+
444
+ def print_nomultiline_prompt(prompt)
445
+ return unless prompt && !@is_multiline
446
+
447
+ # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
448
+ @rendered_screen.lines = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
449
+ @rendered_screen.cursor_y = 0
450
+ @output.write prompt
451
+ end
452
+
453
+ def render_differential
454
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
455
+
456
+ rendered_lines = @rendered_screen.lines
457
+ new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
458
+ [[0, Reline::Unicode.calculate_width(l, true), l]]
459
+ end
460
+ if @menu_info
461
+ @menu_info.list.sort!.each do |item|
462
+ new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
501
463
  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)
510
- Reline::IOGate.erase_after_cursor
464
+ @menu_info = nil # TODO: do not change state here
465
+ end
466
+
467
+ @dialogs.each_with_index do |dialog, index|
468
+ next unless dialog.contents
469
+
470
+ x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
471
+ y_range.each do |row|
472
+ next if row < 0 || row >= screen_height
473
+ dialog_rows = new_lines[row] ||= []
474
+ dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
475
+ end
476
+ end
477
+
478
+ cursor_y = @rendered_screen.cursor_y
479
+ if new_lines != rendered_lines
480
+ # Hide cursor while rendering to avoid cursor flickering.
481
+ Reline::IOGate.hide_cursor
482
+ num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
483
+ if @rendered_screen.base_y + num_lines > screen_height
484
+ Reline::IOGate.scroll_down(num_lines - cursor_y - 1)
485
+ @rendered_screen.base_y = screen_height - num_lines
486
+ cursor_y = num_lines - 1
487
+ end
488
+ num_lines.times do |i|
489
+ rendered_line = rendered_lines[i] || []
490
+ line_to_render = new_lines[i] || []
491
+ next if rendered_line == line_to_render
492
+
493
+ Reline::IOGate.move_cursor_down i - cursor_y
494
+ cursor_y = i
495
+ unless rendered_lines[i]
496
+ Reline::IOGate.move_cursor_column 0
497
+ Reline::IOGate.erase_after_cursor
498
+ end
499
+ render_line_differential(rendered_line, line_to_render)
511
500
  end
501
+ @rendered_screen.lines = new_lines
502
+ Reline::IOGate.show_cursor
512
503
  end
504
+ y = wrapped_cursor_y - screen_scroll_top
505
+ Reline::IOGate.move_cursor_column wrapped_cursor_x
506
+ Reline::IOGate.move_cursor_down y - cursor_y
507
+ @rendered_screen.cursor_y = y
508
+ new_lines.size - y
509
+ end
510
+
511
+ def current_row
512
+ wrapped_lines.flatten[wrapped_cursor_y]
513
+ end
514
+
515
+ def upper_space_height(wrapped_cursor_y)
516
+ wrapped_cursor_y - screen_scroll_top
517
+ end
518
+
519
+ def rest_height(wrapped_cursor_y)
520
+ screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
521
+ end
522
+
523
+ def handle_cleared
524
+ return unless @cleared
525
+
526
+ @cleared = false
527
+ Reline::IOGate.clear_screen
528
+ @screen_size = Reline::IOGate.get_screen_size
529
+ @rendered_screen.lines = []
530
+ @rendered_screen.base_y = 0
531
+ @rendered_screen.cursor_y = 0
532
+ end
533
+
534
+ def rerender
535
+ handle_cleared
536
+ render_differential unless @in_pasting
513
537
  end
514
538
 
515
539
  class DialogProcScope
@@ -563,17 +587,16 @@ class Reline::LineEditor
563
587
  end
564
588
 
565
589
  def screen_width
566
- @line_editor.instance_variable_get(:@screen_size).last
590
+ @line_editor.screen_width
567
591
  end
568
592
 
569
593
  def screen_height
570
- @line_editor.instance_variable_get(:@screen_size).first
594
+ @line_editor.screen_height
571
595
  end
572
596
 
573
597
  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
598
+ _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position
599
+ [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max
577
600
  end
578
601
 
579
602
  def completion_journey_data
@@ -646,14 +669,6 @@ class Reline::LineEditor
646
669
  end
647
670
 
648
671
  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
672
 
658
673
  private def padding_space_with_escape_sequences(str, width)
659
674
  padding_width = width - calculate_width(str, true)
@@ -662,118 +677,15 @@ class Reline::LineEditor
662
677
  str + (' ' * padding_width)
663
678
  end
664
679
 
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
680
  private def dialog_range(dialog, dialog_y)
672
681
  x_range = dialog.column...dialog.column + dialog.width
673
682
  y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
674
683
  [x_range, y_range]
675
684
  end
676
685
 
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)
686
+ private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil)
687
+ dialog.set_cursor_pos(cursor_column, cursor_row)
688
+ dialog_render_info = dialog.call(key)
777
689
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
778
690
  dialog.contents = nil
779
691
  dialog.trap_key = nil
@@ -813,23 +725,22 @@ class Reline::LineEditor
813
725
  else
814
726
  scrollbar_pos = nil
815
727
  end
816
- upper_space = @first_line_started_from - @started_from
817
728
  dialog.column = dialog_render_info.pos.x
818
729
  dialog.width += @block_elem_width if scrollbar_pos
819
- diff = (dialog.column + dialog.width) - (@screen_size.last)
730
+ diff = (dialog.column + dialog.width) - screen_width
820
731
  if diff > 0
821
732
  dialog.column -= diff
822
733
  end
823
- if (@rest_height - dialog_render_info.pos.y) >= height
734
+ if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height
824
735
  dialog.vertical_offset = dialog_render_info.pos.y + 1
825
- elsif upper_space >= height
736
+ elsif cursor_row >= height
826
737
  dialog.vertical_offset = dialog_render_info.pos.y - height
827
738
  else
828
739
  dialog.vertical_offset = dialog_render_info.pos.y + 1
829
740
  end
830
741
  if dialog.column < 0
831
742
  dialog.column = 0
832
- dialog.width = @screen_size.last
743
+ dialog.width = screen_width
833
744
  end
834
745
  face = Reline::Face[dialog_render_info.face || :default]
835
746
  scrollbar_sgr = face[:scrollbar]
@@ -856,373 +767,14 @@ class Reline::LineEditor
856
767
  end
857
768
  end
858
769
 
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?)
770
+ private def modify_lines(before, complete)
771
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
1186
772
  after.lines("\n").map { |l| l.chomp('') }
1187
773
  else
1188
774
  before
1189
775
  end
1190
776
  end
1191
777
 
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
778
  def editing_mode
1227
779
  @config.editing_mode
1228
780
  end
@@ -1256,7 +808,7 @@ class Reline::LineEditor
1256
808
  item_mbchars = item.grapheme_clusters
1257
809
  end
1258
810
  size = [memo_mbchars.size, item_mbchars.size].min
1259
- result = ''
811
+ result = +''
1260
812
  size.times do |i|
1261
813
  if @config.completion_ignore_case
1262
814
  if memo_mbchars[i].casecmp?(item_mbchars[i])
@@ -1312,10 +864,8 @@ class Reline::LineEditor
1312
864
  @completion_state = CompletionState::MENU
1313
865
  end
1314
866
  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)
867
+ @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
1316
868
  line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
1317
- @cursor_max = calculate_width(@line)
1318
- @cursor = calculate_width(line_to_pointer)
1319
869
  @byte_pointer = line_to_pointer.bytesize
1320
870
  end
1321
871
  end
@@ -1358,35 +908,31 @@ class Reline::LineEditor
1358
908
  end
1359
909
  end
1360
910
  completed = @completion_journey_data.list[@completion_journey_data.pointer]
1361
- new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
1362
- @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
1363
- line_to_pointer = (@completion_journey_data.preposing + completed).split("\n")[@line_index]
1364
- line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
1365
- @cursor_max = calculate_width(@line)
1366
- @cursor = calculate_width(line_to_pointer)
1367
- @byte_pointer = line_to_pointer.bytesize
911
+ line_to_pointer = (@completion_journey_data.preposing + completed).split("\n")[@line_index] || String.new(encoding: @encoding)
912
+ new_line = line_to_pointer + (@completion_journey_data.postposing.split("\n").first || '')
913
+ set_current_line(new_line, line_to_pointer.bytesize)
1368
914
  end
1369
915
 
1370
916
  private def run_for_operators(key, method_symbol, &block)
1371
917
  if @waiting_operator_proc
1372
918
  if VI_MOTIONS.include?(method_symbol)
1373
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
919
+ old_byte_pointer = @byte_pointer
1374
920
  @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
1375
921
  block.(true)
1376
922
  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)
923
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
924
+ @byte_pointer = old_byte_pointer
925
+ @waiting_operator_proc.(byte_pointer_diff)
1380
926
  else
1381
927
  old_waiting_proc = @waiting_proc
1382
928
  old_waiting_operator_proc = @waiting_operator_proc
1383
929
  current_waiting_operator_proc = @waiting_operator_proc
1384
930
  @waiting_proc = proc { |k|
1385
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
931
+ old_byte_pointer = @byte_pointer
1386
932
  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)
933
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
934
+ @byte_pointer = old_byte_pointer
935
+ current_waiting_operator_proc.(byte_pointer_diff)
1390
936
  @waiting_operator_proc = old_waiting_operator_proc
1391
937
  }
1392
938
  end
@@ -1397,7 +943,6 @@ class Reline::LineEditor
1397
943
  @waiting_operator_proc = nil
1398
944
  @waiting_operator_vi_arg = nil
1399
945
  if @vi_arg
1400
- @rerender_all = true
1401
946
  @vi_arg = nil
1402
947
  end
1403
948
  else
@@ -1451,7 +996,6 @@ class Reline::LineEditor
1451
996
  end
1452
997
  @kill_ring.process
1453
998
  if @vi_arg
1454
- @rerender_al = true
1455
999
  @vi_arg = nil
1456
1000
  end
1457
1001
  elsif @vi_arg
@@ -1471,7 +1015,6 @@ class Reline::LineEditor
1471
1015
  end
1472
1016
  @kill_ring.process
1473
1017
  if @vi_arg
1474
- @rerender_all = true
1475
1018
  @vi_arg = nil
1476
1019
  end
1477
1020
  end
@@ -1493,7 +1036,6 @@ class Reline::LineEditor
1493
1036
  end
1494
1037
 
1495
1038
  private def normal_char(key)
1496
- method_symbol = method_obj = nil
1497
1039
  if key.combined_char.is_a?(Symbol)
1498
1040
  process_key(key.combined_char, key.combined_char)
1499
1041
  return
@@ -1523,32 +1065,37 @@ class Reline::LineEditor
1523
1065
  end
1524
1066
  @multibyte_buffer.clear
1525
1067
  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)
1068
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1069
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1528
1070
  @byte_pointer -= byte_size
1529
- mbchar = @line.byteslice(@byte_pointer, byte_size)
1530
- width = Reline::Unicode.get_mbchar_width(mbchar)
1531
- @cursor -= width
1071
+ end
1072
+ end
1073
+
1074
+ def update(key)
1075
+ modified = input_key(key)
1076
+ unless @in_pasting
1077
+ scroll_into_view
1078
+ @just_cursor_moving = !modified
1079
+ update_dialogs(key)
1080
+ @just_cursor_moving = false
1532
1081
  end
1533
1082
  end
1534
1083
 
1535
1084
  def input_key(key)
1536
- @last_key = key
1537
1085
  @config.reset_oneshot_key_bindings
1538
1086
  @dialogs.each do |dialog|
1539
1087
  if key.char.instance_of?(Symbol) and key.char == dialog.name
1540
1088
  return
1541
1089
  end
1542
1090
  end
1543
- @just_cursor_moving = nil
1544
1091
  if key.char.nil?
1545
1092
  if @first_char
1546
- @line = nil
1093
+ @eof = true
1547
1094
  end
1548
1095
  finish
1549
1096
  return
1550
1097
  end
1551
- old_line = @line.dup
1098
+ old_lines = @buffer_of_lines.dup
1552
1099
  @first_char = false
1553
1100
  completion_occurs = false
1554
1101
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -1591,19 +1138,20 @@ class Reline::LineEditor
1591
1138
  @completion_state = CompletionState::NORMAL
1592
1139
  @completion_journey_data = nil
1593
1140
  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
1141
+ if @in_pasting
1142
+ clear_dialogs
1602
1143
  else
1603
- @just_cursor_moving = false
1144
+ return old_lines != @buffer_of_lines
1145
+ end
1146
+ end
1147
+
1148
+ def scroll_into_view
1149
+ _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
1150
+ if wrapped_cursor_y < screen_scroll_top
1151
+ @scroll_partial_screen = wrapped_cursor_y
1604
1152
  end
1605
- if @is_multiline and @auto_indent_proc and not simplified_rendering? and @line
1606
- process_auto_indent
1153
+ if wrapped_cursor_y >= screen_scroll_top + screen_height
1154
+ @scroll_partial_screen = wrapped_cursor_y - screen_height + 1
1607
1155
  end
1608
1156
  end
1609
1157
 
@@ -1637,43 +1185,39 @@ class Reline::LineEditor
1637
1185
  result
1638
1186
  end
1639
1187
 
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
1188
+ private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false)
1189
+ return if @in_pasting
1190
+ return unless @auto_indent_proc
1191
+
1192
+ line = @buffer_of_lines[line_index]
1193
+ byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize
1194
+ new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
1195
+ return unless new_indent
1196
+
1197
+ @buffer_of_lines[line_index] = ' ' * new_indent + line.lstrip
1198
+ if @line_index == line_index
1199
+ old_indent = line[/\A */].size
1200
+ @byte_pointer = [@byte_pointer + new_indent - old_indent, 0].max
1201
+ end
1202
+ end
1203
+
1204
+ def line()
1205
+ current_line unless eof?
1206
+ end
1207
+
1208
+ def current_line
1209
+ @buffer_of_lines[@line_index]
1210
+ end
1211
+
1212
+ def set_current_line(line, byte_pointer = nil)
1213
+ cursor = current_byte_pointer_cursor
1214
+ @buffer_of_lines[@line_index] = line
1215
+ if byte_pointer
1216
+ @byte_pointer = byte_pointer
1217
+ else
1218
+ calculate_nearest_cursor(cursor)
1675
1219
  end
1676
- @check_new_auto_indent = false
1220
+ process_auto_indent
1677
1221
  end
1678
1222
 
1679
1223
  def retrieve_completion_block(set_completion_quote_character = false)
@@ -1687,7 +1231,7 @@ class Reline::LineEditor
1687
1231
  else
1688
1232
  quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1689
1233
  end
1690
- before = @line.byteslice(0, @byte_pointer)
1234
+ before = current_line.byteslice(0, @byte_pointer)
1691
1235
  rest = nil
1692
1236
  break_pointer = nil
1693
1237
  quote = nil
@@ -1695,7 +1239,7 @@ class Reline::LineEditor
1695
1239
  escaped_quote = nil
1696
1240
  i = 0
1697
1241
  while i < @byte_pointer do
1698
- slice = @line.byteslice(i, @byte_pointer - i)
1242
+ slice = current_line.byteslice(i, @byte_pointer - i)
1699
1243
  unless slice.valid_encoding?
1700
1244
  i += 1
1701
1245
  next
@@ -1717,15 +1261,15 @@ class Reline::LineEditor
1717
1261
  elsif word_break_regexp and not quote and slice =~ word_break_regexp
1718
1262
  rest = $'
1719
1263
  i += 1
1720
- before = @line.byteslice(i, @byte_pointer - i)
1264
+ before = current_line.byteslice(i, @byte_pointer - i)
1721
1265
  break_pointer = i
1722
1266
  else
1723
1267
  i += 1
1724
1268
  end
1725
1269
  end
1726
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1270
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1727
1271
  if rest
1728
- preposing = @line.byteslice(0, break_pointer)
1272
+ preposing = current_line.byteslice(0, break_pointer)
1729
1273
  target = rest
1730
1274
  if set_completion_quote_character and quote
1731
1275
  Reline.core.instance_variable_set(:@completion_quote_character, quote)
@@ -1736,7 +1280,7 @@ class Reline::LineEditor
1736
1280
  else
1737
1281
  preposing = ''
1738
1282
  if break_pointer
1739
- preposing = @line.byteslice(0, break_pointer)
1283
+ preposing = current_line.byteslice(0, break_pointer)
1740
1284
  else
1741
1285
  preposing = ''
1742
1286
  end
@@ -1756,106 +1300,67 @@ class Reline::LineEditor
1756
1300
 
1757
1301
  def confirm_multiline_termination
1758
1302
  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
1303
  @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1765
1304
  end
1766
1305
 
1767
1306
  def insert_text(text)
1768
- width = calculate_width(text)
1769
- if @cursor == @cursor_max
1770
- @line += text
1307
+ if @buffer_of_lines[@line_index].bytesize == @byte_pointer
1308
+ @buffer_of_lines[@line_index] += text
1771
1309
  else
1772
- @line = byteinsert(@line, @byte_pointer, text)
1310
+ @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text)
1773
1311
  end
1774
1312
  @byte_pointer += text.bytesize
1775
- @cursor += width
1776
- @cursor_max += width
1313
+ process_auto_indent
1777
1314
  end
1778
1315
 
1779
1316
  def delete_text(start = nil, length = nil)
1780
1317
  if start.nil? and length.nil?
1781
1318
  if @is_multiline
1782
1319
  if @buffer_of_lines.size == 1
1783
- @line&.clear
1320
+ @buffer_of_lines[@line_index] = ''
1784
1321
  @byte_pointer = 0
1785
- @cursor = 0
1786
- @cursor_max = 0
1787
1322
  elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1788
1323
  @buffer_of_lines.pop
1789
1324
  @line_index -= 1
1790
- @line = @buffer_of_lines[@line_index]
1791
1325
  @byte_pointer = 0
1792
- @cursor = 0
1793
- @cursor_max = calculate_width(@line)
1794
1326
  elsif @line_index < (@buffer_of_lines.size - 1)
1795
1327
  @buffer_of_lines.delete_at(@line_index)
1796
- @line = @buffer_of_lines[@line_index]
1797
1328
  @byte_pointer = 0
1798
- @cursor = 0
1799
- @cursor_max = calculate_width(@line)
1800
1329
  end
1801
1330
  else
1802
- @line&.clear
1803
- @byte_pointer = 0
1804
- @cursor = 0
1805
- @cursor_max = 0
1331
+ set_current_line('', 0)
1806
1332
  end
1807
1333
  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)
1334
+ if current_line
1335
+ before = current_line.byteslice(0, start)
1336
+ after = current_line.byteslice(start + length, current_line.bytesize)
1337
+ set_current_line(before + after)
1816
1338
  end
1817
1339
  elsif start.is_a?(Range)
1818
1340
  range = start
1819
1341
  first = range.first
1820
1342
  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
1343
+ last = current_line.bytesize - 1 if last > current_line.bytesize
1344
+ last += current_line.bytesize if last < 0
1345
+ first += current_line.bytesize if first < 0
1824
1346
  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)
1347
+ line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1348
+ set_current_line(line)
1830
1349
  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)
1350
+ set_current_line(current_line.byteslice(0, start))
1836
1351
  end
1837
1352
  end
1838
1353
 
1839
1354
  def byte_pointer=(val)
1840
1355
  @byte_pointer = val
1841
- str = @line.byteslice(0, @byte_pointer)
1842
- @cursor = calculate_width(str)
1843
- @cursor_max = calculate_width(@line)
1844
1356
  end
1845
1357
 
1846
1358
  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
1359
+ @buffer_of_lines.dup
1851
1360
  end
1852
1361
 
1853
1362
  def whole_buffer
1854
- if @buffer_of_lines.size == 1 and @line.nil?
1855
- nil
1856
- else
1857
- whole_lines.join("\n")
1858
- end
1363
+ whole_lines.join("\n")
1859
1364
  end
1860
1365
 
1861
1366
  def finished?
@@ -1864,7 +1369,6 @@ class Reline::LineEditor
1864
1369
 
1865
1370
  def finish
1866
1371
  @finished = true
1867
- @rerender_all = true
1868
1372
  @config.reset
1869
1373
  end
1870
1374
 
@@ -1895,14 +1399,9 @@ class Reline::LineEditor
1895
1399
 
1896
1400
  private def key_newline(key)
1897
1401
  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)
1402
+ next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1403
+ cursor_line = current_line.byteslice(0, @byte_pointer)
1903
1404
  insert_new_line(cursor_line, next_line)
1904
- @cursor = 0
1905
- @check_new_auto_indent = true unless @in_pasting
1906
1405
  end
1907
1406
  end
1908
1407
 
@@ -1912,16 +1411,7 @@ class Reline::LineEditor
1912
1411
 
1913
1412
  private def process_insert(force: false)
1914
1413
  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
1414
+ insert_text(@continuous_insertion_buffer)
1925
1415
  @continuous_insertion_buffer.clear
1926
1416
  end
1927
1417
 
@@ -1939,9 +1429,6 @@ class Reline::LineEditor
1939
1429
  # million.
1940
1430
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1941
1431
  private def ed_insert(key)
1942
- str = nil
1943
- width = nil
1944
- bytesize = nil
1945
1432
  if key.instance_of?(String)
1946
1433
  begin
1947
1434
  key.encode(Encoding::UTF_8)
@@ -1949,7 +1436,6 @@ class Reline::LineEditor
1949
1436
  return
1950
1437
  end
1951
1438
  str = key
1952
- bytesize = key.bytesize
1953
1439
  else
1954
1440
  begin
1955
1441
  key.chr.encode(Encoding::UTF_8)
@@ -1957,7 +1443,6 @@ class Reline::LineEditor
1957
1443
  return
1958
1444
  end
1959
1445
  str = key.chr
1960
- bytesize = 1
1961
1446
  end
1962
1447
  if @in_pasting
1963
1448
  @continuous_insertion_buffer << str
@@ -1965,28 +1450,8 @@ class Reline::LineEditor
1965
1450
  elsif not @continuous_insertion_buffer.empty?
1966
1451
  process_insert
1967
1452
  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
1453
+
1454
+ insert_text(str)
1990
1455
  end
1991
1456
  alias_method :ed_digit, :ed_insert
1992
1457
  alias_method :self_insert, :ed_insert
@@ -2008,18 +1473,11 @@ class Reline::LineEditor
2008
1473
  alias_method :quoted_insert, :ed_quoted_insert
2009
1474
 
2010
1475
  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
1476
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1477
+ if (@byte_pointer < current_line.bytesize)
2016
1478
  @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
1479
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
2020
1480
  @byte_pointer = 0
2021
- @cursor_max = calculate_width(next_line)
2022
- @previous_line_index = @line_index
2023
1481
  @line_index += 1
2024
1482
  end
2025
1483
  arg -= 1
@@ -2028,19 +1486,12 @@ class Reline::LineEditor
2028
1486
  alias_method :forward_char, :ed_next_char
2029
1487
 
2030
1488
  private def ed_prev_char(key, arg: 1)
2031
- if @cursor > 0
2032
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1489
+ if @byte_pointer > 0
1490
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2033
1491
  @byte_pointer -= byte_size
2034
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2035
- width = Reline::Unicode.get_mbchar_width(mbchar)
2036
- @cursor -= width
2037
1492
  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
1493
  @line_index -= 1
1494
+ @byte_pointer = current_line.bytesize
2044
1495
  end
2045
1496
  arg -= 1
2046
1497
  ed_prev_char(key, arg: arg) if arg > 0
@@ -2048,24 +1499,18 @@ class Reline::LineEditor
2048
1499
  alias_method :backward_char, :ed_prev_char
2049
1500
 
2050
1501
  private def vi_first_print(key)
2051
- @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
1502
+ @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
2052
1503
  end
2053
1504
 
2054
1505
  private def ed_move_to_beg(key)
2055
- @byte_pointer = @cursor = 0
1506
+ @byte_pointer = 0
2056
1507
  end
2057
1508
  alias_method :beginning_of_line, :ed_move_to_beg
2058
1509
 
2059
1510
  private def ed_move_to_end(key)
2060
1511
  @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
1512
+ while @byte_pointer < current_line.bytesize
1513
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2069
1514
  @byte_pointer += byte_size
2070
1515
  end
2071
1516
  end
@@ -2167,19 +1612,16 @@ class Reline::LineEditor
2167
1612
  @buffer_of_lines = hit.split("\n")
2168
1613
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2169
1614
  @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
1615
+ @byte_pointer = current_line.bytesize
2174
1616
  @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
2175
1617
  else
2176
- @line = hit
1618
+ @buffer_of_lines = [hit]
1619
+ @byte_pointer = hit.bytesize
2177
1620
  @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
2178
1621
  end
2179
1622
  last_hit = hit
2180
1623
  else
2181
1624
  if @is_multiline
2182
- @rerender_all = true
2183
1625
  @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
2184
1626
  else
2185
1627
  @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
@@ -2194,7 +1636,7 @@ class Reline::LineEditor
2194
1636
  if @is_multiline
2195
1637
  @line_backup_in_history = whole_buffer
2196
1638
  else
2197
- @line_backup_in_history = @line
1639
+ @line_backup_in_history = current_line
2198
1640
  end
2199
1641
  end
2200
1642
  searcher = generate_searcher
@@ -2214,35 +1656,26 @@ class Reline::LineEditor
2214
1656
  @buffer_of_lines = buffer.split("\n")
2215
1657
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2216
1658
  @line_index = @buffer_of_lines.size - 1
2217
- @line = @buffer_of_lines.last
2218
- @rerender_all = true
2219
1659
  else
2220
- @line = buffer
1660
+ @buffer_of_lines = [buffer]
2221
1661
  end
2222
1662
  @searching_prompt = nil
2223
1663
  @waiting_proc = nil
2224
- @cursor_max = calculate_width(@line)
2225
- @cursor = @byte_pointer = 0
2226
- @rerender_all = true
2227
- @cached_prompt_list = nil
1664
+ @byte_pointer = 0
2228
1665
  searcher.resume(-1)
2229
1666
  when "\C-g".ord
2230
1667
  if @is_multiline
2231
1668
  @buffer_of_lines = @line_backup_in_history.split("\n")
2232
1669
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2233
1670
  @line_index = @buffer_of_lines.size - 1
2234
- @line = @buffer_of_lines.last
2235
- @rerender_all = true
2236
1671
  else
2237
- @line = @line_backup_in_history
1672
+ @buffer_of_lines = [@line_backup_in_history]
2238
1673
  end
2239
1674
  @history_pointer = nil
2240
1675
  @searching_prompt = nil
2241
1676
  @waiting_proc = nil
2242
1677
  @line_backup_in_history = nil
2243
- @cursor_max = calculate_width(@line)
2244
- @cursor = @byte_pointer = 0
2245
- @rerender_all = true
1678
+ @byte_pointer = 0
2246
1679
  else
2247
1680
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
2248
1681
  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 +1691,13 @@ class Reline::LineEditor
2258
1691
  @buffer_of_lines = line.split("\n")
2259
1692
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2260
1693
  @line_index = @buffer_of_lines.size - 1
2261
- @line = @buffer_of_lines.last
2262
- @rerender_all = true
2263
1694
  else
2264
- @line_backup_in_history = @line
2265
- @line = line
1695
+ @line_backup_in_history = current_line
1696
+ @buffer_of_lines = [line]
2266
1697
  end
2267
1698
  @searching_prompt = nil
2268
1699
  @waiting_proc = nil
2269
- @cursor_max = calculate_width(@line)
2270
- @cursor = @byte_pointer = 0
2271
- @rerender_all = true
2272
- @cached_prompt_list = nil
1700
+ @byte_pointer = 0
2273
1701
  searcher.resume(-1)
2274
1702
  end
2275
1703
  end
@@ -2290,9 +1718,9 @@ class Reline::LineEditor
2290
1718
  history = nil
2291
1719
  h_pointer = nil
2292
1720
  line_no = nil
2293
- substr = @line.slice(0, @byte_pointer)
1721
+ substr = current_line.slice(0, @byte_pointer)
2294
1722
  if @history_pointer.nil?
2295
- return if not @line.empty? and substr.empty?
1723
+ return if not current_line.empty? and substr.empty?
2296
1724
  history = Reline::HISTORY
2297
1725
  elsif @history_pointer.zero?
2298
1726
  history = nil
@@ -2318,23 +1746,23 @@ class Reline::LineEditor
2318
1746
  end
2319
1747
  return if h_pointer.nil?
2320
1748
  @history_pointer = h_pointer
1749
+ cursor = current_byte_pointer_cursor
2321
1750
  if @is_multiline
2322
1751
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2323
1752
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2324
1753
  @line_index = line_no
2325
- @line = @buffer_of_lines[@line_index]
2326
- @rerender_all = true
1754
+ calculate_nearest_cursor(cursor)
2327
1755
  else
2328
- @line = Reline::HISTORY[@history_pointer]
1756
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1757
+ calculate_nearest_cursor(cursor)
2329
1758
  end
2330
- @cursor_max = calculate_width(@line)
2331
1759
  arg -= 1
2332
1760
  ed_search_prev_history(key, arg: arg) if arg > 0
2333
1761
  end
2334
1762
  alias_method :history_search_backward, :ed_search_prev_history
2335
1763
 
2336
1764
  private def ed_search_next_history(key, arg: 1)
2337
- substr = @line.slice(0, @byte_pointer)
1765
+ substr = current_line.slice(0, @byte_pointer)
2338
1766
  if @history_pointer.nil?
2339
1767
  return
2340
1768
  elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
@@ -2365,21 +1793,21 @@ class Reline::LineEditor
2365
1793
  if @history_pointer.nil? and substr.empty?
2366
1794
  @buffer_of_lines = []
2367
1795
  @line_index = 0
1796
+ @byte_pointer = 0
2368
1797
  else
1798
+ cursor = current_byte_pointer_cursor
2369
1799
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2370
1800
  @line_index = line_no
1801
+ calculate_nearest_cursor(cursor)
2371
1802
  end
2372
1803
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2373
- @line = @buffer_of_lines[@line_index]
2374
- @rerender_all = true
2375
1804
  else
2376
1805
  if @history_pointer.nil? and substr.empty?
2377
- @line = ''
1806
+ set_current_line('', 0)
2378
1807
  else
2379
- @line = Reline::HISTORY[@history_pointer]
1808
+ set_current_line(Reline::HISTORY[@history_pointer])
2380
1809
  end
2381
1810
  end
2382
- @cursor_max = calculate_width(@line)
2383
1811
  arg -= 1
2384
1812
  ed_search_next_history(key, arg: arg) if arg > 0
2385
1813
  end
@@ -2387,8 +1815,9 @@ class Reline::LineEditor
2387
1815
 
2388
1816
  private def ed_prev_history(key, arg: 1)
2389
1817
  if @is_multiline and @line_index > 0
2390
- @previous_line_index = @line_index
1818
+ cursor = current_byte_pointer_cursor
2391
1819
  @line_index -= 1
1820
+ calculate_nearest_cursor(cursor)
2392
1821
  return
2393
1822
  end
2394
1823
  if Reline::HISTORY.empty?
@@ -2396,16 +1825,17 @@ class Reline::LineEditor
2396
1825
  end
2397
1826
  if @history_pointer.nil?
2398
1827
  @history_pointer = Reline::HISTORY.size - 1
1828
+ cursor = current_byte_pointer_cursor
2399
1829
  if @is_multiline
2400
1830
  @line_backup_in_history = whole_buffer
2401
1831
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2402
1832
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2403
1833
  @line_index = @buffer_of_lines.size - 1
2404
- @line = @buffer_of_lines.last
2405
- @rerender_all = true
1834
+ calculate_nearest_cursor(cursor)
2406
1835
  else
2407
- @line_backup_in_history = @line
2408
- @line = Reline::HISTORY[@history_pointer]
1836
+ @line_backup_in_history = whole_buffer
1837
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1838
+ calculate_nearest_cursor(cursor)
2409
1839
  end
2410
1840
  elsif @history_pointer.zero?
2411
1841
  return
@@ -2416,20 +1846,16 @@ class Reline::LineEditor
2416
1846
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2417
1847
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2418
1848
  @line_index = @buffer_of_lines.size - 1
2419
- @line = @buffer_of_lines.last
2420
- @rerender_all = true
2421
1849
  else
2422
- Reline::HISTORY[@history_pointer] = @line
1850
+ Reline::HISTORY[@history_pointer] = whole_buffer
2423
1851
  @history_pointer -= 1
2424
- @line = Reline::HISTORY[@history_pointer]
1852
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
2425
1853
  end
2426
1854
  end
2427
1855
  if @config.editing_mode_is?(:emacs, :vi_insert)
2428
- @cursor_max = @cursor = calculate_width(@line)
2429
- @byte_pointer = @line.bytesize
1856
+ @byte_pointer = current_line.bytesize
2430
1857
  elsif @config.editing_mode_is?(:vi_command)
2431
- @byte_pointer = @cursor = 0
2432
- @cursor_max = calculate_width(@line)
1858
+ @byte_pointer = 0
2433
1859
  end
2434
1860
  arg -= 1
2435
1861
  ed_prev_history(key, arg: arg) if arg > 0
@@ -2438,8 +1864,9 @@ class Reline::LineEditor
2438
1864
 
2439
1865
  private def ed_next_history(key, arg: 1)
2440
1866
  if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
2441
- @previous_line_index = @line_index
1867
+ cursor = current_byte_pointer_cursor
2442
1868
  @line_index += 1
1869
+ calculate_nearest_cursor(cursor)
2443
1870
  return
2444
1871
  end
2445
1872
  if @history_pointer.nil?
@@ -2450,11 +1877,9 @@ class Reline::LineEditor
2450
1877
  @buffer_of_lines = @line_backup_in_history.split("\n")
2451
1878
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2452
1879
  @line_index = 0
2453
- @line = @buffer_of_lines.first
2454
- @rerender_all = true
2455
1880
  else
2456
1881
  @history_pointer = nil
2457
- @line = @line_backup_in_history
1882
+ @buffer_of_lines = [@line_backup_in_history]
2458
1883
  end
2459
1884
  else
2460
1885
  if @is_multiline
@@ -2463,21 +1888,16 @@ class Reline::LineEditor
2463
1888
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2464
1889
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2465
1890
  @line_index = 0
2466
- @line = @buffer_of_lines.first
2467
- @rerender_all = true
2468
1891
  else
2469
- Reline::HISTORY[@history_pointer] = @line
1892
+ Reline::HISTORY[@history_pointer] = whole_buffer
2470
1893
  @history_pointer += 1
2471
- @line = Reline::HISTORY[@history_pointer]
1894
+ @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
2472
1895
  end
2473
1896
  end
2474
- @line = '' unless @line
2475
1897
  if @config.editing_mode_is?(:emacs, :vi_insert)
2476
- @cursor_max = @cursor = calculate_width(@line)
2477
- @byte_pointer = @line.bytesize
1898
+ @byte_pointer = current_line.bytesize
2478
1899
  elsif @config.editing_mode_is?(:vi_command)
2479
- @byte_pointer = @cursor = 0
2480
- @cursor_max = calculate_width(@line)
1900
+ @byte_pointer = 0
2481
1901
  end
2482
1902
  arg -= 1
2483
1903
  ed_next_history(key, arg: arg) if arg > 0
@@ -2503,14 +1923,14 @@ class Reline::LineEditor
2503
1923
  end
2504
1924
  else
2505
1925
  # should check confirm_multiline_termination to finish?
2506
- @previous_line_index = @line_index
2507
1926
  @line_index = @buffer_of_lines.size - 1
1927
+ @byte_pointer = current_line.bytesize
2508
1928
  finish
2509
1929
  end
2510
1930
  end
2511
1931
  else
2512
1932
  if @history_pointer
2513
- Reline::HISTORY[@history_pointer] = @line
1933
+ Reline::HISTORY[@history_pointer] = whole_buffer
2514
1934
  @history_pointer = nil
2515
1935
  end
2516
1936
  finish
@@ -2518,25 +1938,18 @@ class Reline::LineEditor
2518
1938
  end
2519
1939
 
2520
1940
  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
1941
+ arg.times do
1942
+ if @is_multiline and @byte_pointer == 0 and @line_index > 0
1943
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1944
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1945
+ @line_index -= 1
1946
+ elsif @byte_pointer > 0
1947
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1948
+ line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1949
+ set_current_line(line, @byte_pointer - byte_size)
1950
+ end
2537
1951
  end
2538
- arg -= 1
2539
- em_delete_prev_char(key, arg: arg) if arg > 0
1952
+ process_auto_indent
2540
1953
  end
2541
1954
  alias_method :backward_delete_char, :em_delete_prev_char
2542
1955
 
@@ -2546,19 +1959,12 @@ class Reline::LineEditor
2546
1959
  # the line. With a negative numeric argument, kill backward
2547
1960
  # from the cursor to the beginning of the current line.
2548
1961
  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)
1962
+ if current_line.bytesize > @byte_pointer
1963
+ line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1964
+ set_current_line(line, line.bytesize)
2553
1965
  @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
1966
+ elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1967
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2562
1968
  end
2563
1969
  end
2564
1970
  alias_method :kill_line, :ed_kill_line
@@ -2577,11 +1983,9 @@ class Reline::LineEditor
2577
1983
  # to the beginning of the current line.
2578
1984
  private def vi_kill_line_prev(key)
2579
1985
  if @byte_pointer > 0
2580
- @line, deleted = byteslice!(@line, 0, @byte_pointer)
2581
- @byte_pointer = 0
1986
+ line, deleted = byteslice!(current_line, 0, @byte_pointer)
1987
+ set_current_line(line, 0)
2582
1988
  @kill_ring.append(deleted, true)
2583
- @cursor_max = calculate_width(@line)
2584
- @cursor = 0
2585
1989
  end
2586
1990
  end
2587
1991
  alias_method :unix_line_discard, :vi_kill_line_prev
@@ -2591,45 +1995,30 @@ class Reline::LineEditor
2591
1995
  # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
2592
1996
  # current line, no matter where point is.
2593
1997
  private def em_kill_line(key)
2594
- if @line.size > 0
2595
- @kill_ring.append(@line.dup, true)
2596
- @line.clear
2597
- @byte_pointer = 0
2598
- @cursor_max = 0
2599
- @cursor = 0
1998
+ if current_line.size > 0
1999
+ @kill_ring.append(current_line.dup, true)
2000
+ set_current_line('', 0)
2600
2001
  end
2601
2002
  end
2602
2003
  alias_method :kill_whole_line, :em_kill_line
2603
2004
 
2604
2005
  private def em_delete(key)
2605
- if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
2606
- @line = nil
2607
- if @buffer_of_lines.size > 1
2608
- scroll_down(@highest_in_all - @first_line_started_from)
2609
- end
2610
- Reline::IOGate.move_cursor_column(0)
2006
+ if current_line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
2611
2007
  @eof = true
2612
2008
  finish
2613
- elsif @byte_pointer < @line.bytesize
2614
- splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
2009
+ elsif @byte_pointer < current_line.bytesize
2010
+ splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize)
2615
2011
  mbchar = splitted_last.grapheme_clusters.first
2616
- width = Reline::Unicode.get_mbchar_width(mbchar)
2617
- @cursor_max -= width
2618
- @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
2619
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2620
- @cursor = calculate_width(@line)
2621
- @byte_pointer = @line.bytesize
2622
- @line += @buffer_of_lines.delete_at(@line_index + 1)
2623
- @cursor_max = calculate_width(@line)
2624
- @buffer_of_lines[@line_index] = @line
2625
- @rerender_all = true
2626
- @rest_height += 1
2012
+ line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
2013
+ set_current_line(line)
2014
+ elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
2015
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2627
2016
  end
2628
2017
  end
2629
2018
  alias_method :delete_char, :em_delete
2630
2019
 
2631
2020
  private def em_delete_or_list(key)
2632
- if @line.empty? or @byte_pointer < @line.bytesize
2021
+ if current_line.empty? or @byte_pointer < current_line.bytesize
2633
2022
  em_delete(key)
2634
2023
  else # show completed list
2635
2024
  result = call_completion_proc
@@ -2642,29 +2031,16 @@ class Reline::LineEditor
2642
2031
 
2643
2032
  private def em_yank(key)
2644
2033
  yanked = @kill_ring.yank
2645
- if yanked
2646
- @line = byteinsert(@line, @byte_pointer, yanked)
2647
- yanked_width = calculate_width(yanked)
2648
- @cursor += yanked_width
2649
- @cursor_max += yanked_width
2650
- @byte_pointer += yanked.bytesize
2651
- end
2034
+ insert_text(yanked) if yanked
2652
2035
  end
2653
2036
  alias_method :yank, :em_yank
2654
2037
 
2655
2038
  private def em_yank_pop(key)
2656
2039
  yanked, prev_yank = @kill_ring.yank_pop
2657
2040
  if yanked
2658
- prev_yank_width = calculate_width(prev_yank)
2659
- @cursor -= prev_yank_width
2660
- @cursor_max -= prev_yank_width
2661
- @byte_pointer -= prev_yank.bytesize
2662
- @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
2663
- @line = byteinsert(@line, @byte_pointer, yanked)
2664
- yanked_width = calculate_width(yanked)
2665
- @cursor += yanked_width
2666
- @cursor_max += yanked_width
2667
- @byte_pointer += yanked.bytesize
2041
+ line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
2042
+ set_current_line(line, @byte_pointer - prev_yank.bytesize)
2043
+ insert_text(yanked)
2668
2044
  end
2669
2045
  end
2670
2046
  alias_method :yank_pop, :em_yank_pop
@@ -2675,131 +2051,112 @@ class Reline::LineEditor
2675
2051
  alias_method :clear_screen, :ed_clear_screen
2676
2052
 
2677
2053
  private def em_next_word(key)
2678
- if @line.bytesize > @byte_pointer
2679
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2054
+ if current_line.bytesize > @byte_pointer
2055
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2680
2056
  @byte_pointer += byte_size
2681
- @cursor += width
2682
2057
  end
2683
2058
  end
2684
2059
  alias_method :forward_word, :em_next_word
2685
2060
 
2686
2061
  private def ed_prev_word(key)
2687
2062
  if @byte_pointer > 0
2688
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2063
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2689
2064
  @byte_pointer -= byte_size
2690
- @cursor -= width
2691
2065
  end
2692
2066
  end
2693
2067
  alias_method :backward_word, :ed_prev_word
2694
2068
 
2695
2069
  private def em_delete_next_word(key)
2696
- if @line.bytesize > @byte_pointer
2697
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2698
- @line, word = byteslice!(@line, @byte_pointer, byte_size)
2070
+ if current_line.bytesize > @byte_pointer
2071
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2072
+ line, word = byteslice!(current_line, @byte_pointer, byte_size)
2073
+ set_current_line(line)
2699
2074
  @kill_ring.append(word)
2700
- @cursor_max -= width
2701
2075
  end
2702
2076
  end
2703
2077
  alias_method :kill_word, :em_delete_next_word
2704
2078
 
2705
2079
  private def ed_delete_prev_word(key)
2706
2080
  if @byte_pointer > 0
2707
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2708
- @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2081
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2082
+ line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2083
+ set_current_line(line, @byte_pointer - byte_size)
2709
2084
  @kill_ring.append(word, true)
2710
- @byte_pointer -= byte_size
2711
- @cursor -= width
2712
- @cursor_max -= width
2713
2085
  end
2714
2086
  end
2715
2087
  alias_method :backward_kill_word, :ed_delete_prev_word
2716
2088
 
2717
2089
  private def ed_transpose_chars(key)
2718
2090
  if @byte_pointer > 0
2719
- if @cursor_max > @cursor
2720
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2721
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2722
- width = Reline::Unicode.get_mbchar_width(mbchar)
2723
- @cursor += width
2091
+ if @byte_pointer < current_line.bytesize
2092
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2724
2093
  @byte_pointer += byte_size
2725
2094
  end
2726
- back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2095
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2727
2096
  if (@byte_pointer - back1_byte_size) > 0
2728
- back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
2097
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size)
2729
2098
  back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
2730
- @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
2731
- @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
2099
+ line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size)
2100
+ set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar))
2732
2101
  end
2733
2102
  end
2734
2103
  end
2735
2104
  alias_method :transpose_chars, :ed_transpose_chars
2736
2105
 
2737
2106
  private def ed_transpose_words(key)
2738
- left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
2739
- before = @line.byteslice(0, left_word_start)
2740
- left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
2741
- middle = @line.byteslice(middle_start, right_word_start - middle_start)
2742
- right_word = @line.byteslice(right_word_start, after_start - right_word_start)
2743
- after = @line.byteslice(after_start, @line.bytesize - after_start)
2107
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer)
2108
+ before = current_line.byteslice(0, left_word_start)
2109
+ left_word = current_line.byteslice(left_word_start, middle_start - left_word_start)
2110
+ middle = current_line.byteslice(middle_start, right_word_start - middle_start)
2111
+ right_word = current_line.byteslice(right_word_start, after_start - right_word_start)
2112
+ after = current_line.byteslice(after_start, current_line.bytesize - after_start)
2744
2113
  return if left_word.empty? or right_word.empty?
2745
- @line = before + right_word + middle + left_word + after
2746
2114
  from_head_to_left_word = before + right_word + middle + left_word
2747
- @byte_pointer = from_head_to_left_word.bytesize
2748
- @cursor = calculate_width(from_head_to_left_word)
2115
+ set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
2749
2116
  end
2750
2117
  alias_method :transpose_words, :ed_transpose_words
2751
2118
 
2752
2119
  private def em_capitol_case(key)
2753
- if @line.bytesize > @byte_pointer
2754
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
2755
- before = @line.byteslice(0, @byte_pointer)
2756
- after = @line.byteslice((@byte_pointer + byte_size)..-1)
2757
- @line = before + new_str + after
2758
- @byte_pointer += new_str.bytesize
2759
- @cursor += calculate_width(new_str)
2120
+ if current_line.bytesize > @byte_pointer
2121
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
2122
+ before = current_line.byteslice(0, @byte_pointer)
2123
+ after = current_line.byteslice((@byte_pointer + byte_size)..-1)
2124
+ set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
2760
2125
  end
2761
2126
  end
2762
2127
  alias_method :capitalize_word, :em_capitol_case
2763
2128
 
2764
2129
  private def em_lower_case(key)
2765
- if @line.bytesize > @byte_pointer
2766
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2767
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2130
+ if current_line.bytesize > @byte_pointer
2131
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2132
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2768
2133
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2769
2134
  }.join
2770
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2771
- @line = @line.byteslice(0, @byte_pointer) + part
2772
- @byte_pointer = @line.bytesize
2773
- @cursor = calculate_width(@line)
2774
- @cursor_max = @cursor + calculate_width(rest)
2775
- @line += rest
2135
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
2136
+ line = current_line.byteslice(0, @byte_pointer) + part
2137
+ set_current_line(line + rest, line.bytesize)
2776
2138
  end
2777
2139
  end
2778
2140
  alias_method :downcase_word, :em_lower_case
2779
2141
 
2780
2142
  private def em_upper_case(key)
2781
- if @line.bytesize > @byte_pointer
2782
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2783
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2143
+ if current_line.bytesize > @byte_pointer
2144
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2145
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2784
2146
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2785
2147
  }.join
2786
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2787
- @line = @line.byteslice(0, @byte_pointer) + part
2788
- @byte_pointer = @line.bytesize
2789
- @cursor = calculate_width(@line)
2790
- @cursor_max = @cursor + calculate_width(rest)
2791
- @line += rest
2148
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
2149
+ line = current_line.byteslice(0, @byte_pointer) + part
2150
+ set_current_line(line + rest, line.bytesize)
2792
2151
  end
2793
2152
  end
2794
2153
  alias_method :upcase_word, :em_upper_case
2795
2154
 
2796
2155
  private def em_kill_region(key)
2797
2156
  if @byte_pointer > 0
2798
- byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
2799
- @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2800
- @byte_pointer -= byte_size
2801
- @cursor -= width
2802
- @cursor_max -= width
2157
+ byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
2158
+ line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2159
+ set_current_line(line, @byte_pointer - byte_size)
2803
2160
  @kill_ring.append(deleted, true)
2804
2161
  end
2805
2162
  end
@@ -2827,10 +2184,9 @@ class Reline::LineEditor
2827
2184
  alias_method :vi_movement_mode, :vi_command_mode
2828
2185
 
2829
2186
  private def vi_next_word(key, arg: 1)
2830
- if @line.bytesize > @byte_pointer
2831
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
2187
+ if current_line.bytesize > @byte_pointer
2188
+ byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
2832
2189
  @byte_pointer += byte_size
2833
- @cursor += width
2834
2190
  end
2835
2191
  arg -= 1
2836
2192
  vi_next_word(key, arg: arg) if arg > 0
@@ -2838,38 +2194,32 @@ class Reline::LineEditor
2838
2194
 
2839
2195
  private def vi_prev_word(key, arg: 1)
2840
2196
  if @byte_pointer > 0
2841
- byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
2197
+ byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2842
2198
  @byte_pointer -= byte_size
2843
- @cursor -= width
2844
2199
  end
2845
2200
  arg -= 1
2846
2201
  vi_prev_word(key, arg: arg) if arg > 0
2847
2202
  end
2848
2203
 
2849
2204
  private def vi_end_word(key, arg: 1, inclusive: false)
2850
- if @line.bytesize > @byte_pointer
2851
- byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
2205
+ if current_line.bytesize > @byte_pointer
2206
+ byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
2852
2207
  @byte_pointer += byte_size
2853
- @cursor += width
2854
2208
  end
2855
2209
  arg -= 1
2856
2210
  if inclusive and arg.zero?
2857
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2211
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2858
2212
  if byte_size > 0
2859
- c = @line.byteslice(@byte_pointer, byte_size)
2860
- width = Reline::Unicode.get_mbchar_width(c)
2861
2213
  @byte_pointer += byte_size
2862
- @cursor += width
2863
2214
  end
2864
2215
  end
2865
2216
  vi_end_word(key, arg: arg) if arg > 0
2866
2217
  end
2867
2218
 
2868
2219
  private def vi_next_big_word(key, arg: 1)
2869
- if @line.bytesize > @byte_pointer
2870
- byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
2220
+ if current_line.bytesize > @byte_pointer
2221
+ byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
2871
2222
  @byte_pointer += byte_size
2872
- @cursor += width
2873
2223
  end
2874
2224
  arg -= 1
2875
2225
  vi_next_big_word(key, arg: arg) if arg > 0
@@ -2877,50 +2227,39 @@ class Reline::LineEditor
2877
2227
 
2878
2228
  private def vi_prev_big_word(key, arg: 1)
2879
2229
  if @byte_pointer > 0
2880
- byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
2230
+ byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2881
2231
  @byte_pointer -= byte_size
2882
- @cursor -= width
2883
2232
  end
2884
2233
  arg -= 1
2885
2234
  vi_prev_big_word(key, arg: arg) if arg > 0
2886
2235
  end
2887
2236
 
2888
2237
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2889
- if @line.bytesize > @byte_pointer
2890
- byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
2238
+ if current_line.bytesize > @byte_pointer
2239
+ byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2891
2240
  @byte_pointer += byte_size
2892
- @cursor += width
2893
2241
  end
2894
2242
  arg -= 1
2895
2243
  if inclusive and arg.zero?
2896
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2244
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2897
2245
  if byte_size > 0
2898
- c = @line.byteslice(@byte_pointer, byte_size)
2899
- width = Reline::Unicode.get_mbchar_width(c)
2900
2246
  @byte_pointer += byte_size
2901
- @cursor += width
2902
2247
  end
2903
2248
  end
2904
2249
  vi_end_big_word(key, arg: arg) if arg > 0
2905
2250
  end
2906
2251
 
2907
2252
  private def vi_delete_prev_char(key)
2908
- if @is_multiline and @cursor == 0 and @line_index > 0
2909
- @buffer_of_lines[@line_index] = @line
2910
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2253
+ if @is_multiline and @byte_pointer == 0 and @line_index > 0
2911
2254
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2912
2255
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2913
2256
  @line_index -= 1
2914
- @line = @buffer_of_lines[@line_index]
2915
- @cursor_max = calculate_width(@line)
2916
- @rerender_all = true
2917
- elsif @cursor > 0
2918
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2257
+ process_auto_indent cursor_dependent: false
2258
+ elsif @byte_pointer > 0
2259
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2919
2260
  @byte_pointer -= byte_size
2920
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2921
- width = Reline::Unicode.get_mbchar_width(mbchar)
2922
- @cursor -= width
2923
- @cursor_max -= width
2261
+ line, _ = byteslice!(current_line, @byte_pointer, byte_size)
2262
+ set_current_line(line)
2924
2263
  end
2925
2264
  end
2926
2265
 
@@ -2935,16 +2274,14 @@ class Reline::LineEditor
2935
2274
  end
2936
2275
 
2937
2276
  private def ed_delete_prev_char(key, arg: 1)
2938
- deleted = ''
2277
+ deleted = +''
2939
2278
  arg.times do
2940
- if @cursor > 0
2941
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2279
+ if @byte_pointer > 0
2280
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2942
2281
  @byte_pointer -= byte_size
2943
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2282
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
2283
+ set_current_line(line)
2944
2284
  deleted.prepend(mbchar)
2945
- width = Reline::Unicode.get_mbchar_width(mbchar)
2946
- @cursor -= width
2947
- @cursor_max -= width
2948
2285
  end
2949
2286
  end
2950
2287
  copy_for_vi(deleted)
@@ -2952,20 +2289,18 @@ class Reline::LineEditor
2952
2289
 
2953
2290
  private def vi_zero(key)
2954
2291
  @byte_pointer = 0
2955
- @cursor = 0
2956
2292
  end
2957
2293
 
2958
2294
  private def vi_change_meta(key, arg: 1)
2959
2295
  @drop_terminate_spaces = true
2960
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2296
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2961
2297
  if byte_pointer_diff > 0
2962
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2298
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2963
2299
  elsif byte_pointer_diff < 0
2964
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2300
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2965
2301
  end
2302
+ set_current_line(line)
2966
2303
  copy_for_vi(cut)
2967
- @cursor += cursor_diff if cursor_diff < 0
2968
- @cursor_max -= cursor_diff.abs
2969
2304
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2970
2305
  @config.editing_mode = :vi_insert
2971
2306
  @drop_terminate_spaces = false
@@ -2974,26 +2309,24 @@ class Reline::LineEditor
2974
2309
  end
2975
2310
 
2976
2311
  private def vi_delete_meta(key, arg: 1)
2977
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2312
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2978
2313
  if byte_pointer_diff > 0
2979
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2314
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2980
2315
  elsif byte_pointer_diff < 0
2981
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2316
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2982
2317
  end
2983
2318
  copy_for_vi(cut)
2984
- @cursor += cursor_diff if cursor_diff < 0
2985
- @cursor_max -= cursor_diff.abs
2986
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2319
+ set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2987
2320
  }
2988
2321
  @waiting_operator_vi_arg = arg
2989
2322
  end
2990
2323
 
2991
2324
  private def vi_yank(key, arg: 1)
2992
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2325
+ @waiting_operator_proc = proc { |byte_pointer_diff|
2993
2326
  if byte_pointer_diff > 0
2994
- cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2327
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2995
2328
  elsif byte_pointer_diff < 0
2996
- cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2329
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2997
2330
  end
2998
2331
  copy_for_vi(cut)
2999
2332
  }
@@ -3001,12 +2334,8 @@ class Reline::LineEditor
3001
2334
  end
3002
2335
 
3003
2336
  private def vi_list_or_eof(key)
3004
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
3005
- @line = nil
3006
- if @buffer_of_lines.size > 1
3007
- scroll_down(@highest_in_all - @first_line_started_from)
3008
- end
3009
- Reline::IOGate.move_cursor_column(0)
2337
+ if (not @is_multiline and current_line.empty?) or (@is_multiline and current_line.empty? and @buffer_of_lines.size == 1)
2338
+ set_current_line('', 0)
3010
2339
  @eof = true
3011
2340
  finish
3012
2341
  else
@@ -3017,18 +2346,15 @@ class Reline::LineEditor
3017
2346
  alias_method :vi_eof_maybe, :vi_list_or_eof
3018
2347
 
3019
2348
  private def ed_delete_next_char(key, arg: 1)
3020
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3021
- unless @line.empty? || byte_size == 0
3022
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2349
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2350
+ unless current_line.empty? || byte_size == 0
2351
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
3023
2352
  copy_for_vi(mbchar)
3024
- width = Reline::Unicode.get_mbchar_width(mbchar)
3025
- @cursor_max -= width
3026
- if @cursor > 0 and @cursor >= @cursor_max
3027
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
3028
- mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
3029
- width = Reline::Unicode.get_mbchar_width(mbchar)
3030
- @byte_pointer -= byte_size
3031
- @cursor -= width
2353
+ if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size
2354
+ byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
2355
+ set_current_line(line, @byte_pointer - byte_size)
2356
+ else
2357
+ set_current_line(line, @byte_pointer)
3032
2358
  end
3033
2359
  end
3034
2360
  arg -= 1
@@ -3041,20 +2367,14 @@ class Reline::LineEditor
3041
2367
  end
3042
2368
  if @history_pointer.nil?
3043
2369
  @history_pointer = 0
3044
- @line_backup_in_history = @line
3045
- @line = Reline::HISTORY[@history_pointer]
3046
- @cursor_max = calculate_width(@line)
3047
- @cursor = 0
3048
- @byte_pointer = 0
2370
+ @line_backup_in_history = current_line
2371
+ set_current_line(Reline::HISTORY[@history_pointer], 0)
3049
2372
  elsif @history_pointer.zero?
3050
2373
  return
3051
2374
  else
3052
- Reline::HISTORY[@history_pointer] = @line
2375
+ Reline::HISTORY[@history_pointer] = current_line
3053
2376
  @history_pointer = 0
3054
- @line = Reline::HISTORY[@history_pointer]
3055
- @cursor_max = calculate_width(@line)
3056
- @cursor = 0
3057
- @byte_pointer = 0
2377
+ set_current_line(Reline::HISTORY[@history_pointer], 0)
3058
2378
  end
3059
2379
  end
3060
2380
 
@@ -3063,7 +2383,7 @@ class Reline::LineEditor
3063
2383
  if @is_multiline
3064
2384
  fp.write whole_lines.join("\n")
3065
2385
  else
3066
- fp.write @line
2386
+ fp.write current_line
3067
2387
  end
3068
2388
  fp.path
3069
2389
  }
@@ -3072,21 +2392,16 @@ class Reline::LineEditor
3072
2392
  @buffer_of_lines = File.read(path).split("\n")
3073
2393
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
3074
2394
  @line_index = 0
3075
- @line = @buffer_of_lines[@line_index]
3076
- @rerender_all = true
3077
2395
  else
3078
- @line = File.read(path)
2396
+ @buffer_of_lines = File.read(path).split("\n")
3079
2397
  end
3080
2398
  finish
3081
2399
  end
3082
2400
 
3083
2401
  private def vi_paste_prev(key, arg: 1)
3084
2402
  if @vi_clipboard.size > 0
3085
- @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
3086
- @cursor_max += calculate_width(@vi_clipboard)
3087
2403
  cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
3088
- @cursor += calculate_width(cursor_point)
3089
- @byte_pointer += cursor_point.bytesize
2404
+ set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
3090
2405
  end
3091
2406
  arg -= 1
3092
2407
  vi_paste_prev(key, arg: arg) if arg > 0
@@ -3094,11 +2409,9 @@ class Reline::LineEditor
3094
2409
 
3095
2410
  private def vi_paste_next(key, arg: 1)
3096
2411
  if @vi_clipboard.size > 0
3097
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3098
- @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
3099
- @cursor_max += calculate_width(@vi_clipboard)
3100
- @cursor += calculate_width(@vi_clipboard)
3101
- @byte_pointer += @vi_clipboard.bytesize
2412
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2413
+ line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard)
2414
+ set_current_line(line, @byte_pointer + @vi_clipboard.bytesize)
3102
2415
  end
3103
2416
  arg -= 1
3104
2417
  vi_paste_next(key, arg: arg) if arg > 0
@@ -3122,12 +2435,13 @@ class Reline::LineEditor
3122
2435
  end
3123
2436
 
3124
2437
  private def vi_to_column(key, arg: 0)
3125
- @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
2438
+ current_row_width = calculate_width(current_row)
2439
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |total, gc|
3126
2440
  # total has [byte_size, cursor]
3127
2441
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
3128
2442
  if (total.last + mbchar_width) >= arg
3129
2443
  break total
3130
- elsif (total.last + mbchar_width) >= @cursor_max
2444
+ elsif (total.last + mbchar_width) >= current_row_width
3131
2445
  break total
3132
2446
  else
3133
2447
  total = [total.first + gc.bytesize, total.last + mbchar_width]
@@ -3139,26 +2453,22 @@ class Reline::LineEditor
3139
2453
  private def vi_replace_char(key, arg: 1)
3140
2454
  @waiting_proc = ->(k) {
3141
2455
  if arg == 1
3142
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3143
- before = @line.byteslice(0, @byte_pointer)
2456
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2457
+ before = current_line.byteslice(0, @byte_pointer)
3144
2458
  remaining_point = @byte_pointer + byte_size
3145
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
3146
- @line = before + k.chr + after
3147
- @cursor_max = calculate_width(@line)
2459
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2460
+ set_current_line(before + k.chr + after)
3148
2461
  @waiting_proc = nil
3149
2462
  elsif arg > 1
3150
2463
  byte_size = 0
3151
2464
  arg.times do
3152
- byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
2465
+ byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size)
3153
2466
  end
3154
- before = @line.byteslice(0, @byte_pointer)
2467
+ before = current_line.byteslice(0, @byte_pointer)
3155
2468
  remaining_point = @byte_pointer + byte_size
3156
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2469
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
3157
2470
  replaced = k.chr * arg
3158
- @line = before + replaced + after
3159
- @byte_pointer += replaced.bytesize
3160
- @cursor += calculate_width(replaced)
3161
- @cursor_max = calculate_width(@line)
2471
+ set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
3162
2472
  @waiting_proc = nil
3163
2473
  end
3164
2474
  }
@@ -3181,7 +2491,7 @@ class Reline::LineEditor
3181
2491
  prev_total = nil
3182
2492
  total = nil
3183
2493
  found = false
3184
- @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
2494
+ current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
3185
2495
  # total has [byte_size, cursor]
3186
2496
  unless total
3187
2497
  # skip cursor point
@@ -3201,21 +2511,16 @@ class Reline::LineEditor
3201
2511
  end
3202
2512
  end
3203
2513
  if not need_prev_char and found and total
3204
- byte_size, width = total
2514
+ byte_size, _ = total
3205
2515
  @byte_pointer += byte_size
3206
- @cursor += width
3207
2516
  elsif need_prev_char and found and prev_total
3208
- byte_size, width = prev_total
2517
+ byte_size, _ = prev_total
3209
2518
  @byte_pointer += byte_size
3210
- @cursor += width
3211
2519
  end
3212
2520
  if inclusive
3213
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2521
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
3214
2522
  if byte_size > 0
3215
- c = @line.byteslice(@byte_pointer, byte_size)
3216
- width = Reline::Unicode.get_mbchar_width(c)
3217
2523
  @byte_pointer += byte_size
3218
- @cursor += width
3219
2524
  end
3220
2525
  end
3221
2526
  @waiting_proc = nil
@@ -3238,7 +2543,7 @@ class Reline::LineEditor
3238
2543
  prev_total = nil
3239
2544
  total = nil
3240
2545
  found = false
3241
- @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2546
+ current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
3242
2547
  # total has [byte_size, cursor]
3243
2548
  unless total
3244
2549
  # skip cursor point
@@ -3258,26 +2563,19 @@ class Reline::LineEditor
3258
2563
  end
3259
2564
  end
3260
2565
  if not need_next_char and found and total
3261
- byte_size, width = total
2566
+ byte_size, _ = total
3262
2567
  @byte_pointer -= byte_size
3263
- @cursor -= width
3264
2568
  elsif need_next_char and found and prev_total
3265
- byte_size, width = prev_total
2569
+ byte_size, _ = prev_total
3266
2570
  @byte_pointer -= byte_size
3267
- @cursor -= width
3268
2571
  end
3269
2572
  @waiting_proc = nil
3270
2573
  end
3271
2574
 
3272
2575
  private def vi_join_lines(key, arg: 1)
3273
2576
  if @is_multiline and @buffer_of_lines.size > @line_index + 1
3274
- @cursor = calculate_width(@line)
3275
- @byte_pointer = @line.bytesize
3276
- @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
3277
- @cursor_max = calculate_width(@line)
3278
- @buffer_of_lines[@line_index] = @line
3279
- @rerender_all = true
3280
- @rest_height += 1
2577
+ next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2578
+ set_current_line(current_line + ' ' + next_line, current_line.bytesize)
3281
2579
  end
3282
2580
  arg -= 1
3283
2581
  vi_join_lines(key, arg: arg) if arg > 0
@@ -3291,10 +2589,7 @@ class Reline::LineEditor
3291
2589
  private def em_exchange_mark(key)
3292
2590
  return unless @mark_pointer
3293
2591
  new_pointer = [@byte_pointer, @line_index]
3294
- @previous_line_index = @line_index
3295
2592
  @byte_pointer, @line_index = @mark_pointer
3296
- @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
3297
- @cursor_max = calculate_width(@line)
3298
2593
  @mark_pointer = new_pointer
3299
2594
  end
3300
2595
  alias_method :exchange_point_and_mark, :em_exchange_mark