1
2
3
4
5 package term
6
7 import (
8 "bytes"
9 "io"
10 "runtime"
11 "strconv"
12 "sync"
13 "unicode/utf8"
14 )
15
16
17
18 type EscapeCodes struct {
19
20 Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
21
22
23 Reset []byte
24 }
25
26 var vt100EscapeCodes = EscapeCodes{
27 Black: []byte{keyEscape, '[', '3', '0', 'm'},
28 Red: []byte{keyEscape, '[', '3', '1', 'm'},
29 Green: []byte{keyEscape, '[', '3', '2', 'm'},
30 Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
31 Blue: []byte{keyEscape, '[', '3', '4', 'm'},
32 Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
33 Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
34 White: []byte{keyEscape, '[', '3', '7', 'm'},
35
36 Reset: []byte{keyEscape, '[', '0', 'm'},
37 }
38
39
40
41 type Terminal struct {
42
43
44
45
46
47 AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
48
49
50
51
52 Escape *EscapeCodes
53
54
55
56 lock sync.Mutex
57
58 c io.ReadWriter
59 prompt []rune
60
61
62 line []rune
63
64 pos int
65
66 echo bool
67
68
69 pasteActive bool
70
71
72
73
74 cursorX, cursorY int
75
76 maxLine int
77
78 termWidth, termHeight int
79
80
81 outBuf []byte
82
83
84 remainder []byte
85 inBuf [256]byte
86
87
88
89 history stRingBuffer
90
91
92 historyIndex int
93
94
95
96 historyPending string
97 }
98
99
100
101
102
103 func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
104 return &Terminal{
105 Escape: &vt100EscapeCodes,
106 c: c,
107 prompt: []rune(prompt),
108 termWidth: 80,
109 termHeight: 24,
110 echo: true,
111 historyIndex: -1,
112 }
113 }
114
115 const (
116 keyCtrlC = 3
117 keyCtrlD = 4
118 keyCtrlU = 21
119 keyEnter = '\r'
120 keyEscape = 27
121 keyBackspace = 127
122 keyUnknown = 0xd800 + iota
123 keyUp
124 keyDown
125 keyLeft
126 keyRight
127 keyAltLeft
128 keyAltRight
129 keyHome
130 keyEnd
131 keyDeleteWord
132 keyDeleteLine
133 keyClearScreen
134 keyPasteStart
135 keyPasteEnd
136 )
137
138 var (
139 crlf = []byte{'\r', '\n'}
140 pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
141 pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
142 )
143
144
145
146 func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
147 if len(b) == 0 {
148 return utf8.RuneError, nil
149 }
150
151 if !pasteActive {
152 switch b[0] {
153 case 1:
154 return keyHome, b[1:]
155 case 2:
156 return keyLeft, b[1:]
157 case 5:
158 return keyEnd, b[1:]
159 case 6:
160 return keyRight, b[1:]
161 case 8:
162 return keyBackspace, b[1:]
163 case 11:
164 return keyDeleteLine, b[1:]
165 case 12:
166 return keyClearScreen, b[1:]
167 case 23:
168 return keyDeleteWord, b[1:]
169 case 14:
170 return keyDown, b[1:]
171 case 16:
172 return keyUp, b[1:]
173 }
174 }
175
176 if b[0] != keyEscape {
177 if !utf8.FullRune(b) {
178 return utf8.RuneError, b
179 }
180 r, l := utf8.DecodeRune(b)
181 return r, b[l:]
182 }
183
184 if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
185 switch b[2] {
186 case 'A':
187 return keyUp, b[3:]
188 case 'B':
189 return keyDown, b[3:]
190 case 'C':
191 return keyRight, b[3:]
192 case 'D':
193 return keyLeft, b[3:]
194 case 'H':
195 return keyHome, b[3:]
196 case 'F':
197 return keyEnd, b[3:]
198 }
199 }
200
201 if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
202 switch b[5] {
203 case 'C':
204 return keyAltRight, b[6:]
205 case 'D':
206 return keyAltLeft, b[6:]
207 }
208 }
209
210 if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
211 return keyPasteStart, b[6:]
212 }
213
214 if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
215 return keyPasteEnd, b[6:]
216 }
217
218
219
220
221
222 for i, c := range b[0:] {
223 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
224 return keyUnknown, b[i+1:]
225 }
226 }
227
228 return utf8.RuneError, b
229 }
230
231
232 func (t *Terminal) queue(data []rune) {
233 t.outBuf = append(t.outBuf, []byte(string(data))...)
234 }
235
236 var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
237 var space = []rune{' '}
238
239 func isPrintable(key rune) bool {
240 isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
241 return key >= 32 && !isInSurrogateArea
242 }
243
244
245
246 func (t *Terminal) moveCursorToPos(pos int) {
247 if !t.echo {
248 return
249 }
250
251 x := visualLength(t.prompt) + pos
252 y := x / t.termWidth
253 x = x % t.termWidth
254
255 up := 0
256 if y < t.cursorY {
257 up = t.cursorY - y
258 }
259
260 down := 0
261 if y > t.cursorY {
262 down = y - t.cursorY
263 }
264
265 left := 0
266 if x < t.cursorX {
267 left = t.cursorX - x
268 }
269
270 right := 0
271 if x > t.cursorX {
272 right = x - t.cursorX
273 }
274
275 t.cursorX = x
276 t.cursorY = y
277 t.move(up, down, left, right)
278 }
279
280 func (t *Terminal) move(up, down, left, right int) {
281 m := []rune{}
282
283
284
285
286 if up == 1 {
287 m = append(m, keyEscape, '[', 'A')
288 } else if up > 1 {
289 m = append(m, keyEscape, '[')
290 m = append(m, []rune(strconv.Itoa(up))...)
291 m = append(m, 'A')
292 }
293
294 if down == 1 {
295 m = append(m, keyEscape, '[', 'B')
296 } else if down > 1 {
297 m = append(m, keyEscape, '[')
298 m = append(m, []rune(strconv.Itoa(down))...)
299 m = append(m, 'B')
300 }
301
302 if right == 1 {
303 m = append(m, keyEscape, '[', 'C')
304 } else if right > 1 {
305 m = append(m, keyEscape, '[')
306 m = append(m, []rune(strconv.Itoa(right))...)
307 m = append(m, 'C')
308 }
309
310 if left == 1 {
311 m = append(m, keyEscape, '[', 'D')
312 } else if left > 1 {
313 m = append(m, keyEscape, '[')
314 m = append(m, []rune(strconv.Itoa(left))...)
315 m = append(m, 'D')
316 }
317
318 t.queue(m)
319 }
320
321 func (t *Terminal) clearLineToRight() {
322 op := []rune{keyEscape, '[', 'K'}
323 t.queue(op)
324 }
325
326 const maxLineLength = 4096
327
328 func (t *Terminal) setLine(newLine []rune, newPos int) {
329 if t.echo {
330 t.moveCursorToPos(0)
331 t.writeLine(newLine)
332 for i := len(newLine); i < len(t.line); i++ {
333 t.writeLine(space)
334 }
335 t.moveCursorToPos(newPos)
336 }
337 t.line = newLine
338 t.pos = newPos
339 }
340
341 func (t *Terminal) advanceCursor(places int) {
342 t.cursorX += places
343 t.cursorY += t.cursorX / t.termWidth
344 if t.cursorY > t.maxLine {
345 t.maxLine = t.cursorY
346 }
347 t.cursorX = t.cursorX % t.termWidth
348
349 if places > 0 && t.cursorX == 0 {
350
351
352
353
354
355
356
357
358
359
360 t.outBuf = append(t.outBuf, '\r', '\n')
361 }
362 }
363
364 func (t *Terminal) eraseNPreviousChars(n int) {
365 if n == 0 {
366 return
367 }
368
369 if t.pos < n {
370 n = t.pos
371 }
372 t.pos -= n
373 t.moveCursorToPos(t.pos)
374
375 copy(t.line[t.pos:], t.line[n+t.pos:])
376 t.line = t.line[:len(t.line)-n]
377 if t.echo {
378 t.writeLine(t.line[t.pos:])
379 for i := 0; i < n; i++ {
380 t.queue(space)
381 }
382 t.advanceCursor(n)
383 t.moveCursorToPos(t.pos)
384 }
385 }
386
387
388
389 func (t *Terminal) countToLeftWord() int {
390 if t.pos == 0 {
391 return 0
392 }
393
394 pos := t.pos - 1
395 for pos > 0 {
396 if t.line[pos] != ' ' {
397 break
398 }
399 pos--
400 }
401 for pos > 0 {
402 if t.line[pos] == ' ' {
403 pos++
404 break
405 }
406 pos--
407 }
408
409 return t.pos - pos
410 }
411
412
413
414 func (t *Terminal) countToRightWord() int {
415 pos := t.pos
416 for pos < len(t.line) {
417 if t.line[pos] == ' ' {
418 break
419 }
420 pos++
421 }
422 for pos < len(t.line) {
423 if t.line[pos] != ' ' {
424 break
425 }
426 pos++
427 }
428 return pos - t.pos
429 }
430
431
432 func visualLength(runes []rune) int {
433 inEscapeSeq := false
434 length := 0
435
436 for _, r := range runes {
437 switch {
438 case inEscapeSeq:
439 if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
440 inEscapeSeq = false
441 }
442 case r == '\x1b':
443 inEscapeSeq = true
444 default:
445 length++
446 }
447 }
448
449 return length
450 }
451
452
453
454 func (t *Terminal) handleKey(key rune) (line string, ok bool) {
455 if t.pasteActive && key != keyEnter {
456 t.addKeyToLine(key)
457 return
458 }
459
460 switch key {
461 case keyBackspace:
462 if t.pos == 0 {
463 return
464 }
465 t.eraseNPreviousChars(1)
466 case keyAltLeft:
467
468 t.pos -= t.countToLeftWord()
469 t.moveCursorToPos(t.pos)
470 case keyAltRight:
471
472 t.pos += t.countToRightWord()
473 t.moveCursorToPos(t.pos)
474 case keyLeft:
475 if t.pos == 0 {
476 return
477 }
478 t.pos--
479 t.moveCursorToPos(t.pos)
480 case keyRight:
481 if t.pos == len(t.line) {
482 return
483 }
484 t.pos++
485 t.moveCursorToPos(t.pos)
486 case keyHome:
487 if t.pos == 0 {
488 return
489 }
490 t.pos = 0
491 t.moveCursorToPos(t.pos)
492 case keyEnd:
493 if t.pos == len(t.line) {
494 return
495 }
496 t.pos = len(t.line)
497 t.moveCursorToPos(t.pos)
498 case keyUp:
499 entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
500 if !ok {
501 return "", false
502 }
503 if t.historyIndex == -1 {
504 t.historyPending = string(t.line)
505 }
506 t.historyIndex++
507 runes := []rune(entry)
508 t.setLine(runes, len(runes))
509 case keyDown:
510 switch t.historyIndex {
511 case -1:
512 return
513 case 0:
514 runes := []rune(t.historyPending)
515 t.setLine(runes, len(runes))
516 t.historyIndex--
517 default:
518 entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
519 if ok {
520 t.historyIndex--
521 runes := []rune(entry)
522 t.setLine(runes, len(runes))
523 }
524 }
525 case keyEnter:
526 t.moveCursorToPos(len(t.line))
527 t.queue([]rune("\r\n"))
528 line = string(t.line)
529 ok = true
530 t.line = t.line[:0]
531 t.pos = 0
532 t.cursorX = 0
533 t.cursorY = 0
534 t.maxLine = 0
535 case keyDeleteWord:
536
537 t.eraseNPreviousChars(t.countToLeftWord())
538 case keyDeleteLine:
539
540
541 for i := t.pos; i < len(t.line); i++ {
542 t.queue(space)
543 t.advanceCursor(1)
544 }
545 t.line = t.line[:t.pos]
546 t.moveCursorToPos(t.pos)
547 case keyCtrlD:
548
549
550
551 if t.pos < len(t.line) {
552 t.pos++
553 t.eraseNPreviousChars(1)
554 }
555 case keyCtrlU:
556 t.eraseNPreviousChars(t.pos)
557 case keyClearScreen:
558
559 t.queue([]rune("\x1b[2J\x1b[H"))
560 t.queue(t.prompt)
561 t.cursorX, t.cursorY = 0, 0
562 t.advanceCursor(visualLength(t.prompt))
563 t.setLine(t.line, t.pos)
564 default:
565 if t.AutoCompleteCallback != nil {
566 prefix := string(t.line[:t.pos])
567 suffix := string(t.line[t.pos:])
568
569 t.lock.Unlock()
570 newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
571 t.lock.Lock()
572
573 if completeOk {
574 t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
575 return
576 }
577 }
578 if !isPrintable(key) {
579 return
580 }
581 if len(t.line) == maxLineLength {
582 return
583 }
584 t.addKeyToLine(key)
585 }
586 return
587 }
588
589
590
591 func (t *Terminal) addKeyToLine(key rune) {
592 if len(t.line) == cap(t.line) {
593 newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
594 copy(newLine, t.line)
595 t.line = newLine
596 }
597 t.line = t.line[:len(t.line)+1]
598 copy(t.line[t.pos+1:], t.line[t.pos:])
599 t.line[t.pos] = key
600 if t.echo {
601 t.writeLine(t.line[t.pos:])
602 }
603 t.pos++
604 t.moveCursorToPos(t.pos)
605 }
606
607 func (t *Terminal) writeLine(line []rune) {
608 for len(line) != 0 {
609 remainingOnLine := t.termWidth - t.cursorX
610 todo := len(line)
611 if todo > remainingOnLine {
612 todo = remainingOnLine
613 }
614 t.queue(line[:todo])
615 t.advanceCursor(visualLength(line[:todo]))
616 line = line[todo:]
617 }
618 }
619
620
621 func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
622 for len(buf) > 0 {
623 i := bytes.IndexByte(buf, '\n')
624 todo := len(buf)
625 if i >= 0 {
626 todo = i
627 }
628
629 var nn int
630 nn, err = w.Write(buf[:todo])
631 n += nn
632 if err != nil {
633 return n, err
634 }
635 buf = buf[todo:]
636
637 if i >= 0 {
638 if _, err = w.Write(crlf); err != nil {
639 return n, err
640 }
641 n++
642 buf = buf[1:]
643 }
644 }
645
646 return n, nil
647 }
648
649 func (t *Terminal) Write(buf []byte) (n int, err error) {
650 t.lock.Lock()
651 defer t.lock.Unlock()
652
653 if t.cursorX == 0 && t.cursorY == 0 {
654
655
656 return writeWithCRLF(t.c, buf)
657 }
658
659
660
661 t.move(0 , 0 , t.cursorX , 0 )
662 t.cursorX = 0
663 t.clearLineToRight()
664
665 for t.cursorY > 0 {
666 t.move(1 , 0, 0, 0)
667 t.cursorY--
668 t.clearLineToRight()
669 }
670
671 if _, err = t.c.Write(t.outBuf); err != nil {
672 return
673 }
674 t.outBuf = t.outBuf[:0]
675
676 if n, err = writeWithCRLF(t.c, buf); err != nil {
677 return
678 }
679
680 t.writeLine(t.prompt)
681 if t.echo {
682 t.writeLine(t.line)
683 }
684
685 t.moveCursorToPos(t.pos)
686
687 if _, err = t.c.Write(t.outBuf); err != nil {
688 return
689 }
690 t.outBuf = t.outBuf[:0]
691 return
692 }
693
694
695
696 func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
697 t.lock.Lock()
698 defer t.lock.Unlock()
699
700 oldPrompt := t.prompt
701 t.prompt = []rune(prompt)
702 t.echo = false
703
704 line, err = t.readLine()
705
706 t.prompt = oldPrompt
707 t.echo = true
708
709 return
710 }
711
712
713 func (t *Terminal) ReadLine() (line string, err error) {
714 t.lock.Lock()
715 defer t.lock.Unlock()
716
717 return t.readLine()
718 }
719
720 func (t *Terminal) readLine() (line string, err error) {
721
722
723 if t.cursorX == 0 && t.cursorY == 0 {
724 t.writeLine(t.prompt)
725 t.c.Write(t.outBuf)
726 t.outBuf = t.outBuf[:0]
727 }
728
729 lineIsPasted := t.pasteActive
730
731 for {
732 rest := t.remainder
733 lineOk := false
734 for !lineOk {
735 var key rune
736 key, rest = bytesToKey(rest, t.pasteActive)
737 if key == utf8.RuneError {
738 break
739 }
740 if !t.pasteActive {
741 if key == keyCtrlD {
742 if len(t.line) == 0 {
743 return "", io.EOF
744 }
745 }
746 if key == keyCtrlC {
747 return "", io.EOF
748 }
749 if key == keyPasteStart {
750 t.pasteActive = true
751 if len(t.line) == 0 {
752 lineIsPasted = true
753 }
754 continue
755 }
756 } else if key == keyPasteEnd {
757 t.pasteActive = false
758 continue
759 }
760 if !t.pasteActive {
761 lineIsPasted = false
762 }
763 line, lineOk = t.handleKey(key)
764 }
765 if len(rest) > 0 {
766 n := copy(t.inBuf[:], rest)
767 t.remainder = t.inBuf[:n]
768 } else {
769 t.remainder = nil
770 }
771 t.c.Write(t.outBuf)
772 t.outBuf = t.outBuf[:0]
773 if lineOk {
774 if t.echo {
775 t.historyIndex = -1
776 t.history.Add(line)
777 }
778 if lineIsPasted {
779 err = ErrPasteIndicator
780 }
781 return
782 }
783
784
785
786 readBuf := t.inBuf[len(t.remainder):]
787 var n int
788
789 t.lock.Unlock()
790 n, err = t.c.Read(readBuf)
791 t.lock.Lock()
792
793 if err != nil {
794 return
795 }
796
797 t.remainder = t.inBuf[:n+len(t.remainder)]
798 }
799 }
800
801
802 func (t *Terminal) SetPrompt(prompt string) {
803 t.lock.Lock()
804 defer t.lock.Unlock()
805
806 t.prompt = []rune(prompt)
807 }
808
809 func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
810
811 t.move(t.cursorY, 0, t.cursorX, 0)
812 t.cursorX, t.cursorY = 0, 0
813 t.clearLineToRight()
814 for t.cursorY < numPrevLines {
815
816 t.move(0, 1, 0, 0)
817 t.cursorY++
818 t.clearLineToRight()
819 }
820
821 t.move(t.cursorY, 0, 0, 0)
822 t.cursorX, t.cursorY = 0, 0
823
824 t.queue(t.prompt)
825 t.advanceCursor(visualLength(t.prompt))
826 t.writeLine(t.line)
827 t.moveCursorToPos(t.pos)
828 }
829
830 func (t *Terminal) SetSize(width, height int) error {
831 t.lock.Lock()
832 defer t.lock.Unlock()
833
834 if width == 0 {
835 width = 1
836 }
837
838 oldWidth := t.termWidth
839 t.termWidth, t.termHeight = width, height
840
841 switch {
842 case width == oldWidth:
843
844
845 return nil
846 case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
847
848
849 return nil
850 case width < oldWidth:
851
852
853
854
855
856
857
858
859
860
861
862 if t.cursorX >= t.termWidth {
863 t.cursorX = t.termWidth - 1
864 }
865 t.cursorY *= 2
866 t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
867 case width > oldWidth:
868
869
870
871
872
873
874
875 t.clearAndRepaintLinePlusNPrevious(t.maxLine)
876 }
877
878 _, err := t.c.Write(t.outBuf)
879 t.outBuf = t.outBuf[:0]
880 return err
881 }
882
883 type pasteIndicatorError struct{}
884
885 func (pasteIndicatorError) Error() string {
886 return "terminal: ErrPasteIndicator not correctly handled"
887 }
888
889
890
891
892
893 var ErrPasteIndicator = pasteIndicatorError{}
894
895
896
897
898
899
900 func (t *Terminal) SetBracketedPasteMode(on bool) {
901 if on {
902 io.WriteString(t.c, "\x1b[?2004h")
903 } else {
904 io.WriteString(t.c, "\x1b[?2004l")
905 }
906 }
907
908
909 type stRingBuffer struct {
910
911 entries []string
912 max int
913
914 head int
915
916 size int
917 }
918
919 func (s *stRingBuffer) Add(a string) {
920 if s.entries == nil {
921 const defaultNumEntries = 100
922 s.entries = make([]string, defaultNumEntries)
923 s.max = defaultNumEntries
924 }
925
926 s.head = (s.head + 1) % s.max
927 s.entries[s.head] = a
928 if s.size < s.max {
929 s.size++
930 }
931 }
932
933
934
935
936
937 func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
938 if n >= s.size {
939 return "", false
940 }
941 index := s.head - n
942 if index < 0 {
943 index += s.max
944 }
945 return s.entries[index], true
946 }
947
948
949
950
951
952
953 func readPasswordLine(reader io.Reader) ([]byte, error) {
954 var buf [1]byte
955 var ret []byte
956
957 for {
958 n, err := reader.Read(buf[:])
959 if n > 0 {
960 switch buf[0] {
961 case '\b':
962 if len(ret) > 0 {
963 ret = ret[:len(ret)-1]
964 }
965 case '\n':
966 if runtime.GOOS != "windows" {
967 return ret, nil
968 }
969
970 case '\r':
971 if runtime.GOOS == "windows" {
972 return ret, nil
973 }
974
975 default:
976 ret = append(ret, buf[0])
977 }
978 continue
979 }
980 if err != nil {
981 if err == io.EOF && len(ret) > 0 {
982 return ret, nil
983 }
984 return ret, err
985 }
986 }
987 }
988
View as plain text