reline 0.5.10 → 0.6.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.
@@ -54,6 +54,22 @@ class Reline::Unicode
54
54
  }.join
55
55
  end
56
56
 
57
+ def self.safe_encode(str, encoding)
58
+ # Reline only supports utf-8 convertible string.
59
+ converted = str.encode(encoding, invalid: :replace, undef: :replace)
60
+ return converted if str.encoding == Encoding::UTF_8 || converted.encoding == Encoding::UTF_8 || converted.ascii_only?
61
+
62
+ # This code is essentially doing the same thing as
63
+ # `str.encode(utf8, **replace_options).encode(encoding, **replace_options)`
64
+ # but also avoids unneccesary irreversible encoding conversion.
65
+ converted.gsub(/\X/) do |c|
66
+ c.encode(Encoding::UTF_8)
67
+ c
68
+ rescue Encoding::UndefinedConversionError
69
+ '?'
70
+ end
71
+ end
72
+
57
73
  require 'reline/unicode/east_asian_width'
58
74
 
59
75
  def self.get_mbchar_width(mbchar)
@@ -105,9 +121,14 @@ class Reline::Unicode
105
121
  end
106
122
  end
107
123
 
108
- def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0)
124
+ # This method is used by IRB
125
+ def self.split_by_width(str, max_width)
126
+ lines = split_line_by_width(str, max_width)
127
+ [lines, lines.size]
128
+ end
129
+
130
+ def self.split_line_by_width(str, max_width, encoding = str.encoding, offset: 0)
109
131
  lines = [String.new(encoding: encoding)]
110
- height = 1
111
132
  width = offset
112
133
  rest = str.encode(Encoding::UTF_8)
113
134
  in_zero_width = false
@@ -116,10 +137,8 @@ class Reline::Unicode
116
137
  case
117
138
  when non_printing_start
118
139
  in_zero_width = true
119
- lines.last << NON_PRINTING_START
120
140
  when non_printing_end
121
141
  in_zero_width = false
122
- lines.last << NON_PRINTING_END
123
142
  when csi
124
143
  lines.last << csi
125
144
  unless in_zero_width
@@ -131,15 +150,13 @@ class Reline::Unicode
131
150
  end
132
151
  when osc
133
152
  lines.last << osc
134
- seq << osc
153
+ seq << osc unless in_zero_width
135
154
  when gc
136
155
  unless in_zero_width
137
156
  mbchar_width = get_mbchar_width(gc)
138
157
  if (width += mbchar_width) > max_width
139
158
  width = mbchar_width
140
- lines << nil
141
159
  lines << seq.dup
142
- height += 1
143
160
  end
144
161
  end
145
162
  lines.last << gc
@@ -147,11 +164,13 @@ class Reline::Unicode
147
164
  end
148
165
  # The cursor moves to next line in first
149
166
  if width == max_width
150
- lines << nil
151
167
  lines << String.new(encoding: encoding)
152
- height += 1
153
168
  end
154
- [lines, height]
169
+ lines
170
+ end
171
+
172
+ def self.strip_non_printing_start_end(prompt)
173
+ prompt.gsub(/\x01([^\x02]*)(?:\x02|\z)/) { $1 }
155
174
  end
156
175
 
157
176
  # Take a chunk of a String cut by width with escape sequences.
@@ -173,10 +192,8 @@ class Reline::Unicode
173
192
  case
174
193
  when non_printing_start
175
194
  in_zero_width = true
176
- chunk << NON_PRINTING_START
177
195
  when non_printing_end
178
196
  in_zero_width = false
179
- chunk << NON_PRINTING_END
180
197
  when csi
181
198
  has_csi = true
182
199
  chunk << csi
@@ -245,427 +262,154 @@ class Reline::Unicode
245
262
  end
246
263
 
247
264
  def self.em_forward_word(line, byte_pointer)
