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 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} 988