reline 0.1.5 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,7 @@ require 'tempfile'
5
5
 
6
6
  class Reline::LineEditor
7
7
  # TODO: undo
8
+ # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
8
9
  attr_reader :line
9
10
  attr_reader :byte_pointer
10
11
  attr_accessor :confirm_multiline_termination_proc
@@ -50,13 +51,49 @@ class Reline::LineEditor
50
51
  CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
51
52
  MenuInfo = Struct.new('MenuInfo', :target, :list)
52
53
 
54
+ PROMPT_LIST_CACHE_TIMEOUT = 0.5
55
+
53
56
  def initialize(config, encoding)
54
57
  @config = config
55
58
  @completion_append_character = ''
56
59
  reset_variables(encoding: encoding)
57
60
  end
58
61
 
59
- private def check_multiline_prompt(buffer, prompt)
62
+ def set_pasting_state(in_pasting)
63
+ @in_pasting = in_pasting
64
+ end
65
+
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
74
+ end
75
+
76
+ private def check_mode_string
77
+ mode_string = nil
78
+ if @config.show_mode_in_prompt
79
+ if @config.editing_mode_is?(:vi_command)
80
+ mode_string = @config.vi_cmd_mode_string
81
+ elsif @config.editing_mode_is?(:vi_insert)
82
+ mode_string = @config.vi_ins_mode_string
83
+ elsif @config.editing_mode_is?(:emacs)
84
+ mode_string = @config.emacs_mode_string
85
+ else
86
+ mode_string = '?'
87
+ end
88
+ end
89
+ if mode_string != @prev_mode_string
90
+ @rerender_all = true
91
+ end
92
+ @prev_mode_string = mode_string
93
+ mode_string
94
+ end
95
+
96
+ private def check_multiline_prompt(buffer)
60
97
  if @vi_arg
61
98
  prompt = "(arg: #{@vi_arg}) "
62
99
  @rerender_all = true
@@ -66,38 +103,45 @@ class Reline::LineEditor
66
103
  else
67
104
  prompt = @prompt
68
105
  end
106
+ if simplified_rendering?
107
+ mode_string = check_mode_string
108
+ prompt = mode_string + prompt if mode_string
109
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
110
+ end
69
111
  if @prompt_proc
70
- prompt_list = @prompt_proc.(buffer)
71
- prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
72
- if @config.show_mode_in_prompt
73
- if @config.editing_mode_is?(:vi_command)
74
- mode_icon = @config.vi_cmd_mode_icon
75
- elsif @config.editing_mode_is?(:vi_insert)
76
- mode_icon = @config.vi_ins_mode_icon
77
- elsif @config.editing_mode_is?(:emacs)
78
- mode_icon = @config.emacs_mode_string
79
- else
80
- mode_icon = '?'
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
81
118
  end
82
- prompt_list.map!{ |pr| mode_icon + pr }
83
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
127
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
128
+ prompt_list = [prompt] if prompt_list.empty?
129
+ mode_string = check_mode_string
130
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
84
131
  prompt = prompt_list[@line_index]
132
+ prompt = prompt_list[0] if prompt.nil?
133
+ prompt = prompt_list.last if prompt.nil?
134
+ if buffer.size > prompt_list.size
135
+ (buffer.size - prompt_list.size).times do
136
+ prompt_list << prompt_list.last
137
+ end
138
+ end
85
139
  prompt_width = calculate_width(prompt, true)
86
140
  [prompt, prompt_width, prompt_list]
87
141
  else
142
+ mode_string = check_mode_string
143
+ prompt = mode_string + prompt if mode_string
88
144
  prompt_width = calculate_width(prompt, true)
89
- if @config.show_mode_in_prompt
90
- if @config.editing_mode_is?(:vi_command)
91
- mode_icon = @config.vi_cmd_mode_icon
92
- elsif @config.editing_mode_is?(:vi_insert)
93
- mode_icon = @config.vi_ins_mode_icon
94
- elsif @config.editing_mode_is?(:emacs)
95
- mode_icon = @config.emacs_mode_string
96
- else
97
- mode_icon = '?'
98
- end
99
- prompt = mode_icon + prompt
100
- end
101
145
  [prompt, prompt_width, nil]
102
146
  end
103
147
  end
@@ -105,51 +149,110 @@ class Reline::LineEditor
105
149
  def reset(prompt = '', encoding:)
106
150
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
107
151
  @screen_size = Reline::IOGate.get_screen_size
152
+ @screen_height = @screen_size.first
108
153
  reset_variables(prompt, encoding: encoding)
109
- @old_trap = Signal.trap('SIGINT') {
110
- @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
111
- raise Interrupt
112
- }
113
154
  Reline::IOGate.set_winch_handler do
114
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
115
- old_screen_size = @screen_size
116
- @screen_size = Reline::IOGate.get_screen_size
117
- if old_screen_size.last < @screen_size.last # columns increase
118
- @rerender_all = true
119
- rerender
120
- else
121
- back = 0
122
- new_buffer = whole_lines
123
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
124
- new_buffer.each_with_index do |line, index|
125
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
126
- width = prompt_width + calculate_width(line)
127
- height = calculate_height_by_width(width)
128
- back += height
129
- end
130
- @highest_in_all = back
131
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
132
- @first_line_started_from =
133
- if @line_index.zero?
134
- 0
135
- else
136
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
137
- end
138
- if @prompt_proc
139
- prompt = prompt_list[@line_index]
140
- prompt_width = calculate_width(prompt, true)
155
+ @resized = true
156
+ end
157
+ if ENV.key?('RELINE_ALT_SCROLLBAR')
158
+ @full_block = '::'
159
+ @upper_half_block = "''"
160
+ @lower_half_block = '..'
161
+ @block_elem_width = 2
162
+ elsif Reline::IOGate.win?
163
+ @full_block = '█'
164
+ @upper_half_block = '▀'
165
+ @lower_half_block = '▄'
166
+ @block_elem_width = 1
167
+ elsif @encoding == Encoding::UTF_8
168
+ @full_block = '█'
169
+ @upper_half_block = '▀'
170
+ @lower_half_block = '▄'
171
+ @block_elem_width = Reline::Unicode.calculate_width('█')
172
+ else
173
+ @full_block = '::'
174
+ @upper_half_block = "''"
175
+ @lower_half_block = '..'
176
+ @block_elem_width = 2
177
+ end
178
+ end
179
+
180
+ def resize
181
+ 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
185
+ @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
190
+ 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)
141
207
  end
142
- calculate_nearest_cursor
143
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
144
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
145
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
146
- @rerender_all = true
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
217
+ end
218
+ end
219
+
220
+ 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)
147
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
148
247
  end
149
248
  end
150
249
 
151
250
  def finalize
152
- Signal.trap('SIGINT', @old_trap)
251
+ Signal.trap('INT', @old_trap)
252
+ begin
253
+ Signal.trap('TSTP', @old_tstp_trap)
254
+ rescue ArgumentError
255
+ end
153
256
  end
154
257
 
155
258
  def eof?
@@ -157,7 +260,7 @@ class Reline::LineEditor
157
260
  end
158
261
 
159
262
  def reset_variables(prompt = '', encoding:)
160
- @prompt = prompt
263
+ @prompt = prompt.gsub("\n", "\\n")
161
264
  @mark_pointer = nil
162
265
  @encoding = encoding
163
266
  @is_multiline = false
@@ -165,11 +268,12 @@ class Reline::LineEditor
165
268
  @cleared = false
166
269
  @rerender_all = false
167
270
  @history_pointer = nil
168
- @kill_ring = Reline::KillRing.new
271
+ @kill_ring ||= Reline::KillRing.new
169
272
  @vi_clipboard = ''
170
273
  @vi_arg = nil
171
274
  @waiting_proc = nil
172
275
  @waiting_operator_proc = nil
276
+ @waiting_operator_vi_arg = nil
173
277
  @completion_journey_data = nil
174
278
  @completion_state = CompletionState::NORMAL
175
279
  @perfect_matched = nil
@@ -177,7 +281,20 @@ class Reline::LineEditor
177
281
  @first_prompt = true
178
282
  @searching_prompt = nil
179
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
180
288
  @eof = false
289
+ @continuous_insertion_buffer = String.new(encoding: @encoding)
290
+ @scroll_partial_screen = nil
291
+ @prev_mode_string = nil
292
+ @drop_terminate_spaces = false
293
+ @in_pasting = false
294
+ @auto_indent_proc = nil
295
+ @dialogs = []
296
+ @last_key = nil
297
+ @resized = false
181
298
  reset_line
182
299
  end
183
300
 
@@ -222,6 +339,7 @@ class Reline::LineEditor
222
339
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
223
340
  @previous_line_index = @line_index
224
341
  @line_index += 1
342
+ @just_cursor_moving = false
225
343
  end
226
344
 
227
345
  private def calculate_height_by_width(width)
@@ -262,28 +380,29 @@ class Reline::LineEditor
262
380
  end
263
381
  end
264
382
 
265
- private def calculate_nearest_cursor
266
- @cursor_max = calculate_width(line)
383
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
384
+ new_cursor_max = calculate_width(line_to_calc)
267
385
  new_cursor = 0
268
386
  new_byte_pointer = 0
269
387
  height = 1
270
388
  max_width = @screen_size.last
271
389
  if @config.editing_mode_is?(:vi_command)
272
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
390
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
273
391
  if last_byte_size > 0
274
- last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
392
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
275
393
  last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
276
- cursor_max = @cursor_max - last_width
394
+ end_of_line_cursor = new_cursor_max - last_width
277
395
  else
278
- cursor_max = @cursor_max
396
+ end_of_line_cursor = new_cursor_max
279
397
  end
280
398
  else
281
- cursor_max = @cursor_max
399
+ end_of_line_cursor = new_cursor_max
282
400
  end