248
- width = 0
249
- byte_size = 0
250
- while line.bytesize > (byte_pointer + byte_size)
251
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
252
- mbchar = line.byteslice(byte_pointer + byte_size, size)
253
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
254
- width += get_mbchar_width(mbchar)
255
- byte_size += size
256
- end
257
- while line.bytesize > (byte_pointer + byte_size)
258
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
259
- mbchar = line.byteslice(byte_pointer + byte_size, size)
260
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
261
- width += get_mbchar_width(mbchar)
262
- byte_size += size
263
- end
264
- [byte_size, width]
265
+ gcs = line.byteslice(byte_pointer..).grapheme_clusters
266
+ nonwords = gcs.take_while { |c| !word_character?(c) }
267
+ words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) }
268
+ nonwords.sum(&:bytesize) + words.sum(&:bytesize)
265
269
  end
266
270
 
267
271
  def self.em_forward_word_with_capitalization(line, byte_pointer)
268
- width = 0
269
- byte_size = 0
270
- new_str = String.new
271
- while line.bytesize > (byte_pointer + byte_size)
272
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
273
- mbchar = line.byteslice(byte_pointer + byte_size, size)
274
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
275
- new_str += mbchar
276
- width += get_mbchar_width(mbchar)
277
- byte_size += size
278
- end
279
- first = true
280
- while line.bytesize > (byte_pointer + byte_size)
281
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
282
- mbchar = line.byteslice(byte_pointer + byte_size, size)
283
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
284
- if first
285
- new_str += mbchar.upcase
286
- first = false
287
- else
288
- new_str += mbchar.downcase
289
- end
290
- width += get_mbchar_width(mbchar)
291
- byte_size += size
292
- end
293
- [byte_size, width, new_str]
272
+ gcs = line.byteslice(byte_pointer..).grapheme_clusters
273
+ nonwords = gcs.take_while { |c| !word_character?(c) }
274
+ words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) }
275
+ [nonwords.sum(&:bytesize) + words.sum(&:bytesize), nonwords.join + words.join.capitalize]
294
276
  end
295
277
 
296
278
  def self.em_backward_word(line, byte_pointer)
297
- width = 0
298
- byte_size = 0
299
- while 0 < (byte_pointer - byte_size)
300
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
301
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
302
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
303
- width += get_mbchar_width(mbchar)
304
- byte_size += size
305
- end
306
- while 0 < (byte_pointer - byte_size)
307
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
308
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
309
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
310
- width += get_mbchar_width(mbchar)
311
- byte_size += size
312
- end
313
- [byte_size, width]
279
+ gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse
280
+ nonwords = gcs.take_while { |c| !word_character?(c) }
281
+ words = gcs.drop(nonwords.size).take_while { |c| word_character?(c) }
282
+ nonwords.sum(&:bytesize) + words.sum(&:bytesize)
314
283
  end
315
284
 
316
285
  def self.em_big_backward_word(line, byte_pointer)
317
- width = 0
318
- byte_size = 0
319
- while 0 < (byte_pointer - byte_size)
320
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
321
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
322
- break if mbchar =~ /\S/
323
- width += get_mbchar_width(mbchar)
324
- byte_size += size
325
- end
326
- while 0 < (byte_pointer - byte_size)
327
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
328
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
329
- break if mbchar =~ /\s/
330
- width += get_mbchar_width(mbchar)
331
- byte_size += size
332
- end
333
- [byte_size, width]
286
+ gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse
287
+ spaces = gcs.take_while { |c| space_character?(c) }
288
+ nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) }
289
+ spaces.sum(&:bytesize) + nonspaces.sum(&:bytesize)
334
290
  end
335
291
 
336
292
  def self.ed_transpose_words(line, byte_pointer)
