reline 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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