@@ -3,8 +3,8 @@ package readline
3
3
import (
4
4
"bufio"
5
5
"bytes"
6
+ "fmt"
6
7
"io"
7
- "strconv"
8
8
"strings"
9
9
"sync"
10
10
)
@@ -20,15 +20,15 @@ type RuneBuffer struct {
20
20
prompt []rune
21
21
w io.Writer
22
22
23
- hadClean bool
24
23
interactive bool
25
24
cfg * Config
26
25
27
26
width int
28
27
29
28
bck * runeBufferBck
30
29
31
- offset string
30
+ offset string // is offset useful? scrolling means row varies
31
+ ppos int // prompt start position (0 == column 1)
32
32
33
33
lastKill []rune
34
34
@@ -163,11 +163,25 @@ func (r *RuneBuffer) WriteRune(s rune) {
163
163
}
164
164
165
165
func (r * RuneBuffer ) WriteRunes (s []rune ) {
166
- r .Refresh (func () {
167
- tail := append (s , r .buf [r .idx :]... )
168
- r .buf = append (r .buf [:r .idx ], tail ... )
166
+ r .Lock ()
167
+ defer r .Unlock ()
168
+
169
+ if r .idx == len (r .buf ) {
170
+ // cursor is already at end of buf data so just call
171
+ // append instead of refesh to save redrawing.
172
+ r .buf = append (r .buf , s ... )
169
173
r .idx += len (s )
170
- })
174
+ if r .interactive {
175
+ r .append (s )
176
+ }
177
+ } else {
178
+ // writing into the data somewhere so do a refresh
179
+ r .refresh (func () {
180
+ tail := append (s , r .buf [r .idx :]... )
181
+ r .buf = append (r .buf [:r .idx ], tail ... )
182
+ r .idx += len (s )
183
+ })
184
+ }
171
185
}
172
186
173
187
func (r * RuneBuffer ) MoveForward () {
@@ -371,11 +385,12 @@ func (r *RuneBuffer) Backspace() {
371
385
}
372
386
373
387
func (r * RuneBuffer ) MoveToLineEnd () {
374
- r .Refresh (func () {
375
- if r .idx == len (r .buf ) {
376
- return
377
- }
378
-
388
+ r .Lock ()
389
+ defer r .Unlock ()
390
+ if r .idx == len (r .buf ) {
391
+ return
392
+ }
393
+ r .refresh (func () {
379
394
r .idx = len (r .buf )
380
395
})
381
396
}
@@ -421,12 +436,18 @@ func (r *RuneBuffer) isInLineEdge() bool {
421
436
if isWindows {
422
437
return false
423
438
}
424
- sp := r .getSplitByLine (r .buf )
425
- return len (sp [len (sp )- 1 ]) == 0
439
+ sp := r .getSplitByLine (r .buf , 1 )
440
+ return len (sp [len (sp )- 1 ]) == 0 // last line is 0 len
426
441
}
427
442
428
- func (r * RuneBuffer ) getSplitByLine (rs []rune ) []string {
429
- return SplitByLine (r .promptLen (), r .width , rs )
443
+ func (r * RuneBuffer ) getSplitByLine (rs []rune , nextWidth int ) [][]rune {
444
+ if r .cfg .EnableMask {
445
+ w := runes .Width (r .cfg .MaskRune )
446
+ masked := []rune (strings .Repeat (string (r .cfg .MaskRune ), len (rs )))
447
+ return SplitByLine (runes .ColorFilter (r .prompt ), masked , r .ppos , r .width , w )
448
+ } else {
449
+ return SplitByLine (runes .ColorFilter (r .prompt ), rs , r .ppos , r .width , nextWidth )
450
+ }
430
451
}
431
452
432
453
func (r * RuneBuffer ) IdxLine (width int ) int {
@@ -439,7 +460,11 @@ func (r *RuneBuffer) idxLine(width int) int {
439
460
if width == 0 {
440
461
return 0
441
462
}
442
- sp := r .getSplitByLine (r .buf [:r .idx ])
463
+ nextWidth := 1
464
+ if r .idx < len (r .buf ) {
465
+ nextWidth = runes .Width (r .buf [r .idx ])
466
+ }
467
+ sp := r .getSplitByLine (r .buf [:r .idx ], nextWidth )
443
468
return len (sp ) - 1
444
469
}
445
470
@@ -450,7 +475,10 @@ func (r *RuneBuffer) CursorLineCount() int {
450
475
func (r * RuneBuffer ) Refresh (f func ()) {
451
476
r .Lock ()
452
477
defer r .Unlock ()
478
+ r .refresh (f )
479
+ }
453
480
481
+ func (r * RuneBuffer ) refresh (f func ()) {
454
482
if ! r .interactive {
455
483
if f != nil {
456
484
f ()
@@ -465,31 +493,100 @@ func (r *RuneBuffer) Refresh(f func()) {
465
493
r .print ()
466
494
}
467
495
496
+ // getAndSetOffset queries the terminal for the current cursor position by
497
+ // writing a control sequence to the terminal. This call is asynchronous
498
+ // and it returns before any offset has actually been set as the terminal
499
+ // will write the offset back to us via stdin and there may already be
500
+ // other data in the stdin buffer ahead of it.
501
+ // This function is called at the start of readline each time.
502
+ func (r * RuneBuffer ) getAndSetOffset (t * Terminal ) {
503
+ if ! r .interactive {
504
+ return
505
+ }
506
+ if ! isWindows {
507
+ // Handle lineedge cases where existing text before before
508
+ // the prompt is printed would leave us at the right edge of
509
+ // the screen but the next character would actually be printed
510
+ // at the beginning of the next line.
511
+ r .w .Write ([]byte (" \b " ))
512
+ }
513
+ t .GetOffset (r .setOffset )
514
+ }
515
+
468
516
func (r * RuneBuffer ) SetOffset (offset string ) {
469
517
r .Lock ()
518
+ defer r .Unlock ()
519
+ r .setOffset (offset )
520
+ }
521
+
522
+ func (r * RuneBuffer ) setOffset (offset string ) {
470
523
r .offset = offset
471
- r .Unlock ()
524
+ if _ , c , ok := (& escapeKeyPair {attr :offset }).Get2 (); ok && c > 0 && c < r .width {
525
+ r .ppos = c - 1 // c should be 1..width
526
+ } else {
527
+ r .ppos = 0
528
+ }
529
+ }
530
+
531
+ // append s to the end of the current output. append is called in
532
+ // place of print() when clean() was avoided. As output is appended on
533
+ // the end, the cursor also needs no extra adjustment.
534
+ // NOTE: assumes len(s) >= 1 which should always be true for append.
535
+ func (r * RuneBuffer ) append (s []rune ) {
536
+ buf := bytes .NewBuffer (nil )
537
+ slen := len (s )
538
+ if r .cfg .EnableMask {
539
+ if slen > 1 && r .cfg .MaskRune != 0 {
540
+ // write a mask character for all runes except the last rune
541
+ buf .WriteString (strings .Repeat (string (r .cfg .MaskRune ), slen - 1 ))
542
+ }
543
+ // for the last rune, write \n or mask it otherwise.
544
+ if s [slen - 1 ] == '\n' {
545
+ buf .WriteRune ('\n' )
546
+ } else if r .cfg .MaskRune != 0 {
547
+ buf .WriteRune (r .cfg .MaskRune )
548
+ }
549
+ } else {
550
+ for _ , e := range r .cfg .Painter .Paint (s , slen ) {
551
+ if e == '\t' {
552
+ buf .WriteString (strings .Repeat (" " , TabWidth ))
553
+ } else {
554
+ buf .WriteRune (e )
555
+ }
556
+ }
557
+ }
558
+ if r .isInLineEdge () {
559
+ buf .WriteString (" \b " )
560
+ }
561
+ r .w .Write (buf .Bytes ())
562
+ }
563
+
564
+ // Print writes out the prompt and buffer contents at the current cursor position
565
+ func (r * RuneBuffer ) Print () {
566
+ r .Lock ()
567
+ defer r .Unlock ()
568
+ if ! r .interactive {
569
+ return
570
+ }
571
+ r .print ()
472
572
}
473
573
474
574
func (r * RuneBuffer ) print () {
475
575
r .w .Write (r .output ())
476
- r .hadClean = false
477
576
}
478
577
479
578
func (r * RuneBuffer ) output () []byte {
480
579
buf := bytes .NewBuffer (nil )
481
580
buf .WriteString (string (r .prompt ))
482
581
if r .cfg .EnableMask && len (r .buf ) > 0 {
483
- buf .Write ([]byte (strings .Repeat (string (r .cfg .MaskRune ), len (r .buf )- 1 )))
484
- if r .buf [len (r .buf )- 1 ] == '\n' {
485
- buf .Write ([]byte {'\n' })
486
- } else {
487
- buf .Write ([]byte (string (r .cfg .MaskRune )))
582
+ if r .cfg .MaskRune != 0 {
583
+ buf .WriteString (strings .Repeat (string (r .cfg .MaskRune ), len (r .buf )- 1 ))
488
584
}
489
- if len (r .buf ) > r .idx {
490
- buf .Write (r .getBackspaceSequence ())
585
+ if r .buf [len (r .buf )- 1 ] == '\n' {
586
+ buf .WriteRune ('\n' )
587
+ } else if r .cfg .MaskRune != 0 {
588
+ buf .WriteRune (r .cfg .MaskRune )
491
589
}
492
-
493
590
} else {
494
591
for _ , e := range r .cfg .Painter .Paint (r .buf , r .idx ) {
495
592
if e == '\t' {
@@ -498,9 +595,9 @@ func (r *RuneBuffer) output() []byte {
498
595
buf .WriteRune (e )
499
596
}
500
597
}
501
- if r . isInLineEdge () {
502
- buf . Write ([] byte ( " \b " ))
503
- }
598
+ }
599
+ if r . isInLineEdge () {
600
+ buf . WriteString ( " \b " )
504
601
}
505
602
// cursor position
506
603
if len (r .buf ) > r .idx {
@@ -510,33 +607,41 @@ func (r *RuneBuffer) output() []byte {
510
607
}
511
608
512
609
func (r * RuneBuffer ) getBackspaceSequence () []byte {
513
- var sep = map [int ]bool {}
514
-
515
- var i int
516
- for {
517
- if i >= runes .WidthAll (r .buf ) {
610
+ bcnt := len (r .buf ) - r .idx // backwards count to index
611
+ sp := r .getSplitByLine (r .buf , 1 )
612
+
613
+ // Calculate how many lines up to the index line
614
+ up := 0
615
+ spi := len (sp ) - 1
616
+ for spi >= 0 {
617
+ bcnt -= len (sp [spi ])
618
+ if bcnt <= 0 {
518
619
break
519
620
}
621
+ up ++
622
+ spi --
623
+ }
520
624
521
- if i == 0 {
522
- i -= r .promptLen ()
523
- }
524
- i += r .width
525
-
526
- sep [i ] = true
625
+ // Calculate what column the index should be set to
626
+ column := 1
627
+ if spi == 0 {
628
+ column += r .ppos
527
629
}
528
- var buf []byte
529
- for i := len (r .buf ); i > r .idx ; i -- {
530
- // move input to the left of one
531
- buf = append (buf , '\b' )
532
- if sep [i ] {
533
- // up one line, go to the start of the line and move cursor right to the end (r.width)
534
- buf = append (buf , "\033 [A\r " + "\033 [" + strconv .Itoa (r .width )+ "C" ... )
630
+ for _ , rune := range sp [spi ] {
631
+ if bcnt >= 0 {
632
+ break
535
633
}
634
+ column += runes .Width (rune )
635
+ bcnt ++
536
636
}
537
637
538
- return buf
638
+ buf := bytes .NewBuffer (nil )
639
+ if up > 0 {
640
+ fmt .Fprintf (buf , "\033 [%dA" , up ) // move cursor up to index line
641
+ }
642
+ fmt .Fprintf (buf , "\033 [%dG" , column ) // move cursor to column
539
643
644
+ return buf .Bytes ()
540
645
}
541
646
542
647
func (r * RuneBuffer ) Reset () []rune {
@@ -595,16 +700,11 @@ func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
595
700
buf .WriteString (strings .Repeat ("\r \b " , len (r .buf )+ r .promptLen ()))
596
701
buf .Write ([]byte ("\033 [J" ))
597
702
} else {
598
- buf .Write ([]byte ("\033 [J" )) // just like ^k :)
599
- if idxLine == 0 {
600
- buf .WriteString ("\033 [2K" )
601
- buf .WriteString ("\r " )
602
- } else {
603
- for i := 0 ; i < idxLine ; i ++ {
604
- io .WriteString (buf , "\033 [2K\r \033 [A" )
605
- }
606
- io .WriteString (buf , "\033 [2K\r " )
703
+ if idxLine > 0 {
704
+ fmt .Fprintf (buf , "\033 [%dA" , idxLine ) // move cursor up by idxLine
607
705
}
706
+ fmt .Fprintf (buf , "\033 [%dG" , r .ppos + 1 ) // move cursor back to initial ppos position
707
+ buf .Write ([]byte ("\033 [J" )) // clear from cursor to end of screen
608
708
}
609
709
buf .Flush ()
610
710
return
@@ -621,9 +721,8 @@ func (r *RuneBuffer) clean() {
621
721
}
622
722
623
723
func (r * RuneBuffer ) cleanWithIdxLine (idxLine int ) {
624
- if r . hadClean || ! r .interactive {
724
+ if ! r .interactive {
625
725
return
626
726
}
627
- r .hadClean = true
628
727
r .cleanOutput (r .w , idxLine )
629
728
}
0 commit comments