337
- right_word_start = nil
338
- size = get_next_mbchar_size(line, byte_pointer)
339
- mbchar = line.byteslice(byte_pointer, size)
340
- if size.zero?
341
- # ' aaa bbb [cursor]'
342
- byte_size = 0
343
- while 0 < (byte_pointer + byte_size)
344
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
345
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
346
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
347
- byte_size -= size
348
- end
349
- while 0 < (byte_pointer + byte_size)
350
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
351
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
352
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
353
- byte_size -= size
354
- end
355
- right_word_start = byte_pointer + byte_size
356
- byte_size = 0
357
- while line.bytesize > (byte_pointer + byte_size)
358
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
359
- mbchar = line.byteslice(byte_pointer + byte_size, size)
360
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
361
- byte_size += size
362
- end
363
- after_start = byte_pointer + byte_size
364
- elsif mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
365
- # ' aaa bb[cursor]b'
366
- byte_size = 0
367
- while 0 < (byte_pointer + byte_size)
368
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
369
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
370
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
371
- byte_size -= size
372
- end
373
- right_word_start = byte_pointer + byte_size
374
- byte_size = 0
375
- while line.bytesize > (byte_pointer + byte_size)
376
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
377
- mbchar = line.byteslice(byte_pointer + byte_size, size)
378
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
379
- byte_size += size
380
- end
381
- after_start = byte_pointer + byte_size
382
- else
383
- byte_size = 0
384
- while (line.bytesize - 1) > (byte_pointer + byte_size)
385
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
386
- mbchar = line.byteslice(byte_pointer + byte_size, size)
387
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
388
- byte_size += size
389
- end
390
- if (byte_pointer + byte_size) == (line.bytesize - 1)
391
- # ' aaa bbb [cursor] '
392
- after_start = line.bytesize
393
- while 0 < (byte_pointer + byte_size)
394
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
395
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
396
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
397
- byte_size -= size
398
- end
399
- while 0 < (byte_pointer + byte_size)
400
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
401
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
402
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
403
- byte_size -= size
404
- end
405
- right_word_start = byte_pointer + byte_size
406
- else
407
- # ' aaa [cursor] bbb '
408
- right_word_start = byte_pointer + byte_size
409
- while line.bytesize > (byte_pointer + byte_size)
410
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
411
- mbchar = line.byteslice(byte_pointer + byte_size, size)
412
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
413
- byte_size += size
414
- end
415
- after_start = byte_pointer + byte_size
416
- end
417
- end
418
- byte_size = right_word_start - byte_pointer
419
- while 0 < (byte_pointer + byte_size)
420
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
421
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
422
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
423
- byte_size -= size
424
- end
425
- middle_start = byte_pointer + byte_size
426
- byte_size = middle_start - byte_pointer
427
- while 0 < (byte_pointer + byte_size)
428
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
429
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
430
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
431
- byte_size -= size
293
+ gcs = line.byteslice(0, byte_pointer).grapheme_clusters
294
+ pos = gcs.size
295
+ gcs += line.byteslice(byte_pointer..).grapheme_clusters
296
+ pos += 1 while pos < gcs.size && !word_character?(gcs[pos])
297
+ if pos == gcs.size # 'aaa bbb [cursor] '
298
+ pos -= 1 while pos > 0 && !word_character?(gcs[pos - 1])
299
+ second_word_end = gcs.size
300
+ else # 'aaa [cursor]bbb'
301
+ pos += 1 while pos < gcs.size && word_character?(gcs[pos])
302
+ second_word_end = pos
303
+ end
304
+ pos -= 1 while pos > 0 && word_character?(gcs[pos - 1])
305
+ second_word_start = pos
306
+ pos -= 1 while pos > 0 && !word_character?(gcs[pos - 1])
307
+ first_word_end = pos
308
+ pos -= 1 while pos > 0 && word_character?(gcs[pos - 1])
309
+ first_word_start = pos
310
+
311
+ [first_word_start, first_word_end, second_word_start, second_word_end].map do |idx|
312
+ gcs.take(idx).sum(&:bytesize)
432
313
  end
433
- left_word_start = byte_pointer + byte_size
434
- [left_word_start, middle_start, right_word_start, after_start]
435
314
  end
436
315
 
