reline 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,7 +6,6 @@ require 'tempfile'
6
6
  class Reline::LineEditor
7
7
  # TODO: undo
8
8
  # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
9
- attr_reader :line
10
9
  attr_reader :byte_pointer
11
10
  attr_accessor :confirm_multiline_termination_proc
12
11
  attr_accessor :completion_proc
@@ -14,7 +13,6 @@ class Reline::LineEditor
14
13
  attr_accessor :output_modifier_proc
15
14
  attr_accessor :prompt_proc
16
15
  attr_accessor :auto_indent_proc
17
- attr_accessor :pre_input_hook
18
16
  attr_accessor :dig_perfect_match_proc
19
17
  attr_writer :output
20
18
 
@@ -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