reline 0.5.11 → 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.
@@ -121,9 +121,14 @@ class Reline::Unicode
121
121
  end
122
122
  end
123
123
 
124
- 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)
125
131
  lines = [String.new(encoding: encoding)]
126
- height = 1
127
132
  width = offset
128
133
  rest = str.encode(Encoding::UTF_8)
129
134
  in_zero_width = false
@@ -151,9 +156,7 @@ class Reline::Unicode
151
156
  mbchar_width = get_mbchar_width(gc)
152
157
  if (width += mbchar_width) > max_width
153
158
  width = mbchar_width
154
- lines << nil
155
159
  lines << seq.dup
156
- height += 1
157
160
  end
158
161
  end
159
162
  lines.last << gc
@@ -161,11 +164,9 @@ class Reline::Unicode
161
164
  end
162
165
  # The cursor moves to next line in first
163
166
  if width == max_width
164
- lines << nil
165
167
  lines << String.new(encoding: encoding)
166
- height += 1
167
168
  end
168
- [lines, height]
169
+ lines
169
170
  end
170
171
 
171
172
  def self.strip_non_printing_start_end(prompt)
@@ -261,427 +262,154 @@ class Reline::Unicode
261
262
  end
262
263
 
263
264
  def self.em_forward_word(line, byte_pointer)
264
- width = 0
265
- byte_size = 0
266
- while line.bytesize > (byte_pointer + byte_size)
267
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
268
- mbchar = line.byteslice(byte_pointer + byte_size, size)
269
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
270
- width += get_mbchar_width(mbchar)
271
- byte_size += size
272
- end
273
- while line.bytesize > (byte_pointer + byte_size)
274
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
275
- mbchar = line.byteslice(byte_pointer + byte_size, size)
276
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
277
- width += get_mbchar_width(mbchar)
278
- byte_size += size
279
- end
280
- [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)
281
269
  end
282
270
 
283
271
  def self.em_forward_word_with_capitalization(line, byte_pointer)
284
- width = 0
285
- byte_size = 0
286
- new_str = String.new
287
- while line.bytesize > (byte_pointer + byte_size)
288
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
289
- mbchar = line.byteslice(byte_pointer + byte_size, size)
290
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
291
- new_str += mbchar
292
- width += get_mbchar_width(mbchar)
293
- byte_size += size
294
- end
295
- first = true
296
- while line.bytesize > (byte_pointer + byte_size)
297
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
298
- mbchar = line.byteslice(byte_pointer + byte_size, size)
299
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
300
- if first
301
- new_str += mbchar.upcase
302
- first = false
303
- else
304
- new_str += mbchar.downcase
305
- end
306
- width += get_mbchar_width(mbchar)
307
- byte_size += size
308
- end
309
- [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]
310
276
  end
311
277
 
312
278
  def self.em_backward_word(line, byte_pointer)
313
- width = 0
314
- byte_size = 0
315
- while 0 < (byte_pointer - byte_size)
316
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
317
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
318
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
319
- width += get_mbchar_width(mbchar)
320
- byte_size += size
321
- end
322
- while 0 < (byte_pointer - byte_size)
323
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
324
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
325
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
326
- width += get_mbchar_width(mbchar)
327
- byte_size += size
328
- end
329
- [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)
330
283
  end
331
284
 
332
285
  def self.em_big_backward_word(line, byte_pointer)
333
- width = 0
334
- byte_size = 0
335
- while 0 < (byte_pointer - byte_size)
336
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
337
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
338
- break if mbchar =~ /\S/
339
- width += get_mbchar_width(mbchar)
340
- byte_size += size
341
- end
342
- while 0 < (byte_pointer - byte_size)
343
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
344
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
345
- break if mbchar =~ /\s/
346
- width += get_mbchar_width(mbchar)
347
- byte_size += size
348
- end
349
- [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)
350
290
  end
351
291
 
352
292
  def self.ed_transpose_words(line, byte_pointer)