283
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
284
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
401
+ line_to_calc.grapheme_clusters.each do |gc|
402
+ mbchar = gc.encode(Encoding::UTF_8)
403
+ mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
285
404
  now = new_cursor + mbchar_width
286
- if now > cursor_max or now > @cursor
405
+ if now > end_of_line_cursor or now > cursor
287
406
  break
288
407
  end
289
408
  new_cursor += mbchar_width
@@ -292,9 +411,21 @@ class Reline::LineEditor
292
411
  end
293
412
  new_byte_pointer += gc.bytesize
294
413
  end
295
- @started_from = height - 1
296
- @cursor = new_cursor
297
- @byte_pointer = new_byte_pointer
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]
422
+ end
423
+ end
424
+
425
+ def rerender_all
426
+ @rerender_all = true
427
+ process_insert(force: true)
428
+ rerender
298
429
  end
299
430
 
300
431
  def rerender
@@ -302,178 +433,731 @@ class Reline::LineEditor
302
433
  if @menu_info
303
434
  scroll_down(@highest_in_all - @first_line_started_from)
304
435
  @rerender_all = true
305
- @menu_info.list.sort!.each do |item|
306
- Reline::IOGate.move_cursor_column(0)
307
- @output.write item
308
- @output.flush
309
- scroll_down(1)
310
- end
311
- scroll_down(@highest_in_all - 1)
312
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
436
+ end
437
+ if @menu_info
438
+ show_menu
313
439
  @menu_info = nil
314
440
  end
315
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
441
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
316
442
  if @cleared
317
- Reline::IOGate.clear_screen
443
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
318
444
  @cleared = false
319
- back = 0
320
- modify_lines(whole_lines).each_with_index do |line, index|
321
- if @prompt_proc
322
- pr = prompt_list[index]
323
- height = render_partial(pr, calculate_width(pr), line, false)
324
- else
325
- height = render_partial(prompt, prompt_width, line, false)
326
- end
327
- if index < (@buffer_of_lines.size - 1)
328
- move_cursor_down(height)
329
- back += height
330
- end
331
- end
332
- move_cursor_up(back)
333
- move_cursor_down(@first_line_started_from + @started_from)
334
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
335
445
  return
336
446
  end
337
- new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
338
- # FIXME: end of logical line sometimes breaks
339
- if @previous_line_index or new_highest_in_this != @highest_in_this
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)
340
453
  if @previous_line_index
341
454
  new_lines = whole_lines(index: @previous_line_index, line: @line)
342
455
  else
343
456
  new_lines = whole_lines
344
457
  end
345
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
346
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
347
- diff = all_height - @highest_in_all
348
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
349
- if diff > 0
350
- scroll_down(diff)
351
- move_cursor_up(all_height - 1)
352
- elsif diff < 0
353
- (-diff).times do
354
- Reline::IOGate.move_cursor_column(0)
355
- Reline::IOGate.erase_after_cursor
356
- move_cursor_up(1)
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
461
+ end
462
+ @output.flush
463
+ clear_dialog
464
+ return
465
+ 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
+ rerender_added_newline(prompt, prompt_width)
470
+ @add_newline_to_end_of_buffer = false
471
+ else
472
+ if @just_cursor_moving and not @rerender_all
473
+ rendered = just_move_cursor
474
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
475
+ @just_cursor_moving = false
476
+ return
477
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
478
+ rerender_changed_current_line
479
+ @previous_line_index = nil
480
+ rendered = true
481
+ elsif @rerender_all
482
+ rerender_all_lines
483
+ @rerender_all = false
484
+ rendered = true
485
+ else
486
+ end
487
+ end
488
+ if @is_multiline
489
+ if finished?
490
+ # Always rerender on finish because output_modifier_proc may return a different output.
491
+ if @previous_line_index
492
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
493
+ else
494
+ new_lines = whole_lines
357
495
  end
358
- move_cursor_up(all_height - 1)
496
+ line = modify_lines(new_lines)[@line_index]
497
+ clear_dialog
498
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
499
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
500
+ move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
501
+ scroll_down(1)
502
+ Reline::IOGate.move_cursor_column(0)
503
+ Reline::IOGate.erase_after_cursor
359
504
  else
360
- move_cursor_up(all_height - 1)
505
+ if not rendered and not @in_pasting
506
+ line = modify_lines(whole_lines)[@line_index]
507
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
508
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
509
+ end
510
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
361
511
  end
362
- @highest_in_all = all_height
363
- back = 0
364
- modify_lines(new_lines).each_with_index do |line, index|
365
- if @prompt_proc
366
- prompt = prompt_list[index]
367
- prompt_width = calculate_width(prompt, true)
512
+ @buffer_of_lines[@line_index] = @line
513
+ @rest_height = 0 if @scroll_partial_screen
514
+ else
515
+ line = modify_lines(whole_lines)[@line_index]
516
+ render_partial(prompt, prompt_width, line, 0)
517
+ if finished?
518
+ scroll_down(1)
519
+ Reline::IOGate.move_cursor_column(0)
520
+ Reline::IOGate.erase_after_cursor
521
+ end
522
+ end
523
+ end
524
+
525
+ class DialogProcScope
526
+ def initialize(line_editor, config, proc_to_exec, context)
527
+ @line_editor = line_editor
528
+ @config = config
529
+ @proc_to_exec = proc_to_exec
530
+ @context = context
531
+ @cursor_pos = Reline::CursorPos.new
532
+ end
533
+
534
+ def context
535
+ @context
536
+ end
537
+
538
+ def retrieve_completion_block(set_completion_quote_character = false)
539
+ @line_editor.retrieve_completion_block(set_completion_quote_character)
540
+ end
541
+
542
+ def call_completion_proc_with_checking_args(pre, target, post)
543
+ @line_editor.call_completion_proc_with_checking_args(pre, target, post)
544
+ end
545
+
546
+ def set_dialog(dialog)
547
+ @dialog = dialog
548
+ end
549
+
550
+ def dialog
551
+ @dialog
552
+ end
553
+
554
+ def set_cursor_pos(col, row)
555
+ @cursor_pos.x = col
556
+ @cursor_pos.y = row
557
+ end
558
+
559
+ def set_key(key)
560
+ @key = key
561
+ end
562
+
563
+ def key
564
+ @key
565
+ end
566
+
567
+ def cursor_pos
568
+ @cursor_pos
569
+ end
570
+
571
+ def just_cursor_moving
572
+ @line_editor.instance_variable_get(:@just_cursor_moving)
573
+ end
574
+
575
+ def screen_width
576
+ @line_editor.instance_variable_get(:@screen_size).last
577
+ end
578
+
579
+ def completion_journey_data
580
+ @line_editor.instance_variable_get(:@completion_journey_data)
581
+ end
582
+
583
+ def config
584
+ @config
585
+ end
586
+
587
+ def call
588
+ instance_exec(&@proc_to_exec)
589
+ end
590
+ end
591
+
592
+ class Dialog
593
+ attr_reader :name, :contents, :width
594
+ attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
595
+
596
+ def initialize(name, config, proc_scope)
597
+ @name = name
598
+ @config = config
599
+ @proc_scope = proc_scope
600
+ @width = nil
601
+ @scroll_top = 0
602
+ @trap_key = nil
603
+ end
604
+
605
+ def set_cursor_pos(col, row)
606
+ @proc_scope.set_cursor_pos(col, row)
607
+ end
608
+
609
+ def width=(v)
610
+ @width = v
611
+ end
612
+
613
+ def contents=(contents)
614
+ @contents = contents
615
+ if contents and @width.nil?
616
+ @width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max
617
+ end
618
+ end
619
+
620
+ def call(key)
621
+ @proc_scope.set_dialog(self)
622
+ @proc_scope.set_key(key)
623
+ dialog_render_info = @proc_scope.call
624
+ if @trap_key
625
+ if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap
626
+ @trap_key.each do |t|
627
+ @config.add_oneshot_key_binding(t, @name)
628
+ end
629
+ elsif @trap_key.is_a?(Array)
630
+ @config.add_oneshot_key_binding(@trap_key, @name)
631
+ elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
632
+ @config.add_oneshot_key_binding([@trap_key], @name)
368
633
  end
369
- height = render_partial(prompt, prompt_width, line, false)
370
- if index < (new_lines.size - 1)
371
- scroll_down(1)
372
- back += height
373
- else
374
- back += height - 1
634
+ end
635
+ dialog_render_info
636
+ end
637
+ end
638
+
639
+ def add_dialog_proc(name, p, context = nil)
640
+ dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
641
+ if index = @dialogs.find_index { |d| d.name == name }
642
+ @dialogs[index] = dialog
643
+ else
644
+ @dialogs << dialog
645
+ end
646
+ end
647
+
648
+ DIALOG_DEFAULT_HEIGHT = 20
649
+ private def render_dialog(cursor_column)
650
+ @dialogs.each do |dialog|
651
+ render_each_dialog(dialog, cursor_column)
652
+ end
653
+ end
654
+
655
+ private def padding_space_with_escape_sequences(str, width)
656
+ str + (' ' * (width - calculate_width(str, true)))
657
+ end
658
+
659
+ private def render_each_dialog(dialog, cursor_column)
660
+ if @in_pasting
661
+ clear_each_dialog(dialog)
662
+ dialog.contents = nil
663
+ dialog.trap_key = nil
664
+ return
665
+ end
666
+ dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
667
+ dialog_render_info = dialog.call(@last_key)
668
+ if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
669
+ dialog.lines_backup = {
670
+ lines: modify_lines(whole_lines),
671
+ line_index: @line_index,
672
+ first_line_started_from: @first_line_started_from,
673
+ started_from: @started_from,
674
+ byte_pointer: @byte_pointer
675
+ }
676
+ clear_each_dialog(dialog)
677
+ dialog.contents = nil
678
+ dialog.trap_key = nil
679
+ return
680
+ end
681
+ old_dialog = dialog.clone
682
+ dialog.contents = dialog_render_info.contents
683
+ pointer = dialog.pointer
684
+ if dialog_render_info.width
685
+ dialog.width = dialog_render_info.width
686
+ else
687
+ dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
688
+ end
689
+ height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
690
+ height = dialog.contents.size if dialog.contents.size < height
691
+ if dialog.contents.size > height
692
+ if dialog.pointer
693
+ if dialog.pointer < 0
694
+ dialog.scroll_top = 0
695
+ elsif (dialog.pointer - dialog.scroll_top) >= (height - 1)
696
+ dialog.scroll_top = dialog.pointer - (height - 1)
697
+ elsif (dialog.pointer - dialog.scroll_top) < 0
698
+ dialog.scroll_top = dialog.pointer
375
699
  end
