reline 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2606 @@
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 simplified_rendering?
62
+ if finished?
63
+ false
64
+ elsif @just_cursor_moving and not @rerender_all
65
+ true
66
+ else
67
+ not @rerender_all and not finished? and Reline::IOGate.in_pasting?
68
+ end
69
+ end
70
+
71
+ private def check_multiline_prompt(buffer, prompt)
72
+ if @vi_arg
73
+ prompt = "(arg: #{@vi_arg}) "
74
+ @rerender_all = true
75
+ elsif @searching_prompt
76
+ prompt = @searching_prompt
77
+ @rerender_all = true
78
+ else
79
+ prompt = @prompt
80
+ end
81
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
82
+ if @prompt_proc
83
+ if @cached_prompt_list and Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
84
+ prompt_list = @cached_prompt_list
85
+ else
86
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
87
+ @prompt_cache_time = Time.now.to_f
88
+ end
89
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
90
+ if @config.show_mode_in_prompt
91
+ if @config.editing_mode_is?(:vi_command)
92
+ mode_icon = @config.vi_cmd_mode_icon
93
+ elsif @config.editing_mode_is?(:vi_insert)
94
+ mode_icon = @config.vi_ins_mode_icon
95
+ elsif @config.editing_mode_is?(:emacs)
96
+ mode_icon = @config.emacs_mode_string
97
+ else
98
+ mode_icon = '?'
99
+ end
100
+ prompt_list.map!{ |pr| mode_icon + pr }
101
+ end
102
+ prompt = prompt_list[@line_index]
103
+ prompt_width = calculate_width(prompt, true)
104
+ [prompt, prompt_width, prompt_list]
105
+ else
106
+ prompt_width = calculate_width(prompt, true)
107
+ if @config.show_mode_in_prompt
108
+ if @config.editing_mode_is?(:vi_command)
109
+ mode_icon = @config.vi_cmd_mode_icon
110
+ elsif @config.editing_mode_is?(:vi_insert)
111
+ mode_icon = @config.vi_ins_mode_icon
112
+ elsif @config.editing_mode_is?(:emacs)
113
+ mode_icon = @config.emacs_mode_string
114
+ else
115
+ mode_icon = '?'
116
+ end
117
+ prompt = mode_icon + prompt
118
+ end
119
+ [prompt, prompt_width, nil]
120
+ end
121
+ end
122
+
123
+ def reset(prompt = '', encoding:)
124
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
125
+ @screen_size = Reline::IOGate.get_screen_size
126
+ @screen_height = @screen_size.first
127
+ reset_variables(prompt, encoding: encoding)
128
+ @old_trap = Signal.trap('SIGINT') {
129
+ @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
130
+ raise Interrupt
131
+ }
132
+ Reline::IOGate.set_winch_handler do
133
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
134
+ old_screen_size = @screen_size
135
+ @screen_size = Reline::IOGate.get_screen_size
136
+ @screen_height = @screen_size.first
137
+ if old_screen_size.last < @screen_size.last # columns increase
138
+ @rerender_all = true
139
+ rerender
140
+ else
141
+ back = 0
142
+ new_buffer = whole_lines
143
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
144
+ new_buffer.each_with_index do |line, index|
145
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
146
+ width = prompt_width + calculate_width(line)
147
+ height = calculate_height_by_width(width)
148
+ back += height
149
+ end
150
+ @highest_in_all = back
151
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
152
+ @first_line_started_from =
153
+ if @line_index.zero?
154
+ 0
155
+ else
156
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
157
+ end
158
+ if @prompt_proc
159
+ prompt = prompt_list[@line_index]
160
+ prompt_width = calculate_width(prompt, true)
161
+ end
162
+ calculate_nearest_cursor
163
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
164
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
165
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
166
+ @rerender_all = true
167
+ end
168
+ end
169
+ end
170
+
171
+ def finalize
172
+ Signal.trap('SIGINT', @old_trap)
173
+ end
174
+
175
+ def eof?
176
+ @eof
177
+ end
178
+
179
+ def reset_variables(prompt = '', encoding:)
180
+ @prompt = prompt
181
+ @mark_pointer = nil
182
+ @encoding = encoding
183
+ @is_multiline = false
184
+ @finished = false
185
+ @cleared = false
186
+ @rerender_all = false
187
+ @history_pointer = nil
188
+ @kill_ring = Reline::KillRing.new
189
+ @vi_clipboard = ''
190
+ @vi_arg = nil
191
+ @waiting_proc = nil
192
+ @waiting_operator_proc = nil
193
+ @waiting_operator_vi_arg = nil
194
+ @completion_journey_data = nil
195
+ @completion_state = CompletionState::NORMAL
196
+ @perfect_matched = nil
197
+ @menu_info = nil
198
+ @first_prompt = true
199
+ @searching_prompt = nil
200
+ @first_char = true
201
+ @add_newline_to_end_of_buffer = false
202
+ @just_cursor_moving = nil
203
+ @cached_prompt_list = nil
204
+ @prompt_cache_time = nil
205
+ @eof = false
206
+ @continuous_insertion_buffer = String.new(encoding: @encoding)
207
+ @scroll_partial_screen = nil
208
+ reset_line
209
+ end
210
+
211
+ def reset_line
212
+ @cursor = 0
213
+ @cursor_max = 0
214
+ @byte_pointer = 0
215
+ @buffer_of_lines = [String.new(encoding: @encoding)]
216
+ @line_index = 0
217
+ @previous_line_index = nil
218
+ @line = @buffer_of_lines[0]
219
+ @first_line_started_from = 0
220
+ @move_up = 0
221
+ @started_from = 0
222
+ @highest_in_this = 1
223
+ @highest_in_all = 1
224
+ @line_backup_in_history = nil
225
+ @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
226
+ @check_new_auto_indent = false
227
+ end
228
+
229
+ def multiline_on
230
+ @is_multiline = true
231
+ end
232
+
233
+ def multiline_off
234
+ @is_multiline = false
235
+ end
236
+
237
+ private def calculate_height_by_lines(lines, prompt)
238
+ result = 0
239
+ prompt_list = prompt.is_a?(Array) ? prompt : nil
240
+ lines.each_with_index { |line, i|
241
+ prompt = prompt_list[i] if prompt_list and prompt_list[i]
242
+ result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
243
+ }
244
+ result
245
+ end
246
+
247
+ private def insert_new_line(cursor_line, next_line)
248
+ @line = cursor_line
249
+ @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
250
+ @previous_line_index = @line_index
251
+ @line_index += 1
252
+ @just_cursor_moving = false
253
+ end
254
+
255
+ private def calculate_height_by_width(width)
256
+ width.div(@screen_size.last) + 1
257
+ end
258
+
259
+ private def split_by_width(str, max_width)
260
+ Reline::Unicode.split_by_width(str, max_width, @encoding)
261
+ end
262
+
263
+ private def scroll_down(val)
264
+ if val <= @rest_height
265
+ Reline::IOGate.move_cursor_down(val)
266
+ @rest_height -= val
267
+ else
268
+ Reline::IOGate.move_cursor_down(@rest_height)
269
+ Reline::IOGate.scroll_down(val - @rest_height)
270
+ @rest_height = 0
271
+ end
272
+ end
273
+
274
+ private def move_cursor_up(val)
275
+ if val > 0
276
+ Reline::IOGate.move_cursor_up(val)
277
+ @rest_height += val
278
+ elsif val < 0
279
+ move_cursor_down(-val)
280
+ end
281
+ end
282
+
283
+ private def move_cursor_down(val)
284
+ if val > 0
285
+ Reline::IOGate.move_cursor_down(val)
286
+ @rest_height -= val
287
+ @rest_height = 0 if @rest_height < 0
288
+ elsif val < 0
289
+ move_cursor_up(-val)
290
+ end
291
+ end
292
+
293
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
294
+ new_cursor_max = calculate_width(line_to_calc)
295
+ new_cursor = 0
296
+ new_byte_pointer = 0
297
+ height = 1
298
+ max_width = @screen_size.last
299
+ if @config.editing_mode_is?(:vi_command)
300
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
301
+ if last_byte_size > 0
302
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
303
+ last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
304
+ end_of_line_cursor = new_cursor_max - last_width
305
+ else
306
+ end_of_line_cursor = new_cursor_max
307
+ end
308
+ else
309
+ end_of_line_cursor = new_cursor_max
310
+ end
311
+ line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
312
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
313
+ now = new_cursor + mbchar_width
314
+ if now > end_of_line_cursor or now > cursor
315
+ break
316
+ end
317
+ new_cursor += mbchar_width
318
+ if new_cursor > max_width * height
319
+ height += 1
320
+ end
321
+ new_byte_pointer += gc.bytesize
322
+ end
323
+ new_started_from = height - 1
324
+ if update
325
+ @cursor = new_cursor
326
+ @cursor_max = new_cursor_max
327
+ @started_from = new_started_from
328
+ @byte_pointer = new_byte_pointer
329
+ else
330
+ [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
331
+ end
332
+ end
333
+
334
+ def rerender_all
335
+ @rerender_all = true
336
+ process_insert(force: true)
337
+ rerender
338
+ end
339
+
340
+ def rerender
341
+ return if @line.nil?
342
+ if @menu_info
343
+ scroll_down(@highest_in_all - @first_line_started_from)
344
+ @rerender_all = true
345
+ end
346
+ if @menu_info
347
+ show_menu
348
+ @menu_info = nil
349
+ end
350
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
351
+ if @cleared
352
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
353
+ @cleared = false
354
+ return
355
+ end
356
+ new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
357
+ # FIXME: end of logical line sometimes breaks
358
+ if @add_newline_to_end_of_buffer
359
+ rerender_added_newline
360
+ @add_newline_to_end_of_buffer = false
361
+ else
362
+ if @just_cursor_moving and not @rerender_all
363
+ rendered = just_move_cursor
364
+ @just_cursor_moving = false
365
+ return
366
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
367
+ rerender_changed_current_line
368
+ @previous_line_index = nil
369
+ rendered = true
370
+ elsif @rerender_all
371
+ rerender_all_lines
372
+ @rerender_all = false
373
+ rendered = true
374
+ else
375
+ end
376
+ end
377
+ line = modify_lines(whole_lines)[@line_index]
378
+ if @is_multiline
379
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
380
+ if finished?
381
+ # Always rerender on finish because output_modifier_proc may return a different output.
382
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
383
+ scroll_down(1)
384
+ Reline::IOGate.move_cursor_column(0)
385
+ Reline::IOGate.erase_after_cursor
386
+ elsif not rendered
387
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
388
+ end
389
+ @buffer_of_lines[@line_index] = @line
390
+ else
391
+ render_partial(prompt, prompt_width, line, 0)
392
+ if finished?
393
+ scroll_down(1)
394
+ Reline::IOGate.move_cursor_column(0)
395
+ Reline::IOGate.erase_after_cursor
396
+ end
397
+ end
398
+ end
399
+
400
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
401
+ if @screen_height < highest_in_all
402
+ old_scroll_partial_screen = @scroll_partial_screen
403
+ if cursor_y == 0
404
+ @scroll_partial_screen = 0
405
+ elsif cursor_y == (highest_in_all - 1)
406
+ @scroll_partial_screen = highest_in_all - @screen_height
407
+ else
408
+ if @scroll_partial_screen
409
+ if cursor_y <= @scroll_partial_screen
410
+ @scroll_partial_screen = cursor_y
411
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
412
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
413
+ end
414
+ else
415
+ if cursor_y > (@screen_height - 1)
416
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
417
+ else
418
+ @scroll_partial_screen = 0
419
+ end
420
+ end
421
+ end
422
+ if @scroll_partial_screen != old_scroll_partial_screen
423
+ @rerender_all = true
424
+ end
425
+ else
426
+ if @scroll_partial_screen
427
+ @rerender_all = true
428
+ end
429
+ @scroll_partial_screen = nil
430
+ end
431
+ end
432
+
433
+ private def rerender_added_newline
434
+ scroll_down(1)
435
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
436
+ prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
437
+ @buffer_of_lines[@previous_line_index] = @line
438
+ @line = @buffer_of_lines[@line_index]
439
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
440
+ @cursor = @cursor_max = calculate_width(@line)
441
+ @byte_pointer = @line.bytesize
442
+ @highest_in_all += @highest_in_this
443
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
444
+ @first_line_started_from += @started_from + 1
445
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
446
+ @previous_line_index = nil
447
+ end
448
+
449
+ def just_move_cursor
450
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
451
+ move_cursor_up(@started_from)
452
+ new_first_line_started_from =
453
+ if @line_index.zero?
454
+ 0
455
+ else
456
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
457
+ end
458
+ first_line_diff = new_first_line_started_from - @first_line_started_from
459
+ new_cursor, _, new_started_from, _ = calculate_nearest_cursor(@line, @cursor, @started_from, @byte_pointer, false)
460
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
461
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
462
+ @previous_line_index = nil
463
+ if @rerender_all
464
+ @line = @buffer_of_lines[@line_index]
465
+ rerender_all_lines
466
+ @rerender_all = false
467
+ true
468
+ else
469
+ @line = @buffer_of_lines[@line_index]
470
+ @first_line_started_from = new_first_line_started_from
471
+ @started_from = new_started_from
472
+ @cursor = new_cursor
473
+ move_cursor_down(first_line_diff + @started_from)
474
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
475
+ false
476
+ end
477
+ end
478
+
479
+ private def rerender_changed_current_line
480
+ if @previous_line_index
481
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
482
+ else
483
+ new_lines = whole_lines
484
+ end
485
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
486
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
487
+ diff = all_height - @highest_in_all
488
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
489
+ if diff > 0
490
+ scroll_down(diff)
491
+ move_cursor_up(all_height - 1)
492
+ elsif diff < 0
493
+ (-diff).times do
494
+ Reline::IOGate.move_cursor_column(0)
495
+ Reline::IOGate.erase_after_cursor
496
+ move_cursor_up(1)
497
+ end
498
+ move_cursor_up(all_height - 1)
499
+ else
500
+ move_cursor_up(all_height - 1)
501
+ end
502
+ @highest_in_all = all_height
503
+ back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
504
+ move_cursor_up(back)
505
+ if @previous_line_index
506
+ @buffer_of_lines[@previous_line_index] = @line
507
+ @line = @buffer_of_lines[@line_index]
508
+ end
509
+ @first_line_started_from =
510
+ if @line_index.zero?
511
+ 0
512
+ else
513
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
514
+ end
515
+ if @prompt_proc
516
+ prompt = prompt_list[@line_index]
517
+ prompt_width = calculate_width(prompt, true)
518
+ end
519
+ move_cursor_down(@first_line_started_from)
520
+ calculate_nearest_cursor
521
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
522
+ move_cursor_down(@started_from)
523
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
524
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
525
+ end
526
+
527
+ private def rerender_all_lines
528
+ move_cursor_up(@first_line_started_from + @started_from)
529
+ Reline::IOGate.move_cursor_column(0)
530
+ back = 0
531
+ new_buffer = whole_lines
532
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
533
+ new_buffer.each_with_index do |line, index|
534
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
535
+ width = prompt_width + calculate_width(line)
536
+ height = calculate_height_by_width(width)
537
+ back += height
538
+ end
539
+ old_highest_in_all = @highest_in_all
540
+ if @line_index.zero?
541
+ new_first_line_started_from = 0
542
+ else
543
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
544
+ end
545
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
546
+ if back > old_highest_in_all
547
+ scroll_down(back - 1)
548
+ move_cursor_up(back - 1)
549
+ elsif back < old_highest_in_all
550
+ scroll_down(back)
551
+ Reline::IOGate.erase_after_cursor
552
+ (old_highest_in_all - back - 1).times do
553
+ scroll_down(1)
554
+ Reline::IOGate.erase_after_cursor
555
+ end
556
+ move_cursor_up(old_highest_in_all - 1)
557
+ end
558
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
559
+ render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
560
+ if @prompt_proc
561
+ prompt = prompt_list[@line_index]
562
+ prompt_width = calculate_width(prompt, true)
563
+ end
564
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
565
+ @highest_in_all = back
566
+ @first_line_started_from = new_first_line_started_from
567
+ @started_from = new_started_from
568
+ if @scroll_partial_screen
569
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
570
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
571
+ else
572
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
573
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
574
+ end
575
+ end
576
+
577
+ private def render_whole_lines(lines, prompt, prompt_width)
578
+ rendered_height = 0
579
+ @output.write "\e[0m" # clear character decorations
580
+ modify_lines(lines).each_with_index do |line, index|
581
+ if prompt.is_a?(Array)
582
+ line_prompt = prompt[index]
583
+ prompt_width = calculate_width(line_prompt, true)
584
+ else
585
+ line_prompt = prompt
586
+ end
587
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
588
+ if index < (lines.size - 1)
589
+ if @scroll_partial_screen
590
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
591
+ move_cursor_down(1)
592
+ end
593
+ else
594
+ scroll_down(1)
595
+ end
596
+ rendered_height += height
597
+ else
598
+ rendered_height += height - 1
599
+ end
600
+ end
601
+ rendered_height
602
+ end
603
+
604
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
605
+ visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
606
+ cursor_up_from_last_line = 0
607
+ # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
608
+ if @scroll_partial_screen
609
+ last_visual_line = this_started_from + (height - 1)
610
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
611
+ if (@scroll_partial_screen - this_started_from) >= height
612
+ # Render nothing because this line is before the screen.
613
+ visual_lines = []
614
+ elsif this_started_from > last_screen_line
615
+ # Render nothing because this line is after the screen.
616
+ visual_lines = []
617
+ else
618
+ deleted_lines_before_screen = []
619
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
620
+ # A part of visual lines are before the screen.
621
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
622
+ deleted_lines_before_screen.compact!
623
+ end
624
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
625
+ # A part of visual lines are after the screen.
626
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
627
+ end
628
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
629
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
630
+ end
631
+ end
632
+ if with_control
633
+ if height > @highest_in_this
634
+ diff = height - @highest_in_this
635
+ scroll_down(diff)
636
+ @highest_in_all += diff
637
+ @highest_in_this = height
638
+ move_cursor_up(diff)
639
+ elsif height < @highest_in_this
640
+ diff = @highest_in_this - height
641
+ @highest_in_all -= diff
642
+ @highest_in_this = height
643
+ end
644
+ move_cursor_up(@started_from)
645
+ cursor_up_from_last_line = height - 1 - @started_from
646
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
647
+ end
648
+ visual_lines.each_with_index do |line, index|
649
+ Reline::IOGate.move_cursor_column(0)
650
+ if line.nil?
651
+ if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
652
+ # reaches the end of line
653
+ if Reline::IOGate.win?
654
+ # A newline is automatically inserted if a character is rendered at
655
+ # eol on command prompt.
656
+ else
657
+ # When the cursor is at the end of the line and erases characters
658
+ # after the cursor, some terminals delete the character at the
659
+ # cursor position.
660
+ move_cursor_down(1)
661
+ Reline::IOGate.move_cursor_column(0)
662
+ end
663
+ else
664
+ Reline::IOGate.erase_after_cursor
665
+ move_cursor_down(1)
666
+ Reline::IOGate.move_cursor_column(0)
667
+ end
668
+ next
669
+ end
670
+ @output.write line
671
+ if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
672
+ # A newline is automatically inserted if a character is rendered at eol on command prompt.
673
+ @rest_height -= 1 if @rest_height > 0
674
+ end
675
+ @output.flush
676
+ if @first_prompt
677
+ @first_prompt = false
678
+ @pre_input_hook&.call
679
+ end
680
+ end
681
+ unless visual_lines.empty?
682
+ Reline::IOGate.erase_after_cursor
683
+ Reline::IOGate.move_cursor_column(0)
684
+ end
685
+ if with_control
686
+ # Just after rendring, so the cursor is on the last line.
687
+ if finished?
688
+ Reline::IOGate.move_cursor_column(0)
689
+ else
690
+ # Moves up from bottom of lines to the cursor position.
691
+ move_cursor_up(cursor_up_from_last_line)
692
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
693
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
694
+ end
695
+ end
696
+ height
697
+ end
698
+
699
+ private def modify_lines(before)
700
+ return before if before.nil? || before.empty? || simplified_rendering?
701
+
702
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
703
+ after.lines("\n").map { |l| l.chomp('') }
704
+ else
705
+ before
706
+ end
707
+ end
708
+
709
+ private def show_menu
710
+ scroll_down(@highest_in_all - @first_line_started_from)
711
+ @rerender_all = true
712
+ @menu_info.list.sort!.each do |item|
713
+ Reline::IOGate.move_cursor_column(0)
714
+ @output.write item
715
+ @output.flush
716
+ scroll_down(1)
717
+ end
718
+ scroll_down(@highest_in_all - 1)
719
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
720
+ end
721
+
722
+ private def clear_screen_buffer(prompt, prompt_list, prompt_width)
723
+ Reline::IOGate.clear_screen
724
+ back = 0
725
+ modify_lines(whole_lines).each_with_index do |line, index|
726
+ if @prompt_proc
727
+ pr = prompt_list[index]
728
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
729
+ else
730
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
731
+ end
732
+ if index < (@buffer_of_lines.size - 1)
733
+ move_cursor_down(height)
734
+ back += height
735
+ end
736
+ end
737
+ move_cursor_up(back)
738
+ move_cursor_down(@first_line_started_from + @started_from)
739
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
740
+ end
741
+
742
+ def editing_mode
743
+ @config.editing_mode
744
+ end
745
+
746
+ private def menu(target, list)
747
+ @menu_info = MenuInfo.new(target, list)
748
+ end
749
+
750
+ private def complete_internal_proc(list, is_menu)
751
+ preposing, target, postposing = retrieve_completion_block
752
+ list = list.select { |i|
753
+ if i and not Encoding.compatible?(target.encoding, i.encoding)
754
+ raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
755
+ end
756
+ if @config.completion_ignore_case
757
+ i&.downcase&.start_with?(target.downcase)
758
+ else
759
+ i&.start_with?(target)
760
+ end
761
+ }.uniq
762
+ if is_menu
763
+ menu(target, list)
764
+ return nil
765
+ end
766
+ completed = list.inject { |memo, item|
767
+ begin
768
+ memo_mbchars = memo.unicode_normalize.grapheme_clusters
769
+ item_mbchars = item.unicode_normalize.grapheme_clusters
770
+ rescue Encoding::CompatibilityError
771
+ memo_mbchars = memo.grapheme_clusters
772
+ item_mbchars = item.grapheme_clusters
773
+ end
774
+ size = [memo_mbchars.size, item_mbchars.size].min
775
+ result = ''
776
+ size.times do |i|
777
+ if @config.completion_ignore_case
778
+ if memo_mbchars[i].casecmp?(item_mbchars[i])
779
+ result << memo_mbchars[i]
780
+ else
781
+ break
782
+ end
783
+ else
784
+ if memo_mbchars[i] == item_mbchars[i]
785
+ result << memo_mbchars[i]
786
+ else
787
+ break
788
+ end
789
+ end
790
+ end
791
+ result
792
+ }
793
+ [target, preposing, completed, postposing]
794
+ end
795
+
796
+ private def complete(list, just_show_list = false)
797
+ case @completion_state
798
+ when CompletionState::NORMAL, CompletionState::JOURNEY
799
+ @completion_state = CompletionState::COMPLETION
800
+ when CompletionState::PERFECT_MATCH
801
+ @dig_perfect_match_proc&.(@perfect_matched)
802
+ end
803
+ if just_show_list
804
+ is_menu = true
805
+ elsif @completion_state == CompletionState::MENU
806
+ is_menu = true
807
+ elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
808
+ is_menu = true
809
+ else
810
+ is_menu = false
811
+ end
812
+ result = complete_internal_proc(list, is_menu)
813
+ if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
814
+ @completion_state = CompletionState::PERFECT_MATCH
815
+ end
816
+ return if result.nil?
817
+ target, preposing, completed, postposing = result
818
+ return if completed.nil?
819
+ if target <= completed and (@completion_state == CompletionState::COMPLETION)
820
+ if list.include?(completed)
821
+ if list.one?
822
+ @completion_state = CompletionState::PERFECT_MATCH
823
+ else
824
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
825
+ end
826
+ @perfect_matched = completed
827
+ else
828
+ @completion_state = CompletionState::MENU
829
+ end
830
+ if not just_show_list and target < completed
831
+ @line = preposing + completed + completion_append_character.to_s + postposing
832
+ line_to_pointer = preposing + completed + completion_append_character.to_s
833
+ @cursor_max = calculate_width(@line)
834
+ @cursor = calculate_width(line_to_pointer)
835
+ @byte_pointer = line_to_pointer.bytesize
836
+ end
837
+ end
838
+ end
839
+
840
+ private def move_completed_list(list, direction)
841
+ case @completion_state
842
+ when CompletionState::NORMAL, CompletionState::COMPLETION,
843
+ CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
844
+ @completion_state = CompletionState::JOURNEY
845
+ result = retrieve_completion_block
846
+ return if result.nil?
847
+ preposing, target, postposing = result
848
+ @completion_journey_data = CompletionJourneyData.new(
849
+ preposing, postposing,
850
+ [target] + list.select{ |item| item.start_with?(target) }, 0)
851
+ @completion_state = CompletionState::JOURNEY
852
+ else
853
+ case direction
854
+ when :up
855
+ @completion_journey_data.pointer -= 1
856
+ if @completion_journey_data.pointer < 0
857
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
858
+ end
859
+ when :down
860
+ @completion_journey_data.pointer += 1
861
+ if @completion_journey_data.pointer >= @completion_journey_data.list.size
862
+ @completion_journey_data.pointer = 0
863
+ end
864
+ end
865
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
866
+ @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
867
+ line_to_pointer = @completion_journey_data.preposing + completed
868
+ @cursor_max = calculate_width(@line)
869
+ @cursor = calculate_width(line_to_pointer)
870
+ @byte_pointer = line_to_pointer.bytesize
871
+ end
872
+ end
873
+
874
+ private def run_for_operators(key, method_symbol, &block)
875
+ if @waiting_operator_proc
876
+ if VI_MOTIONS.include?(method_symbol)
877
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
878
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
879
+ block.(true)
880
+ unless @waiting_proc
881
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
882
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
883
+ @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
884
+ else
885
+ old_waiting_proc = @waiting_proc
886
+ old_waiting_operator_proc = @waiting_operator_proc
887
+ current_waiting_operator_proc = @waiting_operator_proc
888
+ @waiting_proc = proc { |k|
889
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
890
+ old_waiting_proc.(k)
891
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
892
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
893
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
894
+ @waiting_operator_proc = old_waiting_operator_proc
895
+ }
896
+ end
897
+ else
898
+ # Ignores operator when not motion is given.
899
+ block.(false)
900
+ end
901
+ @waiting_operator_proc = nil
902
+ @waiting_operator_vi_arg = nil
903
+ @vi_arg = nil
904
+ else
905
+ block.(false)
906
+ end
907
+ end
908
+
909
+ private def argumentable?(method_obj)
910
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
911
+ end
912
+
913
+ private def inclusive?(method_obj)
914
+ # If a motion method with the keyword argument "inclusive" follows the
915
+ # operator, it must contain the character at the cursor position.
916
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
917
+ end
918
+
919
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
920
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
921
+ not_insertion = method_symbol != :ed_insert
922
+ process_insert(force: not_insertion)
923
+ end
924
+ if @vi_arg and argumentable?(method_obj)
925
+ if with_operator and inclusive?(method_obj)
926
+ method_obj.(key, arg: @vi_arg, inclusive: true)
927
+ else
928
+ method_obj.(key, arg: @vi_arg)
929
+ end
930
+ else
931
+ if with_operator and inclusive?(method_obj)
932
+ method_obj.(key, inclusive: true)
933
+ else
934
+ method_obj.(key)
935
+ end
936
+ end
937
+ end
938
+
939
+ private def process_key(key, method_symbol)
940
+ if method_symbol and respond_to?(method_symbol, true)
941
+ method_obj = method(method_symbol)
942
+ else
943
+ method_obj = nil
944
+ end
945
+ if method_symbol and key.is_a?(Symbol)
946
+ if @vi_arg and argumentable?(method_obj)
947
+ run_for_operators(key, method_symbol) do |with_operator|
948
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
949
+ end
950
+ else
951
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
952
+ end
953
+ @kill_ring.process
954
+ @vi_arg = nil
955
+ elsif @vi_arg
956
+ if key.chr =~ /[0-9]/
957
+ ed_argument_digit(key)
958
+ else
959
+ if argumentable?(method_obj)
960
+ run_for_operators(key, method_symbol) do |with_operator|
961
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
962
+ end
963
+ elsif @waiting_proc
964
+ @waiting_proc.(key)
965
+ elsif method_obj
966
+ wrap_method_call(method_symbol, method_obj, key)
967
+ else
968
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
969
+ end
970
+ @kill_ring.process
971
+ @vi_arg = nil
972
+ end
973
+ elsif @waiting_proc
974
+ @waiting_proc.(key)
975
+ @kill_ring.process
976
+ elsif method_obj
977
+ if method_symbol == :ed_argument_digit
978
+ wrap_method_call(method_symbol, method_obj, key)
979
+ else
980
+ run_for_operators(key, method_symbol) do |with_operator|
981
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
982
+ end
983
+ end
984
+ @kill_ring.process
985
+ else
986
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
987
+ end
988
+ end
989
+
990
+ private def normal_char(key)
991
+ method_symbol = method_obj = nil
992
+ if key.combined_char.is_a?(Symbol)
993
+ process_key(key.combined_char, key.combined_char)
994
+ return
995
+ end
996
+ @multibyte_buffer << key.combined_char
997
+ if @multibyte_buffer.size > 1
998
+ if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
999
+ process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
1000
+ @multibyte_buffer.clear
1001
+ else
1002
+ # invalid
1003
+ return
1004
+ end
1005
+ else # single byte
1006
+ return if key.char >= 128 # maybe, first byte of multi byte
1007
+ method_symbol = @config.editing_mode.get_method(key.combined_char)
1008
+ if key.with_meta and method_symbol == :ed_unassigned
1009
+ # split ESC + key
1010
+ method_symbol = @config.editing_mode.get_method("\e".ord)
1011
+ process_key("\e".ord, method_symbol)
1012
+ method_symbol = @config.editing_mode.get_method(key.char)
1013
+ process_key(key.char, method_symbol)
1014
+ else
1015
+ process_key(key.combined_char, method_symbol)
1016
+ end
1017
+ @multibyte_buffer.clear
1018
+ end
1019
+ if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
1020
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1021
+ @byte_pointer -= byte_size
1022
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1023
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1024
+ @cursor -= width
1025
+ end
1026
+ end
1027
+
1028
+ def input_key(key)
1029
+ @just_cursor_moving = nil
1030
+ if key.char.nil?
1031
+ if @first_char
1032
+ @line = nil
1033
+ end
1034
+ finish
1035
+ return
1036
+ end
1037
+ old_line = @line.dup
1038
+ @first_char = false
1039
+ completion_occurs = false
1040
+ if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1041
+ unless @config.disable_completion
1042
+ result = call_completion_proc
1043
+ if result.is_a?(Array)
1044
+ completion_occurs = true
1045
+ process_insert
1046
+ complete(result)
1047
+ end
1048
+ end
1049
+ elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1050
+ unless @config.disable_completion
1051
+ result = call_completion_proc
1052
+ if result.is_a?(Array)
1053
+ completion_occurs = true
1054
+ process_insert
1055
+ move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
1056
+ end
1057
+ end
1058
+ elsif Symbol === key.char and respond_to?(key.char, true)
1059
+ process_key(key.char, key.char)
1060
+ else
1061
+ normal_char(key)
1062
+ end
1063
+ unless completion_occurs
1064
+ @completion_state = CompletionState::NORMAL
1065
+ end
1066
+ if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1067
+ if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1068
+ @just_cursor_moving = true
1069
+ elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1070
+ @just_cursor_moving = true
1071
+ else
1072
+ @just_cursor_moving = false
1073
+ end
1074
+ else
1075
+ @just_cursor_moving = false
1076
+ end
1077
+ if @is_multiline and @auto_indent_proc and not simplified_rendering?
1078
+ process_auto_indent
1079
+ end
1080
+ end
1081
+
1082
+ def call_completion_proc
1083
+ result = retrieve_completion_block(true)
1084
+ slice = result[1]
1085
+ result = @completion_proc.(slice) if @completion_proc and slice
1086
+ Reline.core.instance_variable_set(:@completion_quote_character, nil)
1087
+ result
1088
+ end
1089
+
1090
+ private def process_auto_indent
1091
+ return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
1092
+ if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
1093
+ # Fix indent of a line when a newline is inserted to the next
1094
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
1095
+ new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
1096
+ md = @line.match(/\A */)
1097
+ prev_indent = md[0].count(' ')
1098
+ @line = ' ' * new_indent + @line.lstrip
1099
+
1100
+ new_indent = nil
1101
+ result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
1102
+ if result
1103
+ new_indent = result
1104
+ end
1105
+ if new_indent&.>= 0
1106
+ @line = ' ' * new_indent + @line.lstrip
1107
+ end
1108
+ end
1109
+ if @previous_line_index
1110
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
1111
+ else
1112
+ new_lines = whole_lines
1113
+ end
1114
+ new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1115
+ if new_indent&.>= 0
1116
+ md = new_lines[@line_index].match(/\A */)
1117
+ prev_indent = md[0].count(' ')
1118
+ if @check_new_auto_indent
1119
+ @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1120
+ @cursor = new_indent
1121
+ @byte_pointer = new_indent
1122
+ else
1123
+ @line = ' ' * new_indent + @line.lstrip
1124
+ @cursor += new_indent - prev_indent
1125
+ @byte_pointer += new_indent - prev_indent
1126
+ end
1127
+ end
1128
+ @check_new_auto_indent = false
1129
+ end
1130
+
1131
+ def retrieve_completion_block(set_completion_quote_character = false)
1132
+ word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1133
+ quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1134
+ before = @line.byteslice(0, @byte_pointer)
1135
+ rest = nil
1136
+ break_pointer = nil
1137
+ quote = nil
1138
+ closing_quote = nil
1139
+ escaped_quote = nil
1140
+ i = 0
1141
+ while i < @byte_pointer do
1142
+ slice = @line.byteslice(i, @byte_pointer - i)
1143
+ unless slice.valid_encoding?
1144
+ i += 1
1145
+ next
1146
+ end
1147
+ if quote and slice.start_with?(closing_quote)
1148
+ quote = nil
1149
+ i += 1
1150
+ rest = nil
1151
+ elsif quote and slice.start_with?(escaped_quote)
1152
+ # skip
1153
+ i += 2
1154
+ elsif slice =~ quote_characters_regexp # find new "
1155
+ rest = $'
1156
+ quote = $&
1157
+ closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1158
+ escaped_quote = /\\#{Regexp.escape(quote)}/
1159
+ i += 1
1160
+ break_pointer = i - 1
1161
+ elsif not quote and slice =~ word_break_regexp
1162
+ rest = $'
1163
+ i += 1
1164
+ before = @line.byteslice(i, @byte_pointer - i)
1165
+ break_pointer = i
1166
+ else
1167
+ i += 1
1168
+ end
1169
+ end
1170
+ postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1171
+ if rest
1172
+ preposing = @line.byteslice(0, break_pointer)
1173
+ target = rest
1174
+ if set_completion_quote_character and quote
1175
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
1176
+ if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
1177
+ insert_text(quote)
1178
+ end
1179
+ end
1180
+ else
1181
+ preposing = ''
1182
+ if break_pointer
1183
+ preposing = @line.byteslice(0, break_pointer)
1184
+ else
1185
+ preposing = ''
1186
+ end
1187
+ target = before
1188
+ end
1189
+ [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1190
+ end
1191
+
1192
+ def confirm_multiline_termination
1193
+ temp_buffer = @buffer_of_lines.dup
1194
+ if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
1195
+ temp_buffer[@previous_line_index] = @line
1196
+ else
1197
+ temp_buffer[@line_index] = @line
1198
+ end
1199
+ @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1200
+ end
1201
+
1202
+ def insert_text(text)
1203
+ width = calculate_width(text)
1204
+ if @cursor == @cursor_max
1205
+ @line += text
1206
+ else
1207
+ @line = byteinsert(@line, @byte_pointer, text)
1208
+ end
1209
+ @byte_pointer += text.bytesize
1210
+ @cursor += width
1211
+ @cursor_max += width
1212
+ end
1213
+
1214
+ def delete_text(start = nil, length = nil)
1215
+ if start.nil? and length.nil?
1216
+ @line&.clear
1217
+ @byte_pointer = 0
1218
+ @cursor = 0
1219
+ @cursor_max = 0
1220
+ elsif not start.nil? and not length.nil?
1221
+ if @line
1222
+ before = @line.byteslice(0, start)
1223
+ after = @line.byteslice(start + length, @line.bytesize)
1224
+ @line = before + after
1225
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1226
+ str = @line.byteslice(0, @byte_pointer)
1227
+ @cursor = calculate_width(str)
1228
+ @cursor_max = calculate_width(@line)
1229
+ end
1230
+ elsif start.is_a?(Range)
1231
+ range = start
1232
+ first = range.first
1233
+ last = range.last
1234
+ last = @line.bytesize - 1 if last > @line.bytesize
1235
+ last += @line.bytesize if last < 0
1236
+ first += @line.bytesize if first < 0
1237
+ range = range.exclude_end? ? first...last : first..last
1238
+ @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1239
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1240
+ str = @line.byteslice(0, @byte_pointer)
1241
+ @cursor = calculate_width(str)
1242
+ @cursor_max = calculate_width(@line)
1243
+ else
1244
+ @line = @line.byteslice(0, start)
1245
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1246
+ str = @line.byteslice(0, @byte_pointer)
1247
+ @cursor = calculate_width(str)
1248
+ @cursor_max = calculate_width(@line)
1249
+ end
1250
+ end
1251
+
1252
+ def byte_pointer=(val)
1253
+ @byte_pointer = val
1254
+ str = @line.byteslice(0, @byte_pointer)
1255
+ @cursor = calculate_width(str)
1256
+ @cursor_max = calculate_width(@line)
1257
+ end
1258
+
1259
+ def whole_lines(index: @line_index, line: @line)
1260
+ temp_lines = @buffer_of_lines.dup
1261
+ temp_lines[index] = line
1262
+ temp_lines
1263
+ end
1264
+
1265
+ def whole_buffer
1266
+ if @buffer_of_lines.size == 1 and @line.nil?
1267
+ nil
1268
+ else
1269
+ whole_lines.join("\n")
1270
+ end
1271
+ end
1272
+
1273
+ def finished?
1274
+ @finished
1275
+ end
1276
+
1277
+ def finish
1278
+ @finished = true
1279
+ @rerender_all = true
1280
+ @config.reset
1281
+ end
1282
+
1283
+ private def byteslice!(str, byte_pointer, size)
1284
+ new_str = str.byteslice(0, byte_pointer)
1285
+ new_str << str.byteslice(byte_pointer + size, str.bytesize)
1286
+ [new_str, str.byteslice(byte_pointer, size)]
1287
+ end
1288
+
1289
+ private def byteinsert(str, byte_pointer, other)
1290
+ new_str = str.byteslice(0, byte_pointer)
1291
+ new_str << other
1292
+ new_str << str.byteslice(byte_pointer, str.bytesize)
1293
+ new_str
1294
+ end
1295
+
1296
+ private def calculate_width(str, allow_escape_code = false)
1297
+ Reline::Unicode.calculate_width(str, allow_escape_code)
1298
+ end
1299
+
1300
+ private def key_delete(key)
1301
+ if @config.editing_mode_is?(:vi_insert, :emacs)
1302
+ ed_delete_next_char(key)
1303
+ end
1304
+ end
1305
+
1306
+ private def key_newline(key)
1307
+ if @is_multiline
1308
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1309
+ @add_newline_to_end_of_buffer = true
1310
+ end
1311
+ next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1312
+ cursor_line = @line.byteslice(0, @byte_pointer)
1313
+ insert_new_line(cursor_line, next_line)
1314
+ @cursor = 0
1315
+ @check_new_auto_indent = true
1316
+ end
1317
+ end
1318
+
1319
+ private def ed_unassigned(key) end # do nothing
1320
+
1321
+ private def process_insert(force: false)
1322
+ return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1323
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1324
+ bytesize = @continuous_insertion_buffer.bytesize
1325
+ if @cursor == @cursor_max
1326
+ @line += @continuous_insertion_buffer
1327
+ else
1328
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1329
+ end
1330
+ @byte_pointer += bytesize
1331
+ @cursor += width
1332
+ @cursor_max += width
1333
+ @continuous_insertion_buffer.clear
1334
+ end
1335
+
1336
+ private def ed_insert(key)
1337
+ str = nil
1338
+ width = nil
1339
+ bytesize = nil
1340
+ if key.instance_of?(String)
1341
+ begin
1342
+ key.encode(Encoding::UTF_8)
1343
+ rescue Encoding::UndefinedConversionError
1344
+ return
1345
+ end
1346
+ str = key
1347
+ bytesize = key.bytesize
1348
+ else
1349
+ begin
1350
+ key.chr.encode(Encoding::UTF_8)
1351
+ rescue Encoding::UndefinedConversionError
1352
+ return
1353
+ end
1354
+ str = key.chr
1355
+ bytesize = 1
1356
+ end
1357
+ if Reline::IOGate.in_pasting?
1358
+ @continuous_insertion_buffer << str
1359
+ return
1360
+ elsif not @continuous_insertion_buffer.empty?
1361
+ process_insert
1362
+ end
1363
+ width = Reline::Unicode.get_mbchar_width(str)
1364
+ if @cursor == @cursor_max
1365
+ @line += str
1366
+ else
1367
+ @line = byteinsert(@line, @byte_pointer, str)
1368
+ end
1369
+ @byte_pointer += bytesize
1370
+ @cursor += width
1371
+ @cursor_max += width
1372
+ end
1373
+ alias_method :ed_digit, :ed_insert
1374
+ alias_method :self_insert, :ed_insert
1375
+
1376
+ private def ed_quoted_insert(str, arg: 1)
1377
+ @waiting_proc = proc { |key|
1378
+ arg.times do
1379
+ if key == "\C-j".ord or key == "\C-m".ord
1380
+ key_newline(key)
1381
+ else
1382
+ ed_insert(key)
1383
+ end
1384
+ end
1385
+ @waiting_proc = nil
1386
+ }
1387
+ end
1388
+ alias_method :quoted_insert, :ed_quoted_insert
1389
+
1390
+ private def ed_next_char(key, arg: 1)
1391
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1392
+ if (@byte_pointer < @line.bytesize)
1393
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1394
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1395
+ @cursor += width if width
1396
+ @byte_pointer += byte_size
1397
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
1398
+ next_line = @buffer_of_lines[@line_index + 1]
1399
+ @cursor = 0
1400
+ @byte_pointer = 0
1401
+ @cursor_max = calculate_width(next_line)
1402
+ @previous_line_index = @line_index
1403
+ @line_index += 1
1404
+ end
1405
+ arg -= 1
1406
+ ed_next_char(key, arg: arg) if arg > 0
1407
+ end
1408
+ alias_method :forward_char, :ed_next_char
1409
+
1410
+ private def ed_prev_char(key, arg: 1)
1411
+ if @cursor > 0
1412
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1413
+ @byte_pointer -= byte_size
1414
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1415
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1416
+ @cursor -= width
1417
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1418
+ prev_line = @buffer_of_lines[@line_index - 1]
1419
+ @cursor = calculate_width(prev_line)
1420
+ @byte_pointer = prev_line.bytesize
1421
+ @cursor_max = calculate_width(prev_line)
1422
+ @previous_line_index = @line_index
1423
+ @line_index -= 1
1424
+ end
1425
+ arg -= 1
1426
+ ed_prev_char(key, arg: arg) if arg > 0
1427
+ end
1428
+ alias_method :backward_char, :ed_prev_char
1429
+
1430
+ private def vi_first_print(key)
1431
+ @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
1432
+ end
1433
+
1434
+ private def ed_move_to_beg(key)
1435
+ @byte_pointer = @cursor = 0
1436
+ end
1437
+ alias_method :beginning_of_line, :ed_move_to_beg
1438
+
1439
+ private def ed_move_to_end(key)
1440
+ @byte_pointer = 0
1441
+ @cursor = 0
1442
+ byte_size = 0
1443
+ while @byte_pointer < @line.bytesize
1444
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1445
+ if byte_size > 0
1446
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1447
+ @cursor += Reline::Unicode.get_mbchar_width(mbchar)
1448
+ end
1449
+ @byte_pointer += byte_size
1450
+ end
1451
+ end
1452
+ alias_method :end_of_line, :ed_move_to_end
1453
+
1454
+ private def generate_searcher
1455
+ Fiber.new do |first_key|
1456
+ prev_search_key = first_key
1457
+ search_word = String.new(encoding: @encoding)
1458
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1459
+ last_hit = nil
1460
+ case first_key
1461
+ when "\C-r".ord
1462
+ prompt_name = 'reverse-i-search'
1463
+ when "\C-s".ord
1464
+ prompt_name = 'i-search'
1465
+ end
1466
+ loop do
1467
+ key = Fiber.yield(search_word)
1468
+ search_again = false
1469
+ case key
1470
+ when -1 # determined
1471
+ Reline.last_incremental_search = search_word
1472
+ break
1473
+ when "\C-h".ord, "\C-?".ord
1474
+ grapheme_clusters = search_word.grapheme_clusters
1475
+ if grapheme_clusters.size > 0
1476
+ grapheme_clusters.pop
1477
+ search_word = grapheme_clusters.join
1478
+ end
1479
+ when "\C-r".ord, "\C-s".ord
1480
+ search_again = true if prev_search_key == key
1481
+ prev_search_key = key
1482
+ else
1483
+ multibyte_buf << key
1484
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1485
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
1486
+ multibyte_buf.clear
1487
+ end
1488
+ end
1489
+ hit = nil
1490
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1491
+ @history_pointer = nil
1492
+ hit = @line_backup_in_history
1493
+ else
1494
+ if search_again
1495
+ if search_word.empty? and Reline.last_incremental_search
1496
+ search_word = Reline.last_incremental_search
1497
+ end
1498
+ if @history_pointer
1499
+ case prev_search_key
1500
+ when "\C-r".ord
1501
+ history_pointer_base = 0
1502
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1503
+ when "\C-s".ord
1504
+ history_pointer_base = @history_pointer + 1
1505
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1506
+ end
1507
+ else
1508
+ history_pointer_base = 0
1509
+ history = Reline::HISTORY
1510
+ end
1511
+ elsif @history_pointer
1512
+ case prev_search_key
1513
+ when "\C-r".ord
1514
+ history_pointer_base = 0
1515
+ history = Reline::HISTORY[0..@history_pointer]
1516
+ when "\C-s".ord
1517
+ history_pointer_base = @history_pointer
1518
+ history = Reline::HISTORY[@history_pointer..-1]
1519
+ end
1520
+ else
1521
+ history_pointer_base = 0
1522
+ history = Reline::HISTORY
1523
+ end
1524
+ case prev_search_key
1525
+ when "\C-r".ord
1526
+ hit_index = history.rindex { |item|
1527
+ item.include?(search_word)
1528
+ }
1529
+ when "\C-s".ord
1530
+ hit_index = history.index { |item|
1531
+ item.include?(search_word)
1532
+ }
1533
+ end
1534
+ if hit_index
1535
+ @history_pointer = history_pointer_base + hit_index
1536
+ hit = Reline::HISTORY[@history_pointer]
1537
+ end
1538
+ end
1539
+ case prev_search_key
1540
+ when "\C-r".ord
1541
+ prompt_name = 'reverse-i-search'
1542
+ when "\C-s".ord
1543
+ prompt_name = 'i-search'
1544
+ end
1545
+ if hit
1546
+ if @is_multiline
1547
+ @buffer_of_lines = hit.split("\n")
1548
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1549
+ @line_index = @buffer_of_lines.size - 1
1550
+ @line = @buffer_of_lines.last
1551
+ @rerender_all = true
1552
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1553
+ else
1554
+ @line = hit
1555
+ @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1556
+ end
1557
+ last_hit = hit
1558
+ else
1559
+ if @is_multiline
1560
+ @rerender_all = true
1561
+ @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1562
+ else
1563
+ @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1564
+ end
1565
+ end
1566
+ end
1567
+ end
1568
+ end
1569
+
1570
+ private def incremental_search_history(key)
1571
+ unless @history_pointer
1572
+ if @is_multiline
1573
+ @line_backup_in_history = whole_buffer
1574
+ else
1575
+ @line_backup_in_history = @line
1576
+ end
1577
+ end
1578
+ searcher = generate_searcher
1579
+ searcher.resume(key)
1580
+ @searching_prompt = "(reverse-i-search)`': "
1581
+ @waiting_proc = ->(k) {
1582
+ case k
1583
+ when "\C-j".ord
1584
+ if @history_pointer
1585
+ buffer = Reline::HISTORY[@history_pointer]
1586
+ else
1587
+ buffer = @line_backup_in_history
1588
+ end
1589
+ if @is_multiline
1590
+ @buffer_of_lines = buffer.split("\n")
1591
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1592
+ @line_index = @buffer_of_lines.size - 1
1593
+ @line = @buffer_of_lines.last
1594
+ @rerender_all = true
1595
+ else
1596
+ @line = buffer
1597
+ end
1598
+ @searching_prompt = nil
1599
+ @waiting_proc = nil
1600
+ @cursor_max = calculate_width(@line)
1601
+ @cursor = @byte_pointer = 0
1602
+ searcher.resume(-1)
1603
+ when "\C-g".ord
1604
+ if @is_multiline
1605
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1606
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1607
+ @line_index = @buffer_of_lines.size - 1
1608
+ @line = @buffer_of_lines.last
1609
+ @rerender_all = true
1610
+ else
1611
+ @line = @line_backup_in_history
1612
+ end
1613
+ @history_pointer = nil
1614
+ @searching_prompt = nil
1615
+ @waiting_proc = nil
1616
+ @line_backup_in_history = nil
1617
+ @cursor_max = calculate_width(@line)
1618
+ @cursor = @byte_pointer = 0
1619
+ @rerender_all = true
1620
+ else
1621
+ chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1622
+ if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1623
+ searcher.resume(k)
1624
+ else
1625
+ if @history_pointer
1626
+ line = Reline::HISTORY[@history_pointer]
1627
+ else
1628
+ line = @line_backup_in_history
1629
+ end
1630
+ if @is_multiline
1631
+ @line_backup_in_history = whole_buffer
1632
+ @buffer_of_lines = line.split("\n")
1633
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1634
+ @line_index = @buffer_of_lines.size - 1
1635
+ @line = @buffer_of_lines.last
1636
+ @rerender_all = true
1637
+ else
1638
+ @line_backup_in_history = @line
1639
+ @line = line
1640
+ end
1641
+ @searching_prompt = nil
1642
+ @waiting_proc = nil
1643
+ @cursor_max = calculate_width(@line)
1644
+ @cursor = @byte_pointer = 0
1645
+ searcher.resume(-1)
1646
+ end
1647
+ end
1648
+ }
1649
+ end
1650
+
1651
+ private def vi_search_prev(key)
1652
+ incremental_search_history(key)
1653
+ end
1654
+ alias_method :reverse_search_history, :vi_search_prev
1655
+
1656
+ private def vi_search_next(key)
1657
+ incremental_search_history(key)
1658
+ end
1659
+ alias_method :forward_search_history, :vi_search_next
1660
+
1661
+ private def ed_search_prev_history(key, arg: 1)
1662
+ history = nil
1663
+ h_pointer = nil
1664
+ line_no = nil
1665
+ substr = @line.slice(0, @byte_pointer)
1666
+ if @history_pointer.nil?
1667
+ return if not @line.empty? and substr.empty?
1668
+ history = Reline::HISTORY
1669
+ elsif @history_pointer.zero?
1670
+ history = nil
1671
+ h_pointer = nil
1672
+ else
1673
+ history = Reline::HISTORY.slice(0, @history_pointer)
1674
+ end
1675
+ return if history.nil?
1676
+ if @is_multiline
1677
+ h_pointer = history.rindex { |h|
1678
+ h.split("\n").each_with_index { |l, i|
1679
+ if l.start_with?(substr)
1680
+ line_no = i
1681
+ break
1682
+ end
1683
+ }
1684
+ not line_no.nil?
1685
+ }
1686
+ else
1687
+ h_pointer = history.rindex { |l|
1688
+ l.start_with?(substr)
1689
+ }
1690
+ end
1691
+ return if h_pointer.nil?
1692
+ @history_pointer = h_pointer
1693
+ if @is_multiline
1694
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1695
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1696
+ @line_index = line_no
1697
+ @line = @buffer_of_lines.last
1698
+ @rerender_all = true
1699
+ else
1700
+ @line = Reline::HISTORY[@history_pointer]
1701
+ end
1702
+ @cursor_max = calculate_width(@line)
1703
+ arg -= 1
1704
+ ed_search_prev_history(key, arg: arg) if arg > 0
1705
+ end
1706
+ alias_method :history_search_backward, :ed_search_prev_history
1707
+
1708
+ private def ed_search_next_history(key, arg: 1)
1709
+ substr = @line.slice(0, @byte_pointer)
1710
+ if @history_pointer.nil?
1711
+ return
1712
+ elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1713
+ return
1714
+ end
1715
+ history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1716
+ h_pointer = nil
1717
+ line_no = nil
1718
+ if @is_multiline
1719
+ h_pointer = history.index { |h|
1720
+ h.split("\n").each_with_index { |l, i|
1721
+ if l.start_with?(substr)
1722
+ line_no = i
1723
+ break
1724
+ end
1725
+ }
1726
+ not line_no.nil?
1727
+ }
1728
+ else
1729
+ h_pointer = history.index { |l|
1730
+ l.start_with?(substr)
1731
+ }
1732
+ end
1733
+ h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1734
+ return if h_pointer.nil? and not substr.empty?
1735
+ @history_pointer = h_pointer
1736
+ if @is_multiline
1737
+ if @history_pointer.nil? and substr.empty?
1738
+ @buffer_of_lines = []
1739
+ @line_index = 0
1740
+ else
1741
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1742
+ @line_index = line_no
1743
+ end
1744
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1745
+ @line = @buffer_of_lines.last
1746
+ @rerender_all = true
1747
+ else
1748
+ if @history_pointer.nil? and substr.empty?
1749
+ @line = ''
1750
+ else
1751
+ @line = Reline::HISTORY[@history_pointer]
1752
+ end
1753
+ end
1754
+ @cursor_max = calculate_width(@line)
1755
+ arg -= 1
1756
+ ed_search_next_history(key, arg: arg) if arg > 0
1757
+ end
1758
+ alias_method :history_search_forward, :ed_search_next_history
1759
+
1760
+ private def ed_prev_history(key, arg: 1)
1761
+ if @is_multiline and @line_index > 0
1762
+ @previous_line_index = @line_index
1763
+ @line_index -= 1
1764
+ return
1765
+ end
1766
+ if Reline::HISTORY.empty?
1767
+ return
1768
+ end
1769
+ if @history_pointer.nil?
1770
+ @history_pointer = Reline::HISTORY.size - 1
1771
+ if @is_multiline
1772
+ @line_backup_in_history = whole_buffer
1773
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1774
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1775
+ @line_index = @buffer_of_lines.size - 1
1776
+ @line = @buffer_of_lines.last
1777
+ @rerender_all = true
1778
+ else
1779
+ @line_backup_in_history = @line
1780
+ @line = Reline::HISTORY[@history_pointer]
1781
+ end
1782
+ elsif @history_pointer.zero?
1783
+ return
1784
+ else
1785
+ if @is_multiline
1786
+ Reline::HISTORY[@history_pointer] = whole_buffer
1787
+ @history_pointer -= 1
1788
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1789
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1790
+ @line_index = @buffer_of_lines.size - 1
1791
+ @line = @buffer_of_lines.last
1792
+ @rerender_all = true
1793
+ else
1794
+ Reline::HISTORY[@history_pointer] = @line
1795
+ @history_pointer -= 1
1796
+ @line = Reline::HISTORY[@history_pointer]
1797
+ end
1798
+ end
1799
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1800
+ @cursor_max = @cursor = calculate_width(@line)
1801
+ @byte_pointer = @line.bytesize
1802
+ elsif @config.editing_mode_is?(:vi_command)
1803
+ @byte_pointer = @cursor = 0
1804
+ @cursor_max = calculate_width(@line)
1805
+ end
1806
+ arg -= 1
1807
+ ed_prev_history(key, arg: arg) if arg > 0
1808
+ end
1809
+
1810
+ private def ed_next_history(key, arg: 1)
1811
+ if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1812
+ @previous_line_index = @line_index
1813
+ @line_index += 1
1814
+ return
1815
+ end
1816
+ if @history_pointer.nil?
1817
+ return
1818
+ elsif @history_pointer == (Reline::HISTORY.size - 1)
1819
+ if @is_multiline
1820
+ @history_pointer = nil
1821
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1822
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1823
+ @line_index = 0
1824
+ @line = @buffer_of_lines.first
1825
+ @rerender_all = true
1826
+ else
1827
+ @history_pointer = nil
1828
+ @line = @line_backup_in_history
1829
+ end
1830
+ else
1831
+ if @is_multiline
1832
+ Reline::HISTORY[@history_pointer] = whole_buffer
1833
+ @history_pointer += 1
1834
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1835
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1836
+ @line_index = 0
1837
+ @line = @buffer_of_lines.first
1838
+ @rerender_all = true
1839
+ else
1840
+ Reline::HISTORY[@history_pointer] = @line
1841
+ @history_pointer += 1
1842
+ @line = Reline::HISTORY[@history_pointer]
1843
+ end
1844
+ end
1845
+ @line = '' unless @line
1846
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1847
+ @cursor_max = @cursor = calculate_width(@line)
1848
+ @byte_pointer = @line.bytesize
1849
+ elsif @config.editing_mode_is?(:vi_command)
1850
+ @byte_pointer = @cursor = 0
1851
+ @cursor_max = calculate_width(@line)
1852
+ end
1853
+ arg -= 1
1854
+ ed_next_history(key, arg: arg) if arg > 0
1855
+ end
1856
+
1857
+ private def ed_newline(key)
1858
+ process_insert(force: true)
1859
+ if @is_multiline
1860
+ if @config.editing_mode_is?(:vi_command)
1861
+ if @line_index < (@buffer_of_lines.size - 1)
1862
+ ed_next_history(key) # means cursor down
1863
+ else
1864
+ # should check confirm_multiline_termination to finish?
1865
+ finish
1866
+ end
1867
+ else
1868
+ if @line_index == (@buffer_of_lines.size - 1)
1869
+ if confirm_multiline_termination
1870
+ finish
1871
+ else
1872
+ key_newline(key)
1873
+ end
1874
+ else
1875
+ # should check confirm_multiline_termination to finish?
1876
+ @previous_line_index = @line_index
1877
+ @line_index = @buffer_of_lines.size - 1
1878
+ finish
1879
+ end
1880
+ end
1881
+ else
1882
+ if @history_pointer
1883
+ Reline::HISTORY[@history_pointer] = @line
1884
+ @history_pointer = nil
1885
+ end
1886
+ finish
1887
+ end
1888
+ end
1889
+
1890
+ private def em_delete_prev_char(key)
1891
+ if @is_multiline and @cursor == 0 and @line_index > 0
1892
+ @buffer_of_lines[@line_index] = @line
1893
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
1894
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1895
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1896
+ @line_index -= 1
1897
+ @line = @buffer_of_lines[@line_index]
1898
+ @cursor_max = calculate_width(@line)
1899
+ @rerender_all = true
1900
+ elsif @cursor > 0
1901
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1902
+ @byte_pointer -= byte_size
1903
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1904
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1905
+ @cursor -= width
1906
+ @cursor_max -= width
1907
+ end
1908
+ end
1909
+ alias_method :backward_delete_char, :em_delete_prev_char
1910
+
1911
+ private def ed_kill_line(key)
1912
+ if @line.bytesize > @byte_pointer
1913
+ @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
1914
+ @byte_pointer = @line.bytesize
1915
+ @cursor = @cursor_max = calculate_width(@line)
1916
+ @kill_ring.append(deleted)
1917
+ elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
1918
+ @cursor = calculate_width(@line)
1919
+ @byte_pointer = @line.bytesize
1920
+ @line += @buffer_of_lines.delete_at(@line_index + 1)
1921
+ @cursor_max = calculate_width(@line)
1922
+ @buffer_of_lines[@line_index] = @line
1923
+ @rerender_all = true
1924
+ @rest_height += 1
1925
+ end
1926
+ end
1927
+
1928
+ private def em_kill_line(key)
1929
+ if @byte_pointer > 0
1930
+ @line, deleted = byteslice!(@line, 0, @byte_pointer)
1931
+ @byte_pointer = 0
1932
+ @kill_ring.append(deleted, true)
1933
+ @cursor_max = calculate_width(@line)
1934
+ @cursor = 0
1935
+ end
1936
+ end
1937
+
1938
+ private def em_delete(key)
1939
+ if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
1940
+ @line = nil
1941
+ if @buffer_of_lines.size > 1
1942
+ scroll_down(@highest_in_all - @first_line_started_from)
1943
+ end
1944
+ Reline::IOGate.move_cursor_column(0)
1945
+ @eof = true
1946
+ finish
1947
+ elsif @byte_pointer < @line.bytesize
1948
+ splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
1949
+ mbchar = splitted_last.grapheme_clusters.first
1950
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1951
+ @cursor_max -= width
1952
+ @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
1953
+ elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
1954
+ @cursor = calculate_width(@line)
1955
+ @byte_pointer = @line.bytesize
1956
+ @line += @buffer_of_lines.delete_at(@line_index + 1)
1957
+ @cursor_max = calculate_width(@line)
1958
+ @buffer_of_lines[@line_index] = @line
1959
+ @rerender_all = true
1960
+ @rest_height += 1
1961
+ end
1962
+ end
1963
+ alias_method :delete_char, :em_delete
1964
+
1965
+ private def em_delete_or_list(key)
1966
+ if @line.empty? or @byte_pointer < @line.bytesize
1967
+ em_delete(key)
1968
+ else # show completed list
1969
+ result = call_completion_proc
1970
+ if result.is_a?(Array)
1971
+ complete(result, true)
1972
+ end
1973
+ end
1974
+ end
1975
+ alias_method :delete_char_or_list, :em_delete_or_list
1976
+
1977
+ private def em_yank(key)
1978
+ yanked = @kill_ring.yank
1979
+ if yanked
1980
+ @line = byteinsert(@line, @byte_pointer, yanked)
1981
+ yanked_width = calculate_width(yanked)
1982
+ @cursor += yanked_width
1983
+ @cursor_max += yanked_width
1984
+ @byte_pointer += yanked.bytesize
1985
+ end
1986
+ end
1987
+
1988
+ private def em_yank_pop(key)
1989
+ yanked, prev_yank = @kill_ring.yank_pop
1990
+ if yanked
1991
+ prev_yank_width = calculate_width(prev_yank)
1992
+ @cursor -= prev_yank_width
1993
+ @cursor_max -= prev_yank_width
1994
+ @byte_pointer -= prev_yank.bytesize
1995
+ @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
1996
+ @line = byteinsert(@line, @byte_pointer, yanked)
1997
+ yanked_width = calculate_width(yanked)
1998
+ @cursor += yanked_width
1999
+ @cursor_max += yanked_width
2000
+ @byte_pointer += yanked.bytesize
2001
+ end
2002
+ end
2003
+
2004
+ private def ed_clear_screen(key)
2005
+ @cleared = true
2006
+ end
2007
+ alias_method :clear_screen, :ed_clear_screen
2008
+
2009
+ private def em_next_word(key)
2010
+ if @line.bytesize > @byte_pointer
2011
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2012
+ @byte_pointer += byte_size
2013
+ @cursor += width
2014
+ end
2015
+ end
2016
+ alias_method :forward_word, :em_next_word
2017
+
2018
+ private def ed_prev_word(key)
2019
+ if @byte_pointer > 0
2020
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2021
+ @byte_pointer -= byte_size
2022
+ @cursor -= width
2023
+ end
2024
+ end
2025
+ alias_method :backward_word, :ed_prev_word
2026
+
2027
+ private def em_delete_next_word(key)
2028
+ if @line.bytesize > @byte_pointer
2029
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2030
+ @line, word = byteslice!(@line, @byte_pointer, byte_size)
2031
+ @kill_ring.append(word)
2032
+ @cursor_max -= width
2033
+ end
2034
+ end
2035
+
2036
+ private def ed_delete_prev_word(key)
2037
+ if @byte_pointer > 0
2038
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2039
+ @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2040
+ @kill_ring.append(word, true)
2041
+ @byte_pointer -= byte_size
2042
+ @cursor -= width
2043
+ @cursor_max -= width
2044
+ end
2045
+ end
2046
+
2047
+ private def ed_transpose_chars(key)
2048
+ if @byte_pointer > 0
2049
+ if @cursor_max > @cursor
2050
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2051
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
2052
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2053
+ @cursor += width
2054
+ @byte_pointer += byte_size
2055
+ end
2056
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2057
+ if (@byte_pointer - back1_byte_size) > 0
2058
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
2059
+ back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
2060
+ @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
2061
+ @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
2062
+ end
2063
+ end
2064
+ end
2065
+ alias_method :transpose_chars, :ed_transpose_chars
2066
+
2067
+ private def ed_transpose_words(key)
2068
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
2069
+ before = @line.byteslice(0, left_word_start)
2070
+ left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
2071
+ middle = @line.byteslice(middle_start, right_word_start - middle_start)
2072
+ right_word = @line.byteslice(right_word_start, after_start - right_word_start)
2073
+ after = @line.byteslice(after_start, @line.bytesize - after_start)
2074
+ return if left_word.empty? or right_word.empty?
2075
+ @line = before + right_word + middle + left_word + after
2076
+ from_head_to_left_word = before + right_word + middle + left_word
2077
+ @byte_pointer = from_head_to_left_word.bytesize
2078
+ @cursor = calculate_width(from_head_to_left_word)
2079
+ end
2080
+ alias_method :transpose_words, :ed_transpose_words
2081
+
2082
+ private def em_capitol_case(key)
2083
+ if @line.bytesize > @byte_pointer
2084
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
2085
+ before = @line.byteslice(0, @byte_pointer)
2086
+ after = @line.byteslice((@byte_pointer + byte_size)..-1)
2087
+ @line = before + new_str + after
2088
+ @byte_pointer += new_str.bytesize
2089
+ @cursor += calculate_width(new_str)
2090
+ end
2091
+ end
2092
+ alias_method :capitalize_word, :em_capitol_case
2093
+
2094
+ private def em_lower_case(key)
2095
+ if @line.bytesize > @byte_pointer
2096
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2097
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2098
+ mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2099
+ }.join
2100
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2101
+ @line = @line.byteslice(0, @byte_pointer) + part
2102
+ @byte_pointer = @line.bytesize
2103
+ @cursor = calculate_width(@line)
2104
+ @cursor_max = @cursor + calculate_width(rest)
2105
+ @line += rest
2106
+ end
2107
+ end
2108
+ alias_method :downcase_word, :em_lower_case
2109
+
2110
+ private def em_upper_case(key)
2111
+ if @line.bytesize > @byte_pointer
2112
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2113
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2114
+ mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2115
+ }.join
2116
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2117
+ @line = @line.byteslice(0, @byte_pointer) + part
2118
+ @byte_pointer = @line.bytesize
2119
+ @cursor = calculate_width(@line)
2120
+ @cursor_max = @cursor + calculate_width(rest)
2121
+ @line += rest
2122
+ end
2123
+ end
2124
+ alias_method :upcase_word, :em_upper_case
2125
+
2126
+ private def em_kill_region(key)
2127
+ if @byte_pointer > 0
2128
+ byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
2129
+ @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2130
+ @byte_pointer -= byte_size
2131
+ @cursor -= width
2132
+ @cursor_max -= width
2133
+ @kill_ring.append(deleted)
2134
+ end
2135
+ end
2136
+
2137
+ private def copy_for_vi(text)
2138
+ if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
2139
+ @vi_clipboard = text
2140
+ end
2141
+ end
2142
+
2143
+ private def vi_insert(key)
2144
+ @config.editing_mode = :vi_insert
2145
+ end
2146
+
2147
+ private def vi_add(key)
2148
+ @config.editing_mode = :vi_insert
2149
+ ed_next_char(key)
2150
+ end
2151
+
2152
+ private def vi_command_mode(key)
2153
+ ed_prev_char(key)
2154
+ @config.editing_mode = :vi_command
2155
+ end
2156
+ alias_method :vi_movement_mode, :vi_command_mode
2157
+
2158
+ private def vi_next_word(key, arg: 1)
2159
+ if @line.bytesize > @byte_pointer
2160
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
2161
+ @byte_pointer += byte_size
2162
+ @cursor += width
2163
+ end
2164
+ arg -= 1
2165
+ vi_next_word(key, arg: arg) if arg > 0
2166
+ end
2167
+
2168
+ private def vi_prev_word(key, arg: 1)
2169
+ if @byte_pointer > 0
2170
+ byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
2171
+ @byte_pointer -= byte_size
2172
+ @cursor -= width
2173
+ end
2174
+ arg -= 1
2175
+ vi_prev_word(key, arg: arg) if arg > 0
2176
+ end
2177
+
2178
+ private def vi_end_word(key, arg: 1, inclusive: false)
2179
+ if @line.bytesize > @byte_pointer
2180
+ byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
2181
+ @byte_pointer += byte_size
2182
+ @cursor += width
2183
+ end
2184
+ arg -= 1
2185
+ if inclusive and arg.zero?
2186
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2187
+ if byte_size > 0
2188
+ c = @line.byteslice(@byte_pointer, byte_size)
2189
+ width = Reline::Unicode.get_mbchar_width(c)
2190
+ @byte_pointer += byte_size
2191
+ @cursor += width
2192
+ end
2193
+ end
2194
+ vi_end_word(key, arg: arg) if arg > 0
2195
+ end
2196
+
2197
+ private def vi_next_big_word(key, arg: 1)
2198
+ if @line.bytesize > @byte_pointer
2199
+ byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
2200
+ @byte_pointer += byte_size
2201
+ @cursor += width
2202
+ end
2203
+ arg -= 1
2204
+ vi_next_big_word(key, arg: arg) if arg > 0
2205
+ end
2206
+
2207
+ private def vi_prev_big_word(key, arg: 1)
2208
+ if @byte_pointer > 0
2209
+ byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
2210
+ @byte_pointer -= byte_size
2211
+ @cursor -= width
2212
+ end
2213
+ arg -= 1
2214
+ vi_prev_big_word(key, arg: arg) if arg > 0
2215
+ end
2216
+
2217
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
2218
+ if @line.bytesize > @byte_pointer
2219
+ byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
2220
+ @byte_pointer += byte_size
2221
+ @cursor += width
2222
+ end
2223
+ arg -= 1
2224
+ if inclusive and arg.zero?
2225
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2226
+ if byte_size > 0
2227
+ c = @line.byteslice(@byte_pointer, byte_size)
2228
+ width = Reline::Unicode.get_mbchar_width(c)
2229
+ @byte_pointer += byte_size
2230
+ @cursor += width
2231
+ end
2232
+ end
2233
+ vi_end_big_word(key, arg: arg) if arg > 0
2234
+ end
2235
+
2236
+ private def vi_delete_prev_char(key)
2237
+ if @is_multiline and @cursor == 0 and @line_index > 0
2238
+ @buffer_of_lines[@line_index] = @line
2239
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2240
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2241
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2242
+ @line_index -= 1
2243
+ @line = @buffer_of_lines[@line_index]
2244
+ @cursor_max = calculate_width(@line)
2245
+ @rerender_all = true
2246
+ elsif @cursor > 0
2247
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2248
+ @byte_pointer -= byte_size
2249
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2250
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2251
+ @cursor -= width
2252
+ @cursor_max -= width
2253
+ end
2254
+ end
2255
+
2256
+ private def vi_insert_at_bol(key)
2257
+ ed_move_to_beg(key)
2258
+ @config.editing_mode = :vi_insert
2259
+ end
2260
+
2261
+ private def vi_add_at_eol(key)
2262
+ ed_move_to_end(key)
2263
+ @config.editing_mode = :vi_insert
2264
+ end
2265
+
2266
+ private def ed_delete_prev_char(key, arg: 1)
2267
+ deleted = ''
2268
+ arg.times do
2269
+ if @cursor > 0
2270
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2271
+ @byte_pointer -= byte_size
2272
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2273
+ deleted.prepend(mbchar)
2274
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2275
+ @cursor -= width
2276
+ @cursor_max -= width
2277
+ end
2278
+ end
2279
+ copy_for_vi(deleted)
2280
+ end
2281
+
2282
+ private def vi_zero(key)
2283
+ @byte_pointer = 0
2284
+ @cursor = 0
2285
+ end
2286
+
2287
+ private def vi_change_meta(key, arg: 1)
2288
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2289
+ if byte_pointer_diff > 0
2290
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2291
+ elsif byte_pointer_diff < 0
2292
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2293
+ end
2294
+ copy_for_vi(cut)
2295
+ @cursor += cursor_diff if cursor_diff < 0
2296
+ @cursor_max -= cursor_diff.abs
2297
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2298
+ @config.editing_mode = :vi_insert
2299
+ }
2300
+ @waiting_operator_vi_arg = arg
2301
+ end
2302
+
2303
+ private def vi_delete_meta(key, arg: 1)
2304
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2305
+ if byte_pointer_diff > 0
2306
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2307
+ elsif byte_pointer_diff < 0
2308
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2309
+ end
2310
+ copy_for_vi(cut)
2311
+ @cursor += cursor_diff if cursor_diff < 0
2312
+ @cursor_max -= cursor_diff.abs
2313
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2314
+ }
2315
+ @waiting_operator_vi_arg = arg
2316
+ end
2317
+
2318
+ private def vi_yank(key, arg: 1)
2319
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2320
+ if byte_pointer_diff > 0
2321
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2322
+ elsif byte_pointer_diff < 0
2323
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2324
+ end
2325
+ copy_for_vi(cut)
2326
+ }
2327
+ @waiting_operator_vi_arg = arg
2328
+ end
2329
+
2330
+ private def vi_list_or_eof(key)
2331
+ if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2332
+ @line = nil
2333
+ if @buffer_of_lines.size > 1
2334
+ scroll_down(@highest_in_all - @first_line_started_from)
2335
+ end
2336
+ Reline::IOGate.move_cursor_column(0)
2337
+ @eof = true
2338
+ finish
2339
+ else
2340
+ ed_newline(key)
2341
+ end
2342
+ end
2343
+ alias_method :vi_end_of_transmission, :vi_list_or_eof
2344
+ alias_method :vi_eof_maybe, :vi_list_or_eof
2345
+
2346
+ private def ed_delete_next_char(key, arg: 1)
2347
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2348
+ unless @line.empty? || byte_size == 0
2349
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2350
+ copy_for_vi(mbchar)
2351
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2352
+ @cursor_max -= width
2353
+ if @cursor > 0 and @cursor >= @cursor_max
2354
+ @byte_pointer -= byte_size
2355
+ @cursor -= width
2356
+ end
2357
+ end
2358
+ arg -= 1
2359
+ ed_delete_next_char(key, arg: arg) if arg > 0
2360
+ end
2361
+
2362
+ private def vi_to_history_line(key)
2363
+ if Reline::HISTORY.empty?
2364
+ return
2365
+ end
2366
+ if @history_pointer.nil?
2367
+ @history_pointer = 0
2368
+ @line_backup_in_history = @line
2369
+ @line = Reline::HISTORY[@history_pointer]
2370
+ @cursor_max = calculate_width(@line)
2371
+ @cursor = 0
2372
+ @byte_pointer = 0
2373
+ elsif @history_pointer.zero?
2374
+ return
2375
+ else
2376
+ Reline::HISTORY[@history_pointer] = @line
2377
+ @history_pointer = 0
2378
+ @line = Reline::HISTORY[@history_pointer]
2379
+ @cursor_max = calculate_width(@line)
2380
+ @cursor = 0
2381
+ @byte_pointer = 0
2382
+ end
2383
+ end
2384
+
2385
+ private def vi_histedit(key)
2386
+ path = Tempfile.open { |fp|
2387
+ fp.write @line
2388
+ fp.path
2389
+ }
2390
+ system("#{ENV['EDITOR']} #{path}")
2391
+ @line = File.read(path)
2392
+ finish
2393
+ end
2394
+
2395
+ private def vi_paste_prev(key, arg: 1)
2396
+ if @vi_clipboard.size > 0
2397
+ @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
2398
+ @cursor_max += calculate_width(@vi_clipboard)
2399
+ cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
2400
+ @cursor += calculate_width(cursor_point)
2401
+ @byte_pointer += cursor_point.bytesize
2402
+ end
2403
+ arg -= 1
2404
+ vi_paste_prev(key, arg: arg) if arg > 0
2405
+ end
2406
+
2407
+ private def vi_paste_next(key, arg: 1)
2408
+ if @vi_clipboard.size > 0
2409
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2410
+ @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
2411
+ @cursor_max += calculate_width(@vi_clipboard)
2412
+ @cursor += calculate_width(@vi_clipboard)
2413
+ @byte_pointer += @vi_clipboard.bytesize
2414
+ end
2415
+ arg -= 1
2416
+ vi_paste_next(key, arg: arg) if arg > 0
2417
+ end
2418
+
2419
+ private def ed_argument_digit(key)
2420
+ if @vi_arg.nil?
2421
+ unless key.chr.to_i.zero?
2422
+ @vi_arg = key.chr.to_i
2423
+ end
2424
+ else
2425
+ @vi_arg = @vi_arg * 10 + key.chr.to_i
2426
+ end
2427
+ end
2428
+
2429
+ private def vi_to_column(key, arg: 0)
2430
+ @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
2431
+ # total has [byte_size, cursor]
2432
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
2433
+ if (total.last + mbchar_width) >= arg
2434
+ break total
2435
+ elsif (total.last + mbchar_width) >= @cursor_max
2436
+ break total
2437
+ else
2438
+ total = [total.first + gc.bytesize, total.last + mbchar_width]
2439
+ total
2440
+ end
2441
+ }
2442
+ end
2443
+
2444
+ private def vi_replace_char(key, arg: 1)
2445
+ @waiting_proc = ->(k) {
2446
+ if arg == 1
2447
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2448
+ before = @line.byteslice(0, @byte_pointer)
2449
+ remaining_point = @byte_pointer + byte_size
2450
+ after = @line.byteslice(remaining_point, @line.size - remaining_point)
2451
+ @line = before + k.chr + after
2452
+ @cursor_max = calculate_width(@line)
2453
+ @waiting_proc = nil
2454
+ elsif arg > 1
2455
+ byte_size = 0
2456
+ arg.times do
2457
+ byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
2458
+ end
2459
+ before = @line.byteslice(0, @byte_pointer)
2460
+ remaining_point = @byte_pointer + byte_size
2461
+ after = @line.byteslice(remaining_point, @line.size - remaining_point)
2462
+ replaced = k.chr * arg
2463
+ @line = before + replaced + after
2464
+ @byte_pointer += replaced.bytesize
2465
+ @cursor += calculate_width(replaced)
2466
+ @cursor_max = calculate_width(@line)
2467
+ @waiting_proc = nil
2468
+ end
2469
+ }
2470
+ end
2471
+
2472
+ private def vi_next_char(key, arg: 1, inclusive: false)
2473
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2474
+ end
2475
+
2476
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
2477
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2478
+ end
2479
+
2480
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2481
+ if key.instance_of?(String)
2482
+ inputed_char = key
2483
+ else
2484
+ inputed_char = key.chr
2485
+ end
2486
+ prev_total = nil
2487
+ total = nil
2488
+ found = false
2489
+ @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
2490
+ # total has [byte_size, cursor]
2491
+ unless total
2492
+ # skip cursor point
2493
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2494
+ total = [mbchar.bytesize, width]
2495
+ else
2496
+ if inputed_char == mbchar
2497
+ arg -= 1
2498
+ if arg.zero?
2499
+ found = true
2500
+ break
2501
+ end
2502
+ end
2503
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2504
+ prev_total = total
2505
+ total = [total.first + mbchar.bytesize, total.last + width]
2506
+ end
2507
+ end
2508
+ if not need_prev_char and found and total
2509
+ byte_size, width = total
2510
+ @byte_pointer += byte_size
2511
+ @cursor += width
2512
+ elsif need_prev_char and found and prev_total
2513
+ byte_size, width = prev_total
2514
+ @byte_pointer += byte_size
2515
+ @cursor += width
2516
+ end
2517
+ if inclusive
2518
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2519
+ if byte_size > 0
2520
+ c = @line.byteslice(@byte_pointer, byte_size)
2521
+ width = Reline::Unicode.get_mbchar_width(c)
2522
+ @byte_pointer += byte_size
2523
+ @cursor += width
2524
+ end
2525
+ end
2526
+ @waiting_proc = nil
2527
+ end
2528
+
2529
+ private def vi_prev_char(key, arg: 1)
2530
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
2531
+ end
2532
+
2533
+ private def vi_to_prev_char(key, arg: 1)
2534
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
2535
+ end
2536
+
2537
+ private def search_prev_char(key, arg, need_next_char = false)
2538
+ if key.instance_of?(String)
2539
+ inputed_char = key
2540
+ else
2541
+ inputed_char = key.chr
2542
+ end
2543
+ prev_total = nil
2544
+ total = nil
2545
+ found = false
2546
+ @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2547
+ # total has [byte_size, cursor]
2548
+ unless total
2549
+ # skip cursor point
2550
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2551
+ total = [mbchar.bytesize, width]
2552
+ else
2553
+ if inputed_char == mbchar
2554
+ arg -= 1
2555
+ if arg.zero?
2556
+ found = true
2557
+ break
2558
+ end
2559
+ end
2560
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2561
+ prev_total = total
2562
+ total = [total.first + mbchar.bytesize, total.last + width]
2563
+ end
2564
+ end
2565
+ if not need_next_char and found and total
2566
+ byte_size, width = total
2567
+ @byte_pointer -= byte_size
2568
+ @cursor -= width
2569
+ elsif need_next_char and found and prev_total
2570
+ byte_size, width = prev_total
2571
+ @byte_pointer -= byte_size
2572
+ @cursor -= width
2573
+ end
2574
+ @waiting_proc = nil
2575
+ end
2576
+
2577
+ private def vi_join_lines(key, arg: 1)
2578
+ if @is_multiline and @buffer_of_lines.size > @line_index + 1
2579
+ @cursor = calculate_width(@line)
2580
+ @byte_pointer = @line.bytesize
2581
+ @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
2582
+ @cursor_max = calculate_width(@line)
2583
+ @buffer_of_lines[@line_index] = @line
2584
+ @rerender_all = true
2585
+ @rest_height += 1
2586
+ end
2587
+ arg -= 1
2588
+ vi_join_lines(key, arg: arg) if arg > 0
2589
+ end
2590
+
2591
+ private def em_set_mark(key)
2592
+ @mark_pointer = [@byte_pointer, @line_index]
2593
+ end
2594
+ alias_method :set_mark, :em_set_mark
2595
+
2596
+ private def em_exchange_mark(key)
2597
+ return unless @mark_pointer
2598
+ new_pointer = [@byte_pointer, @line_index]
2599
+ @previous_line_index = @line_index
2600
+ @byte_pointer, @line_index = @mark_pointer
2601
+ @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
2602
+ @cursor_max = calculate_width(@line)
2603
+ @mark_pointer = new_pointer
2604
+ end
2605
+ alias_method :exchange_point_and_mark, :em_exchange_mark
2606
+ end