353
- right_word_start = nil
354
- size = get_next_mbchar_size(line, byte_pointer)
355
- mbchar = line.byteslice(byte_pointer, size)
356
- if size.zero?
357
- # ' aaa bbb [cursor]'
358
- byte_size = 0
359
- while 0 < (byte_pointer + byte_size)
360
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
361
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
362
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
363
- byte_size -= size
364
- end
365
- while 0 < (byte_pointer + byte_size)
366
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
367
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
368
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
369
- byte_size -= size
370
- end
371
- right_word_start = byte_pointer + byte_size
372
- byte_size = 0
373
- while line.bytesize > (byte_pointer + byte_size)
374
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
375
- mbchar = line.byteslice(byte_pointer + byte_size, size)
376
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
377
- byte_size += size
378
- end
379
- after_start = byte_pointer + byte_size
380
- elsif mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
381
- # ' aaa bb[cursor]b'
382
- byte_size = 0
383
- while 0 < (byte_pointer + byte_size)
384
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
385
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
386
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
387
- byte_size -= size
388
- end
389
- right_word_start = byte_pointer + byte_size
390
- byte_size = 0
391
- while line.bytesize > (byte_pointer + byte_size)
392
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
393
- mbchar = line.byteslice(byte_pointer + byte_size, size)
394
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
395
- byte_size += size
396
- end
397
- after_start = byte_pointer + byte_size
398
- else
399
- byte_size = 0
400
- while (line.bytesize - 1) > (byte_pointer + byte_size)
401
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
402
- mbchar = line.byteslice(byte_pointer + byte_size, size)
403
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
404
- byte_size += size
405
- end
406
- if (byte_pointer + byte_size) == (line.bytesize - 1)
407
- # ' aaa bbb [cursor] '
408
- after_start = line.bytesize
409
- while 0 < (byte_pointer + byte_size)
410
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
411
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
412
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
413
- byte_size -= size
414
- end
415
- while 0 < (byte_pointer + byte_size)
416
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
417
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
418
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
419
- byte_size -= size
420
- end
421
- right_word_start = byte_pointer + byte_size
422
- else
423
- # ' aaa [cursor] bbb '
424
- right_word_start = byte_pointer + byte_size
425
- while line.bytesize > (byte_pointer + byte_size)
426
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
427
- mbchar = line.byteslice(byte_pointer + byte_size, size)
428
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
429
- byte_size += size
430
- end
431
- after_start = byte_pointer + byte_size
432
- end
433
- end
434
- byte_size = right_word_start - byte_pointer
435
- while 0 < (byte_pointer + byte_size)
436
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
437
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
438
- break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
439
- byte_size -= size
440
- end
441
- middle_start = byte_pointer + byte_size
442
- byte_size = middle_start - byte_pointer
443
- while 0 < (byte_pointer + byte_size)
444
- size = get_prev_mbchar_size(line, byte_pointer + byte_size)
445
- mbchar = line.byteslice(byte_pointer + byte_size - size, size)
446
- break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
447
- 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)
448
313
  end
449
- left_word_start = byte_pointer + byte_size
450
- [left_word_start, middle_start, right_word_start, after_start]
451
314
  end
452
315
 
453
316
  def self.vi_big_forward_word(line, byte_pointer)
454
- width = 0
455
- byte_size = 0
456
- while (line.bytesize - 1) > (byte_pointer + byte_size)
457
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
458
- mbchar = line.byteslice(byte_pointer + byte_size, size)
459
- break if mbchar =~ /\s/
460
- width += get_mbchar_width(mbchar)
461
- byte_size += size
462
- end
463
- while (line.bytesize - 1) > (byte_pointer + byte_size)
464
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
465
- mbchar = line.byteslice(byte_pointer + byte_size, size)
466
- break if mbchar =~ /\S/
467
- width += get_mbchar_width(mbchar)
468
- byte_size += size
469
- end
470
- [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)
471
321
  end
472
322
 
473
323
  def self.vi_big_forward_end_word(line, byte_pointer)
474
- if (line.bytesize - 1) > byte_pointer
475
- size = get_next_mbchar_size(line, byte_pointer)
476
- mbchar = line.byteslice(byte_pointer, size)
477
- width = get_mbchar_width(mbchar)
478
- byte_size = size
479
- else
480
- return [0, 0]
481
- end
482
- while (line.bytesize - 1) > (byte_pointer + byte_size)
483
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
484
- mbchar = line.byteslice(byte_pointer + byte_size, size)
485
- break if mbchar =~ /\S/
486
- width += get_mbchar_width(mbchar)
487
- byte_size += size
488
- end
489
- prev_width = width
490
- prev_byte_size = byte_size
491
- while line.bytesize > (byte_pointer + byte_size)
492
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
493
- mbchar = line.byteslice(byte_pointer + byte_size, size)
494
- break if mbchar =~ /\s/
495
- prev_width = width
496
- prev_byte_size = byte_size
497
- width += get_mbchar_width(mbchar)
498
- byte_size += size
499
- end
500
- [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)
501
331
  end
502
332
 
503
333
  def self.vi_big_backward_word(line, byte_pointer)
504
- width = 0
505
- byte_size = 0
506
- while 0 < (byte_pointer - byte_size)
507
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
508
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
509
- break if mbchar =~ /\S/
510
- width += get_mbchar_width(mbchar)
511
- byte_size += size
512
- end
513
- while 0 < (byte_pointer - byte_size)
514
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
515
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
516
- break if mbchar =~ /\s/
517
- width += get_mbchar_width(mbchar)
518
- byte_size += size
519
- end
520
- [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)
521
338
  end