700
+ pointer = dialog.pointer - dialog.scroll_top
376
701
  end
377
- move_cursor_up(back)
378
- if @previous_line_index
379
- @buffer_of_lines[@previous_line_index] = @line
380
- @line = @buffer_of_lines[@line_index]
702
+ dialog.contents = dialog.contents[dialog.scroll_top, height]
703
+ end
704
+ if dialog.contents and dialog.scroll_top >= dialog.contents.size
705
+ dialog.scroll_top = dialog.contents.size - height
706
+ end
707
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
708
+ bar_max_height = height * 2
709
+ moving_distance = (dialog_render_info.contents.size - height) * 2
710
+ position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
711
+ bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
712
+ dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
713
+ else
714
+ dialog.scrollbar_pos = nil
715
+ end
716
+ upper_space = @first_line_started_from - @started_from
717
+ dialog.column = dialog_render_info.pos.x
718
+ dialog.width += @block_elem_width if dialog.scrollbar_pos
719
+ diff = (dialog.column + dialog.width) - (@screen_size.last)
720
+ if diff > 0
721
+ dialog.column -= diff
722
+ end
723
+ if (@rest_height - dialog_render_info.pos.y) >= height
724
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
725
+ elsif upper_space >= height
726
+ dialog.vertical_offset = dialog_render_info.pos.y - height
727
+ else
728
+ if (@rest_height - dialog_render_info.pos.y) < height
729
+ scroll_down(height + dialog_render_info.pos.y)
730
+ move_cursor_up(height + dialog_render_info.pos.y)
731
+ end
732
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
733
+ end
734
+ Reline::IOGate.hide_cursor
735
+ if dialog.column < 0
736
+ dialog.column = 0
737
+ dialog.width = @screen_size.last
738
+ end
739
+ reset_dialog(dialog, old_dialog)
740
+ move_cursor_down(dialog.vertical_offset)
741
+ Reline::IOGate.move_cursor_column(dialog.column)
742
+ dialog.contents.each_with_index do |item, i|
743
+ if i == pointer
744
+ bg_color = '45'
745
+ else
746
+ if dialog_render_info.bg_color
747
+ bg_color = dialog_render_info.bg_color
748
+ else
749
+ bg_color = '46'
750
+ end
381
751
  end
382
- @first_line_started_from =
383
- if @line_index.zero?
384
- 0
752
+ str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
753
+ str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
754
+ @output.write "\e[#{bg_color}m#{str}"
755
+ if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
756
+ @output.write "\e[37m"
757
+ if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
758
+ @output.write @full_block
759
+ elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
760
+ @output.write @upper_half_block
761
+ str += ''
762
+ elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
763
+ @output.write @lower_half_block
385
764
  else
386
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
765
+ @output.write ' ' * @block_elem_width
387
766
  end
388
- if @prompt_proc
389
- prompt = prompt_list[@line_index]
390
- prompt_width = calculate_width(prompt, true)
391
767
  end
392
- move_cursor_down(@first_line_started_from)
393
- calculate_nearest_cursor
394
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
395
- move_cursor_down(@started_from)
396
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
397
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
398
- @previous_line_index = nil
399
- rendered = true
400
- elsif @rerender_all
401
- move_cursor_up(@first_line_started_from + @started_from)
402
- Reline::IOGate.move_cursor_column(0)
403
- back = 0
404
- new_buffer = whole_lines
405
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
406
- new_buffer.each_with_index do |line, index|
407
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
408
- width = prompt_width + calculate_width(line)
409
- height = calculate_height_by_width(width)
410
- back += height
768
+ @output.write "\e[0m"
769
+ Reline::IOGate.move_cursor_column(dialog.column)
770
+ move_cursor_down(1) if i < (dialog.contents.size - 1)
771
+ end
772
+ Reline::IOGate.move_cursor_column(cursor_column)
773
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
774
+ Reline::IOGate.show_cursor
775
+ dialog.lines_backup = {
776
+ lines: modify_lines(whole_lines),
777
+ line_index: @line_index,
778
+ first_line_started_from: @first_line_started_from,
779
+ started_from: @started_from,
780
+ byte_pointer: @byte_pointer
781
+ }
782
+ end
783
+
784
+ private def reset_dialog(dialog, old_dialog)
785
+ return if dialog.lines_backup.nil? or old_dialog.contents.nil?
786
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
787
+ visual_lines = []
788
+ visual_start = nil
789
+ dialog.lines_backup[:lines].each_with_index { |l, i|
790
+ pr = prompt_list ? prompt_list[i] : prompt
791
+ vl, _ = split_by_width(pr + l, @screen_size.last)
792
+ vl.compact!
793
+ if i == dialog.lines_backup[:line_index]
794
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from]
411
795
  end
412
- if back > @highest_in_all
413
- scroll_down(back - 1)
414
- move_cursor_up(back - 1)
415
- elsif back < @highest_in_all
416
- scroll_down(back)
417
- Reline::IOGate.erase_after_cursor
418
- (@highest_in_all - back - 1).times do
419
- scroll_down(1)
420
- Reline::IOGate.erase_after_cursor
796
+ visual_lines.concat(vl)
797
+ }
798
+ old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
799
+ y = @first_line_started_from + @started_from
800
+ y_diff = y - old_y
801
+ if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
802
+ # rerender top
803
+ move_cursor_down(old_dialog.vertical_offset - y_diff)
804
+ start = visual_start + old_dialog.vertical_offset
805
+ line_num = dialog.vertical_offset - old_dialog.vertical_offset
806
+ line_num.times do |i|
807
+ Reline::IOGate.move_cursor_column(old_dialog.column)
808
+ if visual_lines[start + i].nil?
809
+ s = ' ' * old_dialog.width
810
+ else
811
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
812
+ s = padding_space_with_escape_sequences(s, old_dialog.width)
421
813
  end
422
- move_cursor_up(@highest_in_all - 1)
423
- end
424
- modify_lines(new_buffer).each_with_index do |line, index|
425
- if @prompt_proc
426
- prompt = prompt_list[index]
427
- prompt_width = calculate_width(prompt, true)
814
+ @output.write "\e[0m#{s}\e[0m"
815
+ move_cursor_down(1) if i < (line_num - 1)
816
+ end
817
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
818
+ end
819
+ if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
820
+ # rerender bottom
821
+ move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
822
+ start = visual_start + dialog.vertical_offset + dialog.contents.size
823
+ line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
824
+ line_num.times do |i|
825
+ Reline::IOGate.move_cursor_column(old_dialog.column)
826
+ if visual_lines[start + i].nil?
827
+ s = ' ' * old_dialog.width
828
+ else
829
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
830
+ s = padding_space_with_escape_sequences(s, old_dialog.width)
428
831
  end
429
- render_partial(prompt, prompt_width, line, false)
430
- if index < (new_buffer.size - 1)
431
- move_cursor_down(1)
832
+ @output.write "\e[0m#{s}\e[0m"
833
+ move_cursor_down(1) if i < (line_num - 1)
834
+ end
835
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
836
+ end
837
+ if old_dialog.column < dialog.column
838
+ # rerender left
839
+ move_cursor_down(old_dialog.vertical_offset - y_diff)
840
+ width = dialog.column - old_dialog.column
841
+ start = visual_start + old_dialog.vertical_offset
842
+ line_num = old_dialog.contents.size
843
+ line_num.times do |i|
844
+ Reline::IOGate.move_cursor_column(old_dialog.column)
845
+ if visual_lines[start + i].nil?
846
+ s = ' ' * width
847
+ else
848
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
849
+ s = padding_space_with_escape_sequences(s, dialog.width)
432
850
  end
851
+ @output.write "\e[0m#{s}\e[0m"
852
+ move_cursor_down(1) if i < (line_num - 1)
853
+ end
854
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
855
+ end
856
+ if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
857
+ # rerender right
858
+ move_cursor_down(old_dialog.vertical_offset + y_diff)
859
+ width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
860
+ start = visual_start + old_dialog.vertical_offset
861
+ line_num = old_dialog.contents.size
862
+ line_num.times do |i|
863
+ Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
864
+ if visual_lines[start + i].nil?
865
+ s = ' ' * width
866
+ else
867
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
868
+ rerender_width = old_dialog.width - dialog.width
869
+ s = padding_space_with_escape_sequences(s, rerender_width)
870
+ end
871
+ Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
872
+ @output.write "\e[0m#{s}\e[0m"
873
+ move_cursor_down(1) if i < (line_num - 1)
433
874
  end
