reline 0.3.2 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,9 +4,7 @@ require 'reline/unicode'
4
4
  require 'tempfile'
5
5
 
6
6
  class Reline::LineEditor
7
- # TODO: undo
8
7
  # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
9
- attr_reader :line
10
8
  attr_reader :byte_pointer
11
9
  attr_accessor :confirm_multiline_termination_proc
12
10
  attr_accessor :completion_proc
@@ -14,7 +12,6 @@ class Reline::LineEditor
14
12
  attr_accessor :output_modifier_proc
15
13
  attr_accessor :prompt_proc
16
14
  attr_accessor :auto_indent_proc
17
- attr_accessor :pre_input_hook
18
15
  attr_accessor :dig_perfect_match_proc
19
16
  attr_writer :output
20
17
 
@@ -35,98 +32,94 @@ class Reline::LineEditor
35
32
  vi_next_big_word
36
33
  vi_prev_big_word
37
34
  vi_end_big_word
38
- vi_repeat_next_char
39
- vi_repeat_prev_char
40
35
  }
41
36
 
42
37
  module CompletionState
43
38
  NORMAL = :normal
44
39
  COMPLETION = :completion
45
40
  MENU = :menu
46
- JOURNEY = :journey
47
41
  MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
48
42
  PERFECT_MATCH = :perfect_match
49
43
  end
50
44
 
51
- CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
52
- MenuInfo = Struct.new('MenuInfo', :target, :list)
45
+ RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
53
46
 
54
- PROMPT_LIST_CACHE_TIMEOUT = 0.5
47
+ CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
48
+ NullActionState = [nil, nil].freeze
49
+
50
+ class MenuInfo
51
+ attr_reader :list
52
+
53
+ def initialize(list)
54
+ @list = list
55
+ end
56
+
57
+ def lines(screen_width)
58
+ return [] if @list.empty?
59
+
60
+ list = @list.sort
61
+ sizes = list.map { |item| Reline::Unicode.calculate_width(item) }
62
+ item_width = sizes.max + 2
63
+ num_cols = [screen_width / item_width, 1].max
64
+ num_rows = list.size.fdiv(num_cols).ceil
65
+ list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) }
66
+ aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose
67
+ aligned.map do |row|
68
+ row.join.rstrip
69
+ end
70
+ end
71
+ end
72
+
73
+ MINIMUM_SCROLLBAR_HEIGHT = 1
55
74
 
56
75
  def initialize(config, encoding)
57
76
  @config = config
58
77
  @completion_append_character = ''
78
+ @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
59
79
  reset_variables(encoding: encoding)
60
80
  end
61
81
 
62
- def set_pasting_state(in_pasting)
63
- @in_pasting = in_pasting
82
+ def io_gate
83
+ Reline::IOGate
64
84
  end
65
85
 
66
- def simplified_rendering?
67
- if finished?
68
- false
69
- elsif @just_cursor_moving and not @rerender_all
70
- true
71
- else
72
- not @rerender_all and not finished? and @in_pasting
73
- end
86
+ def set_pasting_state(in_pasting)
87
+ # While pasting, text to be inserted is stored to @continuous_insertion_buffer.
88
+ # After pasting, this buffer should be force inserted.
89
+ process_insert(force: true) if @in_pasting && !in_pasting
90
+ @in_pasting = in_pasting
74
91
  end
75
92
 
76
93
  private def check_mode_string
77
- mode_string = nil
78
94
  if @config.show_mode_in_prompt
79
95
  if @config.editing_mode_is?(:vi_command)
80
- mode_string = @config.vi_cmd_mode_string
96
+ @config.vi_cmd_mode_string
81
97
  elsif @config.editing_mode_is?(:vi_insert)
82
- mode_string = @config.vi_ins_mode_string
98
+ @config.vi_ins_mode_string
83
99
  elsif @config.editing_mode_is?(:emacs)
84
- mode_string = @config.emacs_mode_string
100
+ @config.emacs_mode_string
85
101
  else
86
- mode_string = '?'
102
+ '?'
87
103
  end
88
104
  end
89
- if mode_string != @prev_mode_string
90
- @rerender_all = true
91
- end
92
- @prev_mode_string = mode_string
93
- mode_string
94
105
  end
95
106
 
96
- private def check_multiline_prompt(buffer)
107
+ private def check_multiline_prompt(buffer, mode_string)
97
108
  if @vi_arg
98
109
  prompt = "(arg: #{@vi_arg}) "
99
- @rerender_all = true
100
110
  elsif @searching_prompt
101
111
  prompt = @searching_prompt
102
- @rerender_all = true
103
112
  else
104
113
  prompt = @prompt
105
114
  end
106
- if simplified_rendering?
115
+ if !@is_multiline
107
116
  mode_string = check_mode_string
108
117
  prompt = mode_string + prompt if mode_string
109
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
110
- end
111
- if @prompt_proc
112
- use_cached_prompt_list = false
113
- if @cached_prompt_list
114
- if @just_cursor_moving
115
- use_cached_prompt_list = true
116
- elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
117
- use_cached_prompt_list = true
118
- end
119
- end
120
- use_cached_prompt_list = false if @rerender_all
121
- if use_cached_prompt_list
122
- prompt_list = @cached_prompt_list
123
- else
124
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
125
- @prompt_cache_time = Time.now.to_f
126
- end
118
+ [prompt] + [''] * (buffer.size - 1)
119
+ elsif @prompt_proc
120
+ prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
127
121
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
128
122
  prompt_list = [prompt] if prompt_list.empty?
129
- mode_string = check_mode_string
130
123
  prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
131
124
  prompt = prompt_list[@line_index]
132
125
  prompt = prompt_list[0] if prompt.nil?
@@ -136,24 +129,17 @@ class Reline::LineEditor
136
129
  prompt_list << prompt_list.last
137
130
  end
138
131
  end
139
- prompt_width = calculate_width(prompt, true)
140
- [prompt, prompt_width, prompt_list]
132
+ prompt_list
141
133
  else
142
- mode_string = check_mode_string
143
134
  prompt = mode_string + prompt if mode_string
144
- prompt_width = calculate_width(prompt, true)
145
- [prompt, prompt_width, nil]
135
+ [prompt] * buffer.size
146
136
  end
147
137
  end
148
138
 
149
139
  def reset(prompt = '', encoding:)
150
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
151
140
  @screen_size = Reline::IOGate.get_screen_size
152
- @screen_height = @screen_size.first
153
141
  reset_variables(prompt, encoding: encoding)
154
- Reline::IOGate.set_winch_handler do
155
- @resized = true
156
- end
142
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
157
143
  if ENV.key?('RELINE_ALT_SCROLLBAR')
158
144
  @full_block = '::'
159
145
  @upper_half_block = "''"
@@ -177,82 +163,57 @@ class Reline::LineEditor
177
163
  end
178
164
  end
179
165
 
180
- def resize
166
+ def handle_signal
167
+ handle_interrupted
168
+ handle_resized
169
+ end
170
+
171
+ private def handle_resized
181
172
  return unless @resized
182
- @resized = false
183
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
184
- old_screen_size = @screen_size
173
+
185
174
  @screen_size = Reline::IOGate.get_screen_size
186
- @screen_height = @screen_size.first
187
- if old_screen_size.last < @screen_size.last # columns increase
188
- @rerender_all = true
189
- rerender
175
+ @resized = false
176
+ scroll_into_view
177
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
178
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
179
+ @rendered_screen.lines = []
180
+ @rendered_screen.cursor_y = 0
181
+ render_differential
182
+ end
183
+
184
+ private def handle_interrupted
185
+ return unless @interrupted
186
+
187
+ @interrupted = false
188
+ clear_dialogs
189
+ scrolldown = render_differential
190
+ Reline::IOGate.scroll_down scrolldown
191
+ Reline::IOGate.move_cursor_column 0
192
+ @rendered_screen.lines = []
193
+ @rendered_screen.cursor_y = 0
194
+ case @old_trap
195
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
196
+ raise Interrupt
197
+ when 'IGNORE'
198
+ # Do nothing
199
+ when 'EXIT'
200
+ exit
190
201
  else
191
- back = 0
192
- new_buffer = whole_lines
193
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
194
- new_buffer.each_with_index do |line, index|
195
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
196
- width = prompt_width + calculate_width(line)
197
- height = calculate_height_by_width(width)
198
- back += height
199
- end
200
- @highest_in_all = back
201
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
202
- @first_line_started_from =
203
- if @line_index.zero?
204
- 0
205
- else
206
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
207
- end
208
- if @prompt_proc
209
- prompt = prompt_list[@line_index]
210
- prompt_width = calculate_width(prompt, true)
211
- end
212
- calculate_nearest_cursor
213
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
214
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
215
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
216
- @rerender_all = true
202
+ @old_trap.call if @old_trap.respond_to?(:call)
217
203
  end
218
204
  end
219
205
 
220
206
  def set_signal_handlers
221
- @old_trap = Signal.trap('INT') {
222
- clear_dialog
223
- if @scroll_partial_screen
224
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
225
- else
226
- move_cursor_down(@highest_in_all - @line_index - 1)
227
- end
228
- Reline::IOGate.move_cursor_column(0)
229
- scroll_down(1)
230
- case @old_trap
231
- when 'DEFAULT', 'SYSTEM_DEFAULT'
232
- raise Interrupt
233
- when 'IGNORE'
234
- # Do nothing
235
- when 'EXIT'
236
- exit
237
- else
238
- @old_trap.call if @old_trap.respond_to?(:call)
239
- end
240
- }
241
- begin
242
- @old_tstp_trap = Signal.trap('TSTP') {
243
- Reline::IOGate.ungetc("\C-z".ord)
244
- @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
245
- }
246
- rescue ArgumentError
207
+ Reline::IOGate.set_winch_handler do
208
+ @resized = true
209
+ end
210
+ @old_trap = Signal.trap('INT') do
211
+ @interrupted = true
247
212
  end
248
213
  end
249
214
 
250
215
  def finalize
251
216
  Signal.trap('INT', @old_trap)
252
- begin
253
- Signal.trap('TSTP', @old_tstp_trap)
254
- rescue ArgumentError
255
- end
256
217
  end
257
218
 
258
219
  def eof?
@@ -265,55 +226,45 @@ class Reline::LineEditor
265
226
  @encoding = encoding
266
227
  @is_multiline = false
267
228
  @finished = false
268
- @cleared = false
269
- @rerender_all = false
270
229
  @history_pointer = nil
271
230
  @kill_ring ||= Reline::KillRing.new
272
231
  @vi_clipboard = ''
273
232
  @vi_arg = nil
274
233
  @waiting_proc = nil
275
- @waiting_operator_proc = nil
276
- @waiting_operator_vi_arg = nil
277
- @completion_journey_data = nil
234
+ @vi_waiting_operator = nil
235
+ @vi_waiting_operator_arg = nil
236
+ @completion_journey_state = nil
278
237
  @completion_state = CompletionState::NORMAL
279
238
  @perfect_matched = nil
280
239
  @menu_info = nil
281
- @first_prompt = true
282
240
  @searching_prompt = nil
283
- @first_char = true
284
- @add_newline_to_end_of_buffer = false
285
- @just_cursor_moving = nil
286
- @cached_prompt_list = nil
287
- @prompt_cache_time = nil
241
+ @just_cursor_moving = false
288
242
  @eof = false
289
243
  @continuous_insertion_buffer = String.new(encoding: @encoding)
290
- @scroll_partial_screen = nil
291
- @prev_mode_string = nil
244
+ @scroll_partial_screen = 0
292
245
  @drop_terminate_spaces = false
293
246
  @in_pasting = false
294
247
  @auto_indent_proc = nil
295
248
  @dialogs = []
296
- @last_key = nil
249
+ @interrupted = false
297
250
  @resized = false
251
+ @cache = {}
252
+ @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
253
+ @input_lines = [[[""], 0, 0]]
254
+ @input_lines_position = 0
255
+ @undoing = false
256
+ @prev_action_state = NullActionState
257
+ @next_action_state = NullActionState
298
258
  reset_line
299
259
  end
300
260
 
301
261
  def reset_line
302
- @cursor = 0
303
- @cursor_max = 0
304
262
  @byte_pointer = 0
305
263
  @buffer_of_lines = [String.new(encoding: @encoding)]
306
264
  @line_index = 0
307
- @previous_line_index = nil
308
- @line = @buffer_of_lines[0]
309
- @first_line_started_from = 0
310
- @move_up = 0
311
- @started_from = 0
312
- @highest_in_this = 1
313
- @highest_in_all = 1
265
+ @cache.clear
314
266
  @line_backup_in_history = nil
315
267
  @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
316
- @check_new_auto_indent = false
317
268
  end
318
269
 
319
270
  def multiline_on
@@ -324,68 +275,44 @@ class Reline::LineEditor
324
275
  @is_multiline = false
325
276
  end
326
277
 
327
- private def calculate_height_by_lines(lines, prompt)
328
- result = 0
329
- prompt_list = prompt.is_a?(Array) ? prompt : nil
330
- lines.each_with_index { |line, i|
331
- prompt = prompt_list[i] if prompt_list and prompt_list[i]
332
- result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
333
- }
334
- result
335
- end
336
-
337
278
  private def insert_new_line(cursor_line, next_line)
338
- @line = cursor_line
339
279
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
340
- @previous_line_index = @line_index
280
+ @buffer_of_lines[@line_index] = cursor_line
341
281
  @line_index += 1
342
- @just_cursor_moving = false
343
- end
344
-
345
- private def calculate_height_by_width(width)
346
- width.div(@screen_size.last) + 1
347
- end
348
-
349
- private def split_by_width(str, max_width)
350
- Reline::Unicode.split_by_width(str, max_width, @encoding)
351
- end
352
-
353
- private def scroll_down(val)
354
- if val <= @rest_height
355
- Reline::IOGate.move_cursor_down(val)
356
- @rest_height -= val
357
- else
358
- Reline::IOGate.move_cursor_down(@rest_height)
359
- Reline::IOGate.scroll_down(val - @rest_height)
360
- @rest_height = 0
282
+ @byte_pointer = 0
283
+ if @auto_indent_proc && !@in_pasting
284
+ if next_line.empty?
285
+ (
286
+ # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
287
+ indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
288
+ indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
289
+ indent = indent2 || indent1
290
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
291
+ )
292
+ process_auto_indent @line_index, add_newline: true
293
+ else
294
+ process_auto_indent @line_index - 1, cursor_dependent: false
295
+ process_auto_indent @line_index, add_newline: true # Need for compatibility
296
+ process_auto_indent @line_index, cursor_dependent: false
297
+ end
361
298
  end
362
299
  end
363
300
 
364
- private def move_cursor_up(val)
365
- if val > 0
366
- Reline::IOGate.move_cursor_up(val)
367
- @rest_height += val
368
- elsif val < 0
369
- move_cursor_down(-val)
370
- end
301
+ private def split_by_width(str, max_width, offset: 0)
302
+ Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
371
303
  end
372
304
 
373
- private def move_cursor_down(val)
374
- if val > 0
375
- Reline::IOGate.move_cursor_down(val)
376
- @rest_height -= val
377
- @rest_height = 0 if @rest_height < 0
378
- elsif val < 0
379
- move_cursor_up(-val)
380
- end
305
+ def current_byte_pointer_cursor
306
+ calculate_width(current_line.byteslice(0, @byte_pointer))
381
307
  end
382
308
 
383
- private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
309
+ private def calculate_nearest_cursor(cursor)
310
+ line_to_calc = current_line
384
311
  new_cursor_max = calculate_width(line_to_calc)
385
312
  new_cursor = 0
386
313
  new_byte_pointer = 0
387
314
  height = 1
388
- max_width = @screen_size.last
315
+ max_width = screen_width
389
316
  if @config.editing_mode_is?(:vi_command)
390
317
  last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
391
318
  if last_byte_size > 0
@@ -411,120 +338,242 @@ class Reline::LineEditor
411
338
  end
412
339
  new_byte_pointer += gc.bytesize
413
340
  end
414
- new_started_from = height - 1
415
- if update
416
- @cursor = new_cursor
417
- @cursor_max = new_cursor_max
418
- @started_from = new_started_from
419
- @byte_pointer = new_byte_pointer
420
- else
421
- [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
341
+ @byte_pointer = new_byte_pointer
342
+ end
343
+
344
+ def with_cache(key, *deps)
345
+ cached_deps, value = @cache[key]
346
+ if cached_deps != deps
347
+ @cache[key] = [deps, value = yield(*deps, cached_deps, value)]
422
348
  end
349
+ value
423
350
  end
424
351
 
425
- def rerender_all
426
- @rerender_all = true
427
- process_insert(force: true)
428
- rerender
352
+ def modified_lines
353
+ with_cache(__method__, whole_lines, finished?) do |whole, complete|
354
+ modify_lines(whole, complete)
355
+ end
429
356
  end
430
357
 
431
- def rerender
432
- return if @line.nil?
433
- if @menu_info
434
- scroll_down(@highest_in_all - @first_line_started_from)
435
- @rerender_all = true
358
+ def prompt_list
359
+ with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string|
360
+ check_multiline_prompt(lines, mode_string)
436
361
  end
437
- if @menu_info
438
- show_menu
439
- @menu_info = nil
362
+ end
363
+
364
+ def screen_height
365
+ @screen_size.first
366
+ end
367
+
368
+ def screen_width
369
+ @screen_size.last
370
+ end
371
+
372
+ def screen_scroll_top
373
+ @scroll_partial_screen
374
+ end
375
+
376
+ def wrapped_prompt_and_input_lines
377
+ with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
378
+ prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
379
+ cached_wraps = {}
380
+ if prev_width == width
381
+ prev_n.times do |i|
382
+ cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
383
+ end
384
+ end
385
+
386
+ n.times.map do |i|
387
+ prompt = prompts[i] || ''
388
+ line = lines[i] || ''
389
+ if (cached = cached_wraps[[prompt, line]])
390
+ next cached
391
+ end
392
+ *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
393
+ wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
394
+ wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
395
+ end
440
396
  end
441
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
442
- if @cleared
443
- clear_screen_buffer(prompt, prompt_list, prompt_width)
444
- @cleared = false
445
- return
397
+ end
398
+
399
+ def calculate_overlay_levels(overlay_levels)
400
+ levels = []
401
+ overlay_levels.each do |x, w, l|
402
+ levels.fill(l, x, w)
446
403
  end
447
- if @is_multiline and finished? and @scroll_partial_screen
448
- # Re-output all code higher than the screen when finished.
449
- Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
450
- Reline::IOGate.move_cursor_column(0)
451
- @scroll_partial_screen = nil
452
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
453
- if @previous_line_index
454
- new_lines = whole_lines(index: @previous_line_index, line: @line)
404
+ levels
405
+ end
406
+
407
+ def render_line_differential(old_items, new_items)
408
+ 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)
409
+ new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
410
+ base_x = 0
411
+ new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
412
+ width = chunk.size
413
+ if level == :skip
414
+ # do nothing
415
+ elsif level == :blank
416
+ Reline::IOGate.move_cursor_column base_x
417
+ @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
455
418
  else
