1// 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 //
48 // This will be disabled during ReadPassword.
49 AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
50
51 // Escape contains a pointer to the escape codes for this terminal.
52 // It's always a valid pointer, although the escape codes themselves
53 // may be empty if the terminal doesn't support them.
54 Escape *EscapeCodes
55
56 // lock protects the terminal and the state in this object from
57 // concurrent processing of a key press and a Write() call.
58 lock sync.Mutex
59
60 c io.ReadWriter
61 prompt []rune
62
63 // line is the current line being entered.
64 line []rune
65 // pos is the logical position of the cursor in line
66 pos int
67 // echo is true if local echo is enabled
68 echo bool
69 // pasteActive is true iff there is a bracketed paste operation in
70 // progress.
71 pasteActive bool
72
73 // cursorX contains the current X value of the cursor where the left
74 // edge is 0. cursorY contains the row number where the first row of
75 // the current line is 0.
76 cursorX, cursorY int
77 // maxLine is the greatest value of cursorY so far.
78 maxLine int
79
80 termWidth, termHeight int
81
82 // outBuf contains the terminal data to be sent.
83 outBuf []byte
84 // remainder contains the remainder of any partial key sequences after
85 // a read. It aliases into inBuf.
86 remainder []byte
87 inBuf [256]byte
88
89 // history contains previously entered commands so that they can be
90 // accessed with the up and down keys.
91 history stRingBuffer
92 // historyIndex stores the currently accessed history entry, where zero
93 // means the immediately previous entry.
94 historyIndex int
95 // When navigating up and down the history it's possible to return to
96 // the incomplete, initial line. That value is stored in
97 // historyPending.
98 historyPending string
99}
100
101// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
102// a local terminal, that terminal must first have been put into raw mode.
103// prompt is a string that is written at the start of each input line (i.e.
104// "> ").
105func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
106 return &Terminal{
107 Escape: &vt100EscapeCodes,
108 c: c,
109 prompt: []rune(prompt),
110 termWidth: 80,
111 termHeight: 24,
112 echo: true,
113 historyIndex: -1,
114 }
115}
116
117const (
118 keyCtrlC = 3
119 keyCtrlD = 4
120 keyCtrlU = 21
121 keyEnter = '\r'
122 keyEscape = 27
123 keyBackspace = 127
124 keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
125 keyUp
126 keyDown
127 keyLeft
128 keyRight
129 keyAltLeft
130 keyAltRight
131 keyHome
132 keyEnd
133 keyDeleteWord
134 keyDeleteLine
135 keyClearScreen
136 keyPasteStart
137 keyPasteEnd
138)
139
140var (
141 crlf = []byte{'\r', '\n'}
142 pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
143 pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
144)
145
146// bytesToKey tries to parse a key sequence from b. If successful, it returns
147// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
148func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
149 if len(b) == 0 {
150 return utf8.RuneError, nil
151 }
152
153 if !pasteActive {
154 switch b[0] {
155 case 1: // ^A
156 return keyHome, b[1:]
157 case 2: // ^B
158 return keyLeft, b[1:]
159 case 5: // ^E
160 return keyEnd, b[1:]
161 case 6: // ^F
162 return keyRight, b[1:]
163 case 8: // ^H
164 return keyBackspace, b[1:]
165 case 11: // ^K
166 return keyDeleteLine, b[1:]
167 case 12: // ^L
168 return keyClearScreen, b[1:]
169 case 23: // ^W
170 return keyDeleteWord, b[1:]
171 case 14: // ^N
172 return keyDown, b[1:]
173 case 16: // ^P
174 return keyUp, b[1:]
175 }
176 }
177
178 if b[0] != keyEscape {
179 if !utf8.FullRune(b) {
180 return utf8.RuneError, b
181 }
182 r, l := utf8.DecodeRune(b)
183 return r, b[l:]
184 }
185
186 if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
187 switch b[2] {
188 case 'A':
189 return keyUp, b[3:]
190 case 'B':
191 return keyDown, b[3:]
192 case 'C':
193 return keyRight, b[3:]
194 case 'D':
195 return keyLeft, b[3:]
196 case 'H':
197 return keyHome, b[3:]
198 case 'F':
199 return keyEnd, b[3:]
200 }
201 }
202
203 if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
204 switch b[5] {
205 case 'C':
206 return keyAltRight, b[6:]
207 case 'D':
208 return keyAltLeft, b[6:]
209 }
210 }
211
212 if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
213 return keyPasteStart, b[6:]
214 }
215
216 if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
217 return keyPasteEnd, b[6:]
218 }
219
220 // If we get here then we have a key that we don't recognise, or a
221 // partial sequence. It's not clear how one should find the end of a
222 // sequence without knowing them all, but it seems that [a-zA-Z~] only
223 // appears at the end of a sequence.
224 for i, c := range b[0:] {
225 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
226 return keyUnknown, b[i+1:]
227 }
228 }
229
230 return utf8.RuneError, b
231}
232
233// queue appends data to the end of t.outBuf
234func (t *Terminal) queue(data []rune) {
235 t.outBuf = append(t.outBuf, []byte(string(data))...)
236}
237
238var space = []rune{' '}
239
240func isPrintable(key rune) bool {
241 isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
242 return key >= 32 && !isInSurrogateArea
243}
244
245// moveCursorToPos appends data to t.outBuf which will move the cursor to the
246// given, logical position in the text.
247func (t *Terminal) moveCursorToPos(pos int) {
248 if !t.echo {
249 return
250 }
251
252 x := visualLength(t.prompt) + pos
253 y := x / t.termWidth
254 x = x % t.termWidth
255
256 up := 0
257 if y < t.cursorY {
258 up = t.cursorY - y
259 }
260
261 down := 0
262 if y > t.cursorY {
263 down = y - t.cursorY
264 }
265
266 left := 0
267 if x < t.cursorX {
268 left = t.cursorX - x
269 }
270
271 right := 0
272 if x > t.cursorX {
273 right = x - t.cursorX
274 }
275
276 t.cursorX = x
277 t.cursorY = y
278 t.move(up, down, left, right)
279}
280
281func (t *Terminal) move(up, down, left, right int) {
282 m := []rune{}
283
284 // 1 unit up can be expressed as ^[[A or ^[A
285 // 5 units up can be expressed as ^[[5A
286
287 if up == 1 {
288 m = append(m, keyEscape, '[', 'A')
289 } else if up > 1 {
290 m = append(m, keyEscape, '[')
291 m = append(m, []rune(strconv.Itoa(up))...)
292 m = append(m, 'A')
293 }
294
295 if down == 1 {
296 m = append(m, keyEscape, '[', 'B')
297 } else if down > 1 {
298 m = append(m, keyEscape, '[')
299 m = append(m, []rune(strconv.Itoa(down))...)
300 m = append(m, 'B')
301 }
302
303 if right == 1 {
304 m = append(m, keyEscape, '[', 'C')
305 } else if right > 1 {
306 m = append(m, keyEscape, '[')
307 m = append(m, []rune(strconv.Itoa(right))...)
308 m = append(m, 'C')
309 }
310
311 if left == 1 {
312 m = append(m, keyEscape, '[', 'D')
313 } else if left > 1 {
314 m = append(m, keyEscape, '[')
315 m = append(m, []rune(strconv.Itoa(left))...)
316 m = append(m, 'D')
317 }
318
319 t.queue(m)
320}
321
322func (t *Terminal) clearLineToRight() {
323 op := []rune{keyEscape, '[', 'K'}
324 t.queue(op)
325}
326
327const maxLineLength = 4096
328
329func (t *Terminal) setLine(newLine []rune, newPos int) {
330 if t.echo {
331 t.moveCursorToPos(0)
332 t.writeLine(newLine)
333 for i := len(newLine); i < len(t.line); i++ {
334 t.writeLine(space)
335 }
336 t.moveCursorToPos(newPos)
337 }
338 t.line = newLine
339 t.pos = newPos
340}
341
342func (t *Terminal) advanceCursor(places int) {
343 t.cursorX += places
344 t.cursorY += t.cursorX / t.termWidth
345 if t.cursorY > t.maxLine {
346 t.maxLine = t.cursorY
347 }
348 t.cursorX = t.cursorX % t.termWidth
349
350 if places > 0 && t.cursorX == 0 {
351 // Normally terminals will advance the current position
352 // when writing a character. But that doesn't happen
353 // for the last character in a line. However, when
354 // writing a character (except a new line) that causes
355 // a line wrap, the position will be advanced two
356 // places.
357 //
358 // So, if we are stopping at the end of a line, we
359 // need to write a newline so that our cursor can be
360 // advanced to the next line.
361 t.outBuf = append(t.outBuf, '\r', '\n')
362 }
363}
364
365func (t *Terminal) eraseNPreviousChars(n int) {
366 if n == 0 {
367 return
368 }
369
370 if t.pos < n {
371 n = t.pos
372 }
373 t.pos -= n
374 t.moveCursorToPos(t.pos)
375
376 copy(t.line[t.pos:], t.line[n+t.pos:])
377 t.line = t.line[:len(t.line)-n]
378 if t.echo {
379 t.writeLine(t.line[t.pos:])
380 for i := 0; i < n; i++ {
381 t.queue(space)
382 }
383 t.advanceCursor(n)
384 t.moveCursorToPos(t.pos)
385 }
386}
387
388// countToLeftWord returns then number of characters from the cursor to the
389// start of the previous word.
390func (t *Terminal) countToLeftWord() int {
391 if t.pos == 0 {
392 return 0
393 }
394
395 pos := t.pos - 1
396 for pos > 0 {
397 if t.line[pos] != ' ' {
398 break
399 }
400 pos--
401 }
402 for pos > 0 {
403 if t.line[pos] == ' ' {
404 pos++
405 break
406 }
407 pos--
408 }
409
410 return t.pos - pos
411}
412
413// countToRightWord returns then number of characters from the cursor to the
414// start of the next word.
415func (t *Terminal) countToRightWord() int {
416 pos := t.pos
417 for pos < len(t.line) {
418 if t.line[pos] == ' ' {
419 break
420 }
421 pos++
422 }
423 for pos < len(t.line) {
424 if t.line[pos] != ' ' {
425 break
426 }
427 pos++
428 }
429 return pos - t.pos
430}
431
432// visualLength returns the number of visible glyphs in s.
433func visualLength(runes []rune) int {
434 inEscapeSeq := false
435 length := 0
436
437 for _, r := range runes {
438 switch {
439 case inEscapeSeq:
440 if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
441 inEscapeSeq = false
442 }
443 case r == '\x1b':
444 inEscapeSeq = true
445 default:
446 length++
447 }
448 }
449
450 return length
451}
452
453// handleKey processes the given key and, optionally, returns a line of text
454// that the user has entered.
455func (t *Terminal) handleKey(key rune) (line string, ok bool) {
456 if t.pasteActive && key != keyEnter {
457 t.addKeyToLine(key)
458 return
459 }
460
461 switch key {
462 case keyBackspace:
463 if t.pos == 0 {
464 return
465 }
466 t.eraseNPreviousChars(1)
467 case keyAltLeft:
468 // move left by a word.
469 t.pos -= t.countToLeftWord()
470 t.moveCursorToPos(t.pos)
471 case keyAltRight:
472 // move right by a word.
473 t.pos += t.countToRightWord()
474 t.moveCursorToPos(t.pos)
475 case keyLeft:
476 if t.pos == 0 {
477 return
478 }
479 t.pos--
480 t.moveCursorToPos(t.pos)
481 case keyRight:
482 if t.pos == len(t.line) {
483 return
484 }
485 t.pos++
486 t.moveCursorToPos(t.pos)
487 case keyHome:
488 if t.pos == 0 {
489 return
490 }
491 t.pos = 0
492 t.moveCursorToPos(t.pos)
493 case keyEnd:
494 if t.pos == len(t.line) {
495 return
496 }
497 t.pos = len(t.line)
498 t.moveCursorToPos(t.pos)
499 case keyUp:
500 entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
501 if !ok {
502 return "", false
503 }
504 if t.historyIndex == -1 {
505 t.historyPending = string(t.line)
506 }
507 t.historyIndex++
508 runes := []rune(entry)
509 t.setLine(runes, len(runes))
510 case keyDown:
511 switch t.historyIndex {
512 case -1:
513 return
514 case 0:
515 runes := []rune(t.historyPending)
516 t.setLine(runes, len(runes))
517 t.historyIndex--
518 default:
519 entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
520 if ok {
521 t.historyIndex--
522 runes := []rune(entry)
523 t.setLine(runes, len(runes))
524 }
525 }
526 case keyEnter:
527 t.moveCursorToPos(len(t.line))
528 t.queue([]rune("\r\n"))
529 line = string(t.line)
530 ok = true
531 t.line = t.line[:0]
532 t.pos = 0
533 t.cursorX = 0
534 t.cursorY = 0
535 t.maxLine = 0
536 case keyDeleteWord:
537 // Delete zero or more spaces and then one or more characters.
538 t.eraseNPreviousChars(t.countToLeftWord())
539 case keyDeleteLine:
540 // Delete everything from the current cursor position to the
541 // end of line.
542 for i := t.pos; i < len(t.line); i++ {
543 t.queue(space)
544 t.advanceCursor(1)
545 }
546 t.line = t.line[:t.pos]
547 t.moveCursorToPos(t.pos)
548 case keyCtrlD:
549 // Erase the character under the current position.
550 // The EOF case when the line is empty is handled in
551 // readLine().
552 if t.pos < len(t.line) {
553 t.pos++
554 t.eraseNPreviousChars(1)
555 }
556 case keyCtrlU:
557 t.eraseNPreviousChars(t.pos)
558 case keyClearScreen:
559 // Erases the screen and moves the cursor to the home position.
560 t.queue([]rune("\x1b[2J\x1b[H"))
561 t.queue(t.prompt)
562 t.cursorX, t.cursorY = 0, 0
563 t.advanceCursor(visualLength(t.prompt))
564 t.setLine(t.line, t.pos)
565 default:
566 if t.AutoCompleteCallback != nil {
567 prefix := string(t.line[:t.pos])
568 suffix := string(t.line[t.pos:])
569
570 t.lock.Unlock()
571 newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
572 t.lock.Lock()
573
574 if completeOk {
575 t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
576 return
577 }
578 }
579 if !isPrintable(key) {
580 return
581 }
582 if len(t.line) == maxLineLength {
583 return
584 }
585 t.addKeyToLine(key)
586 }
587 return
588}
589
590// addKeyToLine inserts the given key at the current position in the current
591// line.
592func (t *Terminal) addKeyToLine(key rune) {
593 if len(t.line) == cap(t.line) {
594 newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
595 copy(newLine, t.line)
596 t.line = newLine
597 }
598 t.line = t.line[:len(t.line)+1]
599 copy(t.line[t.pos+1:], t.line[t.pos:])
600 t.line[t.pos] = key
601 if t.echo {
602 t.writeLine(t.line[t.pos:])
603 }
604 t.pos++
605 t.moveCursorToPos(t.pos)
606}
607
608func (t *Terminal) writeLine(line []rune) {
609 for len(line) != 0 {
610 remainingOnLine := t.termWidth - t.cursorX
611 todo := len(line)
612 if todo > remainingOnLine {
613 todo = remainingOnLine
614 }
615 t.queue(line[:todo])
616 t.advanceCursor(visualLength(line[:todo]))
617 line = line[todo:]
618 }
619}
620
621// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
622func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
623 for len(buf) > 0 {
624 i := bytes.IndexByte(buf, '\n')
625 todo := len(buf)
626 if i >= 0 {
627 todo = i
628 }
629
630 var nn int
631 nn, err = w.Write(buf[:todo])
632 n += nn
633 if err != nil {
634 return n, err
635 }
636 buf = buf[todo:]
637
638 if i >= 0 {
639 if _, err = w.Write(crlf); err != nil {
640 return n, err
641 }
642 n++
643 buf = buf[1:]
644 }
645 }
646
647 return n, nil
648}
649
650func (t *Terminal) Write(buf []byte) (n int, err error) {
651 t.lock.Lock()
652 defer t.lock.Unlock()
653
654 if t.cursorX == 0 && t.cursorY == 0 {
655 // This is the easy case: there's nothing on the screen that we
656 // have to move out of the way.
657 return writeWithCRLF(t.c, buf)
658 }
659
660 // We have a prompt and possibly user input on the screen. We
661 // have to clear it first.
662 t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
663 t.cursorX = 0
664 t.clearLineToRight()
665
666 for t.cursorY > 0 {
667 t.move(1 /* up */, 0, 0, 0)
668 t.cursorY--
669 t.clearLineToRight()
670 }
671
672 if _, err = t.c.Write(t.outBuf); err != nil {
673 return
674 }
675 t.outBuf = t.outBuf[:0]
676
677 if n, err = writeWithCRLF(t.c, buf); err != nil {
678 return
679 }
680
681 t.writeLine(t.prompt)
682 if t.echo {
683 t.writeLine(t.line)
684 }
685
686 t.moveCursorToPos(t.pos)
687
688 if _, err = t.c.Write(t.outBuf); err != nil {
689 return
690 }
691 t.outBuf = t.outBuf[:0]
692 return
693}
694
695// ReadPassword temporarily changes the prompt and reads a password, without
696// echo, from the terminal.
697//
698// The AutoCompleteCallback is disabled during this call.
699func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
700 t.lock.Lock()
701 defer t.lock.Unlock()
702
703 oldPrompt := t.prompt
704 t.prompt = []rune(prompt)
705 t.echo = false
706 oldAutoCompleteCallback := t.AutoCompleteCallback
707 t.AutoCompleteCallback = nil
708 defer func() {
709 t.AutoCompleteCallback = oldAutoCompleteCallback
710 }()
711
712 line, err = t.readLine()
713
714 t.prompt = oldPrompt
715 t.echo = true
716
717 return
718}
719
720// ReadLine returns a line of input from the terminal.
721func (t *Terminal) ReadLine() (line string, err error) {
722 t.lock.Lock()
723 defer t.lock.Unlock()
724
725 return t.readLine()
726}
727
728func (t *Terminal) readLine() (line string, err error) {
729 // t.lock must be held at this point
730
731 if t.cursorX == 0 && t.cursorY == 0 {
732 t.writeLine(t.prompt)
733 t.c.Write(t.outBuf)
734 t.outBuf = t.outBuf[:0]
735 }
736
737 lineIsPasted := t.pasteActive
738
739 for {
740 rest := t.remainder
741 lineOk := false
742 for !lineOk {
743 var key rune
744 key, rest = bytesToKey(rest, t.pasteActive)
745 if key == utf8.RuneError {
746 break
747 }
748 if !t.pasteActive {
749 if key == keyCtrlD {
750 if len(t.line) == 0 {
751 return "", io.EOF
752 }
753 }
754 if key == keyCtrlC {
755 return "", io.EOF
756 }
757 if key == keyPasteStart {
758 t.pasteActive = true
759 if len(t.line) == 0 {
760 lineIsPasted = true
761 }
762 continue
763 }
764 } else if key == keyPasteEnd {
765 t.pasteActive = false
766 continue
767 }
768 if !t.pasteActive {
769 lineIsPasted = false
770 }
771 line, lineOk = t.handleKey(key)
772 }
773 if len(rest) > 0 {
774 n := copy(t.inBuf[:], rest)
775 t.remainder = t.inBuf[:n]
776 } else {
777 t.remainder = nil
778 }
779 t.c.Write(t.outBuf)
780 t.outBuf = t.outBuf[:0]
781 if lineOk {
782 if t.echo {
783 t.historyIndex = -1
784 t.history.Add(line)
785 }
786 if lineIsPasted {
787 err = ErrPasteIndicator
788 }
789 return
790 }
791
792 // t.remainder is a slice at the beginning of t.inBuf
793 // containing a partial key sequence
794 readBuf := t.inBuf[len(t.remainder):]
795 var n int
796
797 t.lock.Unlock()
798 n, err = t.c.Read(readBuf)
799 t.lock.Lock()
800
801 if err != nil {
802 return
803 }
804
805 t.remainder = t.inBuf[:n+len(t.remainder)]
806 }
807}
808
809// SetPrompt sets the prompt to be used when reading subsequent lines.
810func (t *Terminal) SetPrompt(prompt string) {
811 t.lock.Lock()
812 defer t.lock.Unlock()
813
814 t.prompt = []rune(prompt)
815}
816
817func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
818 // Move cursor to column zero at the start of the line.
819 t.move(t.cursorY, 0, t.cursorX, 0)
820 t.cursorX, t.cursorY = 0, 0
821 t.clearLineToRight()
822 for t.cursorY < numPrevLines {
823 // Move down a line
824 t.move(0, 1, 0, 0)
825 t.cursorY++
826 t.clearLineToRight()
827 }
828 // Move back to beginning.
829 t.move(t.cursorY, 0, 0, 0)
830 t.cursorX, t.cursorY = 0, 0
831
832 t.queue(t.prompt)
833 t.advanceCursor(visualLength(t.prompt))
834 t.writeLine(t.line)
835 t.moveCursorToPos(t.pos)
836}
837
838func (t *Terminal) SetSize(width, height int) error {
839 t.lock.Lock()
840 defer t.lock.Unlock()
841
842 if width == 0 {
843 width = 1
844 }
845
846 oldWidth := t.termWidth
847 t.termWidth, t.termHeight = width, height
848
849 switch {
850 case width == oldWidth:
851 // If the width didn't change then nothing else needs to be
852 // done.
853 return nil
854 case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
855 // If there is nothing on current line and no prompt printed,
856 // just do nothing
857 return nil
858 case width < oldWidth:
859 // Some terminals (e.g. xterm) will truncate lines that were
860 // too long when shinking. Others, (e.g. gnome-terminal) will
861 // attempt to wrap them. For the former, repainting t.maxLine
862 // works great, but that behaviour goes badly wrong in the case
863 // of the latter because they have doubled every full line.
864
865 // We assume that we are working on a terminal that wraps lines
866 // and adjust the cursor position based on every previous line
867 // wrapping and turning into two. This causes the prompt on
868 // xterms to move upwards, which isn't great, but it avoids a
869 // huge mess with gnome-terminal.
870 if t.cursorX >= t.termWidth {
871 t.cursorX = t.termWidth - 1
872 }
873 t.cursorY *= 2
874 t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
875 case width > oldWidth:
876 // If the terminal expands then our position calculations will
877 // be wrong in the future because we think the cursor is
878 // |t.pos| chars into the string, but there will be a gap at
879 // the end of any wrapped line.
880 //
881 // But the position will actually be correct until we move, so
882 // we can move back to the beginning and repaint everything.
883 t.clearAndRepaintLinePlusNPrevious(t.maxLine)
884 }
885
886 _, err := t.c.Write(t.outBuf)
887 t.outBuf = t.outBuf[:0]
888 return err
889}
890
891type pasteIndicatorError struct{}
892
893func (pasteIndicatorError) Error() string {
894 return "terminal: ErrPasteIndicator not correctly handled"
895}
896
897// ErrPasteIndicator may be returned from ReadLine as the error, in addition
898// to valid line data. It indicates that bracketed paste mode is enabled and
899// that the returned line consists only of pasted data. Programs may wish to
900// interpret pasted data more literally than typed data.
901var ErrPasteIndicator = pasteIndicatorError{}
902
903// SetBracketedPasteMode requests that the terminal bracket paste operations
904// with markers. Not all terminals support this but, if it is supported, then
905// enabling this mode will stop any autocomplete callback from running due to
906// pastes. Additionally, any lines that are completely pasted will be returned
907// from ReadLine with the error set to ErrPasteIndicator.
908func (t *Terminal) SetBracketedPasteMode(on bool) {
909 if on {
910 io.WriteString(t.c, "\x1b[?2004h")
911 } else {
912 io.WriteString(t.c, "\x1b[?2004l")
913 }
914}
915
916// stRingBuffer is a ring buffer of strings.
917type stRingBuffer struct {
918 // entries contains max elements.
919 entries []string
920 max int
921 // head contains the index of the element most recently added to the ring.
922 head int
923 // size contains the number of elements in the ring.
924 size int
925}
926
927func (s *stRingBuffer) Add(a string) {
928 if s.entries == nil {
929 const defaultNumEntries = 100
930 s.entries = make([]string, defaultNumEntries)
931 s.max = defaultNumEntries
932 }
933
934 s.head = (s.head + 1) % s.max
935 s.entries[s.head] = a
936 if s.size < s.max {
937 s.size++
938 }
939}
940
941// NthPreviousEntry returns the value passed to the nth previous call to Add.
942// If n is zero then the immediately prior value is returned, if one, then the
943// next most recent, and so on. If such an element doesn't exist then ok is
944// false.
945func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
946 if n < 0 || n >= s.size {
947 return "", false
948 }
949 index := s.head - n
950 if index < 0 {
951 index += s.max
952 }
953 return s.entries[index], true
954}
955
956// readPasswordLine reads from reader until it finds \n or io.EOF.
957// The slice returned does not include the \n.
958// readPasswordLine also ignores any \r it finds.
959// Windows uses \r as end of line. So, on Windows, readPasswordLine
960// reads until it finds \r and ignores any \n it finds during processing.
961func readPasswordLine(reader io.Reader) ([]byte, error) {
962 var buf [1]byte
963 var ret []byte
964
965 for {
966 n, err := reader.Read(buf[:])
967 if n > 0 {
968 switch buf[0] {
969 case '\b':
970 if len(ret) > 0 {
971 ret = ret[:len(ret)-1]
972 }
973 case '\n':
974 if runtime.GOOS != "windows" {
975 return ret, nil
976 }
977 // otherwise ignore \n
978 case '\r':
979 if runtime.GOOS == "windows" {
980 return ret, nil
981 }
982 // otherwise ignore \r
983 default:
984 ret = append(ret, buf[0])
985 }
986 continue
987 }
988 if err != nil {
989 if err == io.EOF && len(ret) > 0 {
990 return ret, nil
991 }
992 return ret, err
993 }
994 }
995}