434
- move_cursor_up(back - 1)
435
- if @prompt_proc
436
- prompt = prompt_list[@line_index]
437
- prompt_width = calculate_width(prompt, true)
875
+ move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
876
+ end
877
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
878
+ end
879
+
880
+ private def clear_dialog
881
+ @dialogs.each do |dialog|
882
+ clear_each_dialog(dialog)
883
+ end
884
+ end
885
+
886
+ private def clear_each_dialog(dialog)
887
+ dialog.trap_key = nil
888
+ return unless dialog.contents
889
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
890
+ visual_lines = []
891
+ visual_lines_under_dialog = []
892
+ visual_start = nil
893
+ dialog.lines_backup[:lines].each_with_index { |l, i|
894
+ pr = prompt_list ? prompt_list[i] : prompt
895
+ vl, _ = split_by_width(pr + l, @screen_size.last)
896
+ vl.compact!
897
+ if i == dialog.lines_backup[:line_index]
898
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
438
899
  end
439
- @highest_in_all = back
440
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
441
- @first_line_started_from =
442
- if @line_index.zero?
443
- 0
900
+ visual_lines.concat(vl)
901
+ }
902
+ visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
903
+ visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
904
+ Reline::IOGate.hide_cursor
905
+ move_cursor_down(dialog.vertical_offset)
906
+ dialog_vertical_size = dialog.contents.size
907
+ dialog_vertical_size.times do |i|
908
+ if i < visual_lines_under_dialog.size
909
+ Reline::IOGate.move_cursor_column(dialog.column)
910
+ str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
911
+ str = padding_space_with_escape_sequences(str, dialog.width)
912
+ @output.write "\e[0m#{str}\e[0m"
913
+ else
914
+ Reline::IOGate.move_cursor_column(dialog.column)
915
+ @output.write "\e[0m#{' ' * dialog.width}\e[0m"
916
+ end
917
+ move_cursor_down(1) if i < (dialog_vertical_size - 1)
918
+ end
919
+ move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
920
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
921
+ Reline::IOGate.show_cursor
922
+ end
923
+
924
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
925
+ if @screen_height < highest_in_all
926
+ old_scroll_partial_screen = @scroll_partial_screen
927
+ if cursor_y == 0
928
+ @scroll_partial_screen = 0
929
+ elsif cursor_y == (highest_in_all - 1)
930
+ @scroll_partial_screen = highest_in_all - @screen_height
931
+ else
932
+ if @scroll_partial_screen
933
+ if cursor_y <= @scroll_partial_screen
934
+ @scroll_partial_screen = cursor_y
935
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
936
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
937
+ end
444
938
  else
445
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
939
+ if cursor_y > (@screen_height - 1)
940
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
941
+ else
942
+ @scroll_partial_screen = 0
943
+ end
446
944
  end
447
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
448
- move_cursor_down(@first_line_started_from + @started_from)
449
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
945
+ end
946
+ if @scroll_partial_screen != old_scroll_partial_screen
947
+ @rerender_all = true
948
+ end
949
+ else
950
+ if @scroll_partial_screen
951
+ @rerender_all = true
952
+ end
953
+ @scroll_partial_screen = nil
954
+ end
955
+ end
956
+
957
+ private def rerender_added_newline(prompt, prompt_width)
958
+ scroll_down(1)
959
+ @buffer_of_lines[@previous_line_index] = @line
960
+ @line = @buffer_of_lines[@line_index]
961
+ unless @in_pasting
962
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
963
+ end
964
+ @cursor = @cursor_max = calculate_width(@line)
965
+ @byte_pointer = @line.bytesize
966
+ @highest_in_all += @highest_in_this
967
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
968
+ @first_line_started_from += @started_from + 1
969
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
970
+ @previous_line_index = nil
971
+ end
972
+
973
+ def just_move_cursor
974
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
975
+ move_cursor_up(@started_from)
976
+ new_first_line_started_from =
977
+ if @line_index.zero?
978
+ 0
979
+ else
980
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
981
+ end
982
+ first_line_diff = new_first_line_started_from - @first_line_started_from
983
+ 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)
984
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
985
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
986
+ @previous_line_index = nil
987
+ if @rerender_all
988
+ @line = @buffer_of_lines[@line_index]
989
+ rerender_all_lines
450
990
  @rerender_all = false
451
- rendered = true
991
+ true
992
+ else
993
+ @line = @buffer_of_lines[@line_index]
994
+ @first_line_started_from = new_first_line_started_from
995
+ @started_from = new_started_from
996
+ @cursor = new_cursor
997
+ @cursor_max = new_cursor_max
998
+ @byte_pointer = new_byte_pointer
999
+ move_cursor_down(first_line_diff + @started_from)
1000
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1001
+ false
452
1002
  end
453
- line = modify_lines(whole_lines)[@line_index]
454
- if @is_multiline
455
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
456
- if finished?
457
- # Always rerender on finish because output_modifier_proc may return a different output.
458
- render_partial(prompt, prompt_width, line)
459
- scroll_down(1)
1003
+ end
1004
+
1005
+ private def rerender_changed_current_line
1006
+ if @previous_line_index
1007
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
1008
+ else
1009
+ new_lines = whole_lines
1010
+ end
1011
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
1012
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
1013
+ diff = all_height - @highest_in_all
1014
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
1015
+ if diff > 0
1016
+ scroll_down(diff)
1017
+ move_cursor_up(all_height - 1)
1018
+ elsif diff < 0
1019
+ (-diff).times do
460
1020
  Reline::IOGate.move_cursor_column(0)
461
1021
  Reline::IOGate.erase_after_cursor
462
- elsif not rendered
463
- render_partial(prompt, prompt_width, line)
1022
+ move_cursor_up(1)
464
1023
  end
1024
+ move_cursor_up(all_height - 1)
465
1025
  else
466
- render_partial(prompt, prompt_width, line)
467
- if finished?
1026
+ move_cursor_up(all_height - 1)
1027
+ end
1028
+ @highest_in_all = all_height
1029
+ back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
1030
+ move_cursor_up(back)
1031
+ if @previous_line_index
1032
+ @buffer_of_lines[@previous_line_index] = @line
1033
+ @line = @buffer_of_lines[@line_index]
1034
+ end
1035
+ @first_line_started_from =
1036
+ if @line_index.zero?
1037
+ 0
1038
+ else
1039
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
1040
+ end
1041
+ if @prompt_proc
1042
+ prompt = prompt_list[@line_index]
1043
+ prompt_width = calculate_width(prompt, true)
1044
+ end
1045
+ move_cursor_down(@first_line_started_from)
1046
+ calculate_nearest_cursor
1047
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1048
+ move_cursor_down(@started_from)
1049
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1050
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
1051
+ end
1052
+
1053
+ private def rerender_all_lines
1054
+ move_cursor_up(@first_line_started_from + @started_from)
1055
+ Reline::IOGate.move_cursor_column(0)
1056
+ back = 0
1057
+ new_buffer = whole_lines
1058
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
1059
+ new_buffer.each_with_index do |line, index|
1060
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
1061
+ width = prompt_width + calculate_width(line)
1062
+ height = calculate_height_by_width(width)
1063
+ back += height
1064
+ end
1065
+ old_highest_in_all = @highest_in_all
1066
+ if @line_index.zero?
1067
+ new_first_line_started_from = 0
1068
+ else
1069
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
1070
+ end
1071
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1072
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
1073
+ if @scroll_partial_screen
1074
+ move_cursor_up(@first_line_started_from + @started_from)
1075
+ scroll_down(@screen_height - 1)
1076
+ move_cursor_up(@screen_height)
1077
+ Reline::IOGate.move_cursor_column(0)
1078
+ elsif back > old_highest_in_all
1079
+ scroll_down(back - 1)
1080
+ move_cursor_up(back - 1)
1081
+ elsif back < old_highest_in_all
1082
+ scroll_down(back)
1083
+ Reline::IOGate.erase_after_cursor
1084
+ (old_highest_in_all - back - 1).times do
468
1085
  scroll_down(1)
469
- Reline::IOGate.move_cursor_column(0)
470
1086
  Reline::IOGate.erase_after_cursor
471
1087
  end
1088
+ move_cursor_up(old_highest_in_all - 1)
1089
+ end
1090
+ render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
1091
+ if @prompt_proc
1092
+ prompt = prompt_list[@line_index]
1093
+ prompt_width = calculate_width(prompt, true)
1094
+ end
1095
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
1096
+ @highest_in_all = back
1097
+ @first_line_started_from = new_first_line_started_from
1098
+ @started_from = new_started_from
1099
+ if @scroll_partial_screen
1100
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
1101
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1102
+ else
1103
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
1104
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
472
1105
  end
473
1106
  end
474
1107
 
475
- private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
1108
+ private def render_whole_lines(lines, prompt, prompt_width)
1109
+ rendered_height = 0
1110
+ modify_lines(lines).each_with_index do |line, index|
1111
+ if prompt.is_a?(Array)
1112
+ line_prompt = prompt[index]
1113
+ prompt_width = calculate_width(line_prompt, true)
1114
+ else
1115
+ line_prompt = prompt
1116
+ end
1117
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
1118
+ if index < (lines.size - 1)
1119
+ if @scroll_partial_screen
1120
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
1121
+ move_cursor_down(1)
1122
+ end
1123
+ else
1124
+ scroll_down(1)
1125
+ end
1126
+ rendered_height += height
1127
+ else
1128
+ rendered_height += height - 1
1129
+ end
1130
+ end
1131
+ rendered_height
1132
+ end
1133
+
1134
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
476
1135
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
1136
+ cursor_up_from_last_line = 0
1137
+ if @scroll_partial_screen
1138
+ last_visual_line = this_started_from + (height - 1)
1139
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
1140
+ if (@scroll_partial_screen - this_started_from) >= height
1141
+ # Render nothing because this line is before the screen.
1142
+ visual_lines = []
1143
+ elsif this_started_from > last_screen_line
1144
+ # Render nothing because this line is after the screen.
1145
+ visual_lines = []
1146
+ else
1147
+ deleted_lines_before_screen = []
1148
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
1149
+ # A part of visual lines are before the screen.
1150
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
1151
+ deleted_lines_before_screen.compact!
1152
+ end
1153
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
1154
+ # A part of visual lines are after the screen.
1155
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
1156
+ end
1157
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
1158
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
1159
+ end
1160
+ end
477
1161
  if with_control
478
1162
  if height > @highest_in_this