456
- new_lines = whole_lines
419
+ x, w, content = new_items[level]
420
+ cover_begin = base_x != 0 && new_levels[base_x - 1] == level
421
+ cover_end = new_levels[base_x + width] == level
422
+ pos = 0
423
+ unless x == base_x && w == width
424
+ content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
425
+ end
426
+ Reline::IOGate.move_cursor_column x + pos
427
+ @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
457
428
  end
458
- modify_lines(new_lines).each_with_index do |line, index|
459
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
460
- Reline::IOGate.erase_after_cursor
429
+ base_x += width
430
+ end
431
+ if old_levels.size > new_levels.size
432
+ Reline::IOGate.move_cursor_column new_levels.size
433
+ Reline::IOGate.erase_after_cursor
434
+ end
435
+ end
436
+
437
+ # Calculate cursor position in word wrapped content.
438
+ def wrapped_cursor_position
439
+ prompt_width = calculate_width(prompt_list[@line_index], true)
440
+ line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
441
+ wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
442
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
443
+ wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
444
+ [wrapped_cursor_x, wrapped_cursor_y]
445
+ end
446
+
447
+ def clear_dialogs
448
+ @dialogs.each do |dialog|
449
+ dialog.contents = nil
450
+ dialog.trap_key = nil
451
+ end
452
+ end
453
+
454
+ def update_dialogs(key = nil)
455
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
456
+ @dialogs.each do |dialog|
457
+ dialog.trap_key = nil
458
+ update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key)
459
+ end
460
+ end
461
+
462
+ def render_finished
463
+ clear_rendered_lines
464
+ render_full_content
465
+ end
466
+
467
+ def clear_rendered_lines
468
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
469
+ Reline::IOGate.move_cursor_column 0
470
+
471
+ num_lines = @rendered_screen.lines.size
472
+ return unless num_lines && num_lines >= 1
473
+
474
+ Reline::IOGate.move_cursor_down num_lines - 1
475
+ (num_lines - 1).times do
476
+ Reline::IOGate.erase_after_cursor
477
+ Reline::IOGate.move_cursor_up 1
478
+ end
479
+ Reline::IOGate.erase_after_cursor
480
+ @rendered_screen.lines = []
481
+ @rendered_screen.cursor_y = 0
482
+ end
483
+
484
+ def render_full_content
485
+ lines = @buffer_of_lines.size.times.map do |i|
486
+ line = prompt_list[i] + modified_lines[i]
487
+ wrapped_lines, = split_by_width(line, screen_width)
488
+ wrapped_lines.last.empty? ? "#{line} " : line
489
+ end
490
+ @output.puts lines.map { |l| "#{l}\r\n" }.join
491
+ end
492
+
493
+ def print_nomultiline_prompt(prompt)
494
+ return unless prompt && !@is_multiline
495
+
496
+ # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
497
+ @rendered_screen.lines = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
498
+ @rendered_screen.cursor_y = 0
499
+ @output.write prompt
500
+ end
501
+
502
+ def render_differential
503
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
504
+
505
+ rendered_lines = @rendered_screen.lines
506
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
507
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
508
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
509
+ end
510
+ if @menu_info
511
+ @menu_info.lines(screen_width).each do |item|
512
+ new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
461
513
  end
462
- @output.flush
463
- clear_dialog
464
- return
514
+ @menu_info = nil # TODO: do not change state here
465
515
  end
466
- new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
467
- rendered = false
468
- if @add_newline_to_end_of_buffer
469
- clear_dialog_with_content
470
- rerender_added_newline(prompt, prompt_width)
471
- @add_newline_to_end_of_buffer = false
472
- else
473
- if @just_cursor_moving and not @rerender_all
474
- clear_dialog_with_content
475
- rendered = just_move_cursor
476
- @just_cursor_moving = false
477
- return
478
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
479
- clear_dialog_with_content
480
- rerender_changed_current_line
481
- @previous_line_index = nil
482
- rendered = true
483
- elsif @rerender_all
484
- rerender_all_lines
485
- @rerender_all = false
486
- rendered = true
487
- else
516
+
517
+ @dialogs.each_with_index do |dialog, index|
518
+ next unless dialog.contents
519
+
520
+ x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
521
+ y_range.each do |row|
522
+ next if row < 0 || row >= screen_height
523
+ dialog_rows = new_lines[row] ||= []
524
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
525
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
488
526
  end
489
527
  end
490
- if @is_multiline
491
- if finished?
492
- # Always rerender on finish because output_modifier_proc may return a different output.
493
- if @previous_line_index
494
- new_lines = whole_lines(index: @previous_line_index, line: @line)
495
- else
496
- new_lines = whole_lines
497
- end
498
- line = modify_lines(new_lines)[@line_index]
499
- clear_dialog
500
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
501
- render_partial(prompt, prompt_width, line, @first_line_started_from)
502
- move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
503
- scroll_down(1)
504
- Reline::IOGate.move_cursor_column(0)
505
- Reline::IOGate.erase_after_cursor
506
- else
507
- if not rendered and not @in_pasting
508
- line = modify_lines(whole_lines)[@line_index]
509
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
510
- render_partial(prompt, prompt_width, line, @first_line_started_from)
511
- end
512
- render_dialog((prompt_width + @cursor) % @screen_size.last)
528
+
529
+ cursor_y = @rendered_screen.cursor_y
530
+ if new_lines != rendered_lines
531
+ # Hide cursor while rendering to avoid cursor flickering.
532
+ Reline::IOGate.hide_cursor
533
+ num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
534
+ if @rendered_screen.base_y + num_lines > screen_height
535
+ Reline::IOGate.scroll_down(num_lines - cursor_y - 1)
536
+ @rendered_screen.base_y = screen_height - num_lines
537
+ cursor_y = num_lines - 1
513
538
  end
514
- @buffer_of_lines[@line_index] = @line
515
- @rest_height = 0 if @scroll_partial_screen
516
- else
517
- line = modify_lines(whole_lines)[@line_index]
518
- render_partial(prompt, prompt_width, line, 0)
519
- if finished?
520
- scroll_down(1)
521
- Reline::IOGate.move_cursor_column(0)
522
- Reline::IOGate.erase_after_cursor
539
+ num_lines.times do |i|
540
+ rendered_line = rendered_lines[i] || []
541
+ line_to_render = new_lines[i] || []
542
+ next if rendered_line == line_to_render
543
+
544
+ Reline::IOGate.move_cursor_down i - cursor_y
545
+ cursor_y = i
546
+ unless rendered_lines[i]
547
+ Reline::IOGate.move_cursor_column 0
548
+ Reline::IOGate.erase_after_cursor
549
+ end
550
+ render_line_differential(rendered_line, line_to_render)
523
551
  end
552
+ @rendered_screen.lines = new_lines
553
+ Reline::IOGate.show_cursor
524
554
  end
555
+ y = wrapped_cursor_y - screen_scroll_top
556
+ Reline::IOGate.move_cursor_column wrapped_cursor_x
557
+ Reline::IOGate.move_cursor_down y - cursor_y
558
+ @rendered_screen.cursor_y = y
559
+ new_lines.size - y
560
+ end
561
+
562
+ def upper_space_height(wrapped_cursor_y)
563
+ wrapped_cursor_y - screen_scroll_top
564
+ end
565
+
566
+ def rest_height(wrapped_cursor_y)
567
+ screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
568
+ end
569
+
570
+ def rerender
571
+ render_differential unless @in_pasting
525
572
  end
526
573
 
527
574
  class DialogProcScope
575
+ CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
576
+
528
577
  def initialize(line_editor, config, proc_to_exec, context)
529
578
  @line_editor = line_editor
530
579
  @config = config
@@ -575,11 +624,20 @@ class Reline::LineEditor
575
624
  end
576
625
 
577
626
  def screen_width
578
- @line_editor.instance_variable_get(:@screen_size).last
627
+ @line_editor.screen_width
628
+ end
629
+
630
+ def screen_height
631
+ @line_editor.screen_height
632
+ end
633
+
634
+ def preferred_dialog_height
635
+ _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position
636
+ [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max
579
637
  end
580
638
 
581
639
  def completion_journey_data
582
- @line_editor.instance_variable_get(:@completion_journey_data)
640
+ @line_editor.dialog_proc_scope_completion_journey_data
583
641
  end
584
642
 
585
643
  def config
@@ -593,7 +651,7 @@ class Reline::LineEditor
593
651
 
594
652
  class Dialog
595
653
  attr_reader :name, :contents, :width
596
- attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
654
+ attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
597
655
 
598
656
  def initialize(name, config, proc_scope)
599
657
  @name = name
@@ -628,10 +686,8 @@ class Reline::LineEditor
628
686
  @trap_key.each do |t|
629
687
  @config.add_oneshot_key_binding(t, @name)
630
688
  end
631
- elsif @trap_key.is_a?(Array)
689
+ else
632
690
  @config.add_oneshot_key_binding(@trap_key, @name)
633
- elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
634
- @config.add_oneshot_key_binding([@trap_key], @name)
635
691
  end
636
692
  end
637
693
  dialog_render_info
@@ -648,52 +704,31 @@ class Reline::LineEditor
648
704
  end
649
705
 
650
706
  DIALOG_DEFAULT_HEIGHT = 20
651
- private def render_dialog(cursor_column)
652
- @dialogs.each do |dialog|
653
- render_each_dialog(dialog, cursor_column)
654
- end
655
- end
656
707
 
657
- private def padding_space_with_escape_sequences(str, width)
658
- padding_width = width - calculate_width(str, true)
659
- # padding_width should be only positive value. But macOS and Alacritty returns negative value.
660
- padding_width = 0 if padding_width < 0
661
- str + (' ' * padding_width)
708
+ private def dialog_range(dialog, dialog_y)
709
+ x_range = dialog.column...dialog.column + dialog.width
710
+ y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
711
+ [x_range, y_range]
662
712
  end
663
713
 
664
- private def render_each_dialog(dialog, cursor_column)
665
- if @in_pasting
666
- clear_each_dialog(dialog)
667
- dialog.contents = nil
668
- dialog.trap_key = nil
669
- return
670
- end
671
- dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
672
- dialog_render_info = dialog.call(@last_key)
714
+ private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil)
715
+ dialog.set_cursor_pos(cursor_column, cursor_row)
716
+ dialog_render_info = dialog.call(key)
673
717
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
674
- dialog.lines_backup = {
675
- lines: modify_lines(whole_lines),
676
- line_index: @line_index,
677
- first_line_started_from: @first_line_started_from,
678
- started_from: @started_from,
679
- byte_pointer: @byte_pointer
680
- }
681
- clear_each_dialog(dialog)
682
718
  dialog.contents = nil
683
719
  dialog.trap_key = nil
684
720
  return
685
721
  end
686
- old_dialog = dialog.clone
687
- dialog.contents = dialog_render_info.contents
722
+ contents = dialog_render_info.contents
688
723
  pointer = dialog.pointer
689
724
  if dialog_render_info.width
690
725
  dialog.width = dialog_render_info.width
691
726
  else
692
- dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
727
+ dialog.width = contents.map { |l| calculate_width(l, true) }.max
693
728
  end
694
729
  height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
695
- height = dialog.contents.size if dialog.contents.size < height
696
- if dialog.contents.size > height
730
+ height = contents.size if contents.size < height
731
+ if contents.size > height
697
732
  if dialog.pointer
698
733
  if dialog.pointer < 0
699
734
  dialog.scroll_top = 0
@@ -703,591 +738,77 @@ class Reline::LineEditor
703
738
  dialog.scroll_top = dialog.pointer
704
739
  end
705
740
  pointer = dialog.pointer - dialog.scroll_top
741
+ else
742
+ dialog.scroll_top = 0
706
743
  end
707
- dialog.contents = dialog.contents[dialog.scroll_top, height]
708
- end
709
- if dialog.contents and dialog.scroll_top >= dialog.contents.size
710
- dialog.scroll_top = dialog.contents.size - height
744
+ contents = contents[dialog.scroll_top, height]
711
745
  end
712
746
  if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
713
747
  bar_max_height = height * 2
714
748
  moving_distance = (dialog_render_info.contents.size - height) * 2
715
749
  position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
716
- bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
717
- dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
750
+ bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
751
+ bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
752
+ scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
718
753
  else
719
- dialog.scrollbar_pos = nil
754
+ scrollbar_pos = nil
720
755
  end
721
- upper_space = @first_line_started_from - @started_from
722
756
  dialog.column = dialog_render_info.pos.x
723
- dialog.width += @block_elem_width if dialog.scrollbar_pos
724
- diff = (dialog.column + dialog.width) - (@screen_size.last)
757
+ dialog.width += @block_elem_width if scrollbar_pos
758
+ diff = (dialog.column + dialog.width) - screen_width
725
759
  if diff > 0
726
760
  dialog.column -= diff
727
761
  end
728
- if (@rest_height - dialog_render_info.pos.y) >= height
762
+ if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height
729
763
  dialog.vertical_offset = dialog_render_info.pos.y + 1
730
- elsif upper_space >= height
764
+ elsif cursor_row >= height
731
765
  dialog.vertical_offset = dialog_render_info.pos.y - height
732
766
  else
733
- if (@rest_height - dialog_render_info.pos.y) < height
734
- scroll_down(height + dialog_render_info.pos.y)
735
- move_cursor_up(height + dialog_render_info.pos.y)
736
- end
737
767
  dialog.vertical_offset = dialog_render_info.pos.y + 1
738
768
  end
739
- Reline::IOGate.hide_cursor
740
769
  if dialog.column < 0
741
770
  dialog.column = 0
742
- dialog.width = @screen_size.last
743
- end
744
- reset_dialog(dialog, old_dialog)
745
- move_cursor_down(dialog.vertical_offset)
746
- Reline::IOGate.move_cursor_column(dialog.column)
747
- dialog.contents.each_with_index do |item, i|
748
- if i == pointer
749
- fg_color = dialog_render_info.pointer_fg_color
750
- bg_color = dialog_render_info.pointer_bg_color
751
- else
752
- fg_color = dialog_render_info.fg_color
753
- bg_color = dialog_render_info.bg_color
754
- end
755
- str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
756
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
757
- @output.write "\e[#{bg_color}m\e[#{fg_color}m#{str}"
758
- if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
759
- @output.write "\e[37m"
760
- if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
761
- @output.write @full_block
762
- elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
763
- @output.write @upper_half_block
764
- elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
765
- @output.write @lower_half_block
766
- else
767
- @output.write ' ' * @block_elem_width
768
- end
769
- end
770
- @output.write "\e[0m"
771
- Reline::IOGate.move_cursor_column(dialog.column)
772
- move_cursor_down(1) if i < (dialog.contents.size - 1)
773
- end
774
- Reline::IOGate.move_cursor_column(cursor_column)
775
- move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
776
- Reline::IOGate.show_cursor
777
- dialog.lines_backup = {
778
- lines: modify_lines(whole_lines),
779
- line_index: @line_index,
780
- first_line_started_from: @first_line_started_from,
781
- started_from: @started_from,
782
- byte_pointer: @byte_pointer
783
- }
784
- end
785
-
786
- private def reset_dialog(dialog, old_dialog)
787
- return if dialog.lines_backup.nil? or old_dialog.contents.nil?
788
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
789
- visual_lines = []
790
- visual_start = nil
791
- dialog.lines_backup[:lines].each_with_index { |l, i|
792
- pr = prompt_list ? prompt_list[i] : prompt
793
- vl, _ = split_by_width(pr + l, @screen_size.last)
794
- vl.compact!
795
- if i == dialog.lines_backup[:line_index]
796
- visual_start = visual_lines.size + dialog.lines_backup[:started_from]
797
- end
798
- visual_lines.concat(vl)
799
- }
800
- old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
801
- y = @first_line_started_from + @started_from
802
- y_diff = y - old_y
803
- if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
804
- # rerender top
805
- move_cursor_down(old_dialog.vertical_offset - y_diff)
806
- start = visual_start + old_dialog.vertical_offset
807
- line_num = dialog.vertical_offset - old_dialog.vertical_offset
808
- line_num.times do |i|
809
- Reline::IOGate.move_cursor_column(old_dialog.column)
810
- if visual_lines[start + i].nil?
811
- s = ' ' * old_dialog.width
771
+ dialog.width = screen_width
772
+ end
773
+ face = Reline::Face[dialog_render_info.face || :default]
774
+ scrollbar_sgr = face[:scrollbar]
775
+ default_sgr = face[:default]
776
+ enhanced_sgr = face[:enhanced]
777
+ dialog.contents = contents.map.with_index do |item, i|
778
+ line_sgr = i == pointer ? enhanced_sgr : default_sgr
779
+ str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
780
+ str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
781
+ colored_content = "#{line_sgr}#{str}"
782
+ if scrollbar_pos
783
+ if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
784
+ colored_content + scrollbar_sgr + @full_block
785
+ elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
786
+ colored_content + scrollbar_sgr + @upper_half_block
787
+ elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
788
+ colored_content + scrollbar_sgr + @lower_half_block
812
789
  else
