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 space = []rune{' '}
237
238 func isPrintable(key rune) bool {
239 isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
240 return key >= 32 && !isInSurrogateArea
241 }
242
243
244
245 func (t *Terminal) moveCursorToPos(pos int) {
246 if !t.echo {
247 return
248 }
249
250 x := visualLength(t.prompt) + pos
251 y := x / t.termWidth
252 x = x % t.termWidth
253
254 up := 0
255 if y < t.cursorY {
256 up = t.cursorY - y
257 }
258
259 down := 0
260 if y > t.cursorY {
261 down = y - t.cursorY
262 }
263
264 left := 0
265 if x < t.cursorX {
266 left = t.cursorX - x
267 }
268
269 right := 0
270 if x > t.cursorX {
271 right = x - t.cursorX
272 }
273
274 t.cursorX = x
275 t.cursorY = y
276 t.move(up, down, left, right)
277 }
278
279 func (t *Terminal) move(up, down, left, right int) {
280 m := []rune{}
281
282
283
284
285 if up == 1 {
286 m = append(m, keyEscape, '[', 'A')
287 } else if up > 1 {
288 m = append(m, keyEscape, '[')
289 m = append(m, []rune(strconv.Itoa(up))...)
290 m = append(m, 'A')
291 }
292
293 if down == 1 {
294 m = append(m, keyEscape, '[', 'B')
295 } else if down > 1 {
296 m = append(m, keyEscape, '[')
297 m = append(m, []rune(strconv.Itoa(down))...)
298 m = append(m, 'B')
299 }
300
301 if right == 1 {
302 m = append(m, keyEscape, '[', 'C')
303 } else if right > 1 {
304 m = append(m, keyEscape, '[')
305 m = append(m, []rune(strconv.Itoa(right))...)
306 m = append(m, 'C')
307 }
308
309 if left == 1 {
310 m = append(m, keyEscape, '[', 'D')
311 } else if left > 1 {
312 m = append(m, keyEscape, '[')
313 m = append(m, []rune(strconv.Itoa(left))...)
314 m = append(m, 'D')
315 }
316
317 t.queue(m)
318 }
319
320 func (t *Terminal) clearLineToRight() {
321 op := []rune{keyEscape, '[', 'K'}
322 t.queue(op)
323 }
324
325 const maxLineLength = 4096
326
327 func (t *Terminal) setLine(newLine []rune, newPos int) {
328 if t.echo {
329 t.moveCursorToPos(0)
330 t.writeLine(newLine)
331 for i := len(newLine); i < len(t.line); i++ {
332 t.writeLine(space)
333 }
334 t.moveCursorToPos(newPos)
335 }
336 t.line = newLine
337 t.pos = newPos
338 }
339
340 func (t *Terminal) advanceCursor(places int) {
341 t.cursorX += places
342 t.cursorY += t.cursorX / t.termWidth
343 if t.cursorY > t.maxLine {
344 t.maxLine = t.cursorY
345 }
346 t.cursorX = t.cursorX % t.termWidth
347
348 if places > 0 && t.cursorX == 0 {
349
350
351
352
353
354
355
356
357
358
359 t.outBuf = append(t.outBuf, '\r', '\n')
360 }
361 }
362
363 func (t *Terminal) eraseNPreviousChars(n int) {
364 if n == 0 {
365 return
366 }
367
368 if t.pos < n {
369 n = t.pos
370 }
371 t.pos -= n
372 t.moveCursorToPos(t.pos)
373
374 copy(t.line[t.pos:], t.line[n+t.pos:])
375 t.line = t.line[:len(t.line)-n]
376 if t.echo {
377 t.writeLine(t.line[t.pos:])
378 for i := 0; i < n; i++ {
379 t.queue(space)
380 }
381 t.advanceCursor(n)
382 t.moveCursorToPos(t.pos)
383 }
384 }
385
386
387
388 func (t *Terminal) countToLeftWord() int {
389 if t.pos == 0 {
390 return 0
391 }
392
393 pos := t.pos - 1
394 for pos > 0 {
395 if t.line[pos] != ' ' {
396 break
397 }
398 pos--
399 }
400 for pos > 0 {
401 if t.line[pos] == ' ' {
402 pos++
403 break
404 }
405 pos--
406 }
407
408 return t.pos - pos
409 }
410
411
412
413 func (t *Terminal) countToRightWord() int {
414 pos := t.pos
415 for pos < len(t.line) {
416 if t.line[pos] == ' ' {
417 break
418 }
419 pos++
420 }
421 for pos < len(t.line) {
422 if t.line[pos] != ' ' {
423 break
424 }
425 pos++
426 }
427 return pos - t.pos
428 }
429
430
431 func visualLength(runes []rune) int {
432 inEscapeSeq := false
433 length := 0
434
435 for _, r := range runes {
436 switch {
437 case inEscapeSeq:
438 if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
439 inEscapeSeq = false
440 }
441 case r == '\x1b':
442 inEscapeSeq = true
443 default:
444 length++
445 }
446 }
447
448 return length
449 }
450
451
452
453 func (t *Terminal) handleKey(key rune) (line string, ok bool) {
454 if t.pasteActive && key != keyEnter {
455 t.addKeyToLine(key)
456 return
457 }
458
459 switch key {
460 case keyBackspace:
461 if t.pos == 0 {
462 return
463 }
464 t.eraseNPreviousChars(1)
465 case keyAltLeft:
466
467 t.pos -= t.countToLeftWord()
468 t.moveCursorToPos(t.pos)
469 case keyAltRight:
470
471 t.pos += t.countToRightWord()
472 t.moveCursorToPos(t.pos)
473 case keyLeft:
474 if t.pos == 0 {
475 return
476 }
477 t.pos--
478 t.moveCursorToPos(t.pos)
479 case keyRight:
480 if t.pos == len(t.line) {
481 return
482 }
483 t.pos++
484 t.moveCursorToPos(t.pos)
485 case keyHome:
486 if t.pos == 0 {
487 return
488 }
489 t.pos = 0
490 t.moveCursorToPos(t.pos)
491 case keyEnd:
492 if t.pos == len(t.line) {
493 return
494 }
495 t.pos = len(t.line)
496 t.moveCursorToPos(t.pos)
497 case keyUp:
498 entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
499 if !ok {
500 return "", false
501 }
502 if t.historyIndex == -1 {
503 t.historyPending = string(t.line)
504 }
505 t.historyIndex++
506 runes := []rune(entry)
507 t.setLine(runes, len(runes))
508 case keyDown:
509 switch t.historyIndex {
510 case -1:
511 return
512 case 0:
513 runes := []rune(t.historyPending)
514 t.setLine(runes, len(runes))
515 t.historyIndex--
516 default:
517 entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
518 if ok {
519 t.historyIndex--
520 runes := []rune(entry)
521 t.setLine(runes, len(runes))
522 }
523 }
524 case keyEnter:
525 t.moveCursorToPos(len(t.line))
526 t.queue([]rune("\r\n"))
527 line = string(t.line)
528 ok = true
529 t.line = t.line[:0]
530 t.pos = 0
531 t.cursorX = 0
532 t.cursorY = 0
533 t.maxLine = 0
534 case keyDeleteWord:
535
536 t.eraseNPreviousChars(t.countToLeftWord())
537 case keyDeleteLine:
538
539
540 for i := t.pos; i < len(t.line); i++ {
541 t.queue(space)
542 t.advanceCursor(1)
543 }
544 t.line = t.line[:t.pos]
545 t.moveCursorToPos(t.pos)
546 case keyCtrlD:
547
548
549
550 if t.pos < len(t.line) {
551 t.pos++
552 t.eraseNPreviousChars(1)
553 }
554 case keyCtrlU:
555 t.eraseNPreviousChars(t.pos)
556 case keyClearScreen:
557
558 t.queue([]rune("\x1b[2J\x1b[H"))
559 t.queue(t.prompt)
560 t.cursorX, t.cursorY = 0, 0
561 t.advanceCursor(visualLength(t.prompt))
562 t.setLine(t.line, t.pos)
563 default:
564 if t.AutoCompleteCallback != nil {
565 prefix := string(t.line[:t.pos])
566 suffix := string(t.line[t.pos:])
567
568 t.lock.Unlock()
569 newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
570 t.lock.Lock()
571
572 if completeOk {
573 t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
574 return
575 }
576 }
577 if !isPrintable(key) {
578 return
579 }
580 if len(t.line) == maxLineLength {
581 return
582 }
583 t.addKeyToLine(key)
584 }
585 return
586 }
587
588
589
590 func (t *Terminal) addKeyToLine(key rune) {
591 if len(t.line) == cap(t.line) {
592 newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
593 copy(newLine, t.line)
594 t.line = newLine
595 }
596 t.line = t.line[:len(t.line)+1]
597 copy(t.line[t.pos+1:], t.line[t.pos:])
598 t.line[t.pos] = key
599 if t.echo {
600 t.writeLine(t.line[t.pos:])
601 }
602 t.pos++
603 t.moveCursorToPos(t.pos)
604 }
605
606 func (t *Terminal) writeLine(line []rune) {
607 for len(line) != 0 {
608 remainingOnLine := t.termWidth - t.cursorX
609 todo := len(line)
610 if todo > remainingOnLine {
611 todo = remainingOnLine
612 }
613 t.queue(line[:todo])
614 t.advanceCursor(visualLength(line[:todo]))
615 line = line[todo:]
616 }
617 }
618
619
620 func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
621 for len(buf) > 0 {
622 i := bytes.IndexByte(buf, '\n')
623 todo := len(buf)
624 if i >= 0 {
625 todo = i
626 }
627
628 var nn int
629 nn, err = w.Write(buf[:todo])
630 n += nn
631 if err != nil {
632 return n, err
633 }
634 buf = buf[todo:]
635
636 if i >= 0 {
637 if _, err = w.Write(crlf); err != nil {
638 return n, err
639 }
640 n++
641 buf = buf[1:]
642 }
643 }
644
645 return n, nil
646 }
647
648 func (t *Terminal) Write(buf []byte) (n int, err error) {
649 t.lock.Lock()
650 defer t.lock.Unlock()
651
652 if t.cursorX == 0 && t.cursorY == 0 {
653
654
655 return writeWithCRLF(t.c, buf)
656 }
657
658
659
660 t.move(0 , 0 , t.cursorX , 0 )
661 t.cursorX = 0
662 t.clearLineToRight()
663
664 for t.cursorY > 0 {
665 t.move(1 , 0, 0, 0)
666 t.cursorY--
667 t.clearLineToRight()
668 }
669
670 if _, err = t.c.Write(t.outBuf); err != nil {
671 return
672 }
673 t.outBuf = t.outBuf[:0]
674
675 if n, err = writeWithCRLF(t.c, buf); err != nil {
676 return
677 }
678
679 t.writeLine(t.prompt)
680 if t.echo {
681 t.writeLine(t.line)
682 }
683
684 t.moveCursorToPos(t.pos)
685
686 if _, err = t.c.Write(t.outBuf); err != nil {
687 return
688 }
689 t.outBuf = t.outBuf[:0]
690 return
691 }
692
693
694
695 func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
696 t.lock.Lock()
697 defer t.lock.Unlock()
698
699 oldPrompt := t.prompt
700 t.prompt = []rune(prompt)
701 t.echo = false
702
703 line, err = t.readLine()
704
705 t.prompt = oldPrompt
706 t.echo = true
707
708 return
709 }
710
711
712 func (t *Terminal) ReadLine() (line string, err error) {
713 t.lock.Lock()
714 defer t.lock.Unlock()
715
716 return t.readLine()
717 }
718
719 func (t *Terminal) readLine() (line string, err error) {
720
721
722 if t.cursorX == 0 && t.cursorY == 0 {
723 t.writeLine(t.prompt)
724 t.c.Write(t.outBuf)
725 t.outBuf = t.outBuf[:0]
726 }
727
728 lineIsPasted := t.pasteActive
729
730 for {
731 rest := t.remainder
732 lineOk := false
733 for !lineOk {
734 var key rune
735 key, rest = bytesToKey(rest, t.pasteActive)
736 if key == utf8.RuneError {
737 break
738 }
739 if !t.pasteActive {
740 if key == keyCtrlD {
741 if len(t.line) == 0 {
742 return "", io.EOF
743 }
744 }
745 if key == keyCtrlC {
746 return "", io.EOF
747 }
748 if key == keyPasteStart {
749 t.pasteActive = true
750 if len(t.line) == 0 {
751 lineIsPasted = true
752 }
753 continue
754 }
755 } else if key == keyPasteEnd {
756 t.pasteActive = false
757 continue
758 }
759 if !t.pasteActive {
760 lineIsPasted = false
761 }
762 line, lineOk = t.handleKey(key)
763 }
764 if len(rest) > 0 {
765 n := copy(t.inBuf[:], rest)
766 t.remainder = t.inBuf[:n]
767 } else {
768 t.remainder = nil
769 }
770 t.c.Write(t.outBuf)
771 t.outBuf = t.outBuf[:0]
772 if lineOk {
773 if t.echo {
774 t.historyIndex = -1
775 t.history.Add(line)
776 }
777 if lineIsPasted {
778 err = ErrPasteIndicator
779 }
780 return
781 }
782
783
784
785 readBuf := t.inBuf[len(t.remainder):]
786 var n int
787
788 t.lock.Unlock()
789 n, err = t.c.Read(readBuf)
790 t.lock.Lock()
791
792 if err != nil {
793 return
794 }
795
796 t.remainder = t.inBuf[:n+len(t.remainder)]
797 }
798 }
799
800
801 func (t *Terminal) SetPrompt(prompt string) {
802 t.lock.Lock()
803 defer t.lock.Unlock()
804
805 t.prompt = []rune(prompt)
806 }
807
808 func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
809
810 t.move(t.cursorY, 0, t.cursorX, 0)
811 t.cursorX, t.cursorY = 0, 0
812 t.clearLineToRight()
813 for t.cursorY < numPrevLines {
814
815 t.move(0, 1, 0, 0)
816 t.cursorY++
817 t.clearLineToRight()
818 }
819
820 t.move(t.cursorY, 0, 0, 0)
821 t.cursorX, t.cursorY = 0, 0
822
823 t.queue(t.prompt)
824 t.advanceCursor(visualLength(t.prompt))
825 t.writeLine(t.line)
826 t.moveCursorToPos(t.pos)
827 }
828
829 func (t *Terminal) SetSize(width, height int) error {
830 t.lock.Lock()
831 defer t.lock.Unlock()
832
833 if width == 0 {
834 width = 1
835 }
836
837 oldWidth := t.termWidth
838 t.termWidth, t.termHeight = width, height
839
840 switch {
841 case width == oldWidth:
842
843
844 return nil
845 case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
846
847
848 return nil
849 case width < oldWidth:
850
851
852
853
854
855
856
857
858
859
860
861 if t.cursorX >= t.termWidth {
862 t.cursorX = t.termWidth - 1
863 }
864 t.cursorY *= 2
865 t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
866 case width > oldWidth:
867
868
869
870
871
872
873
874 t.clearAndRepaintLinePlusNPrevious(t.maxLine)
875 }
876
877 _, err := t.c.Write(t.outBuf)
878 t.outBuf = t.outBuf[:0]
879 return err
880 }
881
882 type pasteIndicatorError struct{}
883
884 func (pasteIndicatorError) Error() string {
885 return "terminal: ErrPasteIndicator not correctly handled"
886 }
887
888
889
890
891
892 var ErrPasteIndicator = pasteIndicatorError{}
893
894
895
896
897
898
899 func (t *Terminal) SetBracketedPasteMode(on bool) {
900 if on {
901 io.WriteString(t.c, "\x1b[?2004h")
902 } else {
903 io.WriteString(t.c, "\x1b[?2004l")
904 }
905 }
906
907
908 type stRingBuffer struct {
909
910 entries []string
911 max int
912
913 head int
914
915 size int
916 }
917
918 func (s *stRingBuffer) Add(a string) {
919 if s.entries == nil {
920 const defaultNumEntries = 100
921 s.entries = make([]string, defaultNumEntries)
922 s.max = defaultNumEntries
923 }
924
925 s.head = (s.head + 1) % s.max
926 s.entries[s.head] = a
927 if s.size < s.max {
928 s.size++
929 }
930 }
931
932
933
934
935
936 func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
937 if n < 0 || n >= s.size {
938 return "", false
939 }
940 index := s.head - n
941 if index < 0 {
942 index += s.max
943 }
944 return s.entries[index], true
945 }
946
947
948
949
950
951
952 func readPasswordLine(reader io.Reader) ([]byte, error) {
953 var buf [1]byte
954 var ret []byte
955
956 for {
957 n, err := reader.Read(buf[:])
958 if n > 0 {
959 switch buf[0] {
960 case '\b':
961 if len(ret) > 0 {
962 ret = ret[:len(ret)-1]
963 }
964 case '\n':
965 if runtime.GOOS != "windows" {
966 return ret, nil
967 }
968
969 case '\r':
970 if runtime.GOOS == "windows" {
971 return ret, nil
972 }
973
974 default:
975 ret = append(ret, buf[0])
976 }
977 continue
978 }
979 if err != nil {
980 if err == io.EOF && len(ret) > 0 {
981 return ret, nil
982 }
983 return ret, err
984 }
985 }
986 }
987
View as plain text