437
316
  def self.vi_big_forward_word(line, byte_pointer)
438
- width = 0
439
- byte_size = 0
440
- while (line.bytesize - 1) > (byte_pointer + byte_size)
441
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
442
- mbchar = line.byteslice(byte_pointer + byte_size, size)
443
- break if mbchar =~ /\s/
444
- width += get_mbchar_width(mbchar)
445
- byte_size += size
446
- end
447
- while (line.bytesize - 1) > (byte_pointer + byte_size)
448
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
449
- mbchar = line.byteslice(byte_pointer + byte_size, size)
450
- break if mbchar =~ /\S/
451
- width += get_mbchar_width(mbchar)
452
- byte_size += size
453
- end
454
- [byte_size, width]
317
+ gcs = line.byteslice(byte_pointer..).grapheme_clusters
318
+ nonspaces = gcs.take_while { |c| !space_character?(c) }
319
+ spaces = gcs.drop(nonspaces.size).take_while { |c| space_character?(c) }
320
+ nonspaces.sum(&:bytesize) + spaces.sum(&:bytesize)
455
321
  end
456
322
 
457
323
  def self.vi_big_forward_end_word(line, byte_pointer)
458
- if (line.bytesize - 1) > byte_pointer
459
- size = get_next_mbchar_size(line, byte_pointer)
460
- mbchar = line.byteslice(byte_pointer, size)
461
- width = get_mbchar_width(mbchar)
462
- byte_size = size
463
- else
464
- return [0, 0]
465
- end
466
- while (line.bytesize - 1) > (byte_pointer + byte_size)
467
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
468
- mbchar = line.byteslice(byte_pointer + byte_size, size)
469
- break if mbchar =~ /\S/
470
- width += get_mbchar_width(mbchar)
471
- byte_size += size
472
- end
473
- prev_width = width
474
- prev_byte_size = byte_size
475
- while line.bytesize > (byte_pointer + byte_size)
476
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
477
- mbchar = line.byteslice(byte_pointer + byte_size, size)
478
- break if mbchar =~ /\s/
479
- prev_width = width
480
- prev_byte_size = byte_size
481
- width += get_mbchar_width(mbchar)
482
- byte_size += size
483
- end
484
- [prev_byte_size, prev_width]
324
+ gcs = line.byteslice(byte_pointer..).grapheme_clusters
325
+ first = gcs.shift(1)
326
+ spaces = gcs.take_while { |c| space_character?(c) }
327
+ nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) }
328
+ matched = spaces + nonspaces
329
+ matched.pop
330
+ first.sum(&:bytesize) + matched.sum(&:bytesize)
485
331
  end
486
332
 
487
333
  def self.vi_big_backward_word(line, byte_pointer)
488
- width = 0
489
- byte_size = 0
490
- while 0 < (byte_pointer - byte_size)
491
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
492
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
493
- break if mbchar =~ /\S/
494
- width += get_mbchar_width(mbchar)
495
- byte_size += size
496
- end
497
- while 0 < (byte_pointer - byte_size)
498
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
499
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
500
- break if mbchar =~ /\s/
501
- width += get_mbchar_width(mbchar)
502
- byte_size += size
503
- end
504
- [byte_size, width]
334
+ gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse
335
+ spaces = gcs.take_while { |c| space_character?(c) }
336
+ nonspaces = gcs.drop(spaces.size).take_while { |c| !space_character?(c) }
337
+ spaces.sum(&:bytesize) + nonspaces.sum(&:bytesize)
505
338
  end
506
339
 
507
340
  def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false)
508
- if line.bytesize > byte_pointer
509
- size = get_next_mbchar_size(line, byte_pointer)
510
- mbchar = line.byteslice(byte_pointer, size)
511
- if mbchar =~ /\w/
512
- started_by = :word
513
- elsif mbchar =~ /\s/
514
- started_by = :space
341
+ gcs = line.byteslice(byte_pointer..).grapheme_clusters
342
+ return 0 if gcs.empty?
343
+
344
+ c = gcs.first
345
+ matched =
346
+ if word_character?(c)
347
+ gcs.take_while { |c| word_character?(c) }
348
+ elsif space_character?(c)
349
+ gcs.take_while { |c| space_character?(c) }
515
350
  else