479
1163
  diff = height - @highest_in_this
@@ -488,13 +1172,17 @@ class Reline::LineEditor
488
1172
  end
489
1173
  move_cursor_up(@started_from)
490
1174
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
1175
+ cursor_up_from_last_line = height - 1 - @started_from
1176
+ end
1177
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
1178
+ @output.write "\e[0m" # clear character decorations
491
1179
  end
492
- Reline::IOGate.move_cursor_column(0)
493
1180
  visual_lines.each_with_index do |line, index|
1181
+ Reline::IOGate.move_cursor_column(0)
494
1182
  if line.nil?
495
1183
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
496
1184
  # reaches the end of line
497
- if Reline::IOGate.win?
1185
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
498
1186
  # A newline is automatically inserted if a character is rendered at
499
1187
  # eol on command prompt.
500
1188
  else
@@ -512,7 +1200,7 @@ class Reline::LineEditor
512
1200
  next
513
1201
  end
514
1202
  @output.write line
515
- if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
1203
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
516
1204
  # A newline is automatically inserted if a character is rendered at eol on command prompt.
517
1205
  @rest_height -= 1 if @rest_height > 0
518
1206
  end
@@ -522,14 +1210,18 @@ class Reline::LineEditor
522
1210
  @pre_input_hook&.call
523
1211
  end
524
1212
  end
525
- Reline::IOGate.erase_after_cursor
1213
+ unless visual_lines.empty?
1214
+ Reline::IOGate.erase_after_cursor
1215
+ Reline::IOGate.move_cursor_column(0)
1216
+ end
526
1217
  if with_control
527
1218
  # Just after rendring, so the cursor is on the last line.
528
1219
  if finished?
529
1220
  Reline::IOGate.move_cursor_column(0)
530
1221
  else
531
1222
  # Moves up from bottom of lines to the cursor position.
532
- move_cursor_up(height - 1 - @started_from)
1223
+ move_cursor_up(cursor_up_from_last_line)
1224
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
533
1225
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
534
1226
  end
535
1227
  end
@@ -537,7 +1229,7 @@ class Reline::LineEditor
537
1229
  end
538
1230
 
539
1231
  private def modify_lines(before)
540
- return before if before.nil? || before.empty?
1232
+ return before if before.nil? || before.empty? || simplified_rendering?
541
1233
 
542
1234
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
543
1235
  after.lines("\n").map { |l| l.chomp('') }
@@ -546,6 +1238,40 @@ class Reline::LineEditor
546
1238
  end
547
1239
  end
548
1240
 
1241
+ private def show_menu
1242
+ scroll_down(@highest_in_all - @first_line_started_from)
1243
+ @rerender_all = true
1244
+ @menu_info.list.sort!.each do |item|
1245
+ Reline::IOGate.move_cursor_column(0)
1246
+ @output.write item
1247
+ @output.flush
1248
+ scroll_down(1)
1249
+ end
1250
+ scroll_down(@highest_in_all - 1)
1251
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
1252
+ end
1253
+
1254
+ private def clear_screen_buffer(prompt, prompt_list, prompt_width)
1255
+ Reline::IOGate.clear_screen
1256
+ back = 0
1257
+ modify_lines(whole_lines).each_with_index do |line, index|
1258
+ if @prompt_proc
1259
+ pr = prompt_list[index]
1260
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
1261
+ else
1262
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
1263
+ end
1264
+ if index < (@buffer_of_lines.size - 1)
1265
+ move_cursor_down(1)
1266
+ back += height
1267
+ end
1268
+ end
1269
+ move_cursor_up(back)
1270
+ move_cursor_down(@first_line_started_from + @started_from)
1271
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
1272
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
1273
+ end
1274
+
549
1275
  def editing_mode
550
1276
  @config.editing_mode
551
1277
  end
@@ -565,7 +1291,7 @@ class Reline::LineEditor
565
1291
  else
566
1292
  i&.start_with?(target)
567
1293
  end
568
- }
1294
+ }.uniq
569
1295
  if is_menu
570
1296
  menu(target, list)
571
1297
  return nil
@@ -655,6 +1381,16 @@ class Reline::LineEditor
655
1381
  @completion_journey_data = CompletionJourneyData.new(
656
1382
  preposing, postposing,
657
1383
  [target] + list.select{ |item| item.start_with?(target) }, 0)
1384
+ if @completion_journey_data.list.size == 1
1385
+ @completion_journey_data.pointer = 0
1386
+ else
1387
+ case direction
1388
+ when :up
1389
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1390
+ when :down
1391
+ @completion_journey_data.pointer = 1
1392
+ end
1393
+ end
658
1394
  @completion_state = CompletionState::JOURNEY
659
1395
  else
660
1396
  case direction
@@ -669,20 +1405,23 @@ class Reline::LineEditor
669
1405
  @completion_journey_data.pointer = 0
670
1406
  end
671
1407
  end
672
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
673
- @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
674
- line_to_pointer = @completion_journey_data.preposing + completed
675
- @cursor_max = calculate_width(@line)
676
- @cursor = calculate_width(line_to_pointer)
677
- @byte_pointer = line_to_pointer.bytesize
678
1408
  end
1409
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
1410
+ new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
1411
+ @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
1412
+ line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
1413
+ line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
1414
+ @cursor_max = calculate_width(@line)
1415
+ @cursor = calculate_width(line_to_pointer)
1416
+ @byte_pointer = line_to_pointer.bytesize
679
1417
  end
680
1418
 
681
1419
  private def run_for_operators(key, method_symbol, &block)
682
1420
  if @waiting_operator_proc
683
1421
  if VI_MOTIONS.include?(method_symbol)
684
1422
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
685
- block.()
1423
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
1424
+ block.(true)
686
1425
  unless @waiting_proc
687
1426
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
688
1427
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
@@ -690,27 +1429,59 @@ class Reline::LineEditor
690
1429
  else
691
1430
  old_waiting_proc = @waiting_proc
692
1431
  old_waiting_operator_proc = @waiting_operator_proc
1432
+ current_waiting_operator_proc = @waiting_operator_proc
693
1433
  @waiting_proc = proc { |k|
694
1434
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
695
1435
  old_waiting_proc.(k)
696
1436
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
697
1437
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
698
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
1438
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
699
1439
  @waiting_operator_proc = old_waiting_operator_proc
700
1440
  }
701
1441
  end
702
1442
  else
703
1443
  # Ignores operator when not motion is given.
704
- block.()
1444
+ block.(false)
705
1445
  end
706
1446
  @waiting_operator_proc = nil
1447
+ @waiting_operator_vi_arg = nil
1448
+ if @vi_arg
1449
+ @rerender_all = true
1450
+ @vi_arg = nil
1451
+ end
707
1452
  else
708
- block.()
1453
+ block.(false)
709
1454
  end
710
1455
  end
711
1456
 
712
1457
  private def argumentable?(method_obj)
713
- method_obj and method_obj.parameters.length != 1
1458
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
1459
+ end
1460
+
1461
+ private def inclusive?(method_obj)
1462
+ # If a motion method with the keyword argument "inclusive" follows the
1463
+ # operator, it must contain the character at the cursor position.
1464
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
1465
+ end
1466
+
1467
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
1468
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
1469
+ not_insertion = method_symbol != :ed_insert
1470
+ process_insert(force: not_insertion)
1471
+ end
1472
+ if @vi_arg and argumentable?(method_obj)
1473
+ if with_operator and inclusive?(method_obj)
1474
+ method_obj.(key, arg: @vi_arg, inclusive: true)
1475
+ else
1476
+ method_obj.(key, arg: @vi_arg)
1477
+ end
1478
+ else
1479
+ if with_operator and inclusive?(method_obj)
1480
+ method_obj.(key, inclusive: true)
1481
+ else
1482
+ method_obj.(key)
1483
+ end
1484
+ end
714
1485
  end
715
1486
 
716
1487
  private def process_key(key, method_symbol)
@@ -721,46 +1492,52 @@ class Reline::LineEditor
721
1492
  end
722
1493
  if method_symbol and key.is_a?(Symbol)
723
1494
  if @vi_arg and argumentable?(method_obj)
724
- run_for_operators(key, method_symbol) do
725
- method_obj.(key, arg: @vi_arg)
1495
+ run_for_operators(key, method_symbol) do |with_operator|
1496
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
726
1497
  end
727
1498
  else
728
- method_obj&.(key)
1499
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
729
1500
  end
730
1501
  @kill_ring.process
731
- @vi_arg = nil
1502
+ if @vi_arg
1503
+ @rerender_al = true
1504
+ @vi_arg = nil
1505
+ end
732
1506
  elsif @vi_arg
733
1507
  if key.chr =~ /[0-9]/
734
1508
  ed_argument_digit(key)
735
1509
  else
736
1510
  if argumentable?(method_obj)
737
- run_for_operators(key, method_symbol) do
738
- method_obj.(key, arg: @vi_arg)
1511
+ run_for_operators(key, method_symbol) do |with_operator|
1512
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
739
1513
  end
740
1514
  elsif @waiting_proc
741
1515
  @waiting_proc.(key)
742
1516
  elsif method_obj
743
- method_obj.(key)
1517
+ wrap_method_call(method_symbol, method_obj, key)
744
1518
  else
745
- ed_insert(key)
1519
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
746
1520
  end
747
1521
  @kill_ring.process
748
- @vi_arg = nil
1522
+ if @vi_arg
1523
+ @rerender_all = true
1524
+ @vi_arg = nil
1525
+ end
749
1526
  end
750
1527
  elsif @waiting_proc
751
1528
  @waiting_proc.(key)
752
1529
  @kill_ring.process
753
1530
  elsif method_obj
754
1531
  if method_symbol == :ed_argument_digit
755
- method_obj.(key)
1532
+ wrap_method_call(method_symbol, method_obj, key)
756
1533
  else