813
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
814
- s = padding_space_with_escape_sequences(s, old_dialog.width)
790
+ colored_content + scrollbar_sgr + ' ' * @block_elem_width
815
791
  end
816
- @output.write "\e[0m#{s}\e[0m"
817
- move_cursor_down(1) if i < (line_num - 1)
818
- end
819
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
820
- end
821
- if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
822
- # rerender bottom
823
- move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
824
- start = visual_start + dialog.vertical_offset + dialog.contents.size
825
- line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
826
- line_num.times do |i|
827
- Reline::IOGate.move_cursor_column(old_dialog.column)
828
- if visual_lines[start + i].nil?
829
- s = ' ' * old_dialog.width
830
- else
831
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
832
- s = padding_space_with_escape_sequences(s, old_dialog.width)
833
- end
834
- @output.write "\e[0m#{s}\e[0m"
835
- move_cursor_down(1) if i < (line_num - 1)
836
- end
837
- move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
838
- end
839
- if old_dialog.column < dialog.column
840
- # rerender left
841
- move_cursor_down(old_dialog.vertical_offset - y_diff)
842
- width = dialog.column - old_dialog.column
843
- start = visual_start + old_dialog.vertical_offset
844
- line_num = old_dialog.contents.size
845
- line_num.times do |i|
846
- Reline::IOGate.move_cursor_column(old_dialog.column)
847
- if visual_lines[start + i].nil?
848
- s = ' ' * width
849
- else
850
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
851
- s = padding_space_with_escape_sequences(s, dialog.width)
852
- end
853
- @output.write "\e[0m#{s}\e[0m"
854
- move_cursor_down(1) if i < (line_num - 1)
855
- end
856
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
857
- end
858
- if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
859
- # rerender right
860
- move_cursor_down(old_dialog.vertical_offset + y_diff)
861
- width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
862
- start = visual_start + old_dialog.vertical_offset
863
- line_num = old_dialog.contents.size
864
- line_num.times do |i|
865
- Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
866
- if visual_lines[start + i].nil?
867
- s = ' ' * width
868
- else
869
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
870
- rerender_width = old_dialog.width - dialog.width
871
- s = padding_space_with_escape_sequences(s, rerender_width)
872
- end
873
- Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
874
- @output.write "\e[0m#{s}\e[0m"
875
- move_cursor_down(1) if i < (line_num - 1)
876
- end
877
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
878
- end
879
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
880
- end
881
-
882
- private def clear_dialog
883
- @dialogs.each do |dialog|
884
- clear_each_dialog(dialog)
885
- end
886
- end
887
-
888
- private def clear_dialog_with_content
889
- @dialogs.each do |dialog|
890
- clear_each_dialog(dialog)
891
- dialog.contents = nil
892
- dialog.trap_key = nil
893
- end
894
- end
895
-
896
- private def clear_each_dialog(dialog)
897
- dialog.trap_key = nil
898
- return unless dialog.contents
899
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
900
- visual_lines = []
901
- visual_lines_under_dialog = []
902
- visual_start = nil
903
- dialog.lines_backup[:lines].each_with_index { |l, i|
904
- pr = prompt_list ? prompt_list[i] : prompt
905
- vl, _ = split_by_width(pr + l, @screen_size.last)
906
- vl.compact!
907
- if i == dialog.lines_backup[:line_index]
908
- visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
909
- end
910
- visual_lines.concat(vl)
911
- }
912
- visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
913
- visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
914
- Reline::IOGate.hide_cursor
915
- move_cursor_down(dialog.vertical_offset)
916
- dialog_vertical_size = dialog.contents.size
917
- dialog_vertical_size.times do |i|
918
- if i < visual_lines_under_dialog.size
919
- Reline::IOGate.move_cursor_column(dialog.column)
920
- str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
921
- str = padding_space_with_escape_sequences(str, dialog.width)
922
- @output.write "\e[0m#{str}\e[0m"
923
- else
924
- Reline::IOGate.move_cursor_column(dialog.column)
925
- @output.write "\e[0m#{' ' * dialog.width}\e[0m"
926
- end
927
- move_cursor_down(1) if i < (dialog_vertical_size - 1)
928
- end
929
- move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
930
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
931
- Reline::IOGate.show_cursor
932
- end
933
-
934
- private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
935
- if @screen_height < highest_in_all
936
- old_scroll_partial_screen = @scroll_partial_screen
937
- if cursor_y == 0
938
- @scroll_partial_screen = 0
939
- elsif cursor_y == (highest_in_all - 1)
940
- @scroll_partial_screen = highest_in_all - @screen_height
941
- else
942
- if @scroll_partial_screen
943
- if cursor_y <= @scroll_partial_screen
944
- @scroll_partial_screen = cursor_y
945
- elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
946
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
947
- end
948
- else
949
- if cursor_y > (@screen_height - 1)
950
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
951
- else
952
- @scroll_partial_screen = 0
953
- end
954
- end
955
- end
956
- if @scroll_partial_screen != old_scroll_partial_screen
957
- @rerender_all = true
958
- end
959
- else
960
- if @scroll_partial_screen
961
- @rerender_all = true
962
- end
963
- @scroll_partial_screen = nil
964
- end
965
- end
966
-
967
- private def rerender_added_newline(prompt, prompt_width)
968
- scroll_down(1)
969
- @buffer_of_lines[@previous_line_index] = @line
970
- @line = @buffer_of_lines[@line_index]
971
- unless @in_pasting
972
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
973
- end
974
- @cursor = @cursor_max = calculate_width(@line)
975
- @byte_pointer = @line.bytesize
976
- @highest_in_all += @highest_in_this
977
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
978
- @first_line_started_from += @started_from + 1
979
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
980
- @previous_line_index = nil
981
- end
982
-
983
- def just_move_cursor
984
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
985
- move_cursor_up(@started_from)
986
- new_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
- first_line_diff = new_first_line_started_from - @first_line_started_from
993
- new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
994
- new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
995
- calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
996
- @previous_line_index = nil
997
- if @rerender_all
998
- @line = @buffer_of_lines[@line_index]
999
- rerender_all_lines
1000
- @rerender_all = false
1001
- true
1002
- else
1003
- @line = @buffer_of_lines[@line_index]
1004
- @first_line_started_from = new_first_line_started_from
1005
- @started_from = new_started_from
1006
- @cursor = new_cursor
1007
- @cursor_max = new_cursor_max
1008
- @byte_pointer = new_byte_pointer
1009
- move_cursor_down(first_line_diff + @started_from)
1010
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1011
- false
1012
- end
1013
- end
1014
-
1015
- private def rerender_changed_current_line
1016
- if @previous_line_index
1017
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1018
- else
1019
- new_lines = whole_lines
1020
- end
1021
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
1022
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
1023
- diff = all_height - @highest_in_all
1024
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
1025
- if diff > 0
1026
- scroll_down(diff)
1027
- move_cursor_up(all_height - 1)
1028
- elsif diff < 0
1029
- (-diff).times do
1030
- Reline::IOGate.move_cursor_column(0)
1031
- Reline::IOGate.erase_after_cursor
1032
- move_cursor_up(1)
1033
- end
1034
- move_cursor_up(all_height - 1)
1035
- else
1036
- move_cursor_up(all_height - 1)
1037
- end
1038
- @highest_in_all = all_height
1039
- back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
1040
- move_cursor_up(back)
1041
- if @previous_line_index
1042
- @buffer_of_lines[@previous_line_index] = @line
1043
- @line = @buffer_of_lines[@line_index]
1044
- end
1045
- @first_line_started_from =
1046
- if @line_index.zero?
1047
- 0
1048
- else
1049
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
1050
- end
1051
- if @prompt_proc
1052
- prompt = prompt_list[@line_index]
1053
- prompt_width = calculate_width(prompt, true)
1054
- end
1055
- move_cursor_down(@first_line_started_from)
1056
- calculate_nearest_cursor
1057
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1058
- move_cursor_down(@started_from)
1059
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1060
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
1061
- end
1062
-
1063
- private def rerender_all_lines
1064
- move_cursor_up(@first_line_started_from + @started_from)
1065
- Reline::IOGate.move_cursor_column(0)
1066
- back = 0
1067
- new_buffer = whole_lines
1068
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
1069
- new_buffer.each_with_index do |line, index|
1070
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
1071
- width = prompt_width + calculate_width(line)
1072
- height = calculate_height_by_width(width)
1073
- back += height
1074
- end
1075
- old_highest_in_all = @highest_in_all
1076
- if @line_index.zero?
1077
- new_first_line_started_from = 0
1078
- else
1079
- new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
1080
- end
1081
- new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1082
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
1083
- if @scroll_partial_screen
1084
- move_cursor_up(@first_line_started_from + @started_from)
1085
- scroll_down(@screen_height - 1)
1086
- move_cursor_up(@screen_height)
1087
- Reline::IOGate.move_cursor_column(0)
1088
- elsif back > old_highest_in_all
1089
- scroll_down(back - 1)
1090
- move_cursor_up(back - 1)
1091
- elsif back < old_highest_in_all
1092
- scroll_down(back)
1093
- Reline::IOGate.erase_after_cursor
1094
- (old_highest_in_all - back - 1).times do
1095
- scroll_down(1)
1096
- Reline::IOGate.erase_after_cursor
1097
- end
1098
- move_cursor_up(old_highest_in_all - 1)
1099
- end
1100
- render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
1101
- if @prompt_proc
1102
- prompt = prompt_list[@line_index]
1103
- prompt_width = calculate_width(prompt, true)
1104
- end
1105
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
1106
- @highest_in_all = back
1107
- @first_line_started_from = new_first_line_started_from
1108
- @started_from = new_started_from
1109
- if @scroll_partial_screen
1110
- Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
1111
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1112
- else
1113
- move_cursor_down(@first_line_started_from + @started_from - back + 1)
1114
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1115
- end
1116
- end
1117
-
1118
- private def render_whole_lines(lines, prompt, prompt_width)
1119
- rendered_height = 0
1120
- modify_lines(lines).each_with_index do |line, index|
1121
- if prompt.is_a?(Array)
1122
- line_prompt = prompt[index]
1123
- prompt_width = calculate_width(line_prompt, true)
1124
- else
1125
- line_prompt = prompt
1126
- end
1127
- height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
1128
- if index < (lines.size - 1)
1129
- if @scroll_partial_screen
1130
- if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
1131
- move_cursor_down(1)
1132
- end
1133
- else
1134
- scroll_down(1)
1135
- end
1136
- rendered_height += height
1137
- else
1138
- rendered_height += height - 1
1139
- end
1140
- end
1141
- rendered_height
1142
- end
1143
-
1144
- private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
1145
- visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
1146
- cursor_up_from_last_line = 0
1147
- if @scroll_partial_screen
1148
- last_visual_line = this_started_from + (height - 1)
1149
- last_screen_line = @scroll_partial_screen + (@screen_height - 1)
1150
- if (@scroll_partial_screen - this_started_from) >= height
1151
- # Render nothing because this line is before the screen.
1152
- visual_lines = []
1153
- elsif this_started_from > last_screen_line
1154
- # Render nothing because this line is after the screen.
1155
- visual_lines = []
1156
- else
1157
- deleted_lines_before_screen = []
1158
- if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
1159
- # A part of visual lines are before the screen.
1160
- deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
1161
- deleted_lines_before_screen.compact!
1162
- end
1163
- if this_started_from <= last_screen_line and last_screen_line < last_visual_line
1164
- # A part of visual lines are after the screen.
1165
- visual_lines.pop((last_visual_line - last_screen_line) * 2)
1166
- end
1167
- move_cursor_up(deleted_lines_before_screen.size - @started_from)
1168
- cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
1169
- end
1170
- end
1171
- if with_control
1172
- if height > @highest_in_this
1173
- diff = height - @highest_in_this
1174
- scroll_down(diff)
1175
- @highest_in_all += diff
1176
- @highest_in_this = height
1177
- move_cursor_up(diff)
1178
- elsif height < @highest_in_this
1179
- diff = @highest_in_this - height
1180
- @highest_in_all -= diff
1181
- @highest_in_this = height
1182
- end
1183
- move_cursor_up(@started_from)
1184
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1185
- cursor_up_from_last_line = height - 1 - @started_from
1186
- end
1187
- if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
1188
- @output.write "\e[0m" # clear character decorations
1189
- end
1190
- visual_lines.each_with_index do |line, index|
1191
- Reline::IOGate.move_cursor_column(0)
1192
- if line.nil?
1193
- if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
1194
- # reaches the end of line
1195
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
1196
- # A newline is automatically inserted if a character is rendered at
1197
- # eol on command prompt.
1198
- else
1199
- # When the cursor is at the end of the line and erases characters
1200
- # after the cursor, some terminals delete the character at the
1201
- # cursor position.
1202
- move_cursor_down(1)
1203
- Reline::IOGate.move_cursor_column(0)
1204
- end
1205
- else
1206
- Reline::IOGate.erase_after_cursor
1207
- move_cursor_down(1)
1208
- Reline::IOGate.move_cursor_column(0)
1209
- end
1210
- next
1211
- end
1212
- @output.write line
1213
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
1214
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
1215
- @rest_height -= 1 if @rest_height > 0
1216
- end
1217
- @output.flush
1218
- if @first_prompt
1219
- @first_prompt = false
1220
- @pre_input_hook&.call
1221
- end
1222
- end
1223
- unless visual_lines.empty?
1224
- Reline::IOGate.erase_after_cursor
1225
- Reline::IOGate.move_cursor_column(0)
1226
- end
1227
- if with_control
1228
- # Just after rendring, so the cursor is on the last line.
1229
- if finished?
1230
- Reline::IOGate.move_cursor_column(0)
1231
792
  else
1232
- # Moves up from bottom of lines to the cursor position.
1233
- move_cursor_up(cursor_up_from_last_line)
1234
- # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
1235
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
793
+ colored_content
1236
794
  end
1237
795
  end
1238
- height
1239
796
  end
1240
797
 
1241
- private def modify_lines(before)
1242
- return before if before.nil? || before.empty? || simplified_rendering?
1243
-
1244
- if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
798
+ private def modify_lines(before, complete)
799
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
1245
800
  after.lines("\n").map { |l| l.chomp('') }
1246
801
  else
1247
- before
1248
- end
1249
- end
1250
-
1251
- private def show_menu
1252
- scroll_down(@highest_in_all - @first_line_started_from)
1253
- @rerender_all = true
1254
- @menu_info.list.sort!.each do |item|
1255
- Reline::IOGate.move_cursor_column(0)
1256
- @output.write item
1257
- @output.flush
1258
- scroll_down(1)
802
+ before.map { |l| Reline::Unicode.escape_for_print(l) }
1259
803
  end
1260
- scroll_down(@highest_in_all - 1)
1261
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
1262
- end
1263
-
1264
- private def clear_screen_buffer(prompt, prompt_list, prompt_width)
1265
- Reline::IOGate.clear_screen
1266
- back = 0
1267
- modify_lines(whole_lines).each_with_index do |line, index|
1268
- if @prompt_proc
1269
- pr = prompt_list[index]
1270
- height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
1271
- else
1272
- height = render_partial(prompt, prompt_width, line, back, with_control: false)
1273
- end
1274
- if index < (@buffer_of_lines.size - 1)
1275
- move_cursor_down(1)
1276
- back += height
1277
- end
1278
- end
1279
- move_cursor_up(back)
1280
- move_cursor_down(@first_line_started_from + @started_from)
1281
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
1282
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1283
804
  end
1284
805
 
1285
806
  def editing_mode
1286
807
  @config.editing_mode
1287
808
  end
1288
809
 
1289
- private def menu(target, list)
1290
- @menu_info = MenuInfo.new(target, list)
810
+ private def menu(_target, list)
811
+ @menu_info = MenuInfo.new(list)
1291
812
  end
1292
813
 
1293
814
  private def complete_internal_proc(list, is_menu)
@@ -1315,7 +836,7 @@ class Reline::LineEditor
1315
836
  item_mbchars = item.grapheme_clusters
1316
837
  end
1317
838
  size = [memo_mbchars.size, item_mbchars.size].min
1318
- result = ''
839
+ result = +''
1319
840
  size.times do |i|
1320
841
  if @config.completion_ignore_case
1321
842
  if memo_mbchars[i].casecmp?(item_mbchars[i])
@@ -1336,9 +857,9 @@ class Reline::LineEditor
1336
857
  [target, preposing, completed, postposing]
1337
858
  end
1338
859
 
1339
- private def complete(list, just_show_list = false)
860
+ private def perform_completion(list, just_show_list)
1340
861
  case @completion_state
1341
- when CompletionState::NORMAL, CompletionState::JOURNEY
862
+ when CompletionState::NORMAL
1342
863
  @completion_state = CompletionState::COMPLETION
1343
864
  when CompletionState::PERFECT_MATCH
