blob: 535ab8257c415b552ab6aa92bcd7e196e783dbf2 [file] [log] [blame]
David K. Bainbridgebd6b2882021-08-26 13:31:02 +00001// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package term
6
7import (
8 "bytes"
9 "io"
10 "runtime"
11 "strconv"
12 "sync"
13 "unicode/utf8"
14)
15
16// EscapeCodes contains escape sequences that can be written to the terminal in
17// order to achieve different styles of text.
18type EscapeCodes struct {
19 // Foreground colors
20 Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
21
22 // Reset all attributes
23 Reset []byte
24}
25
26var 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// Terminal contains the state for running a VT100 terminal that is capable of
40// reading lines of input.
41type Terminal struct {
42 // AutoCompleteCallback, if non-null, is called for each keypress with
43 // the full input line and the current position of the cursor (in
44 // bytes, as an index into |line|). If it returns ok=false, the key
45 // press is processed normally. Otherwise it returns a replacement line
46 // and the new cursor position.
47 AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
48
49 // Escape contains a pointer to the escape codes for this terminal.
50 // It's always a valid pointer, although the escape codes themselves
51 // may be empty if the terminal doesn't support them.
52 Escape *EscapeCodes
53
54 // lock protects the terminal and the state in this object from
55 // concurrent processing of a key press and a Write() call.
56 lock sync.Mutex
57
58 c io.ReadWriter
59 prompt []rune
60
61 // line is the current line being entered.
62 line []rune
63 // pos is the logical position of the cursor in line
64 pos int
65 // echo is true if local echo is enabled
66 echo bool
67 // pasteActive is true iff there is a bracketed paste operation in
68 // progress.
69 pasteActive bool
70
71 // cursorX contains the current X value of the cursor where the left
72 // edge is 0. cursorY contains the row number where the first row of
73 // the current line is 0.
74 cursorX, cursorY int
75 // maxLine is the greatest value of cursorY so far.
76 maxLine int
77
78 termWidth, termHeight int
79
80 // outBuf contains the terminal data to be sent.
81 outBuf []byte
82 // remainder contains the remainder of any partial key sequences after
83 // a read. It aliases into inBuf.
84 remainder []byte
85 inBuf [256]byte
86
87 // history contains previously entered commands so that they can be
88 // accessed with the up and down keys.
89 history stRingBuffer
90 // historyIndex stores the currently accessed history entry, where zero
91 // means the immediately previous entry.
92 historyIndex int
93 // When navigating up and down the history it's possible to return to
94 // the incomplete, initial line. That value is stored in
95 // historyPending.
96 historyPending string
97}
98
99// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
100// a local terminal, that terminal must first have been put into raw mode.
101// prompt is a string that is written at the start of each input line (i.e.
102// "> ").
103func 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
115const (
116 keyCtrlC = 3
117 keyCtrlD = 4
118 keyCtrlU = 21
119 keyEnter = '\r'
120 keyEscape = 27
121 keyBackspace = 127
122 keyUnknown = 0xd800 /* UTF-16 surrogate area */ + 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
138var (
139 crlf = []byte{'\r', '\n'}
140 pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
141 pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
142)
143
144// bytesToKey tries to parse a key sequence from b. If successful, it returns
145// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
146func 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: // ^A
154 return keyHome, b[1:]
155 case 2: // ^B
156 return keyLeft, b[1:]
157 case 5: // ^E
158 return keyEnd, b[1:]
159 case 6: // ^F
160 return keyRight, b[1:]
161 case 8: // ^H
162 return keyBackspace, b[1:]
163 case 11: // ^K
164 return keyDeleteLine, b[1:]
165 case 12: // ^L
166 return keyClearScreen, b[1:]
167 case 23: // ^W
168 return keyDeleteWord, b[1:]
169 case 14: // ^N
170 return keyDown, b[1:]
171 case 16: // ^P
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 // If we get here then we have a key that we don't recognise, or a
219 // partial sequence. It's not clear how one should find the end of a
220 // sequence without knowing them all, but it seems that [a-zA-Z~] only
221 // appears at the end of a sequence.
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// queue appends data to the end of t.outBuf
232func (t *Terminal) queue(data []rune) {
233 t.outBuf = append(t.outBuf, []byte(string(data))...)
234}
235
236var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
237var space = []rune{' '}
238
239func isPrintable(key rune) bool {
240 isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
241 return key >= 32 && !isInSurrogateArea
242}
243
244// moveCursorToPos appends data to t.outBuf which will move the cursor to the
245// given, logical position in the text.
246func (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
280func (t *Terminal) move(up, down, left, right int) {
281 m := []rune{}
282
283 // 1 unit up can be expressed as ^[[A or ^[A
284 // 5 units up can be expressed as ^[[5A
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
321func (t *Terminal) clearLineToRight() {
322 op := []rune{keyEscape, '[', 'K'}
323 t.queue(op)
324}
325
326const maxLineLength = 4096
327
328func (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
341func (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 // Normally terminals will advance the current position
351 // when writing a character. But that doesn't happen
352 // for the last character in a line. However, when
353 // writing a character (except a new line) that causes
354 // a line wrap, the position will be advanced two
355 // places.
356 //
357 // So, if we are stopping at the end of a line, we
358 // need to write a newline so that our cursor can be
359 // advanced to the next line.
360 t.outBuf = append(t.outBuf, '\r', '\n')
361 }
362}
363
364func (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// countToLeftWord returns then number of characters from the cursor to the
388// start of the previous word.
389func (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// countToRightWord returns then number of characters from the cursor to the
413// start of the next word.
414func (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// visualLength returns the number of visible glyphs in s.
432func 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// handleKey processes the given key and, optionally, returns a line of text
453// that the user has entered.
454func (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 // move left by a word.
468 t.pos -= t.countToLeftWord()
469 t.moveCursorToPos(t.pos)
470 case keyAltRight:
471 // move right by a word.
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 // Delete zero or more spaces and then one or more characters.
537 t.eraseNPreviousChars(t.countToLeftWord())
538 case keyDeleteLine:
539 // Delete everything from the current cursor position to the
540 // end of line.
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 // Erase the character under the current position.
549 // The EOF case when the line is empty is handled in
550 // readLine().
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 // Erases the screen and moves the cursor to the home position.
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// addKeyToLine inserts the given key at the current position in the current
590// line.
591func (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
607func (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// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
621func 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
649func (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 // This is the easy case: there's nothing on the screen that we
655 // have to move out of the way.
656 return writeWithCRLF(t.c, buf)
657 }
658
659 // We have a prompt and possibly user input on the screen. We
660 // have to clear it first.
661 t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
662 t.cursorX = 0
663 t.clearLineToRight()
664
665 for t.cursorY > 0 {
666 t.move(1 /* up */, 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// ReadPassword temporarily changes the prompt and reads a password, without
695// echo, from the terminal.
696func (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// ReadLine returns a line of input from the terminal.
713func (t *Terminal) ReadLine() (line string, err error) {
714 t.lock.Lock()
715 defer t.lock.Unlock()
716
717 return t.readLine()
718}
719
720func (t *Terminal) readLine() (line string, err error) {
721 // t.lock must be held at this point
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 // t.remainder is a slice at the beginning of t.inBuf
785 // containing a partial key sequence
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// SetPrompt sets the prompt to be used when reading subsequent lines.
802func (t *Terminal) SetPrompt(prompt string) {
803 t.lock.Lock()
804 defer t.lock.Unlock()
805
806 t.prompt = []rune(prompt)
807}
808
809func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
810 // Move cursor to column zero at the start of the line.
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 // Move down a line
816 t.move(0, 1, 0, 0)
817 t.cursorY++
818 t.clearLineToRight()
819 }
820 // Move back to beginning.
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
830func (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 // If the width didn't change then nothing else needs to be
844 // done.
845 return nil
846 case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
847 // If there is nothing on current line and no prompt printed,
848 // just do nothing
849 return nil
850 case width < oldWidth:
851 // Some terminals (e.g. xterm) will truncate lines that were
852 // too long when shinking. Others, (e.g. gnome-terminal) will
853 // attempt to wrap them. For the former, repainting t.maxLine
854 // works great, but that behaviour goes badly wrong in the case
855 // of the latter because they have doubled every full line.
856
857 // We assume that we are working on a terminal that wraps lines
858 // and adjust the cursor position based on every previous line
859 // wrapping and turning into two. This causes the prompt on
860 // xterms to move upwards, which isn't great, but it avoids a
861 // huge mess with gnome-terminal.
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 // If the terminal expands then our position calculations will
869 // be wrong in the future because we think the cursor is
870 // |t.pos| chars into the string, but there will be a gap at
871 // the end of any wrapped line.
872 //
873 // But the position will actually be correct until we move, so
874 // we can move back to the beginning and repaint everything.
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
883type pasteIndicatorError struct{}
884
885func (pasteIndicatorError) Error() string {
886 return "terminal: ErrPasteIndicator not correctly handled"
887}
888
889// ErrPasteIndicator may be returned from ReadLine as the error, in addition
890// to valid line data. It indicates that bracketed paste mode is enabled and
891// that the returned line consists only of pasted data. Programs may wish to
892// interpret pasted data more literally than typed data.
893var ErrPasteIndicator = pasteIndicatorError{}
894
895// SetBracketedPasteMode requests that the terminal bracket paste operations
896// with markers. Not all terminals support this but, if it is supported, then
897// enabling this mode will stop any autocomplete callback from running due to
898// pastes. Additionally, any lines that are completely pasted will be returned
899// from ReadLine with the error set to ErrPasteIndicator.
900func (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// stRingBuffer is a ring buffer of strings.
909type stRingBuffer struct {
910 // entries contains max elements.
911 entries []string
912 max int
913 // head contains the index of the element most recently added to the ring.
914 head int
915 // size contains the number of elements in the ring.
916 size int
917}
918
919func (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// NthPreviousEntry returns the value passed to the nth previous call to Add.
934// If n is zero then the immediately prior value is returned, if one, then the
935// next most recent, and so on. If such an element doesn't exist then ok is
936// false.
937func (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// readPasswordLine reads from reader until it finds \n or io.EOF.
949// The slice returned does not include the \n.
950// readPasswordLine also ignores any \r it finds.
951// Windows uses \r as end of line. So, on Windows, readPasswordLine
952// reads until it finds \r and ignores any \n it finds during processing.
953func 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 // otherwise ignore \n
970 case '\r':
971 if runtime.GOOS == "windows" {
972 return ret, nil
973 }
974 // otherwise ignore \r
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}