757
- run_for_operators(key, method_symbol) do
758
- method_obj.(key)
1534
+ run_for_operators(key, method_symbol) do |with_operator|
1535
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
759
1536
  end
760
1537
  end
761
1538
  @kill_ring.process
762
1539
  else
763
- ed_insert(key)
1540
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
764
1541
  end
765
1542
  end
766
1543
 
@@ -803,6 +1580,14 @@ class Reline::LineEditor
803
1580
  end
804
1581
 
805
1582
  def input_key(key)
1583
+ @last_key = key
1584
+ @config.reset_oneshot_key_bindings
1585
+ @dialogs.each do |dialog|
1586
+ if key.char.instance_of?(Symbol) and key.char == dialog.name
1587
+ return
1588
+ end
1589
+ end
1590
+ @just_cursor_moving = nil
806
1591
  if key.char.nil?
807
1592
  if @first_char
808
1593
  @line = nil
@@ -810,6 +1595,7 @@ class Reline::LineEditor
810
1595
  finish
811
1596
  return
812
1597
  end
1598
+ old_line = @line.dup
813
1599
  @first_char = false
814
1600
  completion_occurs = false
815
1601
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -817,7 +1603,21 @@ class Reline::LineEditor
817
1603
  result = call_completion_proc
818
1604
  if result.is_a?(Array)
819
1605
  completion_occurs = true
820
- complete(result)
1606
+ process_insert
1607
+ if @config.autocompletion
1608
+ move_completed_list(result, :down)
1609
+ else
1610
+ complete(result)
1611
+ end
1612
+ end
1613
+ end
1614
+ elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
1615
+ if not @config.disable_completion and @config.autocompletion
1616
+ result = call_completion_proc
1617
+ if result.is_a?(Array)
1618
+ completion_occurs = true
1619
+ process_insert
1620
+ move_completed_list(result, :up)
821
1621
  end
822
1622
  end
823
1623
  elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
@@ -825,6 +1625,7 @@ class Reline::LineEditor
825
1625
  result = call_completion_proc
826
1626
  if result.is_a?(Array)
827
1627
  completion_occurs = true
1628
+ process_insert
828
1629
  move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
829
1630
  end
830
1631
  end
@@ -835,20 +1636,54 @@ class Reline::LineEditor
835
1636
  end
836
1637
  unless completion_occurs
837
1638
  @completion_state = CompletionState::NORMAL
1639
+ @completion_journey_data = nil
1640
+ end
1641
+ if not @in_pasting and @just_cursor_moving.nil?
1642
+ if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1643
+ @just_cursor_moving = true
1644
+ elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1645
+ @just_cursor_moving = true
1646
+ else
1647
+ @just_cursor_moving = false
1648
+ end
1649
+ else
1650
+ @just_cursor_moving = false
838
1651
  end
839
- if @is_multiline and @auto_indent_proc
1652
+ if @is_multiline and @auto_indent_proc and not simplified_rendering?
840
1653
  process_auto_indent
841
1654
  end
842
1655
  end
843
1656
 
844
1657
  def call_completion_proc
845
1658
  result = retrieve_completion_block(true)
846
- slice = result[1]
847
- result = @completion_proc.(slice) if @completion_proc and slice
1659
+ pre, target, post = result
1660
+ result = call_completion_proc_with_checking_args(pre, target, post)
848
1661
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
849
1662
  result
850
1663
  end
851
1664
 
1665
+ def call_completion_proc_with_checking_args(pre, target, post)
1666
+ if @completion_proc and target
1667
+ argnum = @completion_proc.parameters.inject(0) { |result, item|
1668
+ case item.first
1669
+ when :req, :opt
1670
+ result + 1
1671
+ when :rest
1672
+ break 3
1673
+ end
1674
+ }
1675
+ case argnum
1676
+ when 1
1677
+ result = @completion_proc.(target)
1678
+ when 2
1679
+ result = @completion_proc.(target, pre)
1680
+ when 3..Float::INFINITY
1681
+ result = @completion_proc.(target, pre, post)
1682
+ end
1683
+ end
1684
+ result
1685
+ end
1686
+
852
1687
  private def process_auto_indent
853
1688
  return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
854
1689
  if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
@@ -874,6 +1709,7 @@ class Reline::LineEditor
874
1709
  new_lines = whole_lines
875
1710
  end
876
1711
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1712
+ new_indent = @cursor_max if new_indent&.> @cursor_max
877
1713
  if new_indent&.>= 0
878
1714
  md = new_lines[@line_index].match(/\A */)
879
1715
  prev_indent = md[0].count(' ')
@@ -891,8 +1727,16 @@ class Reline::LineEditor
891
1727
  end
892
1728
 
893
1729
  def retrieve_completion_block(set_completion_quote_character = false)
894
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
895
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1730
+ if Reline.completer_word_break_characters.empty?
1731
+ word_break_regexp = nil
1732
+ else
1733
+ word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1734
+ end
1735
+ if Reline.completer_quote_characters.empty?
1736
+ quote_characters_regexp = nil
1737
+ else
1738
+ quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1739
+ end
896
1740
  before = @line.byteslice(0, @byte_pointer)
897
1741
  rest = nil
898
1742
  break_pointer = nil
@@ -913,14 +1757,14 @@ class Reline::LineEditor
913
1757
  elsif quote and slice.start_with?(escaped_quote)
914
1758
  # skip
915
1759
  i += 2
916
- elsif slice =~ quote_characters_regexp # find new "
1760
+ elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
917
1761
  rest = $'
918
1762
  quote = $&
919
1763
  closing_quote = /(?!\\)#{Regexp.escape(quote)}/
920
1764
  escaped_quote = /\\#{Regexp.escape(quote)}/
921
1765
  i += 1
922
1766
  break_pointer = i - 1
923
- elsif not quote and slice =~ word_break_regexp
1767
+ elsif word_break_regexp and not quote and slice =~ word_break_regexp
924
1768
  rest = $'
925
1769
  i += 1
926
1770
  before = @line.byteslice(i, @byte_pointer - i)
@@ -948,6 +1792,19 @@ class Reline::LineEditor
948
1792
  end
949
1793
  target = before
950
1794
  end
1795
+ if @is_multiline
1796
+ if @previous_line_index
1797
+ lines = whole_lines(index: @previous_line_index, line: @line)
1798
+ else
1799
+ lines = whole_lines
1800
+ end
1801
+ if @line_index > 0
1802
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1803
+ end
1804
+ if (lines.size - 1) > @line_index
1805
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1806
+ end
1807
+ end
951
1808
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
952
1809
  end
953
1810
 
@@ -975,10 +1832,32 @@ class Reline::LineEditor
975
1832
 
976
1833
  def delete_text(start = nil, length = nil)
977
1834
  if start.nil? and length.nil?
978
- @line&.clear
979
- @byte_pointer = 0
980
- @cursor = 0
981
- @cursor_max = 0
1835
+ if @is_multiline
1836
+ if @buffer_of_lines.size == 1
1837
+ @line&.clear
1838
+ @byte_pointer = 0
1839
+ @cursor = 0
1840
+ @cursor_max = 0
1841
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1842
+ @buffer_of_lines.pop
1843
+ @line_index -= 1
1844
+ @line = @buffer_of_lines[@line_index]
1845
+ @byte_pointer = 0
1846
+ @cursor = 0
1847
+ @cursor_max = calculate_width(@line)
1848
+ elsif @line_index < (@buffer_of_lines.size - 1)
1849
+ @buffer_of_lines.delete_at(@line_index)
1850
+ @line = @buffer_of_lines[@line_index]
1851
+ @byte_pointer = 0
1852
+ @cursor = 0
1853
+ @cursor_max = calculate_width(@line)
1854
+ end
1855
+ else
1856
+ @line&.clear
1857
+ @byte_pointer = 0
1858
+ @cursor = 0
1859
+ @cursor_max = 0
1860
+ end
982
1861
  elsif not start.nil? and not length.nil?
983
1862
  if @line
984
1863
  before = @line.byteslice(0, start)
@@ -1028,7 +1907,11 @@ class Reline::LineEditor
1028
1907
  if @buffer_of_lines.size == 1 and @line.nil?
1029
1908
  nil
1030
1909
  else
1031
- whole_lines.join("\n")
1910
+ if @previous_line_index
1911
+ whole_lines(index: @previous_line_index, line: @line).join("\n")
1912
+ else
1913
+ whole_lines.join("\n")
1914
+ end
1032
1915
  end
1033
1916
  end
1034
1917
 
@@ -1038,6 +1921,7 @@ class Reline::LineEditor
1038
1921
 
1039
1922
  def finish
1040
1923
  @finished = true
1924
+ @rerender_all = true
1041
1925
  @config.reset
1042
1926
  end
1043
1927
 
@@ -1066,48 +1950,98 @@ class Reline::LineEditor
1066
1950
 
1067
1951
  private def key_newline(key)
1068
1952
  if @is_multiline
1953
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1954
+ @add_newline_to_end_of_buffer = true
1955
+ end
1069
1956
  next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1070
1957
  cursor_line = @line.byteslice(0, @byte_pointer)
1071
1958
  insert_new_line(cursor_line, next_line)
1072
1959
  @cursor = 0
1073
- @check_new_auto_indent = true
1960
+ @check_new_auto_indent = true unless @in_pasting
1074
1961
  end
1075
1962
  end
1076
1963
 
1964
+ # Editline:: +ed-unassigned+ This editor command always results in an error.
1965
+ # GNU Readline:: There is no corresponding macro.
1077
1966
  private def ed_unassigned(key) end # do nothing
1078
1967
 
