reline 0.2.8.pre.7 → 0.2.8.pre.11

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