516
- started_by = :non_word_printable
517
- end
518
- width = get_mbchar_width(mbchar)
519
- byte_size = size
520
- else
521
- return [0, 0]
522
- end
523
- while line.bytesize > (byte_pointer + byte_size)
524
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
525
- mbchar = line.byteslice(byte_pointer + byte_size, size)
526
- case started_by
527
- when :word
528
- break if mbchar =~ /\W/
529
- when :space
530
- break if mbchar =~ /\S/
531
- when :non_word_printable
532
- break if mbchar =~ /\w|\s/
351
+ gcs.take_while { |c| !word_character?(c) && !space_character?(c) }
533
352
  end
534
- width += get_mbchar_width(mbchar)
535
- byte_size += size
536
- end
537
- return [byte_size, width] if drop_terminate_spaces
538
- while line.bytesize > (byte_pointer + byte_size)
539
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
540
- mbchar = line.byteslice(byte_pointer + byte_size, size)
541
- break if mbchar =~ /\S/
542
- width += get_mbchar_width(mbchar)
543
- byte_size += size
544
- end
545
- [byte_size, width]
353
+
354
+ return matched.sum(&:bytesize) if drop_terminate_spaces
355
+
356
+ spaces = gcs.drop(matched.size).take_while { |c| space_character?(c) }
357
+ matched.sum(&:bytesize) + spaces.sum(&:bytesize)
546
358
  end
547
359
 
548
360
  def self.vi_forward_end_word(line, byte_pointer)
549
- if (line.bytesize - 1) > byte_pointer
550
- size = get_next_mbchar_size(line, byte_pointer)
551
- mbchar = line.byteslice(byte_pointer, size)
552
- if mbchar =~ /\w/
553
- started_by = :word
554
- elsif mbchar =~ /\s/
555
- started_by = :space
556
- else
557
- started_by = :non_word_printable
558
- end
559
- width = get_mbchar_width(mbchar)
560
- byte_size = size
561
- else
562
- return [0, 0]
563
- end
564
- if (line.bytesize - 1) > (byte_pointer + byte_size)
565
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
566
- mbchar = line.byteslice(byte_pointer + byte_size, size)
567
- if mbchar =~ /\w/
568
- second = :word
569
- elsif mbchar =~ /\s/
570
- second = :space
571
- else
572
- second = :non_word_printable
573
- end
574
- second_width = get_mbchar_width(mbchar)
575
- second_byte_size = size
576
- else
577
- return [byte_size, width]
578
- end
579
- if second == :space
580
- width += second_width
581
- byte_size += second_byte_size
582
- while (line.bytesize - 1) > (byte_pointer + byte_size)
583
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
584
- mbchar = line.byteslice(byte_pointer + byte_size, size)
585
- if mbchar =~ /\S/
586
- if mbchar =~ /\w/
587
- started_by = :word
588
- else
589
- started_by = :non_word_printable
590
- end
591
- break
592
- end
593
- width += get_mbchar_width(mbchar)
594
- byte_size += size
595
- end
596
- else
597
- case [started_by, second]
598
- when [:word, :non_word_printable], [:non_word_printable, :word]
599
- started_by = second
600
- else
601
- width += second_width
602
- byte_size += second_byte_size
603
- started_by = second
604
- end
605
- end
606
- prev_width = width
607
- prev_byte_size = byte_size
608
- while line.bytesize > (byte_pointer + byte_size)
609
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
610
- mbchar = line.byteslice(byte_pointer + byte_size, size)
611
- case started_by
612
- when :word
613
- break if mbchar =~ /\W/
614
- when :non_word_printable
615
- break if mbchar =~ /[\w\s]/
616
- end
617
- prev_width = width
618
- prev_byte_size = byte_size
619
- width += get_mbchar_width(mbchar)
620
- byte_size += size
621
- end
622
- [prev_byte_size, prev_width]
361
+ gcs = line.byteslice(byte_pointer..).grapheme_clusters
362
+ return 0 if gcs.empty?
363
+ return gcs.first.bytesize if gcs.size == 1
364
+
365
+ start = gcs.shift
366
+ skips = [start]
367
+ if space_character?(start) || space_character?(gcs.first)
368
+ spaces = gcs.take_while { |c| space_character?(c) }
369
+ skips += spaces
370
+ gcs.shift(spaces.size)
371
+ end
372
+ start_with_word = word_character?(gcs.first)
373
+ matched = gcs.take_while { |c| start_with_word ? word_character?(c) : !word_character?(c) && !space_character?(c) }
374
+ matched.pop
375
+ skips.sum(&:bytesize) + matched.sum(&:bytesize)
623
376
  end
