reline 0.2.8.pre.8 → 0.3.0

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