1968
+ private def process_insert(force: false)
1969
+ return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1970
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1971
+ bytesize = @continuous_insertion_buffer.bytesize
1972
+ if @cursor == @cursor_max
1973
+ @line += @continuous_insertion_buffer
1974
+ else
1975
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1976
+ end
1977
+ @byte_pointer += bytesize
1978
+ @cursor += width
1979
+ @cursor_max += width
1980
+ @continuous_insertion_buffer.clear
1981
+ end
1982
+
1983
+ # Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters)
1984
+ # In insert mode, insert the input character left of the cursor
1985
+ # position. In replace mode, overwrite the character at the
1986
+ # cursor and move the cursor to the right by one character
1987
+ # position. Accept an argument to do this repeatedly. It is an
1988
+ # error if the input character is the NUL character (+Ctrl-@+).
1989
+ # Failure to enlarge the edit buffer also results in an error.
1990
+ # Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append
1991
+ # the input digit to the argument being read. Otherwise, call
1992
+ # +ed-insert+. It is an error if the input character is not a
1993
+ # digit or if the existing argument is already greater than a
1994
+ # million.
1995
+ # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1079
1996
  private def ed_insert(key)
1997
+ str = nil
1998
+ width = nil
1999
+ bytesize = nil
1080
2000
  if key.instance_of?(String)
1081
2001
  begin
1082
2002
  key.encode(Encoding::UTF_8)
1083
2003
  rescue Encoding::UndefinedConversionError
1084
2004
  return
1085
2005
  end
1086
- width = Reline::Unicode.get_mbchar_width(key)
1087
- if @cursor == @cursor_max
1088
- @line += key
1089
- else
1090
- @line = byteinsert(@line, @byte_pointer, key)
1091
- end
1092
- @byte_pointer += key.bytesize
1093
- @cursor += width
1094
- @cursor_max += width
2006
+ str = key
2007
+ bytesize = key.bytesize
1095
2008
  else
1096
2009
  begin
1097
2010
  key.chr.encode(Encoding::UTF_8)
1098
2011
  rescue Encoding::UndefinedConversionError
1099
2012
  return
1100
2013
  end
1101
- if @cursor == @cursor_max
1102
- @line += key.chr
2014
+ str = key.chr
2015
+ bytesize = 1
2016
+ end
2017
+ if @in_pasting
2018
+ @continuous_insertion_buffer << str
2019
+ return
2020
+ elsif not @continuous_insertion_buffer.empty?
2021
+ process_insert
2022
+ end
2023
+ width = Reline::Unicode.get_mbchar_width(str)
2024
+ if @cursor == @cursor_max
2025
+ @line += str
2026
+ else
2027
+ @line = byteinsert(@line, @byte_pointer, str)
2028
+ end
2029
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2030
+ @byte_pointer += bytesize
2031
+ last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
2032
+ combined_char = last_mbchar + str
2033
+ if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
2034
+ # combined char
2035
+ last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
2036
+ combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
2037
+ if combined_char_width > last_mbchar_width
2038
+ width = combined_char_width - last_mbchar_width
1103
2039
  else
1104
- @line = byteinsert(@line, @byte_pointer, key.chr)
2040
+ width = 0
1105
2041
  end
1106
- width = Reline::Unicode.get_mbchar_width(key.chr)
1107
- @byte_pointer += 1
1108
- @cursor += width
1109
- @cursor_max += width
1110
2042
  end
2043
+ @cursor += width
2044
+ @cursor_max += width
1111
2045
  end
1112
2046
  alias_method :ed_digit, :ed_insert
1113
2047
  alias_method :self_insert, :ed_insert
@@ -1117,6 +2051,8 @@ class Reline::LineEditor
1117
2051
  arg.times do
1118
2052
  if key == "\C-j".ord or key == "\C-m".ord
1119
2053
  key_newline(key)
2054
+ elsif key == 0
2055
+ # Ignore NUL.
1120
2056
  else
1121
2057
  ed_insert(key)
1122
2058
  end
@@ -1164,6 +2100,7 @@ class Reline::LineEditor
1164
2100
  arg -= 1
1165
2101
  ed_prev_char(key, arg: arg) if arg > 0
1166
2102
  end
2103
+ alias_method :backward_char, :ed_prev_char
1167
2104
 
1168
2105
  private def vi_first_print(key)
1169
2106
  @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
@@ -1316,9 +2253,11 @@ class Reline::LineEditor
1316
2253
  searcher = generate_searcher
1317
2254
  searcher.resume(key)
1318
2255
  @searching_prompt = "(reverse-i-search)`': "
2256
+ termination_keys = ["\C-j".ord]
2257
+ termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1319
2258
  @waiting_proc = ->(k) {
1320
2259
  case k
1321
- when "\C-j".ord
2260
+ when *termination_keys
1322
2261
  if @history_pointer
1323
2262
  buffer = Reline::HISTORY[@history_pointer]
1324
2263
  else
@@ -1337,6 +2276,8 @@ class Reline::LineEditor
1337
2276
  @waiting_proc = nil
1338
2277
  @cursor_max = calculate_width(@line)
1339
2278
  @cursor = @byte_pointer = 0
2279
+ @rerender_all = true
2280
+ @cached_prompt_list = nil
1340
2281
  searcher.resume(-1)
1341
2282
  when "\C-g".ord
1342
2283
  if @is_multiline
@@ -1380,6 +2321,8 @@ class Reline::LineEditor
1380
2321
  @waiting_proc = nil
1381
2322
  @cursor_max = calculate_width(@line)
1382
2323
  @cursor = @byte_pointer = 0
2324
+ @rerender_all = true
2325
+ @cached_prompt_list = nil
1383
2326
  searcher.resume(-1)
1384
2327
  end
1385
2328
  end
@@ -1432,7 +2375,7 @@ class Reline::LineEditor
1432
2375
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1433
2376
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1434
2377
  @line_index = line_no
1435
- @line = @buffer_of_lines.last
2378
+ @line = @buffer_of_lines[@line_index]
1436
2379
  @rerender_all = true
1437
2380
  else
1438
2381
  @line = Reline::HISTORY[@history_pointer]
@@ -1480,7 +2423,7 @@ class Reline::LineEditor
1480
2423
  @line_index = line_no
1481
2424
  end
1482
2425
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1483
- @line = @buffer_of_lines.last
2426
+ @line = @buffer_of_lines[@line_index]
1484
2427
  @rerender_all = true
1485
2428
  else
1486
2429
  if @history_pointer.nil? and substr.empty?
@@ -1544,6 +2487,7 @@ class Reline::LineEditor
1544
2487
  arg -= 1
1545
2488
  ed_prev_history(key, arg: arg) if arg > 0
1546
2489
  end
2490
+ alias_method :previous_history, :ed_prev_history
1547
2491
 
1548
2492
  private def ed_next_history(key, arg: 1)
1549
2493
  if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
@@ -1591,8 +2535,10 @@ class Reline::LineEditor
1591
2535
  arg -= 1
1592
2536
  ed_next_history(key, arg: arg) if arg > 0
1593
2537
  end
2538
+ alias_method :next_history, :ed_next_history
1594
2539
 
1595
2540
  private def ed_newline(key)
2541
+ process_insert(force: true)
1596
2542
  if @is_multiline
1597
2543
  if @config.editing_mode_is?(:vi_command)
1598
2544
  if @line_index < (@buffer_of_lines.size - 1)
@@ -1624,7 +2570,7 @@ class Reline::LineEditor
1624
2570
  end
1625
2571
  end
1626
2572
 
1627
- private def em_delete_prev_char(key)
2573
+ private def em_delete_prev_char(key, arg: 1)
1628
2574
  if @is_multiline and @cursor == 0 and @line_index > 0
1629
2575
  @buffer_of_lines[@line_index] = @line
1630
2576
  @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
@@ -1642,9 +2588,16 @@ class Reline::LineEditor
1642
2588
  @cursor -= width
1643
2589
  @cursor_max -= width
1644
2590
  end
2591
+ arg -= 1
2592
+ em_delete_prev_char(key, arg: arg) if arg > 0
1645
2593
  end
1646
2594
  alias_method :backward_delete_char, :em_delete_prev_char
1647
2595
 
2596
+ # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
2597
+ # +Ctrl-U+) + Kill from the cursor to the end of the line.
2598
+ # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
2599
+ # the line. With a negative numeric argument, kill backward
2600
+ # from the cursor to the beginning of the current line.
1648
2601
  private def ed_kill_line(key)
1649
2602
  if @line.bytesize > @byte_pointer
1650
2603
  @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
@@ -1661,8 +2614,14 @@ class Reline::LineEditor
1661
2614
  @rest_height += 1
1662
2615
  end
1663
2616
  end
2617
+ alias_method :kill_line, :ed_kill_line
1664
2618
 
1665
- private def em_kill_line(key)
2619
+ # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
2620
+ # beginning of the edit buffer to the cursor and save it to the
2621
+ # cut buffer.
2622
+ # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
2623
+ # to the beginning of the current line.
2624
+ private def vi_kill_line_prev(key)
1666
2625
  if @byte_pointer > 0
1667
2626
  @line, deleted = byteslice!(@line, 0, @byte_pointer)
1668
2627
  @byte_pointer = 0
@@ -1671,6 +2630,22 @@ class Reline::LineEditor
1671
2630
  @cursor = 0
1672
2631
  end
1673
2632
  end
2633
+ alias_method :unix_line_discard, :vi_kill_line_prev
2634
+
2635
+ # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
2636
+ # edit buffer and save it to the cut buffer. +vi-kill-line-prev+
2637
+ # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
2638
+ # current line, no matter where point is.
2639
+ private def em_kill_line(key)
2640
+ if @line.size > 0
2641
+ @kill_ring.append(@line.dup, true)
2642
+ @line.clear
2643
+ @byte_pointer = 0
2644
+ @cursor_max = 0
2645
+ @cursor = 0
2646
+ end
2647
+ end
2648
+ alias_method :kill_whole_line, :em_kill_line
1674
2649
 
1675
2650
  private def em_delete(key)
1676
2651
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -1721,6 +2696,7 @@ class Reline::LineEditor
1721
2696
  @byte_pointer += yanked.bytesize
1722
2697
  end
1723
2698
  end
2699
+ alias_method :yank, :em_yank
1724
2700
 
1725
2701
  private def em_yank_pop(key)
1726
2702
  yanked, prev_yank = @kill_ring.yank_pop
@@ -1737,6 +2713,7 @@ class Reline::LineEditor
1737
2713
  @byte_pointer += yanked.bytesize
1738
2714
  end
1739
2715
  end
2716
+ alias_method :yank_pop, :em_yank_pop
1740
2717
 
1741
2718
  private def ed_clear_screen(key)
1742
2719
  @cleared = true
@@ -1867,9 +2844,10 @@ class Reline::LineEditor
1867
2844
  @byte_pointer -= byte_size
1868
2845
  @cursor -= width
1869
2846
  @cursor_max -= width
1870
- @kill_ring.append(deleted)
2847
+ @kill_ring.append(deleted, true)
1871
2848
  end
1872
2849
  end
2850
+ alias_method :unix_word_rubout, :em_kill_region
1873
2851
 
1874
2852
  private def copy_for_vi(text)
1875
2853
  if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@@ -1890,11 +2868,11 @@ class Reline::LineEditor
1890
2868
  ed_prev_char(key)
1891
2869
  @config.editing_mode = :vi_command
1892
2870
  end
1893
- alias_method :backward_char, :ed_prev_char
2871
+ alias_method :vi_movement_mode, :vi_command_mode
1894
2872
 
1895
2873
  private def vi_next_word(key, arg: 1)
1896
2874
  if @line.bytesize > @byte_pointer
1897
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
2875
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
1898
2876
  @byte_pointer += byte_size
1899
2877
  @cursor += width
1900
2878
  end
@@ -1912,13 +2890,22 @@ class Reline::LineEditor
1912
2890
  vi_prev_word(key, arg: arg) if arg > 0
1913
2891
  end
1914
2892
 
1915
- private def vi_end_word(key, arg: 1)
2893
+ private def vi_end_word(key, arg: 1, inclusive: false)
1916
2894
  if @line.bytesize > @byte_pointer
1917
2895
  byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1918
2896
  @byte_pointer += byte_size
1919
2897
  @cursor += width
1920
2898
  end
1921
2899
  arg -= 1
2900
+ if inclusive and arg.zero?
2901
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2902
+ if byte_size > 0
2903
+ c = @line.byteslice(@byte_pointer, byte_size)
2904
+ width = Reline::Unicode.get_mbchar_width(c)
2905
+ @byte_pointer += byte_size
2906
+ @cursor += width
2907
+ end
2908
+ end
1922
2909
  vi_end_word(key, arg: arg) if arg > 0
1923
2910
  end
1924
2911
 
@@ -1942,13 +2929,22 @@ class Reline::LineEditor
1942
2929
  vi_prev_big_word(key, arg: arg) if arg > 0
1943
2930
  end
1944
2931
 
1945
- private def vi_end_big_word(key, arg: 1)
2932
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
1946
2933
  if @line.bytesize > @byte_pointer
1947
2934
  byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1948
2935
  @byte_pointer += byte_size
1949
2936
  @cursor += width
1950
2937
  end
1951
2938
  arg -= 1
2939
+ if inclusive and arg.zero?
2940
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2941
+ if byte_size > 0
2942
+ c = @line.byteslice(@byte_pointer, byte_size)
2943
+ width = Reline::Unicode.get_mbchar_width(c)
2944
+ @byte_pointer += byte_size
2945
+ @cursor += width
2946
+ end
2947
+ end
1952
2948
  vi_end_big_word(key, arg: arg) if arg > 0
1953
2949
  end
1954
2950
 
@@ -2003,7 +2999,8 @@ class Reline::LineEditor
2003
2999
  @cursor = 0
2004
3000
  end
2005
3001
 
2006
- private def vi_change_meta(key)
3002
+ private def vi_change_meta(key, arg: 1)
3003
+ @drop_terminate_spaces = true
2007
3004
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2008
3005
  if byte_pointer_diff > 0
2009
3006
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2015,10 +3012,12 @@ class Reline::LineEditor
2015
3012
  @cursor_max -= cursor_diff.abs
2016
3013
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2017
3014
  @config.editing_mode = :vi_insert
3015
+ @drop_terminate_spaces = false
2018
3016
  }