1344
865
  @dig_perfect_match_proc&.(@perfect_matched)
@@ -1365,100 +886,80 @@ class Reline::LineEditor
1365
886
  @completion_state = CompletionState::PERFECT_MATCH
1366
887
  else
1367
888
  @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
889
+ perform_completion(list, true) if @config.show_all_if_ambiguous
1368
890
  end
1369
891
  @perfect_matched = completed
1370
892
  else
1371
893
  @completion_state = CompletionState::MENU
894
+ perform_completion(list, true) if @config.show_all_if_ambiguous
1372
895
  end
1373
896
  if not just_show_list and target < completed
1374
- @line = preposing + completed + completion_append_character.to_s + postposing
1375
- line_to_pointer = preposing + completed + completion_append_character.to_s
1376
- @cursor_max = calculate_width(@line)
1377
- @cursor = calculate_width(line_to_pointer)
897
+ @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
898
+ line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
1378
899
  @byte_pointer = line_to_pointer.bytesize
1379
900
  end
1380
901
  end
1381
902
  end
1382
903
 
1383
- private def move_completed_list(list, direction)
1384
- case @completion_state
1385
- when CompletionState::NORMAL, CompletionState::COMPLETION,
1386
- CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
1387
- @completion_state = CompletionState::JOURNEY
1388
- result = retrieve_completion_block
1389
- return if result.nil?
1390
- preposing, target, postposing = result
1391
- @completion_journey_data = CompletionJourneyData.new(
1392
- preposing, postposing,
1393
- [target] + list.select{ |item| item.start_with?(target) }, 0)
1394
- if @completion_journey_data.list.size == 1
1395
- @completion_journey_data.pointer = 0
1396
- else
1397
- case direction
1398
- when :up
1399
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1400
- when :down
1401
- @completion_journey_data.pointer = 1
1402
- end
1403
- end
1404
- @completion_state = CompletionState::JOURNEY
1405
- else
1406
- case direction
1407
- when :up
1408
- @completion_journey_data.pointer -= 1
1409
- if @completion_journey_data.pointer < 0
1410
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1411
- end
1412
- when :down
1413
- @completion_journey_data.pointer += 1
1414
- if @completion_journey_data.pointer >= @completion_journey_data.list.size
1415
- @completion_journey_data.pointer = 0
1416
- end
1417
- end
904
+ def dialog_proc_scope_completion_journey_data
905
+ return nil unless @completion_journey_state
906
+ line_index = @completion_journey_state.line_index
907
+ pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" }
908
+ post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" }
909
+ DialogProcScope::CompletionJourneyData.new(
910
+ pre_lines.join + @completion_journey_state.pre,
911
+ @completion_journey_state.post + post_lines.join,
912
+ @completion_journey_state.list,
913
+ @completion_journey_state.pointer
914
+ )
915
+ end
916
+
917
+ private def move_completed_list(direction)
918
+ @completion_journey_state ||= retrieve_completion_journey_state
919
+ return false unless @completion_journey_state
920
+
921
+ if (delta = { up: -1, down: +1 }[direction])
922
+ @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size
1418
923
  end
1419
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
1420
- new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
1421
- @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
1422
- line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
1423
- line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
1424
- @cursor_max = calculate_width(@line)
1425
- @cursor = calculate_width(line_to_pointer)
1426
- @byte_pointer = line_to_pointer.bytesize
924
+ completed = @completion_journey_state.list[@completion_journey_state.pointer]
925
+ set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize)
926
+ true
927
+ end
928
+
929
+ private def retrieve_completion_journey_state
930
+ preposing, target, postposing = retrieve_completion_block
931
+ list = call_completion_proc
932
+ return unless list.is_a?(Array)
933
+
934
+ candidates = list.select{ |item| item.start_with?(target) }
935
+ return if candidates.empty?
936
+
937
+ pre = preposing.split("\n", -1).last || ''
938
+ post = postposing.split("\n", -1).first || ''
939
+ CompletionJourneyState.new(
940
+ @line_index, pre, target, post, [target] + candidates, 0
941
+ )
1427
942
  end
1428
943
 
1429
944
  private def run_for_operators(key, method_symbol, &block)
1430
- if @waiting_operator_proc
945
+ if @vi_waiting_operator
1431
946
  if VI_MOTIONS.include?(method_symbol)
1432
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
1433
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
947
+ old_byte_pointer = @byte_pointer
948
+ @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
1434
949
  block.(true)
1435
950
  unless @waiting_proc
1436
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
1437
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
1438
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
1439
- else
1440
- old_waiting_proc = @waiting_proc
1441
- old_waiting_operator_proc = @waiting_operator_proc
1442
- current_waiting_operator_proc = @waiting_operator_proc
1443
- @waiting_proc = proc { |k|
1444
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
1445
- old_waiting_proc.(k)
1446
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
1447
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
1448
- current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
1449
- @waiting_operator_proc = old_waiting_operator_proc
1450
- }
951
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
952
+ @byte_pointer = old_byte_pointer
953
+ method_obj = method(@vi_waiting_operator)
954
+ wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
955
+ cleanup_waiting
1451
956
  end
1452
957
  else
1453
958
  # Ignores operator when not motion is given.
1454
959
  block.(false)
960
+ cleanup_waiting
1455
961
  end
1456
- @waiting_operator_proc = nil
1457
- @waiting_operator_vi_arg = nil
1458
- if @vi_arg
1459
- @rerender_all = true
1460
- @vi_arg = nil
1461
- end
962
+ @vi_arg = nil
1462
963
  else
1463
964
  block.(false)
1464
965
  end
@@ -1475,7 +976,7 @@ class Reline::LineEditor
1475
976
  end
1476
977
 
1477
978
  def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
1478
- if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
979
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
1479
980
  not_insertion = method_symbol != :ed_insert
1480
981
  process_insert(force: not_insertion)
1481
982
  end
@@ -1494,11 +995,33 @@ class Reline::LineEditor
1494
995
  end
1495
996
  end
1496
997
 
998
+ private def cleanup_waiting
999
+ @waiting_proc = nil
1000
+ @vi_waiting_operator = nil
1001
+ @vi_waiting_operator_arg = nil
1002
+ @searching_prompt = nil
1003
+ @drop_terminate_spaces = false
1004
+ end
1005
+
1497
1006
  private def process_key(key, method_symbol)
1007
+ if key.is_a?(Symbol)
1008
+ cleanup_waiting
1009
+ elsif @waiting_proc
1010
+ old_byte_pointer = @byte_pointer
1011
+ @waiting_proc.call(key)
1012
+ if @vi_waiting_operator
1013
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
1014
+ @byte_pointer = old_byte_pointer
1015
+ method_obj = method(@vi_waiting_operator)
1016
+ wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
1017
+ cleanup_waiting
1018
+ end
1019
+ @kill_ring.process
1020
+ return
1021
+ end
1022
+
1498
1023
  if method_symbol and respond_to?(method_symbol, true)
1499
1024
  method_obj = method(method_symbol)
1500
- else
1501
- method_obj = nil
1502
1025
  end
1503
1026
  if method_symbol and key.is_a?(Symbol)
1504
1027
  if @vi_arg and argumentable?(method_obj)
@@ -1510,7 +1033,6 @@ class Reline::LineEditor
1510
1033
  end
1511
1034
  @kill_ring.process
1512
1035
  if @vi_arg
1513
- @rerender_al = true
1514
1036
  @vi_arg = nil
1515
1037
  end
1516
1038
  elsif @vi_arg
@@ -1521,8 +1043,6 @@ class Reline::LineEditor
1521
1043
  run_for_operators(key, method_symbol) do |with_operator|
1522
1044
  wrap_method_call(method_symbol, method_obj, key, with_operator)
1523
1045
  end
1524
- elsif @waiting_proc
1525
- @waiting_proc.(key)
1526
1046
  elsif method_obj
1527
1047
  wrap_method_call(method_symbol, method_obj, key)
1528
1048
  else
@@ -1530,13 +1050,9 @@ class Reline::LineEditor
1530
1050
  end
1531
1051
  @kill_ring.process
1532
1052
  if @vi_arg
1533
- @rerender_all = true
1534
1053
  @vi_arg = nil
1535
1054
  end
1536
1055
  end
1537
- elsif @waiting_proc
1538
- @waiting_proc.(key)
1539
- @kill_ring.process
1540
1056
  elsif method_obj
1541
1057
  if method_symbol == :ed_argument_digit
1542
1058
  wrap_method_call(method_symbol, method_obj, key)
@@ -1552,11 +1068,6 @@ class Reline::LineEditor
1552
1068
  end
1553
1069
 
1554
1070
  private def normal_char(key)
1555
- method_symbol = method_obj = nil
1556
- if key.combined_char.is_a?(Symbol)
1557
- process_key(key.combined_char, key.combined_char)
1558
- return
1559
- end
1560
1071
  @multibyte_buffer << key.combined_char
1561
1072
  if @multibyte_buffer.size > 1
1562
1073
  if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
@@ -1569,98 +1080,101 @@ class Reline::LineEditor
1569
1080
  else # single byte
1570
1081
  return if key.char >= 128 # maybe, first byte of multi byte
1571
1082
  method_symbol = @config.editing_mode.get_method(key.combined_char)
1572
- if key.with_meta and method_symbol == :ed_unassigned
1573
- # split ESC + key
1574
- method_symbol = @config.editing_mode.get_method("\e".ord)
1575
- process_key("\e".ord, method_symbol)
1576
- method_symbol = @config.editing_mode.get_method(key.char)
1577
- process_key(key.char, method_symbol)
1578
- else
1579
- process_key(key.combined_char, method_symbol)
1580
- end
1083
+ process_key(key.combined_char, method_symbol)
1581
1084
  @multibyte_buffer.clear
1582
1085
  end
1583
- if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
1584
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1086
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1087
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1585
1088
  @byte_pointer -= byte_size
1586
- mbchar = @line.byteslice(@byte_pointer, byte_size)
1587
- width = Reline::Unicode.get_mbchar_width(mbchar)
1588
- @cursor -= width
1089
+ end
1090
+ end
1091
+
1092
+ def update(key)
1093
+ modified = input_key(key)
1094
+ unless @in_pasting
1095
+ scroll_into_view
1096
+ @just_cursor_moving = !modified
1097
+ update_dialogs(key)
1098
+ @just_cursor_moving = false
1589
1099
  end
1590
1100
  end
1591
1101
 
1592
1102
  def input_key(key)
1593
- @last_key = key
1103
+ save_old_buffer
1594
1104
  @config.reset_oneshot_key_bindings
1595
1105
  @dialogs.each do |dialog|
1596
1106
  if key.char.instance_of?(Symbol) and key.char == dialog.name
1597
1107
  return
1598
1108
  end
1599
1109
  end
1600
- @just_cursor_moving = nil
1601
1110
  if key.char.nil?
1602
- if @first_char
1603
- @line = nil
1604
- end
1111
+ process_insert(force: true)
1112
+ @eof = buffer_empty?
1605
1113
  finish
1606
1114
  return
1607
1115
  end
1608
- old_line = @line.dup
1609
- @first_char = false
1610
- completion_occurs = false
1611
- if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1612
- unless @config.disable_completion
1613
- result = call_completion_proc
1614
- if result.is_a?(Array)
1615
- completion_occurs = true
1616
- process_insert
1617
- if @config.autocompletion
1618
- move_completed_list(result, :down)
1619
- else
1620
- complete(result)
1621
- end
1622
- end
1623
- end
1624
- elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
1625
- if not @config.disable_completion and @config.autocompletion
1626
- result = call_completion_proc
1627
- if result.is_a?(Array)
1628
- completion_occurs = true
1629
- process_insert
1630
- move_completed_list(result, :up)
1631
- end
1632
- end
1633
- elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1634
- unless @config.disable_completion
1635
- result = call_completion_proc
1636
- if result.is_a?(Array)
1637
- completion_occurs = true
1638
- process_insert
1639
- move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
1640
- end
1641
- end
1642
- elsif Symbol === key.char and respond_to?(key.char, true)
1116
+ @completion_occurs = false
1117
+
1118
+ if key.char.is_a?(Symbol)
1643
1119
  process_key(key.char, key.char)
1644
1120
  else
1645
1121
  normal_char(key)
1646
1122
  end
1647
- unless completion_occurs
1123
+
1124
+ @prev_action_state, @next_action_state = @next_action_state, NullActionState
1125
+
1126
+ unless @completion_occurs
1648
1127
  @completion_state = CompletionState::NORMAL
1649
- @completion_journey_data = nil
1128
+ @completion_journey_state = nil
1650
1129
  end
1651
- if not @in_pasting and @just_cursor_moving.nil?
1652
- if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1653
- @just_cursor_moving = true
1654
- elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1655
- @just_cursor_moving = true
1656
- else
1657
- @just_cursor_moving = false
1658
- end
1130
+
1131
+ push_input_lines unless @undoing
1132
+ @undoing = false
1133
+
1134
+ if @in_pasting
1135
+ clear_dialogs
1136
+ return
1137
+ end
1138
+
1139
+ modified = @old_buffer_of_lines != @buffer_of_lines
1140
+ if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1141
+ # Auto complete starts only when edited
1142
+ process_insert(force: true)
1143
+ @completion_journey_state = retrieve_completion_journey_state
1144
+ end
1145
+ modified
1146
+ end
1147
+
1148
+ def save_old_buffer
1149
+ @old_buffer_of_lines = @buffer_of_lines.dup
1150
+ end
1151
+
1152
+ def push_input_lines
1153
+ if @old_buffer_of_lines == @buffer_of_lines
1154
+ @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1659
1155
  else
1660
- @just_cursor_moving = false
1156
+ @input_lines = @input_lines[0..@input_lines_position]
1157
+ @input_lines_position += 1
1158
+ @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1661
1159
  end
1662
- if @is_multiline and @auto_indent_proc and not simplified_rendering?
1663
- process_auto_indent
1160
+ trim_input_lines
1161
+ end
1162
+
1163
+ MAX_INPUT_LINES = 100
1164
+ def trim_input_lines
1165
+ if @input_lines.size > MAX_INPUT_LINES
1166
+ @input_lines.shift
1167
+ @input_lines_position -= 1
1168
+ end
1169
+ end
1170
+
1171
+ def scroll_into_view
1172
+ _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
1173
+ if wrapped_cursor_y < screen_scroll_top
1174
+ @scroll_partial_screen = wrapped_cursor_y
1175
+ end
1176
+ if wrapped_cursor_y >= screen_scroll_top + screen_height
1177
+ @scroll_partial_screen = wrapped_cursor_y - screen_height + 1
1664
1178
  end
1665
1179
  end
1666
1180
 
@@ -1694,46 +1208,52 @@ class Reline::LineEditor
1694
1208
  result
1695
1209
  end
1696
1210
 
1697
- private def process_auto_indent
1698
- return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
1699
- if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
1700
- # Fix indent of a line when a newline is inserted to the next
1701
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1702
- new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
1703
- md = @line.match(/\A */)
1704
- prev_indent = md[0].count(' ')
1705
- @line = ' ' * new_indent + @line.lstrip
1211
+ private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false)
1212
+ return if @in_pasting
1213
+ return unless @auto_indent_proc
1706
1214
 
1707
- new_indent = nil
1708
- result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
1709
- if result
1710
- new_indent = result
1711
- end
1712
- if new_indent&.>= 0
1713
- @line = ' ' * new_indent + @line.lstrip
1714
- end
1215
+ line = @buffer_of_lines[line_index]
1216
+ byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize
1217
+ new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
1218
+ return unless new_indent
1219
+
1220
+ new_line = ' ' * new_indent + line.lstrip
1221
+ @buffer_of_lines[line_index] = new_line
1222
+ if @line_index == line_index
1223
+ indent_diff = new_line.bytesize - line.bytesize
1224
+ @byte_pointer = [@byte_pointer + indent_diff, 0].max
1715
1225
  end
1716
- if @previous_line_index
1717
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1226
+ end
1227
+
1228
+ def line()
1229
+ @buffer_of_lines.join("\n") unless eof?
1230
+ end
1231
+
1232
+ def current_line
1233
+ @buffer_of_lines[@line_index]
1234
+ end
1235
+
1236
+ def set_current_line(line, byte_pointer = nil)
1237
+ cursor = current_byte_pointer_cursor
1238
+ @buffer_of_lines[@line_index] = line
1239
+ if byte_pointer
1240
+ @byte_pointer = byte_pointer
1718
1241
  else
1719
- new_lines = whole_lines
1720
- end
1721
- new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1722
- new_indent = @cursor_max if new_indent&.> @cursor_max
1723
- if new_indent&.>= 0
1724
- md = new_lines[@line_index].match(/\A */)
1725
- prev_indent = md[0].count(' ')
1726
- if @check_new_auto_indent
1727
- @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1728
- @cursor = new_indent
1729
- @byte_pointer = new_indent
1730
- else
1731
- @line = ' ' * new_indent + @line.lstrip
1732
- @cursor += new_indent - prev_indent
1733
- @byte_pointer += new_indent - prev_indent
1734
- end
1242
+ calculate_nearest_cursor(cursor)
1243
+ end
1244
+ process_auto_indent
1245
+ end
1246
+
1247
+ def set_current_lines(lines, byte_pointer = nil, line_index = 0)
1248
+ cursor = current_byte_pointer_cursor
1249
+ @buffer_of_lines = lines
1250
+ @line_index = line_index
1251
+ if byte_pointer
1252
+ @byte_pointer = byte_pointer
1253
+ else
1254
+ calculate_nearest_cursor(cursor)
1735
1255
  end
1736
- @check_new_auto_indent = false
1256
+ process_auto_indent
1737
1257
  end
1738
1258
 
