reline 0.2.8.pre.4 → 0.2.8.pre.8

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