reline 0.2.8.pre.6 → 0.2.8.pre.7

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