1739
1259
  def retrieve_completion_block(set_completion_quote_character = false)
@@ -1747,7 +1267,7 @@ class Reline::LineEditor
1747
1267
  else
1748
1268
  quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1749
1269
  end
1750
- before = @line.byteslice(0, @byte_pointer)
1270
+ before = current_line.byteslice(0, @byte_pointer)
1751
1271
  rest = nil
1752
1272
  break_pointer = nil
1753
1273
  quote = nil
@@ -1755,7 +1275,7 @@ class Reline::LineEditor
1755
1275
  escaped_quote = nil
1756
1276
  i = 0
1757
1277
  while i < @byte_pointer do
1758
- slice = @line.byteslice(i, @byte_pointer - i)
1278
+ slice = current_line.byteslice(i, @byte_pointer - i)
1759
1279
  unless slice.valid_encoding?
1760
1280
  i += 1
1761
1281
  next
@@ -1777,15 +1297,15 @@ class Reline::LineEditor
1777
1297
  elsif word_break_regexp and not quote and slice =~ word_break_regexp
1778
1298
  rest = $'
1779
1299
  i += 1
1780
- before = @line.byteslice(i, @byte_pointer - i)
1300
+ before = current_line.byteslice(i, @byte_pointer - i)
1781
1301
  break_pointer = i
1782
1302
  else
1783
1303
  i += 1
1784
1304
  end
1785
1305
  end
1786
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1306
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1787
1307
  if rest
1788
- preposing = @line.byteslice(0, break_pointer)
1308
+ preposing = current_line.byteslice(0, break_pointer)
1789
1309
  target = rest
1790
1310
  if set_completion_quote_character and quote
1791
1311
  Reline.core.instance_variable_set(:@completion_quote_character, quote)
@@ -1796,133 +1316,97 @@ class Reline::LineEditor
1796
1316
  else
1797
1317
  preposing = ''
1798
1318
  if break_pointer
1799
- preposing = @line.byteslice(0, break_pointer)
1319
+ preposing = current_line.byteslice(0, break_pointer)
1800
1320
  else
1801
1321
  preposing = ''
1802
1322
  end
1803
1323
  target = before
1804
1324
  end
1805
- if @is_multiline
1806
- if @previous_line_index
1807
- lines = whole_lines(index: @previous_line_index, line: @line)
1808
- else
1809
- lines = whole_lines
1810
- end
1811
- if @line_index > 0
1812
- preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1813
- end
1814
- if (lines.size - 1) > @line_index
1815
- postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1816
- end
1325
+ lines = whole_lines
1326
+ if @line_index > 0
1327
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1328
+ end
1329
+ if (lines.size - 1) > @line_index
1330
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1817
1331
  end
1818
1332
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1819
1333
  end
1820
1334
 
1821
1335
  def confirm_multiline_termination
1822
1336
  temp_buffer = @buffer_of_lines.dup
1823
- if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
1824
- temp_buffer[@previous_line_index] = @line
1825
- else
1826
- temp_buffer[@line_index] = @line
1827
- end
1828
1337
  @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1829
1338
  end
1830
1339
 
1340
+ def insert_pasted_text(text)
1341
+ save_old_buffer
1342
+ pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1343
+ post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1344
+ lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
1345
+ lines << '' if lines.empty?
1346
+ @buffer_of_lines[@line_index, 1] = lines
1347
+ @line_index += lines.size - 1
1348
+ @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1349
+ push_input_lines
1350
+ end
1351
+
1831
1352
  def insert_text(text)
1832
- width = calculate_width(text)
1833
- if @cursor == @cursor_max
1834
- @line += text
1353
+ if @buffer_of_lines[@line_index].bytesize == @byte_pointer
1354
+ @buffer_of_lines[@line_index] += text
1835
1355
  else
1836
- @line = byteinsert(@line, @byte_pointer, text)
1356
+ @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text)
1837
1357
  end
1838
1358
  @byte_pointer += text.bytesize
1839
- @cursor += width
1840
- @cursor_max += width
1359
+ process_auto_indent
1841
1360
  end
1842
1361
 
1843
1362
  def delete_text(start = nil, length = nil)
1844
1363
  if start.nil? and length.nil?
1845
- if @is_multiline
1846
- if @buffer_of_lines.size == 1
1847
- @line&.clear
1848
- @byte_pointer = 0
1849
- @cursor = 0
1850
- @cursor_max = 0
1851
- elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1852
- @buffer_of_lines.pop
1853
- @line_index -= 1
1854
- @line = @buffer_of_lines[@line_index]
1855
- @byte_pointer = 0
1856
- @cursor = 0
1857
- @cursor_max = calculate_width(@line)
1858
- elsif @line_index < (@buffer_of_lines.size - 1)
1859
- @buffer_of_lines.delete_at(@line_index)
1860
- @line = @buffer_of_lines[@line_index]
1861
- @byte_pointer = 0
1862
- @cursor = 0
1863
- @cursor_max = calculate_width(@line)
1864
- end
1865
- else
1866
- @line&.clear
1364
+ if @buffer_of_lines.size == 1
1365
+ @buffer_of_lines[@line_index] = ''
1366
+ @byte_pointer = 0
1367
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1368
+ @buffer_of_lines.pop
1369
+ @line_index -= 1
1370
+ @byte_pointer = 0
1371
+ elsif @line_index < (@buffer_of_lines.size - 1)
1372
+ @buffer_of_lines.delete_at(@line_index)
1867
1373
  @byte_pointer = 0
1868
- @cursor = 0
1869
- @cursor_max = 0
1870
1374
  end
1871
1375
  elsif not start.nil? and not length.nil?
1872
- if @line
1873
- before = @line.byteslice(0, start)
1874
- after = @line.byteslice(start + length, @line.bytesize)
1875
- @line = before + after
1876
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1877
- str = @line.byteslice(0, @byte_pointer)
1878
- @cursor = calculate_width(str)
1879
- @cursor_max = calculate_width(@line)
1376
+ if current_line
1377
+ before = current_line.byteslice(0, start)
1378
+ after = current_line.byteslice(start + length, current_line.bytesize)
1379
+ set_current_line(before + after)
1880
1380
  end
1881
1381
  elsif start.is_a?(Range)
1882
1382
  range = start
1883
1383
  first = range.first
1884
1384
  last = range.last
1885
- last = @line.bytesize - 1 if last > @line.bytesize
1886
- last += @line.bytesize if last < 0
1887
- first += @line.bytesize if first < 0
1385
+ last = current_line.bytesize - 1 if last > current_line.bytesize
1386
+ last += current_line.bytesize if last < 0
1387
+ first += current_line.bytesize if first < 0
1888
1388
  range = range.exclude_end? ? first...last : first..last
1889
- @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1890
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1891
- str = @line.byteslice(0, @byte_pointer)
1892
- @cursor = calculate_width(str)
1893
- @cursor_max = calculate_width(@line)
1389
+ line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1390
+ set_current_line(line)
1894
1391
  else
1895
- @line = @line.byteslice(0, start)
1896
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1897
- str = @line.byteslice(0, @byte_pointer)
1898
- @cursor = calculate_width(str)
1899
- @cursor_max = calculate_width(@line)
1392
+ set_current_line(current_line.byteslice(0, start))
1900
1393
  end
1901
1394
  end
1902
1395
 
1903
1396
  def byte_pointer=(val)
1904
1397
  @byte_pointer = val
1905
- str = @line.byteslice(0, @byte_pointer)
1906
- @cursor = calculate_width(str)
1907
- @cursor_max = calculate_width(@line)
1908
1398
  end
1909
1399
 
1910
- def whole_lines(index: @line_index, line: @line)
1911
- temp_lines = @buffer_of_lines.dup
1912
- temp_lines[index] = line
1913
- temp_lines
1400
+ def whole_lines
1401
+ @buffer_of_lines.dup
1914
1402
  end
1915
1403
 
1916
1404
  def whole_buffer
1917
- if @buffer_of_lines.size == 1 and @line.nil?
1918
- nil
1919
- else
1920
- if @previous_line_index
1921
- whole_lines(index: @previous_line_index, line: @line).join("\n")
1922
- else
1923
- whole_lines.join("\n")
1924
- end
1925
- end
1405
+ whole_lines.join("\n")
1406
+ end
1407
+
1408
+ private def buffer_empty?
1409
+ current_line.empty? and @buffer_of_lines.size == 1
1926
1410
  end
1927
1411
 
1928
1412
  def finished?
@@ -1931,7 +1415,6 @@ class Reline::LineEditor
1931
1415
 
1932
1416
  def finish
1933
1417
  @finished = true
1934
- @rerender_all = true
1935
1418
  @config.reset
1936
1419
  end
1937
1420
 
@@ -1953,40 +1436,65 @@ class Reline::LineEditor
1953
1436
  end
1954
1437
 
1955
1438
  private def key_delete(key)
1956
- if @config.editing_mode_is?(:vi_insert, :emacs)
1439
+ if @config.editing_mode_is?(:vi_insert)
1957
1440
  ed_delete_next_char(key)
1441
+ elsif @config.editing_mode_is?(:emacs)
1442
+ em_delete(key)
1958
1443
  end
1959
1444
  end
1960
1445
 
1961
1446
  private def key_newline(key)
1962
1447
  if @is_multiline
1963
- if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1964
- @add_newline_to_end_of_buffer = true
1965
- end
1966
- next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1967
- cursor_line = @line.byteslice(0, @byte_pointer)
1448
+ next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1449
+ cursor_line = current_line.byteslice(0, @byte_pointer)
1968
1450
  insert_new_line(cursor_line, next_line)
1969
- @cursor = 0
1970
- @check_new_auto_indent = true unless @in_pasting
1971
1451
  end
1972
1452
  end
1973
1453
 
1454
+ private def complete(_key)
1455
+ return if @config.disable_completion
1456
+
1457
+ process_insert(force: true)
1458
+ if @config.autocompletion
1459
+ @completion_state = CompletionState::NORMAL
1460
+ @completion_occurs = move_completed_list(:down)
1461
+ else
1462
+ @completion_journey_state = nil
1463
+ result = call_completion_proc
1464
+ if result.is_a?(Array)
1465
+ @completion_occurs = true
1466
+ perform_completion(result, false)
1467
+ end
1468
+ end
1469
+ end
1470
+
1471
+ private def completion_journey_move(direction)
1472
+ return if @config.disable_completion
1473
+
1474
+ process_insert(force: true)
1475
+ @completion_state = CompletionState::NORMAL
1476
+ @completion_occurs = move_completed_list(direction)
1477
+ end
1478
+
1479
+ private def menu_complete(_key)
1480
+ completion_journey_move(:down)
1481
+ end
1482
+
1483
+ private def menu_complete_backward(_key)
1484
+ completion_journey_move(:up)
1485
+ end
1486
+
1487
+ private def completion_journey_up(_key)
1488
+ completion_journey_move(:up) if @config.autocompletion
1489
+ end
1490
+
1974
1491
  # Editline:: +ed-unassigned+ This editor command always results in an error.
1975
1492
  # GNU Readline:: There is no corresponding macro.
1976
1493
  private def ed_unassigned(key) end # do nothing
1977
1494
 
1978
1495
  private def process_insert(force: false)
1979
1496
  return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1980
- width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1981
- bytesize = @continuous_insertion_buffer.bytesize
1982
- if @cursor == @cursor_max
1983
- @line += @continuous_insertion_buffer
1984
- else
1985
- @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1986
- end
1987
- @byte_pointer += bytesize
1988
- @cursor += width
1989
- @cursor_max += width
1497
+ insert_text(@continuous_insertion_buffer)
1990
1498
  @continuous_insertion_buffer.clear
1991
1499
  end
1992
1500
 
@@ -2004,9 +1512,6 @@ class Reline::LineEditor
2004
1512
  # million.
2005
1513
  # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
2006
1514
  private def ed_insert(key)
2007
- str = nil
2008
- width = nil
2009
- bytesize = nil
2010
1515
  if key.instance_of?(String)
2011
1516
  begin
2012
1517
  key.encode(Encoding::UTF_8)
@@ -2014,7 +1519,6 @@ class Reline::LineEditor
2014
1519
  return
2015
1520
  end
2016
1521
  str = key
2017
- bytesize = key.bytesize
2018
1522
  else
2019
1523
  begin
2020
1524
  key.chr.encode(Encoding::UTF_8)
@@ -2022,7 +1526,6 @@ class Reline::LineEditor
2022
1526
  return
2023
1527
  end
2024
1528
  str = key.chr
2025
- bytesize = 1
2026
1529
  end
2027
1530
  if @in_pasting
2028
1531
  @continuous_insertion_buffer << str
@@ -2030,28 +1533,8 @@ class Reline::LineEditor
2030
1533
  elsif not @continuous_insertion_buffer.empty?
2031
1534
  process_insert
2032
1535
  end
2033
- width = Reline::Unicode.get_mbchar_width(str)
2034
- if @cursor == @cursor_max
2035
- @line += str
2036
- else
2037
- @line = byteinsert(@line, @byte_pointer, str)
2038
- end
2039
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2040
- @byte_pointer += bytesize
2041
- last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
2042
- combined_char = last_mbchar + str
2043
- if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
2044
- # combined char
2045
- last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
2046
- combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
2047
- if combined_char_width > last_mbchar_width
2048
- width = combined_char_width - last_mbchar_width
2049
- else
2050
- width = 0
2051
- end
2052
- end
2053
- @cursor += width
2054
- @cursor_max += width
1536
+
1537
+ insert_text(str)
2055
1538
  end
2056
1539
  alias_method :ed_digit, :ed_insert
2057
1540
  alias_method :self_insert, :ed_insert
@@ -2073,18 +1556,11 @@ class Reline::LineEditor
2073
1556
  alias_method :quoted_insert, :ed_quoted_insert
2074
1557
 
2075
1558
  private def ed_next_char(key, arg: 1)
2076
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2077
- if (@byte_pointer < @line.bytesize)
2078
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2079
- width = Reline::Unicode.get_mbchar_width(mbchar)
2080
- @cursor += width if width
1559
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1560
+ if (@byte_pointer < current_line.bytesize)
2081
1561
  @byte_pointer += byte_size
2082
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
2083
- next_line = @buffer_of_lines[@line_index + 1]
2084
- @cursor = 0
1562
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
2085
1563
  @byte_pointer = 0
2086
- @cursor_max = calculate_width(next_line)
2087
- @previous_line_index = @line_index
2088
1564
  @line_index += 1
2089
1565
  end
2090
1566
  arg -= 1
@@ -2093,19 +1569,12 @@ class Reline::LineEditor
2093
1569
  alias_method :forward_char, :ed_next_char
2094
1570
 
2095
1571
  private def ed_prev_char(key, arg: 1)
2096
- if @cursor > 0
2097
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1572
+ if @byte_pointer > 0
1573
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2098
1574
  @byte_pointer -= byte_size
2099
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2100
- width = Reline::Unicode.get_mbchar_width(mbchar)
2101
- @cursor -= width
2102
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
2103
- prev_line = @buffer_of_lines[@line_index - 1]
2104
- @cursor = calculate_width(prev_line)
2105
- @byte_pointer = prev_line.bytesize
2106
- @cursor_max = calculate_width(prev_line)
2107
- @previous_line_index = @line_index
1575
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
2108
1576
  @line_index -= 1
1577
+ @byte_pointer = current_line.bytesize
2109
1578
  end
2110
1579
  arg -= 1
2111
1580
  ed_prev_char(key, arg: arg) if arg > 0
@@ -2113,157 +1582,109 @@ class Reline::LineEditor
2113
1582
  alias_method :backward_char, :ed_prev_char
2114
1583
 
2115
1584
  private def vi_first_print(key)
2116
- @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
1585
+ @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
2117
1586
  end
2118
1587
 
2119
1588
  private def ed_move_to_beg(key)
2120
- @byte_pointer = @cursor = 0
1589
+ @byte_pointer = 0
2121
1590
  end
2122
1591
  alias_method :beginning_of_line, :ed_move_to_beg
1592
+ alias_method :vi_zero, :ed_move_to_beg
2123
1593
 
2124
1594
  private def ed_move_to_end(key)
2125
- @byte_pointer = 0
2126
- @cursor = 0
2127
- byte_size = 0
2128
- while @byte_pointer < @line.bytesize
2129
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2130
- if byte_size > 0
2131
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2132
- @cursor += Reline::Unicode.get_mbchar_width(mbchar)
2133
- end
2134
- @byte_pointer += byte_size
2135
- end
1595
+ @byte_pointer = current_line.bytesize
2136
1596
  end
2137
1597
  alias_method :end_of_line, :ed_move_to_end
2138
1598
 
2139
- private def generate_searcher
2140
- Fiber.new do |first_key|
2141
- prev_search_key = first_key
2142
- search_word = String.new(encoding: @encoding)
2143
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
2144
- last_hit = nil
2145
- case first_key
2146
- when "\C-r".ord
2147
- prompt_name = 'reverse-i-search'
2148
- when "\C-s".ord
2149
- prompt_name = 'i-search'
1599
+ private def generate_searcher(search_key)
1600
+ search_word = String.new(encoding: @encoding)
1601
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1602
+ hit_pointer = nil
1603
+ lambda do |key|
1604
+ search_again = false
1605
+ case key
1606
+ when "\C-h".ord, "\C-?".ord
1607
+ grapheme_clusters = search_word.grapheme_clusters
1608
+ if grapheme_clusters.size > 0
1609
+ grapheme_clusters.pop
1610
+ search_word = grapheme_clusters.join
1611
+ end
1612
+ when "\C-r".ord, "\C-s".ord
1613
+ search_again = true if search_key == key
1614
+ search_key = key
1615
+ else
1616
+ multibyte_buf << key
1617
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1618
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
1619
+ multibyte_buf.clear
1620
+ end
2150
1621
  end