624
377
 
625
378
  def self.vi_backward_word(line, byte_pointer)
626
- width = 0
627
- byte_size = 0
628
- while 0 < (byte_pointer - byte_size)
629
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
630
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
631
- if mbchar =~ /\S/
632
- if mbchar =~ /\w/
633
- started_by = :word
634
- else
635
- started_by = :non_word_printable
636
- end
637
- break
638
- end
639
- width += get_mbchar_width(mbchar)
640
- byte_size += size
641
- end
642
- while 0 < (byte_pointer - byte_size)
643
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
644
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
645
- case started_by
646
- when :word
647
- break if mbchar =~ /\W/
648
- when :non_word_printable
649
- break if mbchar =~ /[\w\s]/
379
+ gcs = line.byteslice(0, byte_pointer).grapheme_clusters.reverse
380
+ spaces = gcs.take_while { |c| space_character?(c) }
381
+ gcs.shift(spaces.size)
382
+ start_with_word = word_character?(gcs.first)
383
+ matched = gcs.take_while { |c| start_with_word ? word_character?(c) : !word_character?(c) && !space_character?(c) }
384
+ spaces.sum(&:bytesize) + matched.sum(&:bytesize)
385
+ end
386
+
387
+ def self.common_prefix(list, ignore_case: false)
388
+ return '' if list.empty?
389
+
390
+ common_prefix_gcs = list.first.grapheme_clusters
391
+ list.each do |item|
392
+ gcs = item.grapheme_clusters
393
+ common_prefix_gcs = common_prefix_gcs.take_while.with_index do |gc, i|
394
+ ignore_case ? gc.casecmp?(gcs[i]) : gc == gcs[i]
650
395
  end
651
- width += get_mbchar_width(mbchar)
652
- byte_size += size
653
396
  end
654
- [byte_size, width]
397
+ common_prefix_gcs.join
655
398
  end
656
399
 
657
400
  def self.vi_first_print(line)
658
- width = 0
659
- byte_size = 0
660
- while (line.bytesize - 1) > byte_size
661
- size = get_next_mbchar_size(line, byte_size)
662
- mbchar = line.byteslice(byte_size, size)
663
- if mbchar =~ /\S/
664
- break
665
- end
666
- width += get_mbchar_width(mbchar)
667
- byte_size += size
668
- end
669
- [byte_size, width]
401
+ gcs = line.grapheme_clusters
402
+ spaces = gcs.take_while { |c| space_character?(c) }
403
+ spaces.sum(&:bytesize)
404
+ end
405
+
406
+ def self.word_character?(s)
407
+ s.encode(Encoding::UTF_8).match?(/\p{Word}/) if s
408
+ rescue Encoding::UndefinedConversionError
409
+ false
410
+ end
411
+
412
+ def self.space_character?(s)
413
+ s.match?(/\s/) if s
670
414
  end
671
415
  end
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.5.10'
2
+ VERSION = '0.6.0'
3
3
  end