reline 0.0.4

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,527 @@
1
+ class Reline::Unicode
2
+ EscapedPairs = {
3
+ 0x00 => '^@',
4
+ 0x01 => '^A', # C-a
5
+ 0x02 => '^B',
6
+ 0x03 => '^C',
7
+ 0x04 => '^D',
8
+ 0x05 => '^E',
9
+ 0x06 => '^F',
10
+ 0x07 => '^G',
11
+ 0x08 => '^H', # Backspace
12
+ 0x09 => '^I',
13
+ 0x0A => '^J',
14
+ 0x0B => '^K',
15
+ 0x0C => '^L',
16
+ 0x0D => '^M', # Enter
17
+ 0x0E => '^N',
18
+ 0x0F => '^O',
19
+ 0x10 => '^P',
20
+ 0x11 => '^Q',
21
+ 0x12 => '^R',
22
+ 0x13 => '^S',
23
+ 0x14 => '^T',
24
+ 0x15 => '^U',
25
+ 0x16 => '^V',
26
+ 0x17 => '^W',
27
+ 0x18 => '^X',
28
+ 0x19 => '^Y',
29
+ 0x1A => '^Z', # C-z
30
+ 0x1B => '^[', # C-[ C-3
31
+ 0x1D => '^]', # C-]
32
+ 0x1E => '^^', # C-~ C-6
33
+ 0x1F => '^_', # C-_ C-7
34
+ 0x7F => '^?', # C-? C-8
35
+ }
36
+ EscapedChars = EscapedPairs.keys.map(&:chr)
37
+
38
+ def self.get_mbchar_byte_size_by_first_char(c)
39
+ # Checks UTF-8 character byte size
40
+ case c.ord
41
+ # 0b0xxxxxxx
42
+ when ->(code) { (code ^ 0b10000000).allbits?(0b10000000) } then 1
43
+ # 0b110xxxxx
44
+ when ->(code) { (code ^ 0b00100000).allbits?(0b11100000) } then 2
45
+ # 0b1110xxxx
46
+ when ->(code) { (code ^ 0b00010000).allbits?(0b11110000) } then 3
47
+ # 0b11110xxx
48
+ when ->(code) { (code ^ 0b00001000).allbits?(0b11111000) } then 4
49
+ # 0b111110xx
50
+ when ->(code) { (code ^ 0b00000100).allbits?(0b11111100) } then 5
51
+ # 0b1111110x
52
+ when ->(code) { (code ^ 0b00000010).allbits?(0b11111110) } then 6
53
+ # successor of mbchar
54
+ else 0
55
+ end
56
+ end
57
+
58
+ def self.escape_for_print(str)
59
+ str.chars.map! { |gr|
60
+ escaped = EscapedPairs[gr.ord]
61
+ if escaped && gr != -"\n" && gr != -"\t"
62
+ escaped
63
+ else
64
+ gr
65
+ end
66
+ }.join
67
+ end
68
+
69
+ def self.get_mbchar_width(mbchar)
70
+ case mbchar.encode(Encoding::UTF_8)
71
+ when *EscapedChars # ^ + char, such as ^M, ^H, ^[, ...
72
+ 2
73
+ when /^\u{2E3B}/ # THREE-EM DASH
74
+ 3
75
+ when /^\p{M}/
76
+ 0
77
+ when EastAsianWidth::TYPE_A
78
+ Reline.ambiguous_width
79
+ when EastAsianWidth::TYPE_F, EastAsianWidth::TYPE_W
80
+ 2
81
+ when EastAsianWidth::TYPE_H, EastAsianWidth::TYPE_NA, EastAsianWidth::TYPE_N
82
+ 1
83
+ else
84
+ nil
85
+ end
86
+ end
87
+
88
+ def self.get_next_mbchar_size(line, byte_pointer)
89
+ grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
90
+ grapheme ? grapheme.bytesize : 0
91
+ end
92
+
93
+ def self.get_prev_mbchar_size(line, byte_pointer)
94
+ if byte_pointer.zero?
95
+ 0
96
+ else
97
+ grapheme = line.byteslice(0..(byte_pointer - 1)).grapheme_clusters.last
98
+ grapheme ? grapheme.bytesize : 0
99
+ end
100
+ end
101
+
102
+ def self.em_forward_word(line, byte_pointer)
103
+ width = 0
104
+ byte_size = 0
105
+ while line.bytesize > (byte_pointer + byte_size)
106
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
107
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
108
+ break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
109
+ width += get_mbchar_width(mbchar)
110
+ byte_size += size
111
+ end
112
+ while line.bytesize > (byte_pointer + byte_size)
113
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
114
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
115
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
116
+ width += get_mbchar_width(mbchar)
117
+ byte_size += size
118
+ end
119
+ [byte_size, width]
120
+ end
121
+
122
+ def self.em_forward_word_with_capitalization(line, byte_pointer)
123
+ width = 0
124
+ byte_size = 0
125
+ new_str = String.new
126
+ while line.bytesize > (byte_pointer + byte_size)
127
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
128
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
129
+ break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
130
+ new_str += mbchar
131
+ width += get_mbchar_width(mbchar)
132
+ byte_size += size
133
+ end
134
+ first = true
135
+ while line.bytesize > (byte_pointer + byte_size)
136
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
137
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
138
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
139
+ if first
140
+ new_str += mbchar.upcase
141
+ first = false
142
+ else
143
+ new_str += mbchar.downcase
144
+ end
145
+ width += get_mbchar_width(mbchar)
146
+ byte_size += size
147
+ end
148
+ [byte_size, width, new_str]
149
+ end
150
+
151
+ def self.em_backward_word(line, byte_pointer)
152
+ width = 0
153
+ byte_size = 0
154
+ while 0 < (byte_pointer - byte_size)
155
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
156
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
157
+ break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
158
+ width += get_mbchar_width(mbchar)
159
+ byte_size += size
160
+ end
161
+ while 0 < (byte_pointer - byte_size)
162
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
163
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
164
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
165
+ width += get_mbchar_width(mbchar)
166
+ byte_size += size
167
+ end
168
+ [byte_size, width]
169
+ end
170
+
171
+ def self.em_big_backward_word(line, byte_pointer)
172
+ width = 0
173
+ byte_size = 0
174
+ while 0 < (byte_pointer - byte_size)
175
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
176
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
177
+ break if mbchar =~ /\S/
178
+ width += get_mbchar_width(mbchar)
179
+ byte_size += size
180
+ end
181
+ while 0 < (byte_pointer - byte_size)
182
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
183
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
184
+ break if mbchar =~ /\s/
185
+ width += get_mbchar_width(mbchar)
186
+ byte_size += size
187
+ end
188
+ [byte_size, width]
189
+ end
190
+
191
+ def self.ed_transpose_words(line, byte_pointer)
192
+ right_word_start = nil
193
+ size = get_next_mbchar_size(line, byte_pointer)
194
+ mbchar = line.byteslice(byte_pointer, size)
195
+ if size.zero?
196
+ # ' aaa bbb [cursor]'
197
+ byte_size = 0
198
+ while 0 < (byte_pointer + byte_size)
199
+ size = get_prev_mbchar_size(line, byte_pointer + byte_size)
200
+ mbchar = line.byteslice(byte_pointer + byte_size - size, size)
201
+ break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
202
+ byte_size -= size
203
+ end
204
+ while 0 < (byte_pointer + byte_size)
205
+ size = get_prev_mbchar_size(line, byte_pointer + byte_size)
206
+ mbchar = line.byteslice(byte_pointer + byte_size - size, size)
207
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
208
+ byte_size -= size
209
+ end
210
+ right_word_start = byte_pointer + byte_size
211
+ byte_size = 0
212
+ while line.bytesize > (byte_pointer + byte_size)
213
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
214
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
215
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
216
+ byte_size += size
217
+ end
218
+ after_start = byte_pointer + byte_size
219
+ elsif mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
220
+ # ' aaa bb[cursor]b'
221
+ byte_size = 0
222
+ while 0 < (byte_pointer + byte_size)
223
+ size = get_prev_mbchar_size(line, byte_pointer + byte_size)
224
+ mbchar = line.byteslice(byte_pointer + byte_size - size, size)
225
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
226
+ byte_size -= size
227
+ end
228
+ right_word_start = byte_pointer + byte_size
229
+ byte_size = 0
230
+ while line.bytesize > (byte_pointer + byte_size)
231
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
232
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
233
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
234
+ byte_size += size
235
+ end
236
+ after_start = byte_pointer + byte_size
237
+ else
238
+ byte_size = 0
239
+ while (line.bytesize - 1) > (byte_pointer + byte_size)
240
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
241
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
242
+ break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
243
+ byte_size += size
244
+ end
245
+ if (byte_pointer + byte_size) == (line.bytesize - 1)
246
+ # ' aaa bbb [cursor] '
247
+ after_start = line.bytesize
248
+ while 0 < (byte_pointer + byte_size)
249
+ size = get_prev_mbchar_size(line, byte_pointer + byte_size)
250
+ mbchar = line.byteslice(byte_pointer + byte_size - size, size)
251
+ break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
252
+ byte_size -= size
253
+ end
254
+ while 0 < (byte_pointer + byte_size)
255
+ size = get_prev_mbchar_size(line, byte_pointer + byte_size)
256
+ mbchar = line.byteslice(byte_pointer + byte_size - size, size)
257
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
258
+ byte_size -= size
259
+ end
260
+ right_word_start = byte_pointer + byte_size
261
+ else
262
+ # ' aaa [cursor] bbb '
263
+ right_word_start = byte_pointer + byte_size
264
+ while line.bytesize > (byte_pointer + byte_size)
265
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
266
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
267
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
268
+ byte_size += size
269
+ end
270
+ after_start = byte_pointer + byte_size
271
+ end
272
+ end
273
+ byte_size = right_word_start - byte_pointer
274
+ while 0 < (byte_pointer + byte_size)
275
+ size = get_prev_mbchar_size(line, byte_pointer + byte_size)
276
+ mbchar = line.byteslice(byte_pointer + byte_size - size, size)
277
+ break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
278
+ byte_size -= size
279
+ end
280
+ middle_start = byte_pointer + byte_size
281
+ byte_size = middle_start - byte_pointer
282
+ while 0 < (byte_pointer + byte_size)
283
+ size = get_prev_mbchar_size(line, byte_pointer + byte_size)
284
+ mbchar = line.byteslice(byte_pointer + byte_size - size, size)
285
+ break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
286
+ byte_size -= size
287
+ end
288
+ left_word_start = byte_pointer + byte_size
289
+ [left_word_start, middle_start, right_word_start, after_start]
290
+ end
291
+
292
+ def self.vi_big_forward_word(line, byte_pointer)
293
+ width = 0
294
+ byte_size = 0
295
+ while (line.bytesize - 1) > (byte_pointer + byte_size)
296
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
297
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
298
+ break if mbchar =~ /\s/
299
+ width += get_mbchar_width(mbchar)
300
+ byte_size += size
301
+ end
302
+ while (line.bytesize - 1) > (byte_pointer + byte_size)
303
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
304
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
305
+ break if mbchar =~ /\S/
306
+ width += get_mbchar_width(mbchar)
307
+ byte_size += size
308
+ end
309
+ [byte_size, width]
310
+ end
311
+
312
+ def self.vi_big_forward_end_word(line, byte_pointer)
313
+ if (line.bytesize - 1) > byte_pointer
314
+ size = get_next_mbchar_size(line, byte_pointer)
315
+ mbchar = line.byteslice(byte_pointer, size)
316
+ width = get_mbchar_width(mbchar)
317
+ byte_size = size
318
+ else
319
+ return [0, 0]
320
+ end
321
+ while (line.bytesize - 1) > (byte_pointer + byte_size)
322
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
323
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
324
+ break if mbchar =~ /\S/
325
+ width += get_mbchar_width(mbchar)
326
+ byte_size += size
327
+ end
328
+ prev_width = width
329
+ prev_byte_size = byte_size
330
+ while line.bytesize > (byte_pointer + byte_size)
331
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
332
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
333
+ break if mbchar =~ /\s/
334
+ prev_width = width
335
+ prev_byte_size = byte_size
336
+ width += get_mbchar_width(mbchar)
337
+ byte_size += size
338
+ end
339
+ [prev_byte_size, prev_width]
340
+ end
341
+
342
+ def self.vi_big_backward_word(line, byte_pointer)
343
+ width = 0
344
+ byte_size = 0
345
+ while 0 < (byte_pointer - byte_size)
346
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
347
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
348
+ break if mbchar =~ /\S/
349
+ width += get_mbchar_width(mbchar)
350
+ byte_size += size
351
+ end
352
+ while 0 < (byte_pointer - byte_size)
353
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
354
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
355
+ break if mbchar =~ /\s/
356
+ width += get_mbchar_width(mbchar)
357
+ byte_size += size
358
+ end
359
+ [byte_size, width]
360
+ end
361
+
362
+ def self.vi_forward_word(line, byte_pointer)
363
+ if (line.bytesize - 1) > byte_pointer
364
+ size = get_next_mbchar_size(line, byte_pointer)
365
+ mbchar = line.byteslice(byte_pointer, size)
366
+ if mbchar =~ /\w/
367
+ started_by = :word
368
+ elsif mbchar =~ /\s/
369
+ started_by = :space
370
+ else
371
+ started_by = :non_word_printable
372
+ end
373
+ width = get_mbchar_width(mbchar)
374
+ byte_size = size
375
+ else
376
+ return [0, 0]
377
+ end
378
+ while (line.bytesize - 1) > (byte_pointer + byte_size)
379
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
380
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
381
+ case started_by
382
+ when :word
383
+ break if mbchar =~ /\W/
384
+ when :space
385
+ break if mbchar =~ /\S/
386
+ when :non_word_printable
387
+ break if mbchar =~ /\w|\s/
388
+ end
389
+ width += get_mbchar_width(mbchar)
390
+ byte_size += size
391
+ end
392
+ while (line.bytesize - 1) > (byte_pointer + byte_size)
393
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
394
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
395
+ break if mbchar =~ /\S/
396
+ width += get_mbchar_width(mbchar)
397
+ byte_size += size
398
+ end
399
+ [byte_size, width]
400
+ end
401
+
402
+ def self.vi_forward_end_word(line, byte_pointer)
403
+ if (line.bytesize - 1) > byte_pointer
404
+ size = get_next_mbchar_size(line, byte_pointer)
405
+ mbchar = line.byteslice(byte_pointer, size)
406
+ if mbchar =~ /\w/
407
+ started_by = :word
408
+ elsif mbchar =~ /\s/
409
+ started_by = :space
410
+ else
411
+ started_by = :non_word_printable
412
+ end
413
+ width = get_mbchar_width(mbchar)
414
+ byte_size = size
415
+ else
416
+ return [0, 0]
417
+ end
418
+ if (line.bytesize - 1) > (byte_pointer + byte_size)
419
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
420
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
421
+ if mbchar =~ /\w/
422
+ second = :word
423
+ elsif mbchar =~ /\s/
424
+ second = :space
425
+ else
426
+ second = :non_word_printable
427
+ end
428
+ second_width = get_mbchar_width(mbchar)
429
+ second_byte_size = size
430
+ else
431
+ return [byte_size, width]
432
+ end
433
+ if second == :space
434
+ width += second_width
435
+ byte_size += second_byte_size
436
+ while (line.bytesize - 1) > (byte_pointer + byte_size)
437
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
438
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
439
+ if mbchar =~ /\S/
440
+ if mbchar =~ /\w/
441
+ started_by = :word
442
+ else
443
+ started_by = :non_word_printable
444
+ end
445
+ break
446
+ end
447
+ width += get_mbchar_width(mbchar)
448
+ byte_size += size
449
+ end
450
+ else
451
+ case [started_by, second]
452
+ when [:word, :non_word_printable], [:non_word_printable, :word]
453
+ started_by = second
454
+ else
455
+ width += second_width
456
+ byte_size += second_byte_size
457
+ started_by = second
458
+ end
459
+ end
460
+ prev_width = width
461
+ prev_byte_size = byte_size
462
+ while line.bytesize > (byte_pointer + byte_size)
463
+ size = get_next_mbchar_size(line, byte_pointer + byte_size)
464
+ mbchar = line.byteslice(byte_pointer + byte_size, size)
465
+ case started_by
466
+ when :word
467
+ break if mbchar =~ /\W/
468
+ when :non_word_printable
469
+ break if mbchar =~ /[\w\s]/
470
+ end
471
+ prev_width = width
472
+ prev_byte_size = byte_size
473
+ width += get_mbchar_width(mbchar)
474
+ byte_size += size
475
+ end
476
+ [prev_byte_size, prev_width]
477
+ end
478
+
479
+ def self.vi_backward_word(line, byte_pointer)
480
+ width = 0
481
+ byte_size = 0
482
+ while 0 < (byte_pointer - byte_size)
483
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
484
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
485
+ if mbchar =~ /\S/
486
+ if mbchar =~ /\w/
487
+ started_by = :word
488
+ else
489
+ started_by = :non_word_printable
490
+ end
491
+ break
492
+ end
493
+ width += get_mbchar_width(mbchar)
494
+ byte_size += size
495
+ end
496
+ while 0 < (byte_pointer - byte_size)
497
+ size = get_prev_mbchar_size(line, byte_pointer - byte_size)
498
+ mbchar = line.byteslice(byte_pointer - byte_size - size, size)
499
+ case started_by
500
+ when :word
501
+ break if mbchar =~ /\W/
502
+ when :non_word_printable
503
+ break if mbchar =~ /[\w\s]/
504
+ end
505
+ width += get_mbchar_width(mbchar)
506
+ byte_size += size
507
+ end
508
+ [byte_size, width]
509
+ end
510
+
511
+ def self.vi_first_print(line)
512
+ width = 0
513
+ byte_size = 0
514
+ while (line.bytesize - 1) > byte_size
515
+ size = get_next_mbchar_size(line, byte_size)
516
+ mbchar = line.byteslice(byte_size, size)
517
+ if mbchar =~ /\S/
518
+ break
519
+ end
520
+ width += get_mbchar_width(mbchar)
521
+ byte_size += size
522
+ end
523
+ [byte_size, width]
524
+ end
525
+ end
526
+
527
+ require 'reline/unicode/east_asian_width'