2151
- loop do
2152
- key = Fiber.yield(search_word)
2153
- search_again = false
2154
- case key
2155
- when -1 # determined
2156
- Reline.last_incremental_search = search_word
2157
- break
2158
- when "\C-h".ord, "\C-?".ord
2159
- grapheme_clusters = search_word.grapheme_clusters
2160
- if grapheme_clusters.size > 0
2161
- grapheme_clusters.pop
2162
- search_word = grapheme_clusters.join
2163
- end
2164
- when "\C-r".ord, "\C-s".ord
2165
- search_again = true if prev_search_key == key
2166
- prev_search_key = key
2167
- else
2168
- multibyte_buf << key
2169
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
2170
- search_word << multibyte_buf.dup.force_encoding(@encoding)
2171
- multibyte_buf.clear
1622
+ hit = nil
1623
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1624
+ hit_pointer = Reline::HISTORY.size
1625
+ hit = @line_backup_in_history
1626
+ else
1627
+ if search_again
1628
+ if search_word.empty? and Reline.last_incremental_search
1629
+ search_word = Reline.last_incremental_search
2172
1630
  end
2173
- end
2174
- hit = nil
2175
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
2176
- @history_pointer = nil
2177
- hit = @line_backup_in_history
2178
- else
2179
- if search_again
2180
- if search_word.empty? and Reline.last_incremental_search
2181
- search_word = Reline.last_incremental_search
2182
- end
2183
- if @history_pointer
2184
- case prev_search_key
2185
- when "\C-r".ord
2186
- history_pointer_base = 0
2187
- history = Reline::HISTORY[0..(@history_pointer - 1)]
2188
- when "\C-s".ord
2189
- history_pointer_base = @history_pointer + 1
2190
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
2191
- end
2192
- else
2193
- history_pointer_base = 0
2194
- history = Reline::HISTORY
2195
- end
2196
- elsif @history_pointer
2197
- case prev_search_key
1631
+ if @history_pointer
1632
+ case search_key
2198
1633
  when "\C-r".ord
2199
1634
  history_pointer_base = 0
2200
- history = Reline::HISTORY[0..@history_pointer]
1635
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
2201
1636
  when "\C-s".ord
2202
- history_pointer_base = @history_pointer
2203
- history = Reline::HISTORY[@history_pointer..-1]
1637
+ history_pointer_base = @history_pointer + 1
1638
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
2204
1639
  end
2205
1640
  else
2206
1641
  history_pointer_base = 0
2207
1642
  history = Reline::HISTORY
2208
1643
  end
2209
- case prev_search_key
1644
+ elsif @history_pointer
1645
+ case search_key
2210
1646
  when "\C-r".ord
2211
- hit_index = history.rindex { |item|
2212
- item.include?(search_word)
2213
- }
1647
+ history_pointer_base = 0
1648
+ history = Reline::HISTORY[0..@history_pointer]
2214
1649
  when "\C-s".ord
2215
- hit_index = history.index { |item|
2216
- item.include?(search_word)
2217
- }
2218
- end
2219
- if hit_index
2220
- @history_pointer = history_pointer_base + hit_index
2221
- hit = Reline::HISTORY[@history_pointer]
1650
+ history_pointer_base = @history_pointer
1651
+ history = Reline::HISTORY[@history_pointer..-1]
2222
1652
  end
1653
+ else
1654
+ history_pointer_base = 0
1655
+ history = Reline::HISTORY
2223
1656
  end
2224
- case prev_search_key
1657
+ case search_key
2225
1658
  when "\C-r".ord
2226
- prompt_name = 'reverse-i-search'
1659
+ hit_index = history.rindex { |item|
1660
+ item.include?(search_word)
1661
+ }
2227
1662
  when "\C-s".ord
2228
- prompt_name = 'i-search'
1663
+ hit_index = history.index { |item|
1664
+ item.include?(search_word)
1665
+ }
2229
1666
  end
2230
- if hit
2231
- if @is_multiline
2232
- @buffer_of_lines = hit.split("\n")
2233
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2234
- @line_index = @buffer_of_lines.size - 1
2235
- @line = @buffer_of_lines.last
2236
- @byte_pointer = @line.bytesize
2237
- @cursor = @cursor_max = calculate_width(@line)
2238
- @rerender_all = true
2239
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
2240
- else
2241
- @line = hit
2242
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
2243
- end
2244
- last_hit = hit
2245
- else
2246
- if @is_multiline
2247
- @rerender_all = true
2248
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
2249
- else
2250
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
2251
- end
1667
+ if hit_index
1668
+ hit_pointer = history_pointer_base + hit_index
1669
+ hit = Reline::HISTORY[hit_pointer]
2252
1670
  end
2253
1671
  end
1672
+ case search_key
1673
+ when "\C-r".ord
1674
+ prompt_name = 'reverse-i-search'
1675
+ when "\C-s".ord
1676
+ prompt_name = 'i-search'
1677
+ end
1678
+ prompt_name = "failed #{prompt_name}" unless hit
1679
+ [search_word, prompt_name, hit_pointer]
2254
1680
  end
2255
1681
  end
2256
1682
 
2257
1683
  private def incremental_search_history(key)
2258
1684
  unless @history_pointer
2259
- if @is_multiline
2260
- @line_backup_in_history = whole_buffer
2261
- else
2262
- @line_backup_in_history = @line
2263
- end
1685
+ @line_backup_in_history = whole_buffer
2264
1686
  end
2265
- searcher = generate_searcher
2266
- searcher.resume(key)
1687
+ searcher = generate_searcher(key)
2267
1688
  @searching_prompt = "(reverse-i-search)`': "
2268
1689
  termination_keys = ["\C-j".ord]
2269
1690
  termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -2275,67 +1696,41 @@ class Reline::LineEditor
2275
1696
  else
2276
1697
  buffer = @line_backup_in_history
2277
1698
  end
2278
- if @is_multiline
2279
- @buffer_of_lines = buffer.split("\n")
2280
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2281
- @line_index = @buffer_of_lines.size - 1
2282
- @line = @buffer_of_lines.last
2283
- @rerender_all = true
2284
- else
2285
- @line = buffer
2286
- end
1699
+ @buffer_of_lines = buffer.split("\n")
1700
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1701
+ @line_index = @buffer_of_lines.size - 1
2287
1702
  @searching_prompt = nil
2288
1703
  @waiting_proc = nil
2289
- @cursor_max = calculate_width(@line)
2290
- @cursor = @byte_pointer = 0
2291
- @rerender_all = true
2292
- @cached_prompt_list = nil
2293
- searcher.resume(-1)
1704
+ @byte_pointer = 0
2294
1705
  when "\C-g".ord
2295
- if @is_multiline
2296
- @buffer_of_lines = @line_backup_in_history.split("\n")
2297
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2298
- @line_index = @buffer_of_lines.size - 1
2299
- @line = @buffer_of_lines.last
2300
- @rerender_all = true
2301
- else
2302
- @line = @line_backup_in_history
2303
- end
2304
- @history_pointer = nil
1706
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1707
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1708
+ @line_index = @buffer_of_lines.size - 1
1709
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
2305
1710
  @searching_prompt = nil
2306
1711
  @waiting_proc = nil
2307
- @line_backup_in_history = nil
2308
- @cursor_max = calculate_width(@line)
2309
- @cursor = @byte_pointer = 0
2310
- @rerender_all = true
1712
+ @byte_pointer = 0
2311
1713
  else
2312
1714
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
2313
1715
  if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
2314
- searcher.resume(k)
1716
+ search_word, prompt_name, hit_pointer = searcher.call(k)
1717
+ Reline.last_incremental_search = search_word
1718
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1719
+ @searching_prompt += ': ' unless @is_multiline
1720
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
2315
1721
  else
2316
1722
  if @history_pointer
2317
1723
  line = Reline::HISTORY[@history_pointer]
2318
1724
  else
2319
1725
  line = @line_backup_in_history
2320
1726
  end
2321
- if @is_multiline
2322
- @line_backup_in_history = whole_buffer
2323
- @buffer_of_lines = line.split("\n")
2324
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2325
- @line_index = @buffer_of_lines.size - 1
2326
- @line = @buffer_of_lines.last
2327
- @rerender_all = true
2328
- else
2329
- @line_backup_in_history = @line
2330
- @line = line
2331
- end
1727
+ @line_backup_in_history = whole_buffer
1728
+ @buffer_of_lines = line.split("\n")
1729
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1730
+ @line_index = @buffer_of_lines.size - 1
2332
1731
  @searching_prompt = nil
2333
1732
  @waiting_proc = nil
2334
- @cursor_max = calculate_width(@line)
2335
- @cursor = @byte_pointer = 0
2336
- @rerender_all = true
2337
- @cached_prompt_list = nil
2338
- searcher.resume(-1)
1733
+ @byte_pointer = 0
2339
1734
  end
2340
1735
  end
2341
1736
  }
@@ -2351,199 +1746,97 @@ class Reline::LineEditor
2351
1746
  end
2352
1747
  alias_method :forward_search_history, :vi_search_next
2353
1748
 
2354
- private def ed_search_prev_history(key, arg: 1)
2355
- history = nil
2356
- h_pointer = nil
2357
- line_no = nil
2358
- substr = @line.slice(0, @byte_pointer)
2359
- if @history_pointer.nil?
2360
- return if not @line.empty? and substr.empty?
2361
- history = Reline::HISTORY
2362
- elsif @history_pointer.zero?
2363
- history = nil
2364
- h_pointer = nil
2365
- else
2366
- history = Reline::HISTORY.slice(0, @history_pointer)
2367
- end
2368
- return if history.nil?
2369
- if @is_multiline
2370
- h_pointer = history.rindex { |h|
2371
- h.split("\n").each_with_index { |l, i|
2372
- if l.start_with?(substr)
2373
- line_no = i
2374
- break
2375
- end
2376
- }
2377
- not line_no.nil?
2378
- }
2379
- else
2380
- h_pointer = history.rindex { |l|
2381
- l.start_with?(substr)
2382
- }
2383
- end
2384
- return if h_pointer.nil?
2385
- @history_pointer = h_pointer
2386
- if @is_multiline
2387
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2388
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2389
- @line_index = line_no
2390
- @line = @buffer_of_lines[@line_index]
2391
- @rerender_all = true
2392
- else
2393
- @line = Reline::HISTORY[@history_pointer]
1749
+ private def search_history(prefix, pointer_range)
1750
+ pointer_range.each do |pointer|
1751
+ lines = Reline::HISTORY[pointer].split("\n")
1752
+ lines.each_with_index do |line, index|
1753
+ return [pointer, index] if line.start_with?(prefix)
1754
+ end
2394
1755
  end
2395
- @cursor_max = calculate_width(@line)
1756
+ nil
1757
+ end
1758
+
1759
+ private def ed_search_prev_history(key, arg: 1)
1760
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
1761
+ return if @history_pointer == 0
1762
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
1763
+
1764
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
1765
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
1766
+ return unless h_pointer
1767
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
2396
1768
  arg -= 1
1769
+ set_next_action_state(:search_history, :empty) if substr.empty?
2397
1770
  ed_search_prev_history(key, arg: arg) if arg > 0
2398
1771
  end
2399
1772
  alias_method :history_search_backward, :ed_search_prev_history
2400
1773
 
2401
1774
  private def ed_search_next_history(key, arg: 1)
2402
- substr = @line.slice(0, @byte_pointer)
2403
- if @history_pointer.nil?
2404
- return
2405
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
2406
- return
2407
- end
2408
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
2409
- h_pointer = nil
2410
- line_no = nil
2411
- if @is_multiline
2412
- h_pointer = history.index { |h|
2413
- h.split("\n").each_with_index { |l, i|
2414
- if l.start_with?(substr)
2415
- line_no = i
2416
- break
2417
- end
2418
- }
2419
- not line_no.nil?
2420
- }
2421
- else
2422
- h_pointer = history.index { |l|
2423
- l.start_with?(substr)
2424
- }
2425
- end
2426
- h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1775
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
1776
+ return if @history_pointer.nil?
1777
+
1778
+ history_range = @history_pointer + 1...Reline::HISTORY.size
1779
+ h_pointer, line_index = search_history(substr, history_range)
2427
1780
  return if h_pointer.nil? and not substr.empty?
2428
- @history_pointer = h_pointer
2429
- if @is_multiline
2430
- if @history_pointer.nil? and substr.empty?
2431
- @buffer_of_lines = []
2432
- @line_index = 0
2433
- else
2434
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2435
- @line_index = line_no
2436
- end
2437
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2438
- @line = @buffer_of_lines[@line_index]
2439
- @rerender_all = true
2440
- else
2441
- if @history_pointer.nil? and substr.empty?
2442
- @line = ''
2443
- else
2444
- @line = Reline::HISTORY[@history_pointer]
2445
- end
2446
- end
2447
- @cursor_max = calculate_width(@line)
1781
+
1782
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
2448
1783
  arg -= 1
1784
+ set_next_action_state(:search_history, :empty) if substr.empty?
2449
1785
  ed_search_next_history(key, arg: arg) if arg > 0
2450
1786
  end
2451
1787
  alias_method :history_search_forward, :ed_search_next_history
2452
1788
 
2453
- private def ed_prev_history(key, arg: 1)
2454
- if @is_multiline and @line_index > 0
2455
- @previous_line_index = @line_index
2456
- @line_index -= 1
2457
- return
2458
- end
2459
- if Reline::HISTORY.empty?
2460
- return
1789
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
1790
+ history_pointer ||= Reline::HISTORY.size
1791
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1792
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
1793
+ if old_history_pointer == Reline::HISTORY.size
1794
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
1795
+ else
1796
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
2461
1797
  end
2462
- if @history_pointer.nil?
2463
- @history_pointer = Reline::HISTORY.size - 1
2464
- if @is_multiline
2465
- @line_backup_in_history = whole_buffer
2466
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2467
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2468
- @line_index = @buffer_of_lines.size - 1
2469
- @line = @buffer_of_lines.last
2470
- @rerender_all = true
2471
- else
2472
- @line_backup_in_history = @line
2473
- @line = Reline::HISTORY[@history_pointer]
2474
- end
2475
- elsif @history_pointer.zero?
2476
- return
1798
+ if history_pointer == Reline::HISTORY.size
1799
+ buf = @line_backup_in_history
1800
+ @history_pointer = @line_backup_in_history = nil
2477
1801
  else
2478
- if @is_multiline
2479
- Reline::HISTORY[@history_pointer] = whole_buffer
2480
- @history_pointer -= 1
2481
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2482
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2483
- @line_index = @buffer_of_lines.size - 1
2484
- @line = @buffer_of_lines.last
2485
- @rerender_all = true
2486
- else
2487
- Reline::HISTORY[@history_pointer] = @line
2488
- @history_pointer -= 1
2489
- @line = Reline::HISTORY[@history_pointer]
2490
- end
1802
+ buf = Reline::HISTORY[history_pointer]
1803
+ @history_pointer = history_pointer
2491
1804
  end
2492
- if @config.editing_mode_is?(:emacs, :vi_insert)
2493
- @cursor_max = @cursor = calculate_width(@line)
2494
- @byte_pointer = @line.bytesize
2495
- elsif @config.editing_mode_is?(:vi_command)
2496
- @byte_pointer = @cursor = 0
2497
- @cursor_max = calculate_width(@line)
1805
+ @buffer_of_lines = buf.split("\n")
1806
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1807
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1808
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1809
+ end
1810
+
1811
+ private def ed_prev_history(key, arg: 1)
1812
+ if @line_index > 0
1813
+ cursor = current_byte_pointer_cursor
1814
+ @line_index -= 1
1815
+ calculate_nearest_cursor(cursor)
1816
+ return
2498
1817
  end
1818
+ move_history(
1819
+ (@history_pointer || Reline::HISTORY.size) - 1,
1820
+ line: :end,
1821
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1822
+ )
2499
1823
  arg -= 1
2500
1824
  ed_prev_history(key, arg: arg) if arg > 0
2501
1825
  end
2502
1826
  alias_method :previous_history, :ed_prev_history
2503
1827
 
2504
1828
  private def ed_next_history(key, arg: 1)
2505
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
2506
- @previous_line_index = @line_index
1829
+ if @line_index < (@buffer_of_lines.size - 1)
1830
+ cursor = current_byte_pointer_cursor
2507
1831
  @line_index += 1
1832
+ calculate_nearest_cursor(cursor)
2508
1833
  return
2509
1834
  end
2510
- if @history_pointer.nil?
2511
- return
2512
- elsif @history_pointer == (Reline::HISTORY.size - 1)
2513
- if @is_multiline
2514
- @history_pointer = nil
2515
- @buffer_of_lines = @line_backup_in_history.split("\n")
2516
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2517
- @line_index = 0
2518
- @line = @buffer_of_lines.first
2519
- @rerender_all = true
2520
- else
2521
- @history_pointer = nil
2522
- @line = @line_backup_in_history
2523
- end
2524
- else
2525
- if @is_multiline
2526
- Reline::HISTORY[@history_pointer] = whole_buffer
2527
- @history_pointer += 1
2528
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
2529
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2530
- @line_index = 0
2531
- @line = @buffer_of_lines.first
2532
- @rerender_all = true
2533
- else
2534
- Reline::HISTORY[@history_pointer] = @line
2535
- @history_pointer += 1
2536
- @line = Reline::HISTORY[@history_pointer]
2537
- end
2538
- end
2539
- @line = '' unless @line
2540
- if @config.editing_mode_is?(:emacs, :vi_insert)
2541
- @cursor_max = @cursor = calculate_width(@line)
2542
- @byte_pointer = @line.bytesize
2543
- elsif @config.editing_mode_is?(:vi_command)
2544
- @byte_pointer = @cursor = 0
2545
- @cursor_max = calculate_width(@line)
2546
- end
1835
+ move_history(
1836
+ (@history_pointer || Reline::HISTORY.size) + 1,
1837
+ line: :start,
1838
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1839
+ )
2547
1840
  arg -= 1
