reline 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
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
+ @buffer = []
17
+ end
18
+
19
+ def input_to(bytes)
20
+ case match_status(bytes)
21
+ when :matching
22
+ nil
23
+ when :matched
24
+ expand(bytes)
25
+ when :unmatched
26
+ bytes
27
+ end
28
+ end
29
+
30
+ def input_to!(bytes)
31
+ @buffer.concat Array(bytes)
32
+ input_to(@buffer)&.tap { clear }
33
+ end
34
+
35
+ private
36
+
37
+ def match_status(input)
38
+ key_mapping.keys.select { |lhs|
39
+ lhs.start_with? input
40
+ }.tap { |it|
41
+ return :matched if it.size == 1 && (it.max_by(&:size)&.size&.== input.size)
42
+ return :matching if it.size == 1 && (it.max_by(&:size)&.size&.!= input.size)
43
+ return :matched if it.max_by(&:size)&.size&.< input.size
44
+ return :matching if it.size > 1
45
+ }
46
+ key_mapping.keys.select { |lhs|
47
+ input.start_with? lhs
48
+ }.tap { |it|
49
+ return it.size > 0 ? :matched : :unmatched
50
+ }
51
+ end
52
+
53
+ def expand(input)
54
+ lhs = key_mapping.keys.select { |lhs| input.start_with? lhs }.sort_by(&:size).reverse.first
55
+ return input unless lhs
56
+ rhs = key_mapping[lhs]
57
+
58
+ case rhs
59
+ when String
60
+ rhs_bytes = rhs.bytes
61
+ expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
62
+ when Symbol
63
+ [rhs] + expand(input.drop(lhs.size))
64
+ end
65
+ end
66
+
67
+ def key_mapping
68
+ @config[:key_mapping].transform_keys(&:bytes)
69
+ end
70
+
71
+ def clear
72
+ @buffer = []
73
+ end
74
+ 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,1357 @@
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_accessor :confirm_multiline_termination_proc
11
+ attr_accessor :completion_proc
12
+ attr_accessor :dig_perfect_match_proc
13
+ attr_writer :retrieve_completion_block
14
+
15
+ ARGUMENTABLE = %i{
16
+ ed_delete_next_char
17
+ ed_delete_prev_char
18
+ ed_delete_prev_word
19
+ ed_next_char
20
+ ed_next_history
21
+ ed_next_line#
22
+ ed_prev_char
23
+ ed_prev_history
24
+ ed_prev_line#
25
+ ed_prev_word
26
+ ed_quoted_insert
27
+ vi_to_column
28
+ vi_next_word
29
+ vi_prev_word
30
+ vi_end_word
31
+ vi_next_big_word
32
+ vi_prev_big_word
33
+ vi_end_big_word
34
+ vi_next_char
35
+ vi_delete_meta
36
+ vi_paste_prev
37
+ vi_paste_next
38
+ vi_replace_char
39
+ }
40
+
41
+ VI_OPERATORS = %i{
42
+ vi_change_meta
43
+ vi_delete_meta
44
+ vi_yank
45
+ }
46
+
47
+ VI_MOTIONS = %i{
48
+ ed_prev_char
49
+ ed_next_char
50
+ vi_zero
51
+ ed_move_to_beg
52
+ ed_move_to_end
53
+ vi_to_column
54
+ vi_next_char
55
+ vi_prev_char
56
+ vi_next_word
57
+ vi_prev_word
58
+ vi_to_next_char
59
+ vi_to_prev_char
60
+ vi_end_word
61
+ vi_next_big_word
62
+ vi_prev_big_word
63
+ vi_end_big_word
64
+ vi_repeat_next_char
65
+ vi_repeat_prev_char
66
+ }
67
+
68
+ module CompletionState
69
+ NORMAL = :normal
70
+ COMPLETION = :completion
71
+ MENU = :menu
72
+ JOURNEY = :journey
73
+ PERFECT_MATCH = :perfect_match
74
+ end
75
+
76
+ CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
77
+ MenuInfo = Struct.new('MenuInfo', :target, :list)
78
+
79
+ def initialize(config, prompt, encoding = Encoding.default_external)
80
+ @config = config
81
+ @prompt = prompt
82
+ @prompt_width = calculate_width(@prompt)
83
+ @cursor = 0
84
+ @cursor_max = 0
85
+ @byte_pointer = 0
86
+ @encoding = encoding
87
+ @buffer_of_lines = [String.new(encoding: @encoding)]
88
+ @line_index = 0
89
+ @previous_line_index = nil
90
+ @line = @buffer_of_lines[0]
91
+ @is_multiline = false
92
+ @finished = false
93
+ @cleared = false
94
+ @rerender_all = false
95
+ @is_confirm_multiline_termination = false
96
+ @history_pointer = nil
97
+ @line_backup_in_history = nil
98
+ @kill_ring = Reline::KillRing.new
99
+ @vi_clipboard = ''
100
+ @vi_arg = nil
101
+ @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
102
+ @meta_prefix = false
103
+ @waiting_proc = nil
104
+ @waiting_operator_proc = nil
105
+ @completion_journey_data = nil
106
+ @completion_state = CompletionState::NORMAL
107
+ @perfect_matched = nil
108
+ @screen_size = Reline.get_screen_size
109
+ @first_line_started_from = 0
110
+ @move_up = 0
111
+ @started_from = 0
112
+ @highest_in_this = 1
113
+ @highest_in_all = 1
114
+ @menu_info = nil
115
+ end
116
+
117
+ def multiline_on
118
+ @is_multiline = true
119
+ end
120
+
121
+ def multiline_off
122
+ @is_multiline = false
123
+ end
124
+
125
+ private def insert_new_line(cursor_line, next_line)
126
+ @line = cursor_line
127
+ @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
128
+ @previous_line_index = @line_index
129
+ @line_index += 1
130
+ end
131
+
132
+ private def calculate_height_by_width(width)
133
+ return 1 if width.zero?
134
+ height = 1
135
+ max_width = @screen_size.last
136
+ while width > max_width * height
137
+ height += 1
138
+ end
139
+ height += 1 if (width % max_width).zero?
140
+ height
141
+ end
142
+
143
+ private def split_by_width(str, max_width)
144
+ lines = [String.new(encoding: @encoding)]
145
+ width = 0
146
+ str.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
147
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
148
+ width += mbchar_width
149
+ if width > max_width
150
+ width = mbchar_width
151
+ lines << String.new(encoding: @encoding)
152
+ end
153
+ lines.last << gc
154
+ end
155
+ # The cursor moves to next line in first
156
+ lines << String.new(encoding: @encoding) if width == max_width
157
+ lines
158
+ end
159
+
160
+ private def scroll_down(val)
161
+ if val <= @rest_height
162
+ Reline.move_cursor_down(val)
163
+ @rest_height -= val
164
+ else
165
+ Reline.move_cursor_down(@rest_height)
166
+ Reline.scroll_down(val - @rest_height)
167
+ @rest_height = 0
168
+ end
169
+ end
170
+
171
+ private def move_cursor_up(val)
172
+ if val > 0
173
+ Reline.move_cursor_up(val)
174
+ @rest_height += val
175
+ elsif val < 0
176
+ move_cursor_down(-val)
177
+ end
178
+ end
179
+
180
+ private def move_cursor_down(val)
181
+ if val > 0
182
+ Reline.move_cursor_down(val)
183
+ @rest_height -= val
184
+ @rest_height = 0 if @rest_height < 0
185
+ elsif val < 0
186
+ move_cursor_up(-val)
187
+ end
188
+ end
189
+
190
+ private def calculate_nearest_cursor
191
+ @cursor_max = calculate_width(line)
192
+ new_cursor = 0
193
+ new_byte_pointer = 0
194
+ height = 1
195
+ max_width = @screen_size.last
196
+ @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
197
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
198
+ now = new_cursor + mbchar_width
199
+ if now > @cursor_max or now > @cursor
200
+ break
201
+ end
202
+ new_cursor += mbchar_width
203
+ if new_cursor > max_width * height
204
+ height += 1
205
+ end
206
+ new_byte_pointer += gc.bytesize
207
+ end
208
+ @started_from = height - 1
209
+ @cursor = new_cursor
210
+ @byte_pointer = new_byte_pointer
211
+ end
212
+
213
+ def rerender # TODO: support physical and logical lines
214
+ @rest_height ||= (Reline.get_screen_size.first - 1) - Reline.cursor_pos.y
215
+ if @menu_info
216
+ puts
217
+ @menu_info.list.each do |item|
218
+ puts item
219
+ end
220
+ @menu_info = nil
221
+ end
222
+ return if @line.nil?
223
+ if @vi_arg
224
+ prompt = "(arg: #{@vi_arg}) "
225
+ prompt_width = calculate_width(prompt)
226
+ else
227
+ prompt = @prompt
228
+ prompt_width = @prompt_width
229
+ end
230
+ if @cleared
231
+ Reline.clear_screen
232
+ @cleared = false
233
+ back = 0
234
+ @buffer_of_lines.each_with_index do |line, index|
235
+ line = @line if index == @line_index
236
+ height = render_partial(prompt, prompt_width, line, false)
237
+ if index < (@buffer_of_lines.size - 1)
238
+ move_cursor_down(height)
239
+ back += height
240
+ end
241
+ end
242
+ move_cursor_up(back)
243
+ move_cursor_down(@first_line_started_from + @started_from)
244
+ Reline.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
245
+ return
246
+ end
247
+ # FIXME: end of logical line sometimes breaks
248
+ if @previous_line_index
249
+ previous_line = @line
250
+ all_height = @buffer_of_lines.inject(0) { |result, line|
251
+ result + calculate_height_by_width(@prompt_width + calculate_width(line))
252
+ }
253
+ diff = all_height - @highest_in_all
254
+ if diff > 0
255
+ @highest_in_all = all_height
256
+ scroll_down(diff)
257
+ move_cursor_up(@first_line_started_from + @started_from + diff)
258
+ back = 0
259
+ @buffer_of_lines.each_with_index do |line, index|
260
+ line = @line if index == @previous_line_index
261
+ height = render_partial(prompt, prompt_width, line, false)
262
+ if index < (@buffer_of_lines.size - 1)
263
+ move_cursor_down(height)
264
+ back += height
265
+ end
266
+ end
267
+ move_cursor_up(back)
268
+ else
269
+ render_partial(prompt, prompt_width, previous_line)
270
+ move_cursor_up(@first_line_started_from + @started_from)
271
+ end
272
+ @buffer_of_lines[@previous_line_index] = @line
273
+ @line = @buffer_of_lines[@line_index]
274
+ @first_line_started_from =
275
+ if @line_index.zero?
276
+ 0
277
+ else
278
+ @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
279
+ result + calculate_height_by_width(@prompt_width + calculate_width(line))
280
+ }
281
+ end
282
+ move_cursor_down(@first_line_started_from)
283
+ calculate_nearest_cursor
284
+ @highest_in_this = calculate_height_by_width(@prompt_width + @cursor_max)
285
+ @previous_line_index = nil
286
+ elsif @rerender_all
287
+ move_cursor_up(@first_line_started_from + @started_from)
288
+ Reline.move_cursor_column(0)
289
+ back = 0
290
+ @buffer_of_lines.each do |line|
291
+ width = prompt_width + calculate_width(line)
292
+ height = calculate_height_by_width(width)
293
+ back += height
294
+ end
295
+ if back > @highest_in_all
296
+ scroll_down(back)
297
+ move_cursor_up(back)
298
+ elsif back < @highest_in_all
299
+ scroll_down(back)
300
+ Reline.erase_after_cursor
301
+ (@highest_in_all - back).times do
302
+ scroll_down(1)
303
+ Reline.erase_after_cursor
304
+ end
305
+ move_cursor_up(@highest_in_all)
306
+ end
307
+ @buffer_of_lines.each_with_index do |line, index|
308
+ height = render_partial(prompt, prompt_width, line, false)
309
+ if index < (@buffer_of_lines.size - 1)
310
+ move_cursor_down(1)
311
+ end
312
+ end
313
+ move_cursor_up(back - 1)
314
+ @highest_in_all = back
315
+ @highest_in_this = calculate_height_by_width(@prompt_width + @cursor_max)
316
+ @first_line_started_from =
317
+ if @line_index.zero?
318
+ 0
319
+ else
320
+ @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
321
+ result + calculate_height_by_width(@prompt_width + calculate_width(line))
322
+ }
323
+ end
324
+ move_cursor_down(@first_line_started_from)
325
+ @rerender_all = false
326
+ end
327
+ render_partial(prompt, prompt_width, @line) if !@is_multiline or !finished?
328
+ if @is_multiline and finished?
329
+ scroll_down(1) unless @buffer_of_lines.last.empty?
330
+ Reline.move_cursor_column(0)
331
+ Reline.erase_after_cursor
332
+ end
333
+ end
334
+
335
+ private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
336
+ whole_line = prompt + (line_to_render.nil? ? '' : line_to_render)
337
+ visual_lines = split_by_width(whole_line, @screen_size.last)
338
+ if with_control
339
+ if visual_lines.size > @highest_in_this
340
+ diff = visual_lines.size - @highest_in_this
341
+ scroll_down(diff)
342
+ @highest_in_all += diff
343
+ @highest_in_this = visual_lines.size
344
+ move_cursor_up(1)
345
+ end
346
+ move_cursor_up(@started_from)
347
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
348
+ end
349
+ visual_lines.each_with_index do |line, index|
350
+ Reline.move_cursor_column(0)
351
+ escaped_print line
352
+ Reline.erase_after_cursor
353
+ move_cursor_down(1) if index < (visual_lines.size - 1)
354
+ end
355
+ if with_control
356
+ if finished?
357
+ puts
358
+ else
359
+ move_cursor_up((visual_lines.size - 1) - @started_from)
360
+ Reline.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
361
+ end
362
+ end
363
+ visual_lines.size
364
+ end
365
+
366
+ def editing_mode
367
+ @config.editing_mode
368
+ end
369
+
370
+ private def escaped_print(str)
371
+ print str.chars.map { |gr|
372
+ escaped = Reline::Unicode::EscapedPairs[gr.ord]
373
+ if escaped
374
+ escaped
375
+ else
376
+ gr
377
+ end
378
+ }.join
379
+ end
380
+
381
+ private def menu(target, list)
382
+ @menu_info = MenuInfo.new(target, list)
383
+ end
384
+
385
+ private def complete_internal_proc(list, is_menu)
386
+ preposing, target, postposing = @retrieve_completion_block.(@line, @byte_pointer)
387
+ list = list.select { |i| i&.start_with?(target) }
388
+ if is_menu
389
+ menu(target, list)
390
+ return nil
391
+ end
392
+ completed = list.inject { |memo, item|
393
+ memo_mbchars = memo.unicode_normalize.grapheme_clusters
394
+ item_mbchars = item.unicode_normalize.grapheme_clusters
395
+ size = [memo_mbchars.size, item_mbchars.size].min
396
+ result = ''
397
+ size.times do |i|
398
+ if memo_mbchars[i] == item_mbchars[i]
399
+ result << memo_mbchars[i]
400
+ else
401
+ break
402
+ end
403
+ end
404
+ result
405
+ }
406
+ [target, preposing, completed, postposing]
407
+ end
408
+
409
+ private def complete(list)
410
+ case @completion_state
411
+ when CompletionState::NORMAL, CompletionState::JOURNEY
412
+ @completion_state = CompletionState::COMPLETION
413
+ when CompletionState::PERFECT_MATCH
414
+ @dig_perfect_match_proc&.(@perfect_matched)
415
+ end
416
+ is_menu = (@completion_state == CompletionState::MENU)
417
+ result = complete_internal_proc(list, is_menu)
418
+ return if result.nil?
419
+ target, preposing, completed, postposing = result
420
+ return if completed.nil?
421
+ if target <= completed and (@completion_state == CompletionState::COMPLETION or @completion_state == CompletionState::PERFECT_MATCH)
422
+ @completion_state = CompletionState::MENU
423
+ if list.include?(completed)
424
+ @completion_state = CompletionState::PERFECT_MATCH
425
+ @perfect_matched = completed
426
+ end
427
+ if target < completed
428
+ @line = preposing + completed + postposing
429
+ line_to_pointer = preposing + completed
430
+ @cursor_max = calculate_width(@line)
431
+ @cursor = calculate_width(line_to_pointer)
432
+ @byte_pointer = line_to_pointer.bytesize
433
+ end
434
+ end
435
+ end
436
+
437
+ private def move_completed_list(list, direction)
438
+ case @completion_state
439
+ when CompletionState::NORMAL, CompletionState::COMPLETION, CompletionState::MENU
440
+ @completion_state = CompletionState::JOURNEY
441
+ result = @retrieve_completion_block.(@line, @byte_pointer)
442
+ return if result.nil?
443
+ preposing, target, postposing = result
444
+ @completion_journey_data = CompletionJourneyData.new(
445
+ preposing, postposing,
446
+ [target] + list.select{ |item| item.start_with?(target) }, 0)
447
+ @completion_state = CompletionState::JOURNEY
448
+ else
449
+ case direction
450
+ when :up
451
+ @completion_journey_data.pointer -= 1
452
+ if @completion_journey_data.pointer < 0
453
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
454
+ end
455
+ when :down
456
+ @completion_journey_data.pointer += 1
457
+ if @completion_journey_data.pointer >= @completion_journey_data.list.size
458
+ @completion_journey_data.pointer = 0
459
+ end
460
+ end
461
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
462
+ @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
463
+ line_to_pointer = @completion_journey_data.preposing + completed
464
+ @cursor_max = calculate_width(@line)
465
+ @cursor = calculate_width(line_to_pointer)
466
+ @byte_pointer = line_to_pointer.bytesize
467
+ end
468
+ end
469
+
470
+ private def run_for_operators(key, method_symbol, &block)
471
+ if @waiting_operator_proc
472
+ if VI_MOTIONS.include?(method_symbol)
473
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
474
+ block.()
475
+ unless @waiting_proc
476
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
477
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
478
+ @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
479
+ else
480
+ old_waiting_proc = @waiting_proc
481
+ old_waiting_operator_proc = @waiting_operator_proc
482
+ @waiting_proc = proc { |key|
483
+ old_cursor, old_byte_pointer = @cursor, @byte_pointer
484
+ old_waiting_proc.(key)
485
+ cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
486
+ @cursor, @byte_pointer = old_cursor, old_byte_pointer
487
+ @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
488
+ }
489
+ end
490
+ else
491
+ # Ignores operator when not motion is given.
492
+ block.()
493
+ end
494
+ @waiting_operator_proc = nil
495
+ else
496
+ block.()
497
+ end
498
+ end
499
+
500
+ private def process_key(key, method_symbol, method_obj)
501
+ if @vi_arg
502
+ if key.chr =~ /[0-9]/
503
+ ed_argument_digit(key)
504
+ else
505
+ if ARGUMENTABLE.include?(method_symbol) and method_obj
506
+ run_for_operators(key, method_symbol) do
507
+ method_obj.(key, arg: @vi_arg)
508
+ end
509
+ elsif @waiting_proc
510
+ @waiting_proc.(key)
511
+ elsif method_obj
512
+ method_obj.(key)
513
+ else
514
+ ed_insert(key)
515
+ end
516
+ @kill_ring.process
517
+ @vi_arg = nil
518
+ end
519
+ elsif @waiting_proc
520
+ @waiting_proc.(key)
521
+ @kill_ring.process
522
+ elsif method_obj
523
+ if method_symbol == :ed_argument_digit
524
+ method_obj.(key)
525
+ else
526
+ run_for_operators(key, method_symbol) do
527
+ method_obj.(key)
528
+ end
529
+ end
530
+ @kill_ring.process
531
+ else
532
+ ed_insert(key)
533
+ end
534
+ end
535
+
536
+ private def normal_char(key)
537
+ method_symbol = method_obj = nil
538
+ @multibyte_buffer << key
539
+ if @multibyte_buffer.size > 1
540
+ if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
541
+ key = @multibyte_buffer.dup.force_encoding(@encoding)
542
+ @multibyte_buffer.clear
543
+ else
544
+ # invalid
545
+ return
546
+ end
547
+ else # single byte
548
+ return if key >= 128 # maybe, first byte of multi byte
549
+ if @meta_prefix
550
+ key |= 0b10000000 if key.nobits?(0b10000000)
551
+ @meta_prefix = false
552
+ end
553
+ method_symbol = @config.editing_mode.get_method(key)
554
+ if key.allbits?(0b10000000) and method_symbol == :ed_unassigned
555
+ return # This is unknown input
556
+ end
557
+ if method_symbol and respond_to?(method_symbol, true)
558
+ method_obj = method(method_symbol)
559
+ end
560
+ @multibyte_buffer.clear
561
+ end
562
+ process_key(key, method_symbol, method_obj)
563
+ if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
564
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
565
+ @byte_pointer -= byte_size
566
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
567
+ width = Reline::Unicode.get_mbchar_width(mbchar)
568
+ @cursor -= width
569
+ end
570
+ end
571
+
572
+ def input_key(key)
573
+ completion_occurs = false
574
+ if @config.editing_mode_is?(:emacs, :vi_insert) and key == "\C-i".ord
575
+ result = @completion_proc&.(@line)
576
+ if result.is_a?(Array)
577
+ completion_occurs = true
578
+ complete(result)
579
+ end
580
+ elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key)
581
+ result = @completion_proc&.(@line)
582
+ if result.is_a?(Array)
583
+ completion_occurs = true
584
+ move_completed_list(result, "\C-p".ord == key ? :up : :down)
585
+ end
586
+ elsif @config.editing_mode_is?(:emacs) and key == "\e".ord # meta key
587
+ if @meta_prefix
588
+ # escape twice
589
+ @meta_prefix = false
590
+ @kill_ring.process
591
+ else
592
+ @meta_prefix = true
593
+ end
594
+ elsif @config.editing_mode_is?(:vi_command) and key == "\e".ord
595
+ # suppress ^[ when command_mode
596
+ elsif Symbol === key and respond_to?(key, true)
597
+ process_key(key, key, method(key))
598
+ else
599
+ normal_char(key)
600
+ end
601
+ unless completion_occurs
602
+ @completion_state = CompletionState::NORMAL
603
+ end
604
+ if @is_confirm_multiline_termination and @confirm_multiline_termination_proc
605
+ @is_confirm_multiline_termination = false
606
+ temp_buffer = @buffer_of_lines.dup
607
+ if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
608
+ temp_buffer[@previous_line_index] = @line
609
+ end
610
+ finish if @confirm_multiline_termination_proc.(temp_buffer.join("\n"))
611
+ end
612
+ end
613
+
614
+ def whole_buffer
615
+ temp_lines = @buffer_of_lines.dup
616
+ temp_lines[@line_index] = @line
617
+ if @buffer_of_lines.size == 1 and @line.nil?
618
+ nil
619
+ else
620
+ temp_lines.join("\n")
621
+ end
622
+ end
623
+
624
+ def finished?
625
+ @finished
626
+ end
627
+
628
+ def finish
629
+ @finished = true
630
+ @config.reset
631
+ end
632
+
633
+ private def byteslice!(str, byte_pointer, size)
634
+ new_str = str.byteslice(0, byte_pointer)
635
+ new_str << str.byteslice(byte_pointer + size, str.bytesize)
636
+ [new_str, str.byteslice(byte_pointer, size)]
637
+ end
638
+
639
+ private def byteinsert(str, byte_pointer, other)
640
+ new_str = str.byteslice(0, byte_pointer)
641
+ new_str << other
642
+ new_str << str.byteslice(byte_pointer, str.bytesize)
643
+ new_str
644
+ end
645
+
646
+ private def calculate_width(str)
647
+ str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
648
+ width + Reline::Unicode.get_mbchar_width(gc)
649
+ }
650
+ end
651
+
652
+ private def ed_insert(key)
653
+ if key.instance_of?(String)
654
+ width = Reline::Unicode.get_mbchar_width(key)
655
+ if @cursor == @cursor_max
656
+ @line += key
657
+ else
658
+ @line = byteinsert(@line, @byte_pointer, key)
659
+ end
660
+ @byte_pointer += key.bytesize
661
+ @cursor += width
662
+ @cursor_max += width
663
+ else
664
+ if @cursor == @cursor_max
665
+ @line += key.chr
666
+ else
667
+ @line = byteinsert(@line, @byte_pointer, key.chr)
668
+ end
669
+ width = Reline::Unicode.get_mbchar_width(key.chr)
670
+ @byte_pointer += 1
671
+ @cursor += width
672
+ @cursor_max += width
673
+ end
674
+ end
675
+ alias_method :ed_digit, :ed_insert
676
+
677
+ private def ed_quoted_insert(str, arg: 1)
678
+ @waiting_proc = proc { |key|
679
+ arg.times do
680
+ ed_insert(key)
681
+ end
682
+ @waiting_proc = nil
683
+ }
684
+ end
685
+
686
+ private def ed_next_char(key, arg: 1)
687
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
688
+ if (@byte_pointer < @line.bytesize)
689
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
690
+ width = Reline::Unicode.get_mbchar_width(mbchar)
691
+ @cursor += width if width
692
+ @byte_pointer += byte_size
693
+ end
694
+ arg -= 1
695
+ ed_next_char(key, arg: arg) if arg > 0
696
+ end
697
+
698
+ private def ed_prev_char(key, arg: 1)
699
+ if @cursor > 0
700
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
701
+ @byte_pointer -= byte_size
702
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
703
+ width = Reline::Unicode.get_mbchar_width(mbchar)
704
+ @cursor -= width
705
+ end
706
+ arg -= 1
707
+ ed_prev_char(key, arg: arg) if arg > 0
708
+ end
709
+
710
+ private def ed_move_to_beg(key)
711
+ @byte_pointer, @cursor = Reline::Unicode.ed_move_to_begin(@line)
712
+ end
713
+
714
+ private def ed_move_to_end(key)
715
+ @byte_pointer = 0
716
+ @cursor = 0
717
+ byte_size = 0
718
+ while @byte_pointer < @line.bytesize
719
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
720
+ if byte_size > 0
721
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
722
+ @cursor += Reline::Unicode.get_mbchar_width(mbchar)
723
+ end
724
+ @byte_pointer += byte_size
725
+ end
726
+ end
727
+
728
+ private def ed_prev_history(key, arg: 1)
729
+ if @is_multiline and @line_index > 0
730
+ @previous_line_index = @line_index
731
+ @line_index -= 1
732
+ return
733
+ end
734
+ if Reline::HISTORY.empty?
735
+ return
736
+ end
737
+ if @history_pointer.nil?
738
+ @history_pointer = Reline::HISTORY.size - 1
739
+ if @is_multiline
740
+ @line_backup_in_history = whole_buffer
741
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
742
+ @line_index = @buffer_of_lines.size - 1
743
+ @line = @buffer_of_lines.last
744
+ @rerender_all = true
745
+ else
746
+ @line_backup_in_history = @line
747
+ @line = Reline::HISTORY[@history_pointer]
748
+ end
749
+ elsif @history_pointer.zero?
750
+ return
751
+ else
752
+ if @is_multiline
753
+ Reline::HISTORY[@history_pointer] = whole_buffer
754
+ @history_pointer -= 1
755
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
756
+ @line_index = @buffer_of_lines.size - 1
757
+ @line = @buffer_of_lines.last
758
+ @rerender_all = true
759
+ else
760
+ Reline::HISTORY[@history_pointer] = @line
761
+ @history_pointer -= 1
762
+ @line = Reline::HISTORY[@history_pointer]
763
+ end
764
+ end
765
+ if @config.editing_mode_is?(:emacs)
766
+ @cursor_max = @cursor = calculate_width(@line)
767
+ @byte_pointer = @line.bytesize
768
+ elsif @config.editing_mode_is?(:vi_command)
769
+ @byte_pointer = @cursor = 0
770
+ @cursor_max = calculate_width(@line)
771
+ end
772
+ arg -= 1
773
+ ed_prev_history(key, arg: arg) if arg > 0
774
+ end
775
+
776
+ private def ed_next_history(key, arg: 1)
777
+ if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
778
+ @previous_line_index = @line_index
779
+ @line_index += 1
780
+ return
781
+ end
782
+ if @history_pointer.nil?
783
+ return
784
+ elsif @history_pointer == (Reline::HISTORY.size - 1)
785
+ if @is_multiline
786
+ @history_pointer = nil
787
+ @buffer_of_lines = @line_backup_in_history.split("\n")
788
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
789
+ @line_index = 0
790
+ @line = @buffer_of_lines.first
791
+ @rerender_all = true
792
+ else
793
+ @history_pointer = nil
794
+ @line = @line_backup_in_history
795
+ end
796
+ else
797
+ if @is_multiline
798
+ Reline::HISTORY[@history_pointer] = whole_buffer
799
+ @history_pointer += 1
800
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
801
+ @line_index = 0
802
+ @line = @buffer_of_lines.first
803
+ @rerender_all = true
804
+ else
805
+ Reline::HISTORY[@history_pointer] = @line
806
+ @history_pointer += 1
807
+ @line = Reline::HISTORY[@history_pointer]
808
+ end
809
+ end
810
+ @line = '' unless @line
811
+ if @config.editing_mode_is?(:emacs)
812
+ @cursor_max = @cursor = calculate_width(@line)
813
+ @byte_pointer = @line.bytesize
814
+ elsif @config.editing_mode_is?(:vi_command)
815
+ @byte_pointer = @cursor = 0
816
+ @cursor_max = calculate_width(@line)
817
+ end
818
+ arg -= 1
819
+ ed_next_history(key, arg: arg) if arg > 0
820
+ end
821
+
822
+ private def ed_newline(key)
823
+ if @is_multiline
824
+ if @config.editing_mode_is?(:vi_command)
825
+ if @line_index < (@buffer_of_lines.size - 1)
826
+ ed_next_history(key)
827
+ else
828
+ @is_confirm_multiline_termination = true
829
+ end
830
+ else
831
+ next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
832
+ cursor_line = @line.byteslice(0, @byte_pointer)
833
+ insert_new_line(cursor_line, next_line)
834
+ if @line_index == (@buffer_of_lines.size - 1)
835
+ @is_confirm_multiline_termination = true
836
+ end
837
+ end
838
+ return
839
+ end
840
+ if @history_pointer
841
+ Reline::HISTORY[@history_pointer] = @line
842
+ @history_pointer = nil
843
+ end
844
+ finish
845
+ end
846
+
847
+ private def em_delete_prev_char(key)
848
+ if @is_multiline and @cursor == 0 and @line_index > 0
849
+ @buffer_of_lines[@line_index] = @line
850
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
851
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
852
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
853
+ @line_index -= 1
854
+ @line = @buffer_of_lines[@line_index]
855
+ @cursor_max = calculate_width(@line)
856
+ @rerender_all = true
857
+ elsif @cursor > 0
858
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
859
+ @byte_pointer -= byte_size
860
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
861
+ width = Reline::Unicode.get_mbchar_width(mbchar)
862
+ @cursor -= width
863
+ @cursor_max -= width
864
+ end
865
+ end
866
+
867
+ private def ed_kill_line(key)
868
+ if @line.bytesize > @byte_pointer
869
+ @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
870
+ @byte_pointer = @line.bytesize
871
+ @cursor = @cursor_max = calculate_width(@line)
872
+ @kill_ring.append(deleted)
873
+ end
874
+ end
875
+
876
+ private def em_kill_line(key)
877
+ if @byte_pointer > 0
878
+ @line, deleted = byteslice!(@line, 0, @byte_pointer)
879
+ @byte_pointer = 0
880
+ @kill_ring.append(deleted, true)
881
+ @cursor_max = calculate_width(@line)
882
+ @cursor = 0
883
+ end
884
+ end
885
+
886
+ private def em_delete_or_list(key)
887
+ if @line.empty?
888
+ @line = nil
889
+ finish
890
+ elsif @byte_pointer < @line.bytesize
891
+ splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
892
+ mbchar = splitted_last.grapheme_clusters.first
893
+ width = Reline::Unicode.get_mbchar_width(mbchar)
894
+ @cursor_max -= width
895
+ @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
896
+ end
897
+ end
898
+
899
+ private def em_yank(key)
900
+ yanked = @kill_ring.yank
901
+ if yanked
902
+ @line = byteinsert(@line, @byte_pointer, yanked)
903
+ yanked_width = calculate_width(yanked)
904
+ @cursor += yanked_width
905
+ @cursor_max += yanked_width
906
+ @byte_pointer += yanked.bytesize
907
+ end
908
+ end
909
+
910
+ private def em_yank_pop(key)
911
+ yanked, prev_yank = @kill_ring.yank_pop
912
+ if yanked
913
+ prev_yank_width = calculate_width(prev_yank)
914
+ @cursor -= prev_yank_width
915
+ @cursor_max -= prev_yank_width
916
+ @byte_pointer -= prev_yank.bytesize
917
+ @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
918
+ @line = byteinsert(@line, @byte_pointer, yanked)
919
+ yanked_width = calculate_width(yanked)
920
+ @cursor += yanked_width
921
+ @cursor_max += yanked_width
922
+ @byte_pointer += yanked.bytesize
923
+ end
924
+ end
925
+
926
+ private def ed_clear_screen(key)
927
+ @cleared = true
928
+ end
929
+
930
+ private def em_next_word(key)
931
+ if @line.bytesize > @byte_pointer
932
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
933
+ @byte_pointer += byte_size
934
+ @cursor += width
935
+ end
936
+ end
937
+
938
+ private def ed_prev_word(key)
939
+ if @byte_pointer > 0
940
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
941
+ @byte_pointer -= byte_size
942
+ @cursor -= width
943
+ end
944
+ end
945
+
946
+ private def em_delete_next_word(key)
947
+ if @line.bytesize > @byte_pointer
948
+ byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
949
+ @line, word = byteslice!(@line, @byte_pointer, byte_size)
950
+ @kill_ring.append(word)
951
+ @cursor_max -= width
952
+ end
953
+ end
954
+
955
+ private def ed_delete_prev_word(key)
956
+ if @byte_pointer > 0
957
+ byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
958
+ @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
959
+ @kill_ring.append(word, true)
960
+ @byte_pointer -= byte_size
961
+ @cursor -= width
962
+ @cursor_max -= width
963
+ end
964
+ end
965
+
966
+ private def ed_transpose_chars(key)
967
+ if @byte_pointer > 0
968
+ if @cursor_max > @cursor
969
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
970
+ mbchar = @line.byteslice(@byte_pointer, byte_size)
971
+ width = Reline::Unicode.get_mbchar_width(mbchar)
972
+ @cursor += width
973
+ @byte_pointer += byte_size
974
+ end
975
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
976
+ if (@byte_pointer - back1_byte_size) > 0
977
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
978
+ back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
979
+ @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
980
+ @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
981
+ end
982
+ end
983
+ end
984
+
985
+ private def em_capitol_case(key)
986
+ if @line.bytesize > @byte_pointer
987
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
988
+ before = @line.byteslice(0, @byte_pointer)
989
+ after = @line.byteslice((@byte_pointer + byte_size)..-1)
990
+ @line = before + new_str + after
991
+ @byte_pointer += new_str.bytesize
992
+ @cursor += calculate_width(new_str)
993
+ end
994
+ end
995
+
996
+ private def em_lower_case(key)
997
+ if @line.bytesize > @byte_pointer
998
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
999
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
1000
+ mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
1001
+ }.join
1002
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
1003
+ @line = @line.byteslice(0, @byte_pointer) + part
1004
+ @byte_pointer = @line.bytesize
1005
+ @cursor = calculate_width(@line)
1006
+ @cursor_max = @cursor + calculate_width(rest)
1007
+ @line += rest
1008
+ end
1009
+ end
1010
+
1011
+ private def em_upper_case(key)
1012
+ if @line.bytesize > @byte_pointer
1013
+ byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
1014
+ part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
1015
+ mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
1016
+ }.join
1017
+ rest = @line.byteslice((@byte_pointer + byte_size)..-1)
1018
+ @line = @line.byteslice(0, @byte_pointer) + part
1019
+ @byte_pointer = @line.bytesize
1020
+ @cursor = calculate_width(@line)
1021
+ @cursor_max = @cursor + calculate_width(rest)
1022
+ @line += rest
1023
+ end
1024
+ end
1025
+
1026
+ private def em_kill_region(key)
1027
+ if @byte_pointer > 0
1028
+ byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
1029
+ @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
1030
+ @byte_pointer -= byte_size
1031
+ @cursor -= width
1032
+ @cursor_max -= width
1033
+ @kill_ring.append(deleted)
1034
+ end
1035
+ end
1036
+
1037
+ private def copy_for_vi(text)
1038
+ if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
1039
+ @vi_clipboard = text
1040
+ end
1041
+ end
1042
+
1043
+ private def vi_insert(key)
1044
+ @config.editing_mode = :vi_insert
1045
+ end
1046
+
1047
+ private def vi_add(key)
1048
+ @config.editing_mode = :vi_insert
1049
+ ed_next_char(key)
1050
+ end
1051
+
1052
+ private def vi_command_mode(key)
1053
+ ed_prev_char(key)
1054
+ @config.editing_mode = :vi_command
1055
+ end
1056
+
1057
+ private def vi_next_word(key, arg: 1)
1058
+ if @line.bytesize > @byte_pointer
1059
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
1060
+ @byte_pointer += byte_size
1061
+ @cursor += width
1062
+ end
1063
+ arg -= 1
1064
+ vi_next_word(key, arg: arg) if arg > 0
1065
+ end
1066
+
1067
+ private def vi_prev_word(key, arg: 1)
1068
+ if @byte_pointer > 0
1069
+ byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
1070
+ @byte_pointer -= byte_size
1071
+ @cursor -= width
1072
+ end
1073
+ arg -= 1
1074
+ vi_prev_word(key, arg: arg) if arg > 0
1075
+ end
1076
+
1077
+ private def vi_end_word(key, arg: 1)
1078
+ if @line.bytesize > @byte_pointer
1079
+ byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1080
+ @byte_pointer += byte_size
1081
+ @cursor += width
1082
+ end
1083
+ arg -= 1
1084
+ vi_end_word(key, arg: arg) if arg > 0
1085
+ end
1086
+
1087
+ private def vi_next_big_word(key, arg: 1)
1088
+ if @line.bytesize > @byte_pointer
1089
+ byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
1090
+ @byte_pointer += byte_size
1091
+ @cursor += width
1092
+ end
1093
+ arg -= 1
1094
+ vi_next_big_word(key, arg: arg) if arg > 0
1095
+ end
1096
+
1097
+ private def vi_prev_big_word(key, arg: 1)
1098
+ if @byte_pointer > 0
1099
+ byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
1100
+ @byte_pointer -= byte_size
1101
+ @cursor -= width
1102
+ end
1103
+ arg -= 1
1104
+ vi_prev_big_word(key, arg: arg) if arg > 0
1105
+ end
1106
+
1107
+ private def vi_end_big_word(key, arg: 1)
1108
+ if @line.bytesize > @byte_pointer
1109
+ byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1110
+ @byte_pointer += byte_size
1111
+ @cursor += width
1112
+ end
1113
+ arg -= 1
1114
+ vi_end_big_word(key, arg: arg) if arg > 0
1115
+ end
1116
+
1117
+ private def vi_delete_prev_char(key)
1118
+ if @is_multiline and @cursor == 0 and @line_index > 0
1119
+ @buffer_of_lines[@line_index] = @line
1120
+ @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
1121
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1122
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1123
+ @line_index -= 1
1124
+ @line = @buffer_of_lines[@line_index]
1125
+ @cursor_max = calculate_width(@line)
1126
+ @rerender_all = true
1127
+ elsif @cursor > 0
1128
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1129
+ @byte_pointer -= byte_size
1130
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1131
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1132
+ @cursor -= width
1133
+ @cursor_max -= width
1134
+ end
1135
+ end
1136
+
1137
+ private def ed_delete_prev_char(key, arg: 1)
1138
+ deleted = ''
1139
+ arg.times do
1140
+ if @cursor > 0
1141
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1142
+ @byte_pointer -= byte_size
1143
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1144
+ deleted.prepend(mbchar)
1145
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1146
+ @cursor -= width
1147
+ @cursor_max -= width
1148
+ end
1149
+ end
1150
+ copy_for_vi(deleted)
1151
+ end
1152
+
1153
+ private def vi_zero(key)
1154
+ @byte_pointer = 0
1155
+ @cursor = 0
1156
+ end
1157
+
1158
+ private def vi_change_meta(key)
1159
+ end
1160
+
1161
+ private def vi_delete_meta(key)
1162
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
1163
+ if byte_pointer_diff > 0
1164
+ @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
1165
+ elsif byte_pointer_diff < 0
1166
+ @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
1167
+ end
1168
+ copy_for_vi(cut)
1169
+ @cursor += cursor_diff if cursor_diff < 0
1170
+ @cursor_max -= cursor_diff.abs
1171
+ @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
1172
+ }
1173
+ end
1174
+
1175
+ private def vi_yank(key)
1176
+ end
1177
+
1178
+ private def vi_end_of_transmission(key)
1179
+ if @line.empty?
1180
+ @line = nil
1181
+ finish
1182
+ end
1183
+ end
1184
+
1185
+ private def vi_list_or_eof(key)
1186
+ if @line.empty?
1187
+ @line = nil
1188
+ finish
1189
+ else
1190
+ # TODO: list
1191
+ end
1192
+ end
1193
+
1194
+ private def ed_delete_next_char(key, arg: 1)
1195
+ unless @line.empty?
1196
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1197
+ @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1198
+ copy_for_vi(mbchar)
1199
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1200
+ @cursor_max -= width
1201
+ if @cursor > 0 and @cursor >= @cursor_max
1202
+ @byte_pointer -= byte_size
1203
+ @cursor -= width
1204
+ end
1205
+ end
1206
+ arg -= 1
1207
+ ed_delete_next_char(key, arg: arg) if arg > 0
1208
+ end
1209
+
1210
+ private def vi_to_history_line(key)
1211
+ if Reline::HISTORY.empty?
1212
+ return
1213
+ end
1214
+ if @history_pointer.nil?
1215
+ @history_pointer = 0
1216
+ @line_backup_in_history = @line
1217
+ @line = Reline::HISTORY[@history_pointer]
1218
+ @cursor_max = calculate_width(@line)
1219
+ @cursor = 0
1220
+ @byte_pointer = 0
1221
+ elsif @history_pointer.zero?
1222
+ return
1223
+ else
1224
+ Reline::HISTORY[@history_pointer] = @line
1225
+ @history_pointer = 0
1226
+ @line = Reline::HISTORY[@history_pointer]
1227
+ @cursor_max = calculate_width(@line)
1228
+ @cursor = 0
1229
+ @byte_pointer = 0
1230
+ end
1231
+ end
1232
+
1233
+ private def vi_histedit(key)
1234
+ path = Tempfile.open { |fp|
1235
+ fp.write @line
1236
+ fp.path
1237
+ }
1238
+ system("#{ENV['EDITOR']} #{path}")
1239
+ @line = Pathname.new(path).read
1240
+ finish
1241
+ end
1242
+
1243
+ private def vi_paste_prev(key, arg: 1)
1244
+ if @vi_clipboard.size > 0
1245
+ @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
1246
+ @cursor_max += calculate_width(@vi_clipboard)
1247
+ cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
1248
+ @cursor += calculate_width(cursor_point)
1249
+ @byte_pointer += cursor_point.bytesize
1250
+ end
1251
+ arg -= 1
1252
+ vi_paste_prev(key, arg: arg) if arg > 0
1253
+ end
1254
+
1255
+ private def vi_paste_next(key, arg: 1)
1256
+ if @vi_clipboard.size > 0
1257
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1258
+ @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
1259
+ @cursor_max += calculate_width(@vi_clipboard)
1260
+ @cursor += calculate_width(@vi_clipboard)
1261
+ @byte_pointer += @vi_clipboard.bytesize
1262
+ end
1263
+ arg -= 1
1264
+ vi_paste_next(key, arg: arg) if arg > 0
1265
+ end
1266
+
1267
+ private def ed_argument_digit(key)
1268
+ if @vi_arg.nil?
1269
+ unless key.chr.to_i.zero?
1270
+ @vi_arg = key.chr.to_i
1271
+ end
1272
+ else
1273
+ @vi_arg = @vi_arg * 10 + key.chr.to_i
1274
+ end
1275
+ end
1276
+
1277
+ private def vi_to_column(key, arg: 0)
1278
+ @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
1279
+ # total has [byte_size, cursor]
1280
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
1281
+ if (total.last + mbchar_width) >= arg
1282
+ break total
1283
+ elsif (total.last + mbchar_width) >= @cursor_max
1284
+ break total
1285
+ else
1286
+ total = [total.first + gc.bytesize, total.last + mbchar_width]
1287
+ total
1288
+ end
1289
+ }
1290
+ end
1291
+
1292
+ private def vi_replace_char(key, arg: 1)
1293
+ @waiting_proc = ->(key) {
1294
+ if arg == 1
1295
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1296
+ before = @line.byteslice(0, @byte_pointer)
1297
+ remaining_point = @byte_pointer + byte_size
1298
+ after = @line.byteslice(remaining_point, @line.size - remaining_point)
1299
+ @line = before + key.chr + after
1300
+ @cursor_max = calculate_width(@line)
1301
+ @waiting_proc = nil
1302
+ elsif arg > 1
1303
+ byte_size = 0
1304
+ arg.times do
1305
+ byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
1306
+ end
1307
+ before = @line.byteslice(0, @byte_pointer)
1308
+ remaining_point = @byte_pointer + byte_size
1309
+ after = @line.byteslice(remaining_point, @line.size - remaining_point)
1310
+ replaced = key.chr * arg
1311
+ @line = before + replaced + after
1312
+ @byte_pointer += replaced.bytesize
1313
+ @cursor += calculate_width(replaced)
1314
+ @cursor_max = calculate_width(@line)
1315
+ @waiting_proc = nil
1316
+ end
1317
+ }
1318
+ end
1319
+
1320
+ private def vi_next_char(key, arg: 1)
1321
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
1322
+ end
1323
+
1324
+ private def search_next_char(key, arg)
1325
+ if key.instance_of?(String)
1326
+ inputed_char = key
1327
+ else
1328
+ inputed_char = key.chr
1329
+ end
1330
+ total = nil
1331
+ found = false
1332
+ @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
1333
+ # total has [byte_size, cursor]
1334
+ unless total
1335
+ # skip cursor point
1336
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1337
+ total = [mbchar.bytesize, width]
1338
+ else
1339
+ if inputed_char == mbchar
1340
+ arg -= 1
1341
+ if arg.zero?
1342
+ found = true
1343
+ break
1344
+ end
1345
+ end
1346
+ width = Reline::Unicode.get_mbchar_width(mbchar)
1347
+ total = [total.first + mbchar.bytesize, total.last + width]
1348
+ end
1349
+ end
1350
+ if found and total
1351
+ byte_size, width = total
1352
+ @byte_pointer += byte_size
1353
+ @cursor += width
1354
+ end
1355
+ @waiting_proc = nil
1356
+ end
1357
+ end