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