2548
1841
  ed_next_history(key, arg: arg) if arg > 0
2549
1842
  end
@@ -2568,40 +1861,29 @@ class Reline::LineEditor
2568
1861
  end
2569
1862
  else
2570
1863
  # should check confirm_multiline_termination to finish?
2571
- @previous_line_index = @line_index
2572
1864
  @line_index = @buffer_of_lines.size - 1
1865
+ @byte_pointer = current_line.bytesize
2573
1866
  finish
2574
1867
  end
2575
1868
  end
2576
1869
  else
2577
- if @history_pointer
2578
- Reline::HISTORY[@history_pointer] = @line
2579
- @history_pointer = nil
2580
- end
2581
1870
  finish
2582
1871
  end
2583
1872
  end
2584
1873
 
2585
1874
  private def em_delete_prev_char(key, arg: 1)
2586
- if @is_multiline and @cursor == 0 and @line_index > 0
2587
- @buffer_of_lines[@line_index] = @line
2588
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2589
- @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2590
- @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2591
- @line_index -= 1
2592
- @line = @buffer_of_lines[@line_index]
2593
- @cursor_max = calculate_width(@line)
2594
- @rerender_all = true
2595
- elsif @cursor > 0
2596
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2597
- @byte_pointer -= byte_size
2598
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2599
- width = Reline::Unicode.get_mbchar_width(mbchar)
2600
- @cursor -= width
2601
- @cursor_max -= width
1875
+ arg.times do
1876
+ if @byte_pointer == 0 and @line_index > 0
1877
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1878
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1879
+ @line_index -= 1
1880
+ elsif @byte_pointer > 0
1881
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1882
+ line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1883
+ set_current_line(line, @byte_pointer - byte_size)
1884
+ end
2602
1885
  end
2603
- arg -= 1
2604
- em_delete_prev_char(key, arg: arg) if arg > 0
1886
+ process_auto_indent
2605
1887
  end
2606
1888
  alias_method :backward_delete_char, :em_delete_prev_char
2607
1889
 
@@ -2611,23 +1893,23 @@ class Reline::LineEditor
2611
1893
  # the line. With a negative numeric argument, kill backward
2612
1894
  # from the cursor to the beginning of the current line.
2613
1895
  private def ed_kill_line(key)
2614
- if @line.bytesize > @byte_pointer
2615
- @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
2616
- @byte_pointer = @line.bytesize
2617
- @cursor = @cursor_max = calculate_width(@line)
1896
+ if current_line.bytesize > @byte_pointer
1897
+ line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1898
+ set_current_line(line, line.bytesize)
2618
1899
  @kill_ring.append(deleted)
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
1900
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1901
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2627
1902
  end
2628
1903
  end
2629
1904
  alias_method :kill_line, :ed_kill_line
2630
1905
 
1906
+ # Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line.
1907
+ private def vi_change_to_eol(key)
1908
+ ed_kill_line(key)
1909
+
1910
+ @config.editing_mode = :vi_insert
1911
+ end
1912
+
2631
1913
  # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
2632
1914
  # beginning of the edit buffer to the cursor and save it to the
2633
1915
  # cut buffer.
@@ -2635,11 +1917,9 @@ class Reline::LineEditor
2635
1917
  # to the beginning of the current line.
2636
1918
  private def vi_kill_line_prev(key)
2637
1919
  if @byte_pointer > 0
2638
- @line, deleted = byteslice!(@line, 0, @byte_pointer)
2639
- @byte_pointer = 0
1920
+ line, deleted = byteslice!(current_line, 0, @byte_pointer)
1921
+ set_current_line(line, 0)
2640
1922
  @kill_ring.append(deleted, true)
2641
- @cursor_max = calculate_width(@line)
2642
- @cursor = 0
2643
1923
  end
2644
1924
  end
2645
1925
  alias_method :unix_line_discard, :vi_kill_line_prev
@@ -2649,50 +1929,35 @@ class Reline::LineEditor
2649
1929
  # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
2650
1930
  # current line, no matter where point is.
2651
1931
  private def em_kill_line(key)
2652
- if @line.size > 0
2653
- @kill_ring.append(@line.dup, true)
2654
- @line.clear
2655
- @byte_pointer = 0
2656
- @cursor_max = 0
2657
- @cursor = 0
1932
+ if current_line.size > 0
1933
+ @kill_ring.append(current_line.dup, true)
1934
+ set_current_line('', 0)
2658
1935
  end
2659
1936
  end
2660
1937
  alias_method :kill_whole_line, :em_kill_line
2661
1938
 
2662
1939
  private def em_delete(key)
2663
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2664
- @line = nil
2665
- if @buffer_of_lines.size > 1
2666
- scroll_down(@highest_in_all - @first_line_started_from)
2667
- end
2668
- Reline::IOGate.move_cursor_column(0)
1940
+ if buffer_empty? and key == "\C-d".ord
2669
1941
  @eof = true
2670
1942
  finish
2671
- elsif @byte_pointer < @line.bytesize
2672
- splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
1943
+ elsif @byte_pointer < current_line.bytesize
1944
+ splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize)
2673
1945
  mbchar = splitted_last.grapheme_clusters.first
2674
- width = Reline::Unicode.get_mbchar_width(mbchar)
2675
- @cursor_max -= width
2676
- @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
2677
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2678
- @cursor = calculate_width(@line)
2679
- @byte_pointer = @line.bytesize
2680
- @line += @buffer_of_lines.delete_at(@line_index + 1)
2681
- @cursor_max = calculate_width(@line)
2682
- @buffer_of_lines[@line_index] = @line
2683
- @rerender_all = true
2684
- @rest_height += 1
1946
+ line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
1947
+ set_current_line(line)
1948
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1949
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2685
1950
  end
2686
1951
  end
2687
1952
  alias_method :delete_char, :em_delete
2688
1953
 
2689
1954
  private def em_delete_or_list(key)
2690
- if @line.empty? or @byte_pointer < @line.bytesize
1955
+ if current_line.empty? or @byte_pointer < current_line.bytesize
2691
1956
  em_delete(key)
2692
- else # show completed list
1957
+ elsif !@config.autocompletion # show completed list
2693
1958
  result = call_completion_proc
2694
1959
  if result.is_a?(Array)
2695
- complete(result, true)
1960
+ perform_completion(result, true)
2696
1961
  end
2697
1962
  end
2698
1963
  end
@@ -2700,162 +1965,136 @@ class Reline::LineEditor
2700
1965
 
2701
1966
  private def em_yank(key)
2702
1967
  yanked = @kill_ring.yank
2703
- if yanked
2704
- @line = byteinsert(@line, @byte_pointer, yanked)
2705
- yanked_width = calculate_width(yanked)
2706
- @cursor += yanked_width
2707
- @cursor_max += yanked_width
2708
- @byte_pointer += yanked.bytesize
2709
- end
1968
+ insert_text(yanked) if yanked
2710
1969
  end
2711
1970
  alias_method :yank, :em_yank
2712
1971
 
2713
1972
  private def em_yank_pop(key)
2714
1973
  yanked, prev_yank = @kill_ring.yank_pop
2715
1974
  if yanked
2716
- prev_yank_width = calculate_width(prev_yank)
2717
- @cursor -= prev_yank_width
2718
- @cursor_max -= prev_yank_width
2719
- @byte_pointer -= prev_yank.bytesize
2720
- @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
2721
- @line = byteinsert(@line, @byte_pointer, yanked)
2722
- yanked_width = calculate_width(yanked)
2723
- @cursor += yanked_width
2724
- @cursor_max += yanked_width
2725
- @byte_pointer += yanked.bytesize
1975
+ line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
1976
+ set_current_line(line, @byte_pointer - prev_yank.bytesize)
1977
+ insert_text(yanked)
2726
1978
  end
2727
1979
  end
2728
1980
  alias_method :yank_pop, :em_yank_pop
2729
1981
 
2730
1982
  private def ed_clear_screen(key)
2731
- @cleared = true
1983
+ Reline::IOGate.clear_screen
1984
+ @screen_size = Reline::IOGate.get_screen_size
1985
+ @rendered_screen.lines = []
1986
+ @rendered_screen.base_y = 0
1987
+ @rendered_screen.cursor_y = 0
2732
1988
  end
2733
1989
  alias_method :clear_screen, :ed_clear_screen
2734
1990
 
2735
1991
  private def em_next_word(key)
2736
- if @line.bytesize > @byte_pointer
2737
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
1992
+ if current_line.bytesize > @byte_pointer
1993
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2738
1994
  @byte_pointer += byte_size
2739
- @cursor += width
2740
1995
  end
2741
1996
  end
2742
1997
  alias_method :forward_word, :em_next_word
2743
1998
 
2744
1999
  private def ed_prev_word(key)
2745
2000
  if @byte_pointer > 0
2746
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2001
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2747
2002
  @byte_pointer -= byte_size
2748
- @cursor -= width
2749
2003
  end
2750
2004
  end
2751
2005
  alias_method :backward_word, :ed_prev_word
2752
2006
 
2753
2007
  private def em_delete_next_word(key)
2754
- if @line.bytesize > @byte_pointer
2755
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2756
- @line, word = byteslice!(@line, @byte_pointer, byte_size)
2008
+ if current_line.bytesize > @byte_pointer
2009
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2010
+ line, word = byteslice!(current_line, @byte_pointer, byte_size)
2011
+ set_current_line(line)
2757
2012
  @kill_ring.append(word)
2758
- @cursor_max -= width
2759
2013
  end
2760
2014
  end
2015
+ alias_method :kill_word, :em_delete_next_word
2761
2016
 
2762
2017
  private def ed_delete_prev_word(key)
2763
2018
  if @byte_pointer > 0
2764
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2765
- @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2019
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
2020
+ line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2021
+ set_current_line(line, @byte_pointer - byte_size)
2766
2022
  @kill_ring.append(word, true)
2767
- @byte_pointer -= byte_size
2768
- @cursor -= width
2769
- @cursor_max -= width
2770
2023
  end
2771
2024
  end
2025
+ alias_method :backward_kill_word, :ed_delete_prev_word
2772
2026
 
2773
2027
  private def ed_transpose_chars(key)
2774
2028
  if @byte_pointer > 0
2775
- if @cursor_max > @cursor
2776
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2777
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2778
- width = Reline::Unicode.get_mbchar_width(mbchar)
2779
- @cursor += width
2029
+ if @byte_pointer < current_line.bytesize
2030
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2780
2031
  @byte_pointer += byte_size
2781
2032
  end
2782
- back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2033
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2783
2034
  if (@byte_pointer - back1_byte_size) > 0
2784
- back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
2035
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size)
2785
2036
  back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
2786
- @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
2787
- @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
2037
+ line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size)
2038
+ set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar))
2788
2039
  end
2789
2040
  end
2790
2041
  end
2791
2042
  alias_method :transpose_chars, :ed_transpose_chars
2792
2043
 
2793
2044
  private def ed_transpose_words(key)
2794
- left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
2795
- before = @line.byteslice(0, left_word_start)
2796
- left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
2797
- middle = @line.byteslice(middle_start, right_word_start - middle_start)
2798
- right_word = @line.byteslice(right_word_start, after_start - right_word_start)
2799
- after = @line.byteslice(after_start, @line.bytesize - after_start)
2045
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer)
2046
+ before = current_line.byteslice(0, left_word_start)
2047
+ left_word = current_line.byteslice(left_word_start, middle_start - left_word_start)
2048
+ middle = current_line.byteslice(middle_start, right_word_start - middle_start)
2049
+ right_word = current_line.byteslice(right_word_start, after_start - right_word_start)
2050
+ after = current_line.byteslice(after_start, current_line.bytesize - after_start)
2800
2051
  return if left_word.empty? or right_word.empty?
2801
- @line = before + right_word + middle + left_word + after
2802
2052
  from_head_to_left_word = before + right_word + middle + left_word
2803
- @byte_pointer = from_head_to_left_word.bytesize
2804
- @cursor = calculate_width(from_head_to_left_word)
2053
+ set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
2805
2054
  end
2806
2055
  alias_method :transpose_words, :ed_transpose_words
2807
2056
 
2808
2057
  private def em_capitol_case(key)
2809
- if @line.bytesize > @byte_pointer
2810
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
2811
- before = @line.byteslice(0, @byte_pointer)
2812
- after = @line.byteslice((@byte_pointer + byte_size)..-1)
2813
- @line = before + new_str + after
2814
- @byte_pointer += new_str.bytesize
2815
- @cursor += calculate_width(new_str)
2058
+ if current_line.bytesize > @byte_pointer
2059
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
2060
+ before = current_line.byteslice(0, @byte_pointer)
2061
+ after = current_line.byteslice((@byte_pointer + byte_size)..-1)
2062
+ set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
2816
2063
  end
2817
2064
  end
2818
2065
  alias_method :capitalize_word, :em_capitol_case
2819
2066
 
2820
2067
  private def em_lower_case(key)
2821
- if @line.bytesize > @byte_pointer
2822
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2823
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2068
+ if current_line.bytesize > @byte_pointer
2069
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2070
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2824
2071
  mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2825
2072
  }.join
2826
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2827
- @line = @line.byteslice(0, @byte_pointer) + part
2828
- @byte_pointer = @line.bytesize
2829
- @cursor = calculate_width(@line)
2830
- @cursor_max = @cursor + calculate_width(rest)
2831
- @line += rest
2073
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
2074
+ line = current_line.byteslice(0, @byte_pointer) + part
2075
+ set_current_line(line + rest, line.bytesize)
2832
2076
  end
2833
2077
  end
2834
2078
  alias_method :downcase_word, :em_lower_case
2835
2079
 
2836
2080
  private def em_upper_case(key)
2837
- if @line.bytesize > @byte_pointer
2838
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2839
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2081
+ if current_line.bytesize > @byte_pointer
2082
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
2083
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2840
2084
  mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2841
2085
  }.join
2842
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2843
- @line = @line.byteslice(0, @byte_pointer) + part
2844
- @byte_pointer = @line.bytesize
2845
- @cursor = calculate_width(@line)
2846
- @cursor_max = @cursor + calculate_width(rest)
2847
- @line += rest
2086
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
2087
+ line = current_line.byteslice(0, @byte_pointer) + part
2088
+ set_current_line(line + rest, line.bytesize)
2848
2089
  end
2849
2090
  end
2850
2091
  alias_method :upcase_word, :em_upper_case
2851
2092
 
2852
2093
  private def em_kill_region(key)
2853
2094
  if @byte_pointer > 0
2854
- byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
2855
- @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2856
- @byte_pointer -= byte_size
2857
- @cursor -= width
2858
- @cursor_max -= width
2095
+ byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
2096
+ line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
2097
+ set_current_line(line, @byte_pointer - byte_size)
2859
2098
  @kill_ring.append(deleted, true)
2860
2099
  end
2861
2100
  end
@@ -2883,10 +2122,9 @@ class Reline::LineEditor
2883
2122
  alias_method :vi_movement_mode, :vi_command_mode
2884
2123
 
2885
2124
  private def vi_next_word(key, arg: 1)
2886
- if @line.bytesize > @byte_pointer
2887
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
2125
+ if current_line.bytesize > @byte_pointer
2126
+ byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
2888
2127
  @byte_pointer += byte_size
2889
- @cursor += width
2890
2128
  end
2891
2129
  arg -= 1
2892
2130
  vi_next_word(key, arg: arg) if arg > 0
@@ -2894,38 +2132,32 @@ class Reline::LineEditor
2894
2132
 
2895
2133
  private def vi_prev_word(key, arg: 1)
2896
2134
  if @byte_pointer > 0
2897
- byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
2135
+ byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
2898
2136
  @byte_pointer -= byte_size
2899
- @cursor -= width
2900
2137
  end
2901
2138
  arg -= 1
2902
2139
  vi_prev_word(key, arg: arg) if arg > 0
2903
2140
  end
2904
2141
 
2905
2142
  private def vi_end_word(key, arg: 1, inclusive: false)
2906
- if @line.bytesize > @byte_pointer
2907
- byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
2143
+ if current_line.bytesize > @byte_pointer
2144
+ byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
2908
2145
  @byte_pointer += byte_size
2909
- @cursor += width
2910
2146
  end
2911
2147
  arg -= 1
2912
2148
  if inclusive and arg.zero?
2913
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2149
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2914
2150
  if byte_size > 0
2915
- c = @line.byteslice(@byte_pointer, byte_size)
2916
- width = Reline::Unicode.get_mbchar_width(c)
2917
2151
  @byte_pointer += byte_size
2918
- @cursor += width
2919
2152
  end
2920
2153
  end
2921
2154
  vi_end_word(key, arg: arg) if arg > 0
2922
2155
  end
2923
2156
 
2924
2157
  private def vi_next_big_word(key, arg: 1)
2925
- if @line.bytesize > @byte_pointer
2926
- byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
2158
+ if current_line.bytesize > @byte_pointer
2159
+ byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
2927
2160
  @byte_pointer += byte_size
2928
- @cursor += width
2929
2161
  end
2930
2162
  arg -= 1
2931
2163
  vi_next_big_word(key, arg: arg) if arg > 0
@@ -2933,50 +2165,39 @@ class Reline::LineEditor
2933
2165
 
