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