522
339
 
523
340
  def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false)
524
- if line.bytesize > byte_pointer
525
- size = get_next_mbchar_size(line, byte_pointer)
526
- mbchar = line.byteslice(byte_pointer, size)
527
- if mbchar =~ /\w/
528
- started_by = :word
529
- elsif mbchar =~ /\s/
530
- 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) }
531
350
  else
532
- started_by = :non_word_printable
533
- end
534
- width = get_mbchar_width(mbchar)
535
- byte_size = size
536
- else
537
- return [0, 0]
538
- end
539
- while line.bytesize > (byte_pointer + byte_size)
540
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
541
- mbchar = line.byteslice(byte_pointer + byte_size, size)
542
- case started_by
543
- when :word
544
- break if mbchar =~ /\W/
545
- when :space
546
- break if mbchar =~ /\S/
547
- when :non_word_printable
548
- break if mbchar =~ /\w|\s/
351
+ gcs.take_while { |c| !word_character?(c) && !space_character?(c) }
549
352
  end
550
- width += get_mbchar_width(mbchar)
551
- byte_size += size
552
- end
553
- return [byte_size, width] if drop_terminate_spaces
554
- while line.bytesize > (byte_pointer + byte_size)
555
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
556
- mbchar = line.byteslice(byte_pointer + byte_size, size)
557
- break if mbchar =~ /\S/
558
- width += get_mbchar_width(mbchar)
559
- byte_size += size
560
- end
561
- [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)
562
358
  end
563
359
 
564
360
  def self.vi_forward_end_word(line, byte_pointer)
565
- if (line.bytesize - 1) > byte_pointer
566
- size = get_next_mbchar_size(line, byte_pointer)
567
- mbchar = line.byteslice(byte_pointer, size)
568
- if mbchar =~ /\w/
569
- started_by = :word
570
- elsif mbchar =~ /\s/
571
- started_by = :space
572
- else
573
- started_by = :non_word_printable
574
- end
575
- width = get_mbchar_width(mbchar)
576
- byte_size = size
577
- else
578
- return [0, 0]
579
- end
580
- if (line.bytesize - 1) > (byte_pointer + byte_size)
581
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
582
- mbchar = line.byteslice(byte_pointer + byte_size, size)
583
- if mbchar =~ /\w/
584
- second = :word
585
- elsif mbchar =~ /\s/
586
- second = :space
587
- else
588
- second = :non_word_printable
589
- end
590
- second_width = get_mbchar_width(mbchar)
591
- second_byte_size = size
592
- else
593
- return [byte_size, width]
594
- end
595
- if second == :space
596
- width += second_width
597
- byte_size += second_byte_size
598
- while (line.bytesize - 1) > (byte_pointer + byte_size)
599
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
600
- mbchar = line.byteslice(byte_pointer + byte_size, size)
601
- if mbchar =~ /\S/
602
- if mbchar =~ /\w/
603
- started_by = :word
604
- else
605
- started_by = :non_word_printable
606
- end
607
- break
608
- end
609
- width += get_mbchar_width(mbchar)
610
- byte_size += size
611
- end
612
- else
613
- case [started_by, second]
614
- when [:word, :non_word_printable], [:non_word_printable, :word]
615
- started_by = second
616
- else
617
- width += second_width
618
- byte_size += second_byte_size
619
- started_by = second
620
- end
621
- end
622
- prev_width = width
623
- prev_byte_size = byte_size
624
- while line.bytesize > (byte_pointer + byte_size)
625
- size = get_next_mbchar_size(line, byte_pointer + byte_size)
626
- mbchar = line.byteslice(byte_pointer + byte_size, size)
627
- case started_by
628
- when :word
629
- break if mbchar =~ /\W/
630
- when :non_word_printable
631
- break if mbchar =~ /[\w\s]/
632
- end
633
- prev_width = width
634
- prev_byte_size = byte_size
635
- width += get_mbchar_width(mbchar)
636
- byte_size += size
637
- end
638
- [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)
639
376
  end
640
377
 
641
378
  def self.vi_backward_word(line, byte_pointer)
642
- width = 0
643
- byte_size = 0
644
- while 0 < (byte_pointer - byte_size)
645
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
646
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
647
- if mbchar =~ /\S/
648
- if mbchar =~ /\w/
649
- started_by = :word
650
- else
651
- started_by = :non_word_printable
652
- end
653
- break
654
- end
655
- width += get_mbchar_width(mbchar)
656
- byte_size += size
657
- end
658
- while 0 < (byte_pointer - byte_size)
659
- size = get_prev_mbchar_size(line, byte_pointer - byte_size)
660
- mbchar = line.byteslice(byte_pointer - byte_size - size, size)
661
- case started_by
662
- when :word
663
- break if mbchar =~ /\W/
664
- when :non_word_printable
665
- 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]
666
395
  end