2934
2166
  private def vi_prev_big_word(key, arg: 1)
2935
2167
  if @byte_pointer > 0
2936
- byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
2168
+ byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
2937
2169
  @byte_pointer -= byte_size
2938
- @cursor -= width
2939
2170
  end
2940
2171
  arg -= 1
2941
2172
  vi_prev_big_word(key, arg: arg) if arg > 0
2942
2173
  end
2943
2174
 
2944
2175
  private def vi_end_big_word(key, arg: 1, inclusive: false)
2945
- if @line.bytesize > @byte_pointer
2946
- byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
2176
+ if current_line.bytesize > @byte_pointer
2177
+ byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
2947
2178
  @byte_pointer += byte_size
2948
- @cursor += width
2949
2179
  end
2950
2180
  arg -= 1
2951
2181
  if inclusive and arg.zero?
2952
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2182
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2953
2183
  if byte_size > 0
2954
- c = @line.byteslice(@byte_pointer, byte_size)
2955
- width = Reline::Unicode.get_mbchar_width(c)
2956
2184
  @byte_pointer += byte_size
2957
- @cursor += width
2958
2185
  end
2959
2186
  end
2960
2187
  vi_end_big_word(key, arg: arg) if arg > 0
2961
2188
  end
2962
2189
 
2963
2190
  private def vi_delete_prev_char(key)
2964
- if @is_multiline and @cursor == 0 and @line_index > 0
2965
- @buffer_of_lines[@line_index] = @line
2966
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2191
+ if @byte_pointer == 0 and @line_index > 0
2967
2192
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2968
2193
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2969
2194
  @line_index -= 1
2970
- @line = @buffer_of_lines[@line_index]
2971
- @cursor_max = calculate_width(@line)
2972
- @rerender_all = true
2973
- elsif @cursor > 0
2974
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2195
+ process_auto_indent cursor_dependent: false
2196
+ elsif @byte_pointer > 0
2197
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2975
2198
  @byte_pointer -= byte_size
2976
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2977
- width = Reline::Unicode.get_mbchar_width(mbchar)
2978
- @cursor -= width
2979
- @cursor_max -= width
2199
+ line, _ = byteslice!(current_line, @byte_pointer, byte_size)
2200
+ set_current_line(line)
2980
2201
  end
2981
2202
  end
2982
2203
 
@@ -2991,78 +2212,80 @@ class Reline::LineEditor
2991
2212
  end
2992
2213
 
2993
2214
  private def ed_delete_prev_char(key, arg: 1)
2994
- deleted = ''
2215
+ deleted = +''
2995
2216
  arg.times do
2996
- if @cursor > 0
2997
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2217
+ if @byte_pointer > 0
2218
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2998
2219
  @byte_pointer -= byte_size
2999
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2220
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
2221
+ set_current_line(line)
3000
2222
  deleted.prepend(mbchar)
3001
- width = Reline::Unicode.get_mbchar_width(mbchar)
3002
- @cursor -= width
3003
- @cursor_max -= width
3004
2223
  end
3005
2224
  end
3006
2225
  copy_for_vi(deleted)
3007
2226
  end
3008
2227
 
3009
- private def vi_zero(key)
3010
- @byte_pointer = 0
3011
- @cursor = 0
3012
- end
3013
-
3014
- private def vi_change_meta(key, arg: 1)
3015
- @drop_terminate_spaces = true
3016
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
3017
- if byte_pointer_diff > 0
3018
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
3019
- elsif byte_pointer_diff < 0
3020
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
3021
- end
3022
- copy_for_vi(cut)
3023
- @cursor += cursor_diff if cursor_diff < 0
3024
- @cursor_max -= cursor_diff.abs
3025
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
3026
- @config.editing_mode = :vi_insert
3027
- @drop_terminate_spaces = false
3028
- }
3029
- @waiting_operator_vi_arg = arg
2228
+ private def vi_change_meta(key, arg: nil)
2229
+ if @vi_waiting_operator
2230
+ set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil?
2231
+ @vi_waiting_operator = nil
2232
+ @vi_waiting_operator_arg = nil
2233
+ else
2234
+ @drop_terminate_spaces = true
2235
+ @vi_waiting_operator = :vi_change_meta_confirm
2236
+ @vi_waiting_operator_arg = arg || 1
2237
+ end
3030
2238
  end
3031
2239
 
3032
- private def vi_delete_meta(key, arg: 1)
3033
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
3034
- if byte_pointer_diff > 0
3035
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
3036
- elsif byte_pointer_diff < 0
3037
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
3038
- end
3039
- copy_for_vi(cut)
3040
- @cursor += cursor_diff if cursor_diff < 0
3041
- @cursor_max -= cursor_diff.abs
3042
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
3043
- }
3044
- @waiting_operator_vi_arg = arg
2240
+ private def vi_change_meta_confirm(byte_pointer_diff)
2241
+ vi_delete_meta_confirm(byte_pointer_diff)
2242
+ @config.editing_mode = :vi_insert
2243
+ @drop_terminate_spaces = false
3045
2244
  end
3046
2245
 
3047
- private def vi_yank(key, arg: 1)
3048
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
3049
- if byte_pointer_diff > 0
3050
- cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
3051
- elsif byte_pointer_diff < 0
3052
- cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
3053
- end
3054
- copy_for_vi(cut)
3055
- }
3056
- @waiting_operator_vi_arg = arg
2246
+ private def vi_delete_meta(key, arg: nil)
2247
+ if @vi_waiting_operator
2248
+ set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil?
2249
+ @vi_waiting_operator = nil
2250
+ @vi_waiting_operator_arg = nil
2251
+ else
2252
+ @vi_waiting_operator = :vi_delete_meta_confirm
2253
+ @vi_waiting_operator_arg = arg || 1
2254
+ end
2255
+ end
2256
+
2257
+ private def vi_delete_meta_confirm(byte_pointer_diff)
2258
+ if byte_pointer_diff > 0
2259
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2260
+ elsif byte_pointer_diff < 0
2261
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2262
+ end
2263
+ copy_for_vi(cut)
2264
+ set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2265
+ end
2266
+
2267
+ private def vi_yank(key, arg: nil)
2268
+ if @vi_waiting_operator
2269
+ copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil?
2270
+ @vi_waiting_operator = nil
2271
+ @vi_waiting_operator_arg = nil
2272
+ else
2273
+ @vi_waiting_operator = :vi_yank_confirm
2274
+ @vi_waiting_operator_arg = arg || 1
2275
+ end
2276
+ end
2277
+
2278
+ private def vi_yank_confirm(byte_pointer_diff)
2279
+ if byte_pointer_diff > 0
2280
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2281
+ elsif byte_pointer_diff < 0
2282
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2283
+ end
2284
+ copy_for_vi(cut)
3057
2285
  end
3058
2286
 
3059
2287
  private def vi_list_or_eof(key)
3060
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
3061
- @line = nil
3062
- if @buffer_of_lines.size > 1
3063
- scroll_down(@highest_in_all - @first_line_started_from)
3064
- end
3065
- Reline::IOGate.move_cursor_column(0)
2288
+ if buffer_empty?
3066
2289
  @eof = true
3067
2290
  finish
3068
2291
  else
@@ -3073,18 +2296,15 @@ class Reline::LineEditor
3073
2296
  alias_method :vi_eof_maybe, :vi_list_or_eof
3074
2297
 
3075
2298
  private def ed_delete_next_char(key, arg: 1)
3076
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3077
- unless @line.empty? || byte_size == 0
3078
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2299
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2300
+ unless current_line.empty? || byte_size == 0
2301
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
3079
2302
  copy_for_vi(mbchar)
3080
- width = Reline::Unicode.get_mbchar_width(mbchar)
3081
- @cursor_max -= width
3082
- if @cursor > 0 and @cursor >= @cursor_max
3083
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
3084
- mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
3085
- width = Reline::Unicode.get_mbchar_width(mbchar)
3086
- @byte_pointer -= byte_size
3087
- @cursor -= width
2303
+ if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size
2304
+ byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
2305
+ set_current_line(line, @byte_pointer - byte_size)
2306
+ else
2307
+ set_current_line(line, @byte_pointer)
3088
2308
  end
3089
2309
  end
3090
2310
  arg -= 1
@@ -3095,54 +2315,25 @@ class Reline::LineEditor
3095
2315
  if Reline::HISTORY.empty?
3096
2316
  return
3097
2317
  end
3098
- if @history_pointer.nil?
3099
- @history_pointer = 0
3100
- @line_backup_in_history = @line
3101
- @line = Reline::HISTORY[@history_pointer]
3102
- @cursor_max = calculate_width(@line)
3103
- @cursor = 0
3104
- @byte_pointer = 0
3105
- elsif @history_pointer.zero?
3106
- return
3107
- else
3108
- Reline::HISTORY[@history_pointer] = @line
3109
- @history_pointer = 0
3110
- @line = Reline::HISTORY[@history_pointer]
3111
- @cursor_max = calculate_width(@line)
3112
- @cursor = 0
3113
- @byte_pointer = 0
3114
- end
2318
+ move_history(0, line: :start, cursor: :start)
3115
2319
  end
3116
2320
 
3117
2321
  private def vi_histedit(key)
3118
2322
  path = Tempfile.open { |fp|
3119
- if @is_multiline
3120
- fp.write whole_lines.join("\n")
3121
- else
3122
- fp.write @line
3123
- end
2323
+ fp.write whole_lines.join("\n")
3124
2324
  fp.path
3125
2325
  }
3126
2326
  system("#{ENV['EDITOR']} #{path}")
3127
- if @is_multiline
3128
- @buffer_of_lines = File.read(path).split("\n")
3129
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
3130
- @line_index = 0
3131
- @line = @buffer_of_lines[@line_index]
3132
- @rerender_all = true
3133
- else
3134
- @line = File.read(path)
3135
- end
2327
+ @buffer_of_lines = File.read(path).split("\n")
2328
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2329
+ @line_index = 0
3136
2330
  finish
3137
2331
  end
3138
2332
 
3139
2333
  private def vi_paste_prev(key, arg: 1)
3140
2334
  if @vi_clipboard.size > 0
3141
- @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
3142
- @cursor_max += calculate_width(@vi_clipboard)
3143
2335
  cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
3144
- @cursor += calculate_width(cursor_point)
3145
- @byte_pointer += cursor_point.bytesize
2336
+ set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
3146
2337
  end
3147
2338
  arg -= 1
3148
2339
  vi_paste_prev(key, arg: arg) if arg > 0
@@ -3150,11 +2341,9 @@ class Reline::LineEditor
3150
2341
 
3151
2342
  private def vi_paste_next(key, arg: 1)
3152
2343
  if @vi_clipboard.size > 0
3153
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3154
- @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
3155
- @cursor_max += calculate_width(@vi_clipboard)
3156
- @cursor += calculate_width(@vi_clipboard)
3157
- @byte_pointer += @vi_clipboard.bytesize
2344
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2345
+ line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard)
2346
+ set_current_line(line, @byte_pointer + @vi_clipboard.bytesize)
3158
2347
  end
3159
2348
  arg -= 1
3160
2349
  vi_paste_next(key, arg: arg) if arg > 0
@@ -3178,43 +2367,33 @@ class Reline::LineEditor
3178
2367
  end
3179
2368
 
3180
2369
  private def vi_to_column(key, arg: 0)
3181
- @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
3182
- # total has [byte_size, cursor]
2370
+ # Implementing behavior of vi, not Readline's vi-mode.
2371
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc|
3183
2372
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
3184
- if (total.last + mbchar_width) >= arg
3185
- break total
3186
- elsif (total.last + mbchar_width) >= @cursor_max
3187
- break total
3188
- else
3189
- total = [total.first + gc.bytesize, total.last + mbchar_width]
3190
- total
3191
- end
2373
+ break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
2374
+ [total_byte_size + gc.bytesize, total_width + mbchar_width]
3192
2375
  }
3193
2376
  end
3194
2377
 
3195
2378
  private def vi_replace_char(key, arg: 1)
3196
2379
  @waiting_proc = ->(k) {
3197
2380
  if arg == 1
3198
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3199
- before = @line.byteslice(0, @byte_pointer)
2381
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2382
+ before = current_line.byteslice(0, @byte_pointer)
3200
2383
  remaining_point = @byte_pointer + byte_size
3201
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
3202
- @line = before + k.chr + after
3203
- @cursor_max = calculate_width(@line)
2384
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2385
+ set_current_line(before + k.chr + after)
3204
2386
  @waiting_proc = nil
3205
2387
  elsif arg > 1
3206
2388
  byte_size = 0
3207
2389
  arg.times do
3208
- byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
2390
+ byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size)
3209
2391
  end
3210
- before = @line.byteslice(0, @byte_pointer)
2392
+ before = current_line.byteslice(0, @byte_pointer)
3211
2393
  remaining_point = @byte_pointer + byte_size
3212
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2394
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
3213
2395
  replaced = k.chr * arg
3214
- @line = before + replaced + after
3215
- @byte_pointer += replaced.bytesize
3216
- @cursor += calculate_width(replaced)
3217
- @cursor_max = calculate_width(@line)
2396
+ set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
3218
2397
  @waiting_proc = nil
3219
2398
  end
3220
2399
  }
@@ -3237,7 +2416,7 @@ class Reline::LineEditor
3237
2416
  prev_total = nil
3238
2417
  total = nil
3239
2418
  found = false
3240
- @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
2419
+ current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
3241
2420
  # total has [byte_size, cursor]
3242
2421
  unless total
3243
2422
  # skip cursor point
@@ -3257,21 +2436,16 @@ class Reline::LineEditor
3257
2436
  end
3258
2437
  end
3259
2438
  if not need_prev_char and found and total
3260
- byte_size, width = total
2439
+ byte_size, _ = total
3261
2440
  @byte_pointer += byte_size
3262
- @cursor += width
3263
2441
  elsif need_prev_char and found and prev_total
3264
- byte_size, width = prev_total
2442
+ byte_size, _ = prev_total
3265
2443
  @byte_pointer += byte_size
3266
- @cursor += width
3267
2444
  end
3268
2445
  if inclusive
3269
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2446
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
3270
2447
  if byte_size > 0
3271
- c = @line.byteslice(@byte_pointer, byte_size)
3272
- width = Reline::Unicode.get_mbchar_width(c)
3273
2448
  @byte_pointer += byte_size
3274
- @cursor += width
3275
2449
  end
3276
2450
  end
3277
2451
  @waiting_proc = nil
@@ -3294,7 +2468,7 @@ class Reline::LineEditor
3294
2468
  prev_total = nil
3295
2469
  total = nil
3296
2470
  found = false
3297
- @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2471
+ current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
3298
2472
  # total has [byte_size, cursor]
3299
2473
  unless total
3300
2474
  # skip cursor point
@@ -3314,26 +2488,19 @@ class Reline::LineEditor
3314
2488
  end
3315
2489
  end
3316
2490
  if not need_next_char and found and total
3317
- byte_size, width = total
2491
+ byte_size, _ = total
3318
2492
  @byte_pointer -= byte_size
3319
- @cursor -= width
3320
2493
  elsif need_next_char and found and prev_total
3321
- byte_size, width = prev_total
2494
+ byte_size, _ = prev_total
3322
2495
  @byte_pointer -= byte_size
3323
- @cursor -= width
3324
2496
  end
3325
2497
  @waiting_proc = nil
3326
2498
  end
3327
2499
 
3328
2500
  private def vi_join_lines(key, arg: 1)
3329
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
3330
- @cursor = calculate_width(@line)
3331
- @byte_pointer = @line.bytesize
3332
- @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
3333
- @cursor_max = calculate_width(@line)
3334
- @buffer_of_lines[@line_index] = @line
3335
- @rerender_all = true
3336
- @rest_height += 1
2501
+ if @buffer_of_lines.size > @line_index + 1
2502
+ next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2503
+ set_current_line(current_line + ' ' + next_line, current_line.bytesize)
3337
2504
  end
3338
2505
  arg -= 1
3339
2506
  vi_join_lines(key, arg: arg) if arg > 0
@@ -3347,11 +2514,44 @@ class Reline::LineEditor
3347
2514
  private def em_exchange_mark(key)
3348
2515
  return unless @mark_pointer
3349
2516
  new_pointer = [@byte_pointer, @line_index]
3350
- @previous_line_index = @line_index
3351
2517
  @byte_pointer, @line_index = @mark_pointer
3352
- @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
3353
- @cursor_max = calculate_width(@line)
3354
2518
  @mark_pointer = new_pointer
3355
2519
  end
3356
2520
  alias_method :exchange_point_and_mark, :em_exchange_mark
2521
+
2522
+ private def emacs_editing_mode(key)
2523
+ @config.editing_mode = :emacs
2524
+ end
2525
+
2526
+ private def vi_editing_mode(key)
2527
+ @config.editing_mode = :vi_insert
2528
+ end
2529
+
2530
+ private def undo(_key)
2531
+ @undoing = true
2532
+
2533
+ return if @input_lines_position <= 0
2534
+
2535
+ @input_lines_position -= 1
2536
+ target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2537
+ set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2538
+ end
2539
+
2540
+ private def redo(_key)
2541
+ @undoing = true
2542
+
2543
+ return if @input_lines_position >= @input_lines.size - 1
2544
+
2545
+ @input_lines_position += 1
2546
+ target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
2547
+ set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
2548
+ end
2549
+
2550
+ private def prev_action_state_value(type)
2551
+ @prev_action_state[0] == type ? @prev_action_state[1] : nil
2552
+ end
2553
+
2554
+ private def set_next_action_state(type, value)
2555
+ @next_action_state = [type, value]
2556
+ end
3357
2557
  end