reline 0.1.3

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