667
- width += get_mbchar_width(mbchar)
668
- byte_size += size
669
396
  end
670
- [byte_size, width]
397
+ common_prefix_gcs.join
671
398
  end
672
399
 
673
400
  def self.vi_first_print(line)
674
- width = 0
675
- byte_size = 0
676
- while (line.bytesize - 1) > byte_size
677
- size = get_next_mbchar_size(line, byte_size)
678
- mbchar = line.byteslice(byte_size, size)
679
- if mbchar =~ /\S/
680
- break
681
- end
682
- width += get_mbchar_width(mbchar)
683
- byte_size += size
684
- end
685
- [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
686
414
  end
687
415
  end
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.5.11'
2
+ VERSION = '0.6.0'
3
3
  end
data/lib/reline.rb CHANGED
@@ -6,7 +6,6 @@ require 'reline/key_actor'
6
6
  require 'reline/key_stroke'
7
7
  require 'reline/line_editor'
8
8
  require 'reline/history'
9
- require 'reline/terminfo'
10
9
  require 'reline/io'
11
10
  require 'reline/face'
12
11
  require 'rbconfig'
@@ -18,10 +17,12 @@ module Reline
18
17
 
19
18
  class ConfigEncodingConversionError < StandardError; end
20
19
 
21
- Key = Struct.new(:char, :combined_char, :with_meta) do
20
+ # EOF key: { char: nil, method_symbol: nil }
21
+ # Other key: { char: String, method_symbol: Symbol }
22
+ Key = Struct.new(:char, :method_symbol, :unused_boolean) do
22
23
  # For dialog_proc `key.match?(dialog.name)`
23
24
  def match?(sym)
24
- combined_char.is_a?(Symbol) && combined_char == sym
25
+ method_symbol && method_symbol == sym
25
26
  end
26
27
  end
27
28
  CursorPos = Struct.new(:x, :y)
@@ -182,9 +183,7 @@ module Reline
182
183
  def output=(val)
183
184
  raise TypeError unless val.respond_to?(:write) or val.nil?
184
185
  @output = val
185
- if io_gate.respond_to?(:output=)
186
- io_gate.output = val
187
- end
186
+ io_gate.output = val
188
187
  end
189
188
 
190
189
  def vi_editing_mode
@@ -308,6 +307,7 @@ module Reline
308
307
  otio = io_gate.prep
309
308
 
310
309
  may_req_ambiguous_char_width
310
+ key_stroke.encoding = encoding
311
311
  line_editor.reset(prompt)
312
312
  if multiline
313
313
  line_editor.multiline_on
@@ -317,7 +317,6 @@ module Reline
317
317
  else
318
318
  line_editor.multiline_off
319
319
  end
320
- line_editor.output = output
321
320
  line_editor.completion_proc = completion_proc
322
321
  line_editor.completion_append_character = completion_append_character
323
322
  line_editor.output_modifier_proc = output_modifier_proc
@@ -344,13 +343,14 @@ module Reline
344
343
  read_io(config.keyseq_timeout) { |inputs|
345
344
  line_editor.set_pasting_state(io_gate.in_pasting?)
346
345
  inputs.each do |key|
347
- if key.char == :bracketed_paste_start
348
- text = io_gate.read_bracketed_paste
349
- line_editor.insert_multiline_text(text)
350
- line_editor.scroll_into_view
351
- else
352
- line_editor.update(key)
346
+ case key.method_symbol
347
+ when :bracketed_paste_start
348
+ # io_gate is Reline::ANSI because the key :bracketed_paste_start is only assigned in Reline::ANSI
349
+ key = Reline::Key.new(io_gate.read_bracketed_paste, :insert_multiline_text)
350
+ when :quoted_insert, :ed_quoted_insert
351
+ key = Reline::Key.new(io_gate.read_single_char(config.keyseq_timeout), :insert_raw_char)
353
352
  end
353
+ line_editor.update(key)
354
354
  end
355
355
  }
356
356
  if line_editor.finished?
@@ -486,7 +486,7 @@ module Reline
486
486
  def self.core
487
487
  @core ||= Core.new { |core|
488
488
  core.config = Reline::Config.new
489
- core.key_stroke = Reline::KeyStroke.new(core.config)
489
+ core.key_stroke = Reline::KeyStroke.new(core.config, core.encoding)
490
490
  core.line_editor = Reline::LineEditor.new(core.config)
491
491
 
492
492
  core.basic_word_break_characters = " \t\n`><=;|&{("