reline 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,53 @@
1
+ class Reline::KeyStroke
2
+ using Module.new {
3
+ refine Array do
4
+ def start_with?(other)
5
+ other.size <= size && other == self.take(other.size)
6
+ end
7
+
8
+ def bytes
9
+ self
10
+ end
11
+ end
12
+ }
13
+
14
+ def initialize(config)
15
+ @config = config
16
+ end
17
+
18
+ def match_status(input)
19
+ key_mapping.keys.select { |lhs|
20
+ lhs.start_with? input
21
+ }.tap { |it|
22
+ return :matched if it.size == 1 && (it.max_by(&:size)&.size&.== input.size)
23
+ return :matching if it.size == 1 && (it.max_by(&:size)&.size&.!= input.size)
24
+ return :matched if it.max_by(&:size)&.size&.< input.size
25
+ return :matching if it.size > 1
26
+ }
27
+ key_mapping.keys.select { |lhs|
28
+ input.start_with? lhs
29
+ }.tap { |it|
30
+ return it.size > 0 ? :matched : :unmatched
31
+ }
32
+ end
33
+
34
+ def expand(input)
35
+ lhs = key_mapping.keys.select { |lhs| input.start_with? lhs }.sort_by(&:size).reverse.first
36
+ return input unless lhs
37
+ rhs = key_mapping[lhs]
38
+
39
+ case rhs
40
+ when String
41
+ rhs_bytes = rhs.bytes
42
+ expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
43
+ when Symbol
44
+ [rhs] + expand(input.drop(lhs.size))
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def key_mapping
51
+ @config.key_bindings
52
+ end
53
+ end
@@ -0,0 +1,113 @@
1
+ class Reline::KillRing
2
+ module State
3
+ FRESH = :fresh
4
+ CONTINUED = :continued
5
+ PROCESSED = :processed
6
+ YANK = :yank
7
+ end
8
+
9
+ RingPoint = Struct.new(:backward, :forward, :str) do
10
+ def initialize(str)
11
+ super(nil, nil, str)
12
+ end
13
+
14
+ def ==(other)
15
+ object_id == other.object_id
16
+ end
17
+ end
18
+
19
+ class RingBuffer
20
+ attr_reader :size
21
+ attr_reader :head
22
+
23
+ def initialize(max = 1024)
24
+ @max = max
25
+ @size = 0
26
+ @head = nil # reading head of ring-shaped tape
27
+ end
28
+
29
+ def <<(point)
30
+ if @size.zero?
31
+ @head = point
32
+ @head.backward = @head
33
+ @head.forward = @head
34
+ @size = 1
35
+ elsif @size >= @max
36
+ tail = @head.forward
37
+ new_tail = tail.forward
38
+ @head.forward = point
39
+ point.backward = @head
40
+ new_tail.backward = point
41
+ point.forward = new_tail
42
+ @head = point
43
+ else
44
+ tail = @head.forward
45
+ @head.forward = point
46
+ point.backward = @head
47
+ tail.backward = point
48
+ point.forward = tail
49
+ @head = point
50
+ @size += 1
51
+ end
52
+ end
53
+
54
+ def empty?
55
+ @size.zero?
56
+ end
57
+ end
58
+
59
+ def initialize(max = 1024)
60
+ @ring = RingBuffer.new(max)
61
+ @ring_pointer = nil
62
+ @buffer = nil
63
+ @state = State::FRESH
64
+ end
65
+
66
+ def append(string, before_p = false)
67
+ case @state
68
+ when State::FRESH, State::YANK
69
+ @ring << RingPoint.new(string)
70
+ @state = State::CONTINUED
71
+ when State::CONTINUED, State::PROCESSED
72
+ if before_p
73
+ @ring.head.str.prepend(string)
74
+ else
75
+ @ring.head.str.concat(string)
76
+ end
77
+ @state = State::CONTINUED
78
+ end
79
+ end
80
+
81
+ def process
82
+ case @state
83
+ when State::FRESH
84
+ # nothing to do
85
+ when State::CONTINUED
86
+ @state = State::PROCESSED
87
+ when State::PROCESSED
88
+ @state = State::FRESH
89
+ when State::YANK
90
+ # nothing to do
91
+ end
92
+ end
93
+
94
+ def yank
95
+ unless @ring.empty?
96
+ @state = State::YANK
97
+ @ring_pointer = @ring.head
98
+ @ring_pointer.str
99
+ else
100
+ nil
101
+ end
102
+ end
103
+
104
+ def yank_pop
105
+ if @state == State::YANK
106
+ prev_yank = @ring_pointer.str
107
+ @ring_pointer = @ring_pointer.backward
108
+ [@ring_pointer.str, prev_yank]
109
+ else
110
+ nil
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,1942 @@
1
+ require 'reline/kill_ring'
2
+ require 'reline/unicode'
3
+
4
+ require 'tempfile'
5
+ require 'pathname'
6
+
7
+ class Reline::LineEditor
8
+ # TODO: undo
9
+ attr_reader :line
10
+ attr_reader :byte_pointer
11
+ attr_accessor :confirm_multiline_termination_proc
12
+ attr_accessor :completion_proc
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
+ PERFECT_MATCH = :perfect_match
47
+ end
48
+
49
+ CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
50
+ MenuInfo = Struct.new('MenuInfo', :target, :list)
51
+
52
+ CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
53
+ OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
54
+ NON_PRINTING_START = "\1"
55
+ NON_PRINTING_END = "\2"
56
+ WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
57
+
58
+ def initialize(config)
59
+ @config = config
60
+ reset_variables
61
+ end
62
+
63
+ private def check_multiline_prompt(buffer, prompt)
64
+ if @vi_arg
65
+ prompt = "(arg: #{@vi_arg}) "
66
+ @rerender_all = true
67
+ elsif @searching_prompt
68
+ prompt = @searching_prompt
69
+ @rerender_all = true
70
+ else
71
+ prompt = @prompt
72
+ end
73
+ if @prompt_proc
74
+ prompt_list = @prompt_proc.(buffer)
75
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
76
+ prompt = prompt_list[@line_index]
77
+ prompt_width = calculate_width(prompt, true)
78
+ [prompt, prompt_width, prompt_list]
79
+ else
80
+ prompt_width = calculate_width(prompt, true)
81
+ [prompt, prompt_width, nil]
82
+ end
83
+ end
84
+
85
+ def reset(prompt = '', encoding = Encoding.default_external)
86
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
87
+ @screen_size = Reline::IOGate.get_screen_size
88
+ reset_variables(prompt, encoding)
89
+ @old_trap = Signal.trap('SIGINT') {
90
+ @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
91
+ raise Interrupt
92
+ }
93
+ Reline::IOGate.set_winch_handler do
94
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
95
+ old_screen_size = @screen_size
96
+ @screen_size = Reline::IOGate.get_screen_size
97
+ if old_screen_size.last < @screen_size.last # columns increase
98
+ @rerender_all = true
99
+ rerender
100
+ else
101
+ back = 0
102
+ new_buffer = whole_lines
103
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
104
+ new_buffer.each_with_index do |line, index|
105
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
106
+ width = prompt_width + calculate_width(line)
107
+ height = calculate_height_by_width(width)
108
+ back += height
109
+ end
110
+ @highest_in_all = back
111
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
112
+ @first_line_started_from =
113
+ if @line_index.zero?
114
+ 0
115
+ else
116
+ @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
117
+ result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
118
+ }
119
+ end
120
+ if @prompt_proc
121
+ prompt = prompt_list[@line_index]
122
+ prompt_width = calculate_width(prompt, true)
123
+ end
124
+ calculate_nearest_cursor
125
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
126
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
127
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
128
+ @rerender_all = true
129
+ end
130
+ end
131
+ end
132
+
133
+ def finalize
134
+ Signal.trap('SIGINT', @old_trap)
135
+ end
136
+
137
+ def eof?
138
+ @eof
139
+ end
140
+
141
+ def reset_variables(prompt = '', encoding = Encoding.default_external)
142
+ @prompt = prompt
143
+ @encoding = encoding
144
+ @is_multiline = false
145
+ @finished = false
146
+ @cleared = false
147
+ @rerender_all = false
148
+ @history_pointer = nil
149
+ @kill_ring = Reline::KillRing.new
150
+ @vi_clipboard = ''
151
+ @vi_arg = nil
152
+ @waiting_proc = nil
153
+ @waiting_operator_proc = nil
154
+ @completion_journey_data = nil
155
+ @completion_state = CompletionState::NORMAL
156
+ @perfect_matched = nil
157
+ @menu_info = nil
158
+ @first_prompt = true
159
+ @searching_prompt = nil
160
+ @first_char = true
161
+ @eof = false
162
+ reset_line
163
+ end
164
+
165
+ def reset_line
166
+ @cursor = 0
167
+ @cursor_max = 0
168
+ @byte_pointer = 0
169
+ @buffer_of_lines = [String.new(encoding: @encoding)]
170
+ @line_index = 0
171
+ @previous_line_index = nil
172
+ @line = @buffer_of_lines[0]
173
+ @first_line_started_from = 0
174
+ @move_up = 0
175
+ @started_from = 0
176
+ @highest_in_this = 1
177
+ @highest_in_all = 1
178
+ @line_backup_in_history = nil
179
+ @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
180
+ @check_new_auto_indent = false
181
+ end
182
+
183
+ def multiline_on
184
+ @is_multiline = true
185
+ end
186
+
187
+ def multiline_off
188
+ @is_multiline = false
189
+ end
190
+
191
+ private def insert_new_line(cursor_line, next_line)
192
+ @line = cursor_line
193
+ @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
194
+ @previous_line_index = @line_index
195
+ @line_index += 1
196
+ end
197
+
198
+ private def calculate_height_by_width(width)
199
+ width.div(@screen_size.last) + 1
200
+ end
201
+
202
+ private def split_by_width(prompt, str, max_width)
203
+ lines = [String.new(encoding: @encoding)]
204
+ height = 1
205
+ width = 0
206
+ rest = "#{prompt}#{str}".encode(Encoding::UTF_8)
207
+ in_zero_width = false
208
+ rest.scan(WIDTH_SCANNER) do |gc|
209
+ case gc
210
+ when NON_PRINTING_START
211
+ in_zero_width = true
212
+ when NON_PRINTING_END
213
+ in_zero_width = false
214
+ when CSI_REGEXP, OSC_REGEXP
215
+ lines.last << gc
216
+ else
217
+ unless in_zero_width
218
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
219
+ if (width += mbchar_width) > max_width
220
+ width = mbchar_width
221
+ lines << nil
222
+ lines << String.new(encoding: @encoding)
223
+ height += 1
224
+ end
225
+ end
226
+ lines.last << gc
227
+ end
228
+ end
229
+ # The cursor moves to next line in first
230
+ if width == max_width
231
+ lines << nil
232
+ lines << String.new(encoding: @encoding)
233
+ height += 1
234
+ end
235
+ [lines, height]
236
+ end
237
+
238
+ private def scroll_down(val)
239
+ if val <= @rest_height
240
+ Reline::IOGate.move_cursor_down(val)
241
+ @rest_height -= val
242
+ else
243
+ Reline::IOGate.move_cursor_down(@rest_height)
244
+ Reline::IOGate.scroll_down(val - @rest_height)
245
+ @rest_height = 0
246
+ end
247
+ end
248
+
249
+ private def move_cursor_up(val)
250
+ if val > 0
251
+ Reline::IOGate.move_cursor_up(val)
252
+ @rest_height += val
253
+ elsif val < 0
254
+ move_cursor_down(-val)
255
+ end
256
+ end
257
+
258
+ private def move_cursor_down(val)
259
+ if val > 0
260
+ Reline::IOGate.move_cursor_down(val)
261
+ @rest_height -= val
262
+ @rest_height = 0 if @rest_height < 0
263
+ elsif val < 0
264
+ move_cursor_up(-val)
265
+ end
266
+ end
267
+
268
+ private def calculate_nearest_cursor
269
+ @cursor_max = calculate_width(line)
270
+ new_cursor = 0
271
+ new_byte_pointer = 0
272
+ height = 1
273
+ max_width = @screen_size.last
274
+ if @config.editing_mode_is?(:vi_command)
275
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
276
+ if last_byte_size > 0
277
+ last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
278
+ last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
279
+ cursor_max = @cursor_max - last_width
280
+ else
281
+ cursor_max = @cursor_max
282
+ end
283
+ else
284
+ cursor_max = @cursor_max
285
+ end
286
+ @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
287
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
288
+ now = new_cursor + mbchar_width
289
+ if now > cursor_max or now > @cursor
290
+ break
291
+ end
292
+ new_cursor += mbchar_width
293
+ if new_cursor > max_width * height
294
+ height += 1
295
+ end
296
+ new_byte_pointer += gc.bytesize
297
+ end
298
+ @started_from = height - 1
299
+ @cursor = new_cursor
300
+ @byte_pointer = new_byte_pointer
301
+ end
302
+
303
+ def rerender
304
+ return if @line.nil?
305
+ if @menu_info
306
+ scroll_down(@highest_in_all - @first_line_started_from)
307
+ @rerender_all = true
308
+ @menu_info.list.each do |item|
309
+ Reline::IOGate.move_cursor_column(0)
310
+ @output.print item
311
+ @output.flush
312
+ scroll_down(1)
313
+ end
314
+ scroll_down(@highest_in_all - 1)
315
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
316
+ @menu_info = nil
317
+ end
318
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
319
+ if @cleared
320
+ Reline::IOGate.clear_screen
321
+ @cleared = false
322
+ back = 0
323
+ modify_lines(whole_lines).each_with_index do |line, index|
324
+ if @prompt_proc
325
+ pr = prompt_list[index]
326
+ height = render_partial(pr, calculate_width(pr), line, false)
327
+ else
328
+ height = render_partial(prompt, prompt_width, line, false)
329
+ end
330
+ if index < (@buffer_of_lines.size - 1)
331
+ move_cursor_down(height)
332
+ back += height
333
+ end
334
+ end
335
+ move_cursor_up(back)
336
+ move_cursor_down(@first_line_started_from + @started_from)
337
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
338
+ return
339
+ end
340
+ new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
341
+ # FIXME: end of logical line sometimes breaks
342
+ if @previous_line_index or new_highest_in_this != @highest_in_this
343
+ if @previous_line_index
344
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
345
+ else
346
+ new_lines = whole_lines
347
+ end
348
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
349
+ all_height = new_lines.inject(0) { |result, line|
350
+ result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
351
+ }
352
+ diff = all_height - @highest_in_all
353
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
354
+ if diff > 0
355
+ scroll_down(diff)
356
+ move_cursor_up(all_height - 1)
357
+ elsif diff < 0
358
+ (-diff).times do
359
+ Reline::IOGate.move_cursor_column(0)
360
+ Reline::IOGate.erase_after_cursor
361
+ move_cursor_up(1)
362
+ end
363
+ move_cursor_up(all_height - 1)
364
+ else
365
+ move_cursor_up(all_height - 1)
366
+ end
367
+ @highest_in_all = all_height
368
+ back = 0
369
+ modify_lines(new_lines).each_with_index do |line, index|
370
+ if @prompt_proc
371
+ prompt = prompt_list[index]
372
+ prompt_width = calculate_width(prompt, true)
373
+ end
374
+ height = render_partial(prompt, prompt_width, line, false)
375
+ if index < (new_lines.size - 1)
376
+ scroll_down(1)
377
+ back += height
378
+ else
379
+ back += height - 1
380
+ end
381
+ end
382
+ move_cursor_up(back)
383
+ if @previous_line_index
384
+ @buffer_of_lines[@previous_line_index] = @line
385
+ @line = @buffer_of_lines[@line_index]
386
+ end
387
+ @first_line_started_from =
388
+ if @line_index.zero?
389
+ 0
390
+ else
391
+ @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
392
+ result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
393
+ }
394
+ end
395
+ if @prompt_proc
396
+ prompt = prompt_list[@line_index]
397
+ prompt_width = calculate_width(prompt, true)
398
+ end
399
+ move_cursor_down(@first_line_started_from)
400
+ calculate_nearest_cursor
401
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
402
+ move_cursor_down(@started_from)
403
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
404
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
405
+ @previous_line_index = nil
406
+ rendered = true
407
+ elsif @rerender_all
408
+ move_cursor_up(@first_line_started_from + @started_from)
409
+ Reline::IOGate.move_cursor_column(0)
410
+ back = 0
411
+ new_buffer = whole_lines
412
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
413
+ new_buffer.each_with_index do |line, index|
414
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
415
+ width = prompt_width + calculate_width(line)
416
+ height = calculate_height_by_width(width)
417
+ back += height
418
+ end
419
+ if back > @highest_in_all
420
+ scroll_down(back - 1)
421
+ move_cursor_up(back - 1)
422
+ elsif back < @highest_in_all
423
+ scroll_down(back)
424
+ Reline::IOGate.erase_after_cursor
425
+ (@highest_in_all - back - 1).times do
426
+ scroll_down(1)
427
+ Reline::IOGate.erase_after_cursor
428
+ end
429
+ move_cursor_up(@highest_in_all - 1)
430
+ end
431
+ modify_lines(new_buffer).each_with_index do |line, index|
432
+ if @prompt_proc
433
+ prompt = prompt_list[index]
434
+ prompt_width = calculate_width(prompt, true)
435
+ end
436
+ render_partial(prompt, prompt_width, line, false)
437
+ if index < (new_buffer.size - 1)
438
+ move_cursor_down(1)
439
+ end
440
+ end
441
+ move_cursor_up(back - 1)
442
+ if @prompt_proc
443
+ prompt = prompt_list[@line_index]
444
+ prompt_width = calculate_width(prompt, true)
445
+ end
446
+ @highest_in_all = back
447
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
448
+ @first_line_started_from =
449
+ if @line_index.zero?
450
+ 0
451
+ else
452
+ new_buffer[0..(@line_index - 1)].inject(0) { |result, line|
453
+ result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
454
+ }
455
+ end
456
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
457
+ move_cursor_down(@first_line_started_from + @started_from)
458
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
459
+ @rerender_all = false
460
+ rendered = true
461
+ end
462
+ line = modify_lines(whole_lines)[@line_index]
463
+ if @is_multiline
464
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
465
+ if finished?
466
+ # Always rerender on finish because output_modifier_proc may return a different output.
467
+ render_partial(prompt, prompt_width, line)
468
+ scroll_down(1)
469
+ Reline::IOGate.move_cursor_column(0)
470
+ Reline::IOGate.erase_after_cursor
471
+ elsif not rendered
472
+ render_partial(prompt, prompt_width, line)
473
+ end
474
+ else
475
+ render_partial(prompt, prompt_width, line)
476
+ if finished?
477
+ scroll_down(1)
478
+ Reline::IOGate.move_cursor_column(0)
479
+ Reline::IOGate.erase_after_cursor
480
+ end
481
+ end
482
+ end
483
+
484
+ private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
485
+ visual_lines, height = split_by_width(prompt, line_to_render.nil? ? '' : line_to_render, @screen_size.last)
486
+ if with_control
487
+ if height > @highest_in_this
488
+ diff = height - @highest_in_this
489
+ scroll_down(diff)
490
+ @highest_in_all += diff
491
+ @highest_in_this = height
492
+ move_cursor_up(diff)
493
+ elsif height < @highest_in_this
494
+ diff = @highest_in_this - height
495
+ @highest_in_all -= diff
496
+ @highest_in_this = height
497
+ end
498
+ move_cursor_up(@started_from)
499
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
500
+ end
501
+ Reline::IOGate.move_cursor_column(0)
502
+ visual_lines.each_with_index do |line, index|
503
+ if line.nil?
504
+ Reline::IOGate.erase_after_cursor
505
+ move_cursor_down(1)
506
+ Reline::IOGate.move_cursor_column(0)
507
+ next
508
+ end
509
+ @output.print line
510
+ @output.flush
511
+ if @first_prompt
512
+ @first_prompt = false
513
+ @pre_input_hook&.call
514
+ end
515
+ end
516
+ Reline::IOGate.erase_after_cursor
517
+ if with_control
518
+ move_cursor_up(height - 1)
519
+ if finished?
520
+ move_cursor_down(@started_from)
521
+ end
522
+ move_cursor_down(@started_from)
523
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
524
+ end
525
+ height
526
+ end
527
+
528
+ private def modify_lines(before)
529
+ return before if before.nil? || before.empty?
530
+
531
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
532
+ after.lines(chomp: true)
533
+ else
534
+ before
535
+ end
536
+ end
537
+
538
+ def editing_mode
539
+ @config.editing_mode
540
+ end
541
+
542
+ private def menu(target, list)
543
+ @menu_info = MenuInfo.new(target, list)
544
+ end
545
+
546
+ private def complete_internal_proc(list, is_menu)
547
+ preposing, target, postposing = retrieve_completion_block
548
+ list = list.select { |i|
549
+ if i and i.encoding != Encoding::US_ASCII and i.encoding != @encoding
550
+ raise Encoding::CompatibilityError
551
+ end
552
+ i&.start_with?(target)
553
+ }
554
+ if is_menu
555
+ menu(target, list)
556
+ return nil
557
+ end
558
+ completed = list.inject { |memo, item|
559
+ begin
560
+ memo_mbchars = memo.unicode_normalize.grapheme_clusters
561
+ item_mbchars = item.unicode_normalize.grapheme_clusters
562
+ rescue Encoding::CompatibilityError
563
+ memo_mbchars = memo.grapheme_clusters
564
+ item_mbchars = item.grapheme_clusters
565
+ end
566
+ size = [memo_mbchars.size, item_mbchars.size].min
567
+ result = ''
568
+ size.times do |i|
569
+ if memo_mbchars[i] == item_mbchars[i]
570
+ result << memo_mbchars[i]
571
+ else
572
+ break
573
+ end
574
+ end
575
+ result
576
+ }
577
+ [target, preposing, completed, postposing]
578
+ end
579
+
580
+ private def complete(list)
581
+ case @completion_state
582
+ when CompletionState::NORMAL, CompletionState::JOURNEY
583
+ @completion_state = CompletionState::COMPLETION
584
+ when CompletionState::PERFECT_MATCH
585
+ @dig_perfect_match_proc&.(@perfect_matched)
586
+ end
587
+ is_menu = (@completion_state == CompletionState::MENU)
588
+ result = complete_internal_proc(list, is_menu)
589
+ return if result.nil?
590
+ target, preposing, completed, postposing = result
591
+ return if completed.nil?
592
+ if target <= completed and (@completion_state == CompletionState::COMPLETION or @completion_state == CompletionState::PERFECT_MATCH)
593
+ @completion_state = CompletionState::MENU
594
+ if list.include?(completed)
595
+ @completion_state = CompletionState::PERFECT_MATCH
596
+ @perfect_matched = completed
597
+ end
598
+ if target < completed
599
+ @line = preposing + completed + postposing
600
+ line_to_pointer = preposing + completed
601
+ @cursor_max = calculate_width(@line)
602
+ @cursor = calculate_width(line_to_pointer)
603
+ @byte_pointer = line_to_pointer.bytesize
604
+ end
605
+ end
606
+ end
607
+
608
+ private def move_completed_list(list, direction)
609
+ case @completion_state
610
+ when CompletionState::NORMAL, CompletionState::COMPLETION, CompletionState::MENU
611
+ @completion_state = CompletionState::JOURNEY
612
+ result = retrieve_completion_block
613
+ return if result.nil?
614
+ preposing, target, postposing = result
615
+ @completion_journey_data = CompletionJourneyData.new(
616
+ preposing, postposing,
617
+ [target] + list.select{ |item| item.start_with?(target) }, 0)
618
+ @completion_state = CompletionState::JOURNEY
619
+ else
620
+ case direction
621
+ when :up
622
+ @completion_journey_data.pointer -= 1
623
+ if @completion_journey_data.pointer < 0
624
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
625
+ end
626
+ when :down
627
+ @completion_journey_data.pointer += 1
628
+ if @completion_journey_data.pointer >= @completion_journey_data.list.size
629
+ @completion_journey_data.pointer = 0
630
+ end
631
+ end
632
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
633
+ @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
634
+ line_to_pointer = @completion_journey_data.preposing + completed
635
+ @cursor_max = calculate_width(@line)
636
+ @cursor = calculate_width(line_to_pointer)
637
+ @byte_pointer = line_to_pointer.bytesize
638
+ end
639
+ end
640
+
641
+ private def run_for_operators(key, method_symbol, &block)
642
+ if @waiting_operator_proc
643
+ if VI_MOTIONS.include?(method_symbol)
644
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
645
+ block.()
646
+ unless @waiting_proc
647
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
648
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
649
+ @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
650
+ else
651
+ old_waiting_proc = @waiting_proc
652
+ old_waiting_operator_proc = @waiting_operator_proc
653
+ @waiting_proc = proc { |key|
654
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
655
+ old_waiting_proc.(key)
656
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
657
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
658
+ @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
659
+ @waiting_operator_proc = old_waiting_operator_proc
660
+ }
661
+ end
662
+ else
663
+ # Ignores operator when not motion is given.
664
+ block.()
665
+ end
666
+ @waiting_operator_proc = nil
667
+ else
668
+ block.()
669
+ end
670
+ end
671
+
672
+ private def argumentable?(method_obj)
673
+ method_obj and method_obj.parameters.length != 1
674
+ end
675
+
676
+ private def process_key(key, method_symbol)
677
+ if method_symbol and respond_to?(method_symbol, true)
678
+ method_obj = method(method_symbol)
679
+ else
680
+ method_obj = nil
681
+ end
682
+ if method_symbol and key.is_a?(Symbol)
683
+ if @vi_arg and argumentable?(method_obj)
684
+ run_for_operators(key, method_symbol) do
685
+ method_obj.(key, arg: @vi_arg)
686
+ end
687
+ else
688
+ method_obj&.(key)
689
+ end
690
+ @kill_ring.process
691
+ @vi_arg = nil
692
+ elsif @vi_arg
693
+ if key.chr =~ /[0-9]/
694
+ ed_argument_digit(key)
695
+ else
696
+ if argumentable?(method_obj)
697
+ run_for_operators(key, method_symbol) do
698
+ method_obj.(key, arg: @vi_arg)
699
+ end
700
+ elsif @waiting_proc
701
+ @waiting_proc.(key)
702
+ elsif method_obj
703
+ method_obj.(key)
704
+ else
705
+ ed_insert(key)
706
+ end
707
+ @kill_ring.process
708
+ @vi_arg = nil
709
+ end
710
+ elsif @waiting_proc
711
+ @waiting_proc.(key)
712
+ @kill_ring.process
713
+ elsif method_obj
714
+ if method_symbol == :ed_argument_digit
715
+ method_obj.(key)
716
+ else
717
+ run_for_operators(key, method_symbol) do
718
+ method_obj.(key)
719
+ end
720
+ end
721
+ @kill_ring.process
722
+ else
723
+ ed_insert(key)
724
+ end
725
+ end
726
+
727
+ private def normal_char(key)
728
+ method_symbol = method_obj = nil
729
+ if key.combined_char.is_a?(Symbol)
730
+ process_key(key.combined_char, key.combined_char)
731
+ return
732
+ end
733
+ @multibyte_buffer << key.combined_char
734
+ if @multibyte_buffer.size > 1
735
+ if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
736
+ process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
737
+ @multibyte_buffer.clear
738
+ else
739
+ # invalid
740
+ return
741
+ end
742
+ else # single byte
743
+ return if key.char >= 128 # maybe, first byte of multi byte
744
+ method_symbol = @config.editing_mode.get_method(key.combined_char)
745
+ if key.with_meta and method_symbol == :ed_unassigned
746
+ # split ESC + key
747
+ method_symbol = @config.editing_mode.get_method("\e".ord)
748
+ process_key("\e".ord, method_symbol)
749
+ method_symbol = @config.editing_mode.get_method(key.char)
750
+ process_key(key.char, method_symbol)
751
+ else
752
+ process_key(key.combined_char, method_symbol)
753
+ end
754
+ @multibyte_buffer.clear
755
+ end
756
+ if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
757
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
758
+ @byte_pointer -= byte_size
759
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
760
+ width = Reline::Unicode.get_mbchar_width(mbchar)
761
+ @cursor -= width
762
+ end
763
+ end
764
+
765
+ def input_key(key)
766
+ if key.nil? or key.char.nil?
767
+ if @first_char
768
+ @line = nil
769
+ end
770
+ finish
771
+ return
772
+ end
773
+ @first_char = false
774
+ completion_occurs = false
775
+ if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
776
+ result = retrieve_completion_block
777
+ slice = result[1]
778
+ result = @completion_proc.(slice) if @completion_proc and slice
779
+ if result.is_a?(Array)
780
+ completion_occurs = true
781
+ complete(result)
782
+ end
783
+ elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
784
+ result = retrieve_completion_block
785
+ slice = result[1]
786
+ result = @completion_proc.(slice) if @completion_proc and slice
787
+ if result.is_a?(Array)
788
+ completion_occurs = true
789
+ move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
790
+ end
791
+ elsif Symbol === key.char and respond_to?(key.char, true)
792
+ process_key(key.char, key.char)
793
+ else
794
+ normal_char(key)
795
+ end
796
+ unless completion_occurs
797
+ @completion_state = CompletionState::NORMAL
798
+ end
799
+ if @is_multiline and @auto_indent_proc
800
+ process_auto_indent
801
+ end
802
+ end
803
+
804
+ private def process_auto_indent
805
+ return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
806
+ if @previous_line_index
807
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
808
+ else
809
+ new_lines = whole_lines
810
+ end
811
+ new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
812
+ if new_indent&.>= 0
813
+ md = new_lines[@line_index].match(/\A */)
814
+ prev_indent = md[0].count(' ')
815
+ if @check_new_auto_indent
816
+ @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
817
+ @cursor = new_indent
818
+ @byte_pointer = new_indent
819
+ else
820
+ @line = ' ' * new_indent + @line.lstrip
821
+ @cursor += new_indent - prev_indent
822
+ @byte_pointer += new_indent - prev_indent
823
+ end
824
+ end
825
+ @check_new_auto_indent = false
826
+ end
827
+
828
+ def retrieve_completion_block
829
+ word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
830
+ quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
831
+ before = @line.byteslice(0, @byte_pointer)
832
+ rest = nil
833
+ break_pointer = nil
834
+ quote = nil
835
+ closing_quote = nil
836
+ escaped_quote = nil
837
+ i = 0
838
+ while i < @byte_pointer do
839
+ slice = @line.byteslice(i, @byte_pointer - i)
840
+ unless slice.valid_encoding?
841
+ i += 1
842
+ next
843
+ end
844
+ if quote and slice.start_with?(closing_quote)
845
+ quote = nil
846
+ i += 1
847
+ elsif quote and slice.start_with?(escaped_quote)
848
+ # skip
849
+ i += 2
850
+ elsif slice =~ quote_characters_regexp # find new "
851
+ quote = $&
852
+ closing_quote = /(?!\\)#{Regexp.escape(quote)}/
853
+ escaped_quote = /\\#{Regexp.escape(quote)}/
854
+ i += 1
855
+ elsif not quote and slice =~ word_break_regexp
856
+ rest = $'
857
+ i += 1
858
+ break_pointer = i
859
+ else
860
+ i += 1
861
+ end
862
+ end
863
+ if rest
864
+ preposing = @line.byteslice(0, break_pointer)
865
+ target = rest
866
+ else
867
+ preposing = ''
868
+ target = before
869
+ end
870
+ postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
871
+ [preposing, target, postposing]
872
+ end
873
+
874
+ def confirm_multiline_termination
875
+ temp_buffer = @buffer_of_lines.dup
876
+ if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
877
+ temp_buffer[@previous_line_index] = @line
878
+ else
879
+ temp_buffer[@line_index] = @line
880
+ end
881
+ @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
882
+ end
883
+
884
+ def insert_text(text)
885
+ width = calculate_width(text)
886
+ if @cursor == @cursor_max
887
+ @line += text
888
+ else
889
+ @line = byteinsert(@line, @byte_pointer, text)
890
+ end
891
+ @byte_pointer += text.bytesize
892
+ @cursor += width
893
+ @cursor_max += width
894
+ end
895
+
896
+ def delete_text(start = nil, length = nil)
897
+ if start.nil? and length.nil?
898
+ @line&.clear
899
+ @byte_pointer = 0
900
+ @cursor = 0
901
+ @cursor_max = 0
902
+ elsif not start.nil? and not length.nil?
903
+ if @line
904
+ before = @line.byteslice(0, start)
905
+ after = @line.byteslice(start + length, @line.bytesize)
906
+ @line = before + after
907
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
908
+ str = @line.byteslice(0, @byte_pointer)
909
+ @cursor = calculate_width(str)
910
+ @cursor_max = calculate_width(@line)
911
+ end
912
+ elsif start.is_a?(Range)
913
+ range = start
914
+ first = range.first
915
+ last = range.last
916
+ last = @line.bytesize - 1 if last > @line.bytesize
917
+ last += @line.bytesize if last < 0
918
+ first += @line.bytesize if first < 0
919
+ range = range.exclude_end? ? first...last : first..last
920
+ @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
921
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
922
+ str = @line.byteslice(0, @byte_pointer)
923
+ @cursor = calculate_width(str)
924
+ @cursor_max = calculate_width(@line)
925
+ else
926
+ @line = @line.byteslice(0, start)
927
+ @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
928
+ str = @line.byteslice(0, @byte_pointer)
929
+ @cursor = calculate_width(str)
930
+ @cursor_max = calculate_width(@line)
931
+ end
932
+ end
933
+
934
+ def byte_pointer=(val)
935
+ @byte_pointer = val
936
+ str = @line.byteslice(0, @byte_pointer)
937
+ @cursor = calculate_width(str)
938
+ @cursor_max = calculate_width(@line)
939
+ end
940
+
941
+ def whole_lines(index: @line_index, line: @line)
942
+ temp_lines = @buffer_of_lines.dup
943
+ temp_lines[index] = line
944
+ temp_lines
945
+ end
946
+
947
+ def whole_buffer
948
+ if @buffer_of_lines.size == 1 and @line.nil?
949
+ nil
950
+ else
951
+ whole_lines.join("\n")
952
+ end
953
+ end
954
+
955
+ def finished?
956
+ @finished
957
+ end
958
+
959
+ def finish
960
+ @finished = true
961
+ @config.reset
962
+ end
963
+
964
+ private def byteslice!(str, byte_pointer, size)
965
+ new_str = str.byteslice(0, byte_pointer)
966
+ new_str << str.byteslice(byte_pointer + size, str.bytesize)
967
+ [new_str, str.byteslice(byte_pointer, size)]
968
+ end
969
+
970
+ private def byteinsert(str, byte_pointer, other)
971
+ new_str = str.byteslice(0, byte_pointer)
972
+ new_str << other
973
+ new_str << str.byteslice(byte_pointer, str.bytesize)
974
+ new_str
975
+ end
976
+
977
+ private def calculate_width(str, allow_escape_code = false)
978
+ if allow_escape_code
979
+ width = 0
980
+ rest = str.encode(Encoding::UTF_8)
981
+ in_zero_width = false
982
+ rest.scan(WIDTH_SCANNER) do |gc|
983
+ case gc
984
+ when NON_PRINTING_START
985
+ in_zero_width = true
986
+ when NON_PRINTING_END
987
+ in_zero_width = false
988
+ when CSI_REGEXP, OSC_REGEXP
989
+ else
990
+ unless in_zero_width
991
+ width += Reline::Unicode.get_mbchar_width(gc)
992
+ end
993
+ end
994
+ end
995
+ width
996
+ else
997
+ str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
998
+ width + Reline::Unicode.get_mbchar_width(gc)
999
+ }
1000
+ end
1001
+ end
1002
+
1003
+ private def key_delete(key)
1004
+ if @config.editing_mode_is?(:vi_insert, :emacs)
1005
+ ed_delete_next_char(key)
1006
+ end
1007
+ end
1008
+
1009
+ private def key_newline(key)
1010
+ if @is_multiline
1011
+ next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1012
+ cursor_line = @line.byteslice(0, @byte_pointer)
1013
+ insert_new_line(cursor_line, next_line)
1014
+ @cursor = 0
1015
+ @check_new_auto_indent = true
1016
+ end
1017
+ end
1018
+
1019
+ private def ed_unassigned(key) end # do nothing
1020
+
1021
+ private def ed_insert(key)
1022
+ if key.instance_of?(String)
1023
+ width = Reline::Unicode.get_mbchar_width(key)
1024
+ if @cursor == @cursor_max
1025
+ @line += key
1026
+ else
1027
+ @line = byteinsert(@line, @byte_pointer, key)
1028
+ end
1029
+ @byte_pointer += key.bytesize
1030
+ @cursor += width
1031
+ @cursor_max += width
1032
+ else
1033
+ if @cursor == @cursor_max
1034
+ @line += key.chr
1035
+ else
1036
+ @line = byteinsert(@line, @byte_pointer, key.chr)
1037
+ end
1038
+ width = Reline::Unicode.get_mbchar_width(key.chr)
1039
+ @byte_pointer += 1
1040
+ @cursor += width
1041
+ @cursor_max += width
1042
+ end
1043
+ end
1044
+ alias_method :ed_digit, :ed_insert
1045
+ alias_method :self_insert, :ed_insert
1046
+
1047
+ private def ed_quoted_insert(str, arg: 1)
1048
+ @waiting_proc = proc { |key|
1049
+ arg.times do
1050
+ if key == "\C-j".ord or key == "\C-m".ord
1051
+ key_newline(key)
1052
+ else
1053
+ ed_insert(key)
1054
+ end
1055
+ end
1056
+ @waiting_proc = nil
1057
+ }
1058
+ end
1059
+ alias_method :quoted_insert, :ed_quoted_insert
1060
+
1061
+ private def ed_next_char(key, arg: 1)
1062
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1063
+ if (@byte_pointer < @line.bytesize)
1064
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1065
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1066
+ @cursor += width if width
1067
+ @byte_pointer += byte_size
1068
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
1069
+ next_line = @buffer_of_lines[@line_index + 1]
1070
+ @cursor = 0
1071
+ @byte_pointer = 0
1072
+ @cursor_max = calculate_width(next_line)
1073
+ @previous_line_index = @line_index
1074
+ @line_index += 1
1075
+ end
1076
+ arg -= 1
1077
+ ed_next_char(key, arg: arg) if arg > 0
1078
+ end
1079
+ alias_method :forward_char, :ed_next_char
1080
+
1081
+ private def ed_prev_char(key, arg: 1)
1082
+ if @cursor > 0
1083
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1084
+ @byte_pointer -= byte_size
1085
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1086
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1087
+ @cursor -= width
1088
+ elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1089
+ prev_line = @buffer_of_lines[@line_index - 1]
1090
+ @cursor = calculate_width(prev_line)
1091
+ @byte_pointer = prev_line.bytesize
1092
+ @cursor_max = calculate_width(prev_line)
1093
+ @previous_line_index = @line_index
1094
+ @line_index -= 1
1095
+ end
1096
+ arg -= 1
1097
+ ed_prev_char(key, arg: arg) if arg > 0
1098
+ end
1099
+
1100
+ private def vi_first_print(key)
1101
+ @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
1102
+ end
1103
+
1104
+ private def ed_move_to_beg(key)
1105
+ @byte_pointer = @cursor = 0
1106
+ end
1107
+ alias_method :beginning_of_line, :ed_move_to_beg
1108
+
1109
+ private def ed_move_to_end(key)
1110
+ @byte_pointer = 0
1111
+ @cursor = 0
1112
+ byte_size = 0
1113
+ while @byte_pointer < @line.bytesize
1114
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1115
+ if byte_size > 0
1116
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1117
+ @cursor += Reline::Unicode.get_mbchar_width(mbchar)
1118
+ end
1119
+ @byte_pointer += byte_size
1120
+ end
1121
+ end
1122
+ alias_method :end_of_line, :ed_move_to_end
1123
+
1124
+ private def ed_search_prev_history(key)
1125
+ if @is_multiline
1126
+ @line_backup_in_history = whole_buffer
1127
+ else
1128
+ @line_backup_in_history = @line
1129
+ end
1130
+ searcher = Fiber.new do
1131
+ search_word = String.new(encoding: @encoding)
1132
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1133
+ last_hit = nil
1134
+ loop do
1135
+ key = Fiber.yield(search_word)
1136
+ case key
1137
+ when "\C-h".ord, 127
1138
+ grapheme_clusters = search_word.grapheme_clusters
1139
+ if grapheme_clusters.size > 0
1140
+ grapheme_clusters.pop
1141
+ search_word = grapheme_clusters.join
1142
+ end
1143
+ else
1144
+ multibyte_buf << key
1145
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1146
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
1147
+ multibyte_buf.clear
1148
+ end
1149
+ end
1150
+ hit = nil
1151
+ if @line_backup_in_history.include?(search_word)
1152
+ @history_pointer = nil
1153
+ hit = @line_backup_in_history
1154
+ else
1155
+ hit_index = Reline::HISTORY.rindex { |item|
1156
+ item.include?(search_word)
1157
+ }
1158
+ if hit_index
1159
+ @history_pointer = hit_index
1160
+ hit = Reline::HISTORY[@history_pointer]
1161
+ end
1162
+ end
1163
+ if hit
1164
+ if @is_multiline
1165
+ @buffer_of_lines = hit.split("\n")
1166
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1167
+ @line_index = @buffer_of_lines.size - 1
1168
+ @line = @buffer_of_lines.last
1169
+ @rerender_all = true
1170
+ @searching_prompt = "(reverse-i-search)`%s'" % [search_word]
1171
+ else
1172
+ @line = hit
1173
+ @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
1174
+ end
1175
+ last_hit = hit
1176
+ else
1177
+ if @is_multiline
1178
+ @rerender_all = true
1179
+ @searching_prompt = "(failed reverse-i-search)`%s'" % [search_word]
1180
+ else
1181
+ @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
1182
+ end
1183
+ end
1184
+ end
1185
+ end
1186
+ searcher.resume
1187
+ @searching_prompt = "(reverse-i-search)`': "
1188
+ @waiting_proc = ->(key) {
1189
+ case key
1190
+ when "\C-j".ord, "\C-?".ord
1191
+ if @history_pointer
1192
+ @line = Reline::HISTORY[@history_pointer]
1193
+ else
1194
+ @line = @line_backup_in_history
1195
+ end
1196
+ @searching_prompt = nil
1197
+ @waiting_proc = nil
1198
+ @cursor_max = calculate_width(@line)
1199
+ @cursor = @byte_pointer = 0
1200
+ when "\C-g".ord
1201
+ @line = @line_backup_in_history
1202
+ @history_pointer = nil
1203
+ @searching_prompt = nil
1204
+ @waiting_proc = nil
1205
+ @line_backup_in_history = nil
1206
+ @cursor_max = calculate_width(@line)
1207
+ @cursor = @byte_pointer = 0
1208
+ else
1209
+ chr = key.is_a?(String) ? key : key.chr(Encoding::ASCII_8BIT)
1210
+ if chr.match?(/[[:print:]]/) or key == "\C-h".ord or key == 127
1211
+ searcher.resume(key)
1212
+ else
1213
+ if @history_pointer
1214
+ line = Reline::HISTORY[@history_pointer]
1215
+ else
1216
+ line = @line_backup_in_history
1217
+ end
1218
+ if @is_multiline
1219
+ @line_backup_in_history = whole_buffer
1220
+ @buffer_of_lines = line.split("\n")
1221
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1222
+ @line_index = @buffer_of_lines.size - 1
1223
+ @line = @buffer_of_lines.last
1224
+ @rerender_all = true
1225
+ else
1226
+ @line_backup_in_history = @line
1227
+ @line = line
1228
+ end
1229
+ @searching_prompt = nil
1230
+ @waiting_proc = nil
1231
+ @cursor_max = calculate_width(@line)
1232
+ @cursor = @byte_pointer = 0
1233
+ end
1234
+ end
1235
+ }
1236
+ end
1237
+
1238
+ private def ed_search_next_history(key)
1239
+ end
1240
+
1241
+ private def ed_prev_history(key, arg: 1)
1242
+ if @is_multiline and @line_index > 0
1243
+ @previous_line_index = @line_index
1244
+ @line_index -= 1
1245
+ return
1246
+ end
1247
+ if Reline::HISTORY.empty?
1248
+ return
1249
+ end
1250
+ if @history_pointer.nil?
1251
+ @history_pointer = Reline::HISTORY.size - 1
1252
+ if @is_multiline
1253
+ @line_backup_in_history = whole_buffer
1254
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1255
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1256
+ @line_index = @buffer_of_lines.size - 1
1257
+ @line = @buffer_of_lines.last
1258
+ @rerender_all = true
1259
+ else
1260
+ @line_backup_in_history = @line
1261
+ @line = Reline::HISTORY[@history_pointer]
1262
+ end
1263
+ elsif @history_pointer.zero?
1264
+ return
1265
+ else
1266
+ if @is_multiline
1267
+ Reline::HISTORY[@history_pointer] = whole_buffer
1268
+ @history_pointer -= 1
1269
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1270
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1271
+ @line_index = @buffer_of_lines.size - 1
1272
+ @line = @buffer_of_lines.last
1273
+ @rerender_all = true
1274
+ else
1275
+ Reline::HISTORY[@history_pointer] = @line
1276
+ @history_pointer -= 1
1277
+ @line = Reline::HISTORY[@history_pointer]
1278
+ end
1279
+ end
1280
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1281
+ @cursor_max = @cursor = calculate_width(@line)
1282
+ @byte_pointer = @line.bytesize
1283
+ elsif @config.editing_mode_is?(:vi_command)
1284
+ @byte_pointer = @cursor = 0
1285
+ @cursor_max = calculate_width(@line)
1286
+ end
1287
+ arg -= 1
1288
+ ed_prev_history(key, arg: arg) if arg > 0
1289
+ end
1290
+
1291
+ private def ed_next_history(key, arg: 1)
1292
+ if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1293
+ @previous_line_index = @line_index
1294
+ @line_index += 1
1295
+ return
1296
+ end
1297
+ if @history_pointer.nil?
1298
+ return
1299
+ elsif @history_pointer == (Reline::HISTORY.size - 1)
1300
+ if @is_multiline
1301
+ @history_pointer = nil
1302
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1303
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1304
+ @line_index = 0
1305
+ @line = @buffer_of_lines.first
1306
+ @rerender_all = true
1307
+ else
1308
+ @history_pointer = nil
1309
+ @line = @line_backup_in_history
1310
+ end
1311
+ else
1312
+ if @is_multiline
1313
+ Reline::HISTORY[@history_pointer] = whole_buffer
1314
+ @history_pointer += 1
1315
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1316
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1317
+ @line_index = 0
1318
+ @line = @buffer_of_lines.first
1319
+ @rerender_all = true
1320
+ else
1321
+ Reline::HISTORY[@history_pointer] = @line
1322
+ @history_pointer += 1
1323
+ @line = Reline::HISTORY[@history_pointer]
1324
+ end
1325
+ end
1326
+ @line = '' unless @line
1327
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1328
+ @cursor_max = @cursor = calculate_width(@line)
1329
+ @byte_pointer = @line.bytesize
1330
+ elsif @config.editing_mode_is?(:vi_command)
1331
+ @byte_pointer = @cursor = 0
1332
+ @cursor_max = calculate_width(@line)
1333
+ end
1334
+ arg -= 1
1335
+ ed_next_history(key, arg: arg) if arg > 0
1336
+ end
1337
+
1338
+ private def ed_newline(key)
1339
+ if @is_multiline
1340
+ if @config.editing_mode_is?(:vi_command)
1341
+ if @line_index < (@buffer_of_lines.size - 1)
1342
+ ed_next_history(key) # means cursor down
1343
+ else
1344
+ # should check confirm_multiline_termination to finish?
1345
+ finish
1346
+ end
1347
+ else
1348
+ if @line_index == (@buffer_of_lines.size - 1)
1349
+ if confirm_multiline_termination
1350
+ finish
1351
+ else
1352
+ key_newline(key)
1353
+ end
1354
+ else
1355
+ # should check confirm_multiline_termination to finish?
1356
+ @previous_line_index = @line_index
1357
+ @line_index = @buffer_of_lines.size - 1
1358
+ finish
1359
+ end
1360
+ end
1361
+ else
1362
+ if @history_pointer
1363
+ Reline::HISTORY[@history_pointer] = @line
1364
+ @history_pointer = nil
1365
+ end
1366
+ finish
1367
+ end
1368
+ end
1369
+
1370
+ private def em_delete_prev_char(key)
1371
+ if @is_multiline and @cursor == 0 and @line_index > 0
1372
+ @buffer_of_lines[@line_index] = @line
1373
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
1374
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1375
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1376
+ @line_index -= 1
1377
+ @line = @buffer_of_lines[@line_index]
1378
+ @cursor_max = calculate_width(@line)
1379
+ @rerender_all = true
1380
+ elsif @cursor > 0
1381
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1382
+ @byte_pointer -= byte_size
1383
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1384
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1385
+ @cursor -= width
1386
+ @cursor_max -= width
1387
+ end
1388
+ end
1389
+ alias_method :backward_delete_char, :em_delete_prev_char
1390
+
1391
+ private def ed_kill_line(key)
1392
+ if @line.bytesize > @byte_pointer
1393
+ @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
1394
+ @byte_pointer = @line.bytesize
1395
+ @cursor = @cursor_max = calculate_width(@line)
1396
+ @kill_ring.append(deleted)
1397
+ end
1398
+ end
1399
+
1400
+ private def em_kill_line(key)
1401
+ if @byte_pointer > 0
1402
+ @line, deleted = byteslice!(@line, 0, @byte_pointer)
1403
+ @byte_pointer = 0
1404
+ @kill_ring.append(deleted, true)
1405
+ @cursor_max = calculate_width(@line)
1406
+ @cursor = 0
1407
+ end
1408
+ end
1409
+
1410
+ private def em_delete_or_list(key)
1411
+ if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
1412
+ @line = nil
1413
+ if @buffer_of_lines.size > 1
1414
+ scroll_down(@highest_in_all - @first_line_started_from)
1415
+ end
1416
+ Reline::IOGate.move_cursor_column(0)
1417
+ @eof = true
1418
+ finish
1419
+ elsif @byte_pointer < @line.bytesize
1420
+ splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
1421
+ mbchar = splitted_last.grapheme_clusters.first
1422
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1423
+ @cursor_max -= width
1424
+ @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
1425
+ elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
1426
+ @cursor = calculate_width(@line)
1427
+ @byte_pointer = @line.bytesize
1428
+ @line += @buffer_of_lines.delete_at(@line_index + 1)
1429
+ @cursor_max = calculate_width(@line)
1430
+ @buffer_of_lines[@line_index] = @line
1431
+ @rerender_all = true
1432
+ @rest_height += 1
1433
+ end
1434
+ end
1435
+ alias_method :delete_char, :em_delete_or_list
1436
+
1437
+ private def em_yank(key)
1438
+ yanked = @kill_ring.yank
1439
+ if yanked
1440
+ @line = byteinsert(@line, @byte_pointer, yanked)
1441
+ yanked_width = calculate_width(yanked)
1442
+ @cursor += yanked_width
1443
+ @cursor_max += yanked_width
1444
+ @byte_pointer += yanked.bytesize
1445
+ end
1446
+ end
1447
+
1448
+ private def em_yank_pop(key)
1449
+ yanked, prev_yank = @kill_ring.yank_pop
1450
+ if yanked
1451
+ prev_yank_width = calculate_width(prev_yank)
1452
+ @cursor -= prev_yank_width
1453
+ @cursor_max -= prev_yank_width
1454
+ @byte_pointer -= prev_yank.bytesize
1455
+ @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
1456
+ @line = byteinsert(@line, @byte_pointer, yanked)
1457
+ yanked_width = calculate_width(yanked)
1458
+ @cursor += yanked_width
1459
+ @cursor_max += yanked_width
1460
+ @byte_pointer += yanked.bytesize
1461
+ end
1462
+ end
1463
+
1464
+ private def ed_clear_screen(key)
1465
+ @cleared = true
1466
+ end
1467
+ alias_method :clear_screen, :ed_clear_screen
1468
+
1469
+ private def em_next_word(key)
1470
+ if @line.bytesize > @byte_pointer
1471
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
1472
+ @byte_pointer += byte_size
1473
+ @cursor += width
1474
+ end
1475
+ end
1476
+ alias_method :forward_word, :em_next_word
1477
+
1478
+ private def ed_prev_word(key)
1479
+ if @byte_pointer > 0
1480
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
1481
+ @byte_pointer -= byte_size
1482
+ @cursor -= width
1483
+ end
1484
+ end
1485
+ alias_method :backward_word, :ed_prev_word
1486
+
1487
+ private def em_delete_next_word(key)
1488
+ if @line.bytesize > @byte_pointer
1489
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
1490
+ @line, word = byteslice!(@line, @byte_pointer, byte_size)
1491
+ @kill_ring.append(word)
1492
+ @cursor_max -= width
1493
+ end
1494
+ end
1495
+
1496
+ private def ed_delete_prev_word(key)
1497
+ if @byte_pointer > 0
1498
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
1499
+ @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
1500
+ @kill_ring.append(word, true)
1501
+ @byte_pointer -= byte_size
1502
+ @cursor -= width
1503
+ @cursor_max -= width
1504
+ end
1505
+ end
1506
+
1507
+ private def ed_transpose_chars(key)
1508
+ if @byte_pointer > 0
1509
+ if @cursor_max > @cursor
1510
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1511
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
1512
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1513
+ @cursor += width
1514
+ @byte_pointer += byte_size
1515
+ end
1516
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1517
+ if (@byte_pointer - back1_byte_size) > 0
1518
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
1519
+ back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
1520
+ @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
1521
+ @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
1522
+ end
1523
+ end
1524
+ end
1525
+ alias_method :transpose_chars, :ed_transpose_chars
1526
+
1527
+ private def ed_transpose_words(key)
1528
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
1529
+ before = @line.byteslice(0, left_word_start)
1530
+ left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
1531
+ middle = @line.byteslice(middle_start, right_word_start - middle_start)
1532
+ right_word = @line.byteslice(right_word_start, after_start - right_word_start)
1533
+ after = @line.byteslice(after_start, @line.bytesize - after_start)
1534
+ return if left_word.empty? or right_word.empty?
1535
+ @line = before + right_word + middle + left_word + after
1536
+ from_head_to_left_word = before + right_word + middle + left_word
1537
+ @byte_pointer = from_head_to_left_word.bytesize
1538
+ @cursor = calculate_width(from_head_to_left_word)
1539
+ end
1540
+ alias_method :transpose_words, :ed_transpose_words
1541
+
1542
+ private def em_capitol_case(key)
1543
+ if @line.bytesize > @byte_pointer
1544
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
1545
+ before = @line.byteslice(0, @byte_pointer)
1546
+ after = @line.byteslice((@byte_pointer + byte_size)..-1)
1547
+ @line = before + new_str + after
1548
+ @byte_pointer += new_str.bytesize
1549
+ @cursor += calculate_width(new_str)
1550
+ end
1551
+ end
1552
+ alias_method :capitalize_word, :em_capitol_case
1553
+
1554
+ private def em_lower_case(key)
1555
+ if @line.bytesize > @byte_pointer
1556
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
1557
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
1558
+ mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
1559
+ }.join
1560
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
1561
+ @line = @line.byteslice(0, @byte_pointer) + part
1562
+ @byte_pointer = @line.bytesize
1563
+ @cursor = calculate_width(@line)
1564
+ @cursor_max = @cursor + calculate_width(rest)
1565
+ @line += rest
1566
+ end
1567
+ end
1568
+ alias_method :downcase_word, :em_lower_case
1569
+
1570
+ private def em_upper_case(key)
1571
+ if @line.bytesize > @byte_pointer
1572
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
1573
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
1574
+ mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
1575
+ }.join
1576
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
1577
+ @line = @line.byteslice(0, @byte_pointer) + part
1578
+ @byte_pointer = @line.bytesize
1579
+ @cursor = calculate_width(@line)
1580
+ @cursor_max = @cursor + calculate_width(rest)
1581
+ @line += rest
1582
+ end
1583
+ end
1584
+ alias_method :upcase_word, :em_upper_case
1585
+
1586
+ private def em_kill_region(key)
1587
+ if @byte_pointer > 0
1588
+ byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
1589
+ @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
1590
+ @byte_pointer -= byte_size
1591
+ @cursor -= width
1592
+ @cursor_max -= width
1593
+ @kill_ring.append(deleted)
1594
+ end
1595
+ end
1596
+
1597
+ private def copy_for_vi(text)
1598
+ if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
1599
+ @vi_clipboard = text
1600
+ end
1601
+ end
1602
+
1603
+ private def vi_insert(key)
1604
+ @config.editing_mode = :vi_insert
1605
+ end
1606
+
1607
+ private def vi_add(key)
1608
+ @config.editing_mode = :vi_insert
1609
+ ed_next_char(key)
1610
+ end
1611
+
1612
+ private def vi_command_mode(key)
1613
+ ed_prev_char(key)
1614
+ @config.editing_mode = :vi_command
1615
+ end
1616
+ alias_method :backward_char, :ed_prev_char
1617
+
1618
+ private def vi_next_word(key, arg: 1)
1619
+ if @line.bytesize > @byte_pointer
1620
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
1621
+ @byte_pointer += byte_size
1622
+ @cursor += width
1623
+ end
1624
+ arg -= 1
1625
+ vi_next_word(key, arg: arg) if arg > 0
1626
+ end
1627
+
1628
+ private def vi_prev_word(key, arg: 1)
1629
+ if @byte_pointer > 0
1630
+ byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
1631
+ @byte_pointer -= byte_size
1632
+ @cursor -= width
1633
+ end
1634
+ arg -= 1
1635
+ vi_prev_word(key, arg: arg) if arg > 0
1636
+ end
1637
+
1638
+ private def vi_end_word(key, arg: 1)
1639
+ if @line.bytesize > @byte_pointer
1640
+ byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1641
+ @byte_pointer += byte_size
1642
+ @cursor += width
1643
+ end
1644
+ arg -= 1
1645
+ vi_end_word(key, arg: arg) if arg > 0
1646
+ end
1647
+
1648
+ private def vi_next_big_word(key, arg: 1)
1649
+ if @line.bytesize > @byte_pointer
1650
+ byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
1651
+ @byte_pointer += byte_size
1652
+ @cursor += width
1653
+ end
1654
+ arg -= 1
1655
+ vi_next_big_word(key, arg: arg) if arg > 0
1656
+ end
1657
+
1658
+ private def vi_prev_big_word(key, arg: 1)
1659
+ if @byte_pointer > 0
1660
+ byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
1661
+ @byte_pointer -= byte_size
1662
+ @cursor -= width
1663
+ end
1664
+ arg -= 1
1665
+ vi_prev_big_word(key, arg: arg) if arg > 0
1666
+ end
1667
+
1668
+ private def vi_end_big_word(key, arg: 1)
1669
+ if @line.bytesize > @byte_pointer
1670
+ byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1671
+ @byte_pointer += byte_size
1672
+ @cursor += width
1673
+ end
1674
+ arg -= 1
1675
+ vi_end_big_word(key, arg: arg) if arg > 0
1676
+ end
1677
+
1678
+ private def vi_delete_prev_char(key)
1679
+ if @is_multiline and @cursor == 0 and @line_index > 0
1680
+ @buffer_of_lines[@line_index] = @line
1681
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
1682
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1683
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1684
+ @line_index -= 1
1685
+ @line = @buffer_of_lines[@line_index]
1686
+ @cursor_max = calculate_width(@line)
1687
+ @rerender_all = true
1688
+ elsif @cursor > 0
1689
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1690
+ @byte_pointer -= byte_size
1691
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1692
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1693
+ @cursor -= width
1694
+ @cursor_max -= width
1695
+ end
1696
+ end
1697
+
1698
+ private def ed_delete_prev_char(key, arg: 1)
1699
+ deleted = ''
1700
+ arg.times do
1701
+ if @cursor > 0
1702
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1703
+ @byte_pointer -= byte_size
1704
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1705
+ deleted.prepend(mbchar)
1706
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1707
+ @cursor -= width
1708
+ @cursor_max -= width
1709
+ end
1710
+ end
1711
+ copy_for_vi(deleted)
1712
+ end
1713
+
1714
+ private def vi_zero(key)
1715
+ @byte_pointer = 0
1716
+ @cursor = 0
1717
+ end
1718
+
1719
+ private def vi_change_meta(key)
1720
+ end
1721
+
1722
+ private def vi_delete_meta(key)
1723
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
1724
+ if byte_pointer_diff > 0
1725
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
1726
+ elsif byte_pointer_diff < 0
1727
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
1728
+ end
1729
+ copy_for_vi(cut)
1730
+ @cursor += cursor_diff if cursor_diff < 0
1731
+ @cursor_max -= cursor_diff.abs
1732
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
1733
+ }
1734
+ end
1735
+
1736
+ private def vi_yank(key)
1737
+ end
1738
+
1739
+ private def vi_end_of_transmission(key)
1740
+ if @line.empty?
1741
+ @line = nil
1742
+ if @buffer_of_lines.size > 1
1743
+ scroll_down(@highest_in_all - @first_line_started_from)
1744
+ end
1745
+ Reline::IOGate.move_cursor_column(0)
1746
+ @eof = true
1747
+ finish
1748
+ end
1749
+ end
1750
+
1751
+ private def vi_list_or_eof(key)
1752
+ if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
1753
+ @line = nil
1754
+ if @buffer_of_lines.size > 1
1755
+ scroll_down(@highest_in_all - @first_line_started_from)
1756
+ end
1757
+ Reline::IOGate.move_cursor_column(0)
1758
+ @eof = true
1759
+ finish
1760
+ else
1761
+ # TODO: list
1762
+ end
1763
+ end
1764
+
1765
+ private def ed_delete_next_char(key, arg: 1)
1766
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1767
+ unless @line.empty? || byte_size == 0
1768
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1769
+ copy_for_vi(mbchar)
1770
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1771
+ @cursor_max -= width
1772
+ if @cursor > 0 and @cursor >= @cursor_max
1773
+ @byte_pointer -= byte_size
1774
+ @cursor -= width
1775
+ end
1776
+ end
1777
+ arg -= 1
1778
+ ed_delete_next_char(key, arg: arg) if arg > 0
1779
+ end
1780
+
1781
+ private def vi_to_history_line(key)
1782
+ if Reline::HISTORY.empty?
1783
+ return
1784
+ end
1785
+ if @history_pointer.nil?
1786
+ @history_pointer = 0
1787
+ @line_backup_in_history = @line
1788
+ @line = Reline::HISTORY[@history_pointer]
1789
+ @cursor_max = calculate_width(@line)
1790
+ @cursor = 0
1791
+ @byte_pointer = 0
1792
+ elsif @history_pointer.zero?
1793
+ return
1794
+ else
1795
+ Reline::HISTORY[@history_pointer] = @line
1796
+ @history_pointer = 0
1797
+ @line = Reline::HISTORY[@history_pointer]
1798
+ @cursor_max = calculate_width(@line)
1799
+ @cursor = 0
1800
+ @byte_pointer = 0
1801
+ end
1802
+ end
1803
+
1804
+ private def vi_histedit(key)
1805
+ path = Tempfile.open { |fp|
1806
+ fp.write @line
1807
+ fp.path
1808
+ }
1809
+ system("#{ENV['EDITOR']} #{path}")
1810
+ @line = Pathname.new(path).read
1811
+ finish
1812
+ end
1813
+
1814
+ private def vi_paste_prev(key, arg: 1)
1815
+ if @vi_clipboard.size > 0
1816
+ @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
1817
+ @cursor_max += calculate_width(@vi_clipboard)
1818
+ cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
1819
+ @cursor += calculate_width(cursor_point)
1820
+ @byte_pointer += cursor_point.bytesize
1821
+ end
1822
+ arg -= 1
1823
+ vi_paste_prev(key, arg: arg) if arg > 0
1824
+ end
1825
+
1826
+ private def vi_paste_next(key, arg: 1)
1827
+ if @vi_clipboard.size > 0
1828
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1829
+ @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
1830
+ @cursor_max += calculate_width(@vi_clipboard)
1831
+ @cursor += calculate_width(@vi_clipboard)
1832
+ @byte_pointer += @vi_clipboard.bytesize
1833
+ end
1834
+ arg -= 1
1835
+ vi_paste_next(key, arg: arg) if arg > 0
1836
+ end
1837
+
1838
+ private def ed_argument_digit(key)
1839
+ if @vi_arg.nil?
1840
+ unless key.chr.to_i.zero?
1841
+ @vi_arg = key.chr.to_i
1842
+ end
1843
+ else
1844
+ @vi_arg = @vi_arg * 10 + key.chr.to_i
1845
+ end
1846
+ end
1847
+
1848
+ private def vi_to_column(key, arg: 0)
1849
+ @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
1850
+ # total has [byte_size, cursor]
1851
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
1852
+ if (total.last + mbchar_width) >= arg
1853
+ break total
1854
+ elsif (total.last + mbchar_width) >= @cursor_max
1855
+ break total
1856
+ else
1857
+ total = [total.first + gc.bytesize, total.last + mbchar_width]
1858
+ total
1859
+ end
1860
+ }
1861
+ end
1862
+
1863
+ private def vi_replace_char(key, arg: 1)
1864
+ @waiting_proc = ->(key) {
1865
+ if arg == 1
1866
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1867
+ before = @line.byteslice(0, @byte_pointer)
1868
+ remaining_point = @byte_pointer + byte_size
1869
+ after = @line.byteslice(remaining_point, @line.size - remaining_point)
1870
+ @line = before + key.chr + after
1871
+ @cursor_max = calculate_width(@line)
1872
+ @waiting_proc = nil
1873
+ elsif arg > 1
1874
+ byte_size = 0
1875
+ arg.times do
1876
+ byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
1877
+ end
1878
+ before = @line.byteslice(0, @byte_pointer)
1879
+ remaining_point = @byte_pointer + byte_size
1880
+ after = @line.byteslice(remaining_point, @line.size - remaining_point)
1881
+ replaced = key.chr * arg
1882
+ @line = before + replaced + after
1883
+ @byte_pointer += replaced.bytesize
1884
+ @cursor += calculate_width(replaced)
1885
+ @cursor_max = calculate_width(@line)
1886
+ @waiting_proc = nil
1887
+ end
1888
+ }
1889
+ end
1890
+
1891
+ private def vi_next_char(key, arg: 1)
1892
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
1893
+ end
1894
+
1895
+ private def search_next_char(key, arg)
1896
+ if key.instance_of?(String)
1897
+ inputed_char = key
1898
+ else
1899
+ inputed_char = key.chr
1900
+ end
1901
+ total = nil
1902
+ found = false
1903
+ @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
1904
+ # total has [byte_size, cursor]
1905
+ unless total
1906
+ # skip cursor point
1907
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1908
+ total = [mbchar.bytesize, width]
1909
+ else
1910
+ if inputed_char == mbchar
1911
+ arg -= 1
1912
+ if arg.zero?
1913
+ found = true
1914
+ break
1915
+ end
1916
+ end
1917
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1918
+ total = [total.first + mbchar.bytesize, total.last + width]
1919
+ end
1920
+ end
1921
+ if found and total
1922
+ byte_size, width = total
1923
+ @byte_pointer += byte_size
1924
+ @cursor += width
1925
+ end
1926
+ @waiting_proc = nil
1927
+ end
1928
+
1929
+ private def vi_join_lines(key, arg: 1)
1930
+ if @is_multiline and @buffer_of_lines.size > @line_index + 1
1931
+ @cursor = calculate_width(@line)
1932
+ @byte_pointer = @line.bytesize
1933
+ @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
1934
+ @cursor_max = calculate_width(@line)
1935
+ @buffer_of_lines[@line_index] = @line
1936
+ @rerender_all = true
1937
+ @rest_height += 1
1938
+ end
1939
+ arg -= 1
1940
+ vi_join_lines(key, arg: arg) if arg > 0
1941
+ end
1942
+ end