3017
+ @waiting_operator_vi_arg = arg
2019
3018
  end
2020
3019
 
2021
- private def vi_delete_meta(key)
3020
+ private def vi_delete_meta(key, arg: 1)
2022
3021
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2023
3022
  if byte_pointer_diff > 0
2024
3023
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2030,9 +3029,19 @@ class Reline::LineEditor
2030
3029
  @cursor_max -= cursor_diff.abs
2031
3030
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2032
3031
  }
3032
+ @waiting_operator_vi_arg = arg
2033
3033
  end
2034
3034
 
2035
- private def vi_yank(key)
3035
+ private def vi_yank(key, arg: 1)
3036
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
3037
+ if byte_pointer_diff > 0
3038
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
3039
+ elsif byte_pointer_diff < 0
3040
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
3041
+ end
3042
+ copy_for_vi(cut)
3043
+ }
3044
+ @waiting_operator_vi_arg = arg
2036
3045
  end
2037
3046
 
2038
3047
  private def vi_list_or_eof(key)
@@ -2059,6 +3068,9 @@ class Reline::LineEditor
2059
3068
  width = Reline::Unicode.get_mbchar_width(mbchar)
2060
3069
  @cursor_max -= width
2061
3070
  if @cursor > 0 and @cursor >= @cursor_max
3071
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
3072
+ mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
3073
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2062
3074
  @byte_pointer -= byte_size
2063
3075
  @cursor -= width
2064
3076
  end
@@ -2092,11 +3104,23 @@ class Reline::LineEditor
2092
3104
 
2093
3105
  private def vi_histedit(key)
2094
3106
  path = Tempfile.open { |fp|
2095
- fp.write @line
3107
+ if @is_multiline
3108
+ fp.write whole_lines.join("\n")
3109
+ else
3110
+ fp.write @line
3111
+ end
2096
3112
  fp.path
2097
3113
  }
2098
3114
  system("#{ENV['EDITOR']} #{path}")
2099
- @line = File.read(path)
3115
+ if @is_multiline
3116
+ @buffer_of_lines = File.read(path).split("\n")
3117
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
3118
+ @line_index = 0
3119
+ @line = @buffer_of_lines[@line_index]
3120
+ @rerender_all = true
3121
+ else
3122
+ @line = File.read(path)
3123
+ end
2100
3124
  finish
2101
3125
  end
2102
3126
 
@@ -2126,7 +3150,14 @@ class Reline::LineEditor
2126
3150
 
2127
3151
  private def ed_argument_digit(key)
2128
3152
  if @vi_arg.nil?
2129
- unless key.chr.to_i.zero?
3153
+ if key.chr.to_i.zero?
3154
+ if key.anybits?(0b10000000)
3155
+ unescaped_key = key ^ 0b10000000
3156
+ unless unescaped_key.chr.to_i.zero?
3157
+ @vi_arg = unescaped_key.chr.to_i
3158
+ end
3159
+ end
3160
+ else
2130
3161
  @vi_arg = key.chr.to_i
2131
3162
  end
2132
3163
  else
@@ -2155,7 +3186,7 @@ class Reline::LineEditor
2155
3186
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2156
3187
  before = @line.byteslice(0, @byte_pointer)
2157
3188
  remaining_point = @byte_pointer + byte_size
2158
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
3189
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2159
3190
  @line = before + k.chr + after
2160
3191
  @cursor_max = calculate_width(@line)
2161
3192
  @waiting_proc = nil
@@ -2166,7 +3197,7 @@ class Reline::LineEditor
2166
3197
  end
2167
3198
  before = @line.byteslice(0, @byte_pointer)
2168
3199
  remaining_point = @byte_pointer + byte_size
2169
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
3200
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2170
3201
  replaced = k.chr * arg
2171
3202
  @line = before + replaced + after
2172
3203
  @byte_pointer += replaced.bytesize
@@ -2177,15 +3208,15 @@ class Reline::LineEditor
2177
3208
  }
2178
3209
  end
2179
3210
 
2180
- private def vi_next_char(key, arg: 1)
2181
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
3211
+ private def vi_next_char(key, arg: 1, inclusive: false)
3212
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2182
3213
  end
2183
3214
 
2184
- private def vi_to_next_char(key, arg: 1)
2185
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
3215
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
3216
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2186
3217
  end
2187
3218
 
2188
- private def search_next_char(key, arg, need_prev_char = false)
3219
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2189
3220
  if key.instance_of?(String)
2190
3221
  inputed_char = key
2191
3222
  else
@@ -2222,6 +3253,15 @@ class Reline::LineEditor
2222
3253
  @byte_pointer += byte_size
2223
3254
  @cursor += width
2224
3255
  end
3256
+ if inclusive
3257
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
3258
+ if byte_size > 0
3259
+ c = @line.byteslice(@byte_pointer, byte_size)
3260
+ width = Reline::Unicode.get_mbchar_width(c)
3261
+ @byte_pointer += byte_size
3262
+ @cursor += width
3263
+ end
3264
+ end
2225
3265
  @waiting_proc = nil
2226
3266
  end
2227
3267
 
@@ -2293,6 +3333,7 @@ class Reline::LineEditor
2293
3333
  alias_method :set_mark, :em_set_mark
2294
3334
 
2295
3335
  private def em_exchange_mark(key)
3336
+ return unless @mark_pointer
2296
3337
  new_pointer = [@byte_pointer, @line_index]
2297
3338
  @previous_line_index = @line_index
2298
3339
  @byte_pointer, @line_index = @mark_pointer