| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | // Copyright 2011 The Go Authors. All rights reserved. | 
					
						
							|  |  |  | // Use of this source code is governed by a BSD-style | 
					
						
							|  |  |  | // license that can be found in the LICENSE file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package terminal | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"unicode/utf8" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // EscapeCodes contains escape sequences that can be written to the terminal in | 
					
						
							|  |  |  | // order to achieve different styles of text. | 
					
						
							|  |  |  | type EscapeCodes struct { | 
					
						
							|  |  |  | 	// Foreground colors | 
					
						
							|  |  |  | 	Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reset all attributes | 
					
						
							|  |  |  | 	Reset []byte | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var vt100EscapeCodes = EscapeCodes{ | 
					
						
							|  |  |  | 	Black:   []byte{keyEscape, '[', '3', '0', 'm'}, | 
					
						
							|  |  |  | 	Red:     []byte{keyEscape, '[', '3', '1', 'm'}, | 
					
						
							|  |  |  | 	Green:   []byte{keyEscape, '[', '3', '2', 'm'}, | 
					
						
							|  |  |  | 	Yellow:  []byte{keyEscape, '[', '3', '3', 'm'}, | 
					
						
							|  |  |  | 	Blue:    []byte{keyEscape, '[', '3', '4', 'm'}, | 
					
						
							|  |  |  | 	Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, | 
					
						
							|  |  |  | 	Cyan:    []byte{keyEscape, '[', '3', '6', 'm'}, | 
					
						
							|  |  |  | 	White:   []byte{keyEscape, '[', '3', '7', 'm'}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Reset: []byte{keyEscape, '[', '0', 'm'}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Terminal contains the state for running a VT100 terminal that is capable of | 
					
						
							|  |  |  | // reading lines of input. | 
					
						
							|  |  |  | type Terminal struct { | 
					
						
							|  |  |  | 	// AutoCompleteCallback, if non-null, is called for each keypress with | 
					
						
							|  |  |  | 	// the full input line and the current position of the cursor (in | 
					
						
							|  |  |  | 	// bytes, as an index into |line|). If it returns ok=false, the key | 
					
						
							|  |  |  | 	// press is processed normally. Otherwise it returns a replacement line | 
					
						
							|  |  |  | 	// and the new cursor position. | 
					
						
							|  |  |  | 	AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Escape contains a pointer to the escape codes for this terminal. | 
					
						
							|  |  |  | 	// It's always a valid pointer, although the escape codes themselves | 
					
						
							|  |  |  | 	// may be empty if the terminal doesn't support them. | 
					
						
							|  |  |  | 	Escape *EscapeCodes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// lock protects the terminal and the state in this object from | 
					
						
							|  |  |  | 	// concurrent processing of a key press and a Write() call. | 
					
						
							|  |  |  | 	lock sync.Mutex | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	c      io.ReadWriter | 
					
						
							|  |  |  | 	prompt []rune | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// line is the current line being entered. | 
					
						
							|  |  |  | 	line []rune | 
					
						
							|  |  |  | 	// pos is the logical position of the cursor in line | 
					
						
							|  |  |  | 	pos int | 
					
						
							|  |  |  | 	// echo is true if local echo is enabled | 
					
						
							|  |  |  | 	echo bool | 
					
						
							|  |  |  | 	// pasteActive is true iff there is a bracketed paste operation in | 
					
						
							|  |  |  | 	// progress. | 
					
						
							|  |  |  | 	pasteActive bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// cursorX contains the current X value of the cursor where the left | 
					
						
							|  |  |  | 	// edge is 0. cursorY contains the row number where the first row of | 
					
						
							|  |  |  | 	// the current line is 0. | 
					
						
							|  |  |  | 	cursorX, cursorY int | 
					
						
							|  |  |  | 	// maxLine is the greatest value of cursorY so far. | 
					
						
							|  |  |  | 	maxLine int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	termWidth, termHeight int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// outBuf contains the terminal data to be sent. | 
					
						
							|  |  |  | 	outBuf []byte | 
					
						
							|  |  |  | 	// remainder contains the remainder of any partial key sequences after | 
					
						
							|  |  |  | 	// a read. It aliases into inBuf. | 
					
						
							|  |  |  | 	remainder []byte | 
					
						
							|  |  |  | 	inBuf     [256]byte | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// history contains previously entered commands so that they can be | 
					
						
							|  |  |  | 	// accessed with the up and down keys. | 
					
						
							|  |  |  | 	history stRingBuffer | 
					
						
							|  |  |  | 	// historyIndex stores the currently accessed history entry, where zero | 
					
						
							|  |  |  | 	// means the immediately previous entry. | 
					
						
							|  |  |  | 	historyIndex int | 
					
						
							|  |  |  | 	// When navigating up and down the history it's possible to return to | 
					
						
							|  |  |  | 	// the incomplete, initial line. That value is stored in | 
					
						
							|  |  |  | 	// historyPending. | 
					
						
							|  |  |  | 	historyPending string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is | 
					
						
							|  |  |  | // a local terminal, that terminal must first have been put into raw mode. | 
					
						
							|  |  |  | // prompt is a string that is written at the start of each input line (i.e. | 
					
						
							|  |  |  | // "> "). | 
					
						
							|  |  |  | func NewTerminal(c io.ReadWriter, prompt string) *Terminal { | 
					
						
							|  |  |  | 	return &Terminal{ | 
					
						
							|  |  |  | 		Escape:       &vt100EscapeCodes, | 
					
						
							|  |  |  | 		c:            c, | 
					
						
							|  |  |  | 		prompt:       []rune(prompt), | 
					
						
							|  |  |  | 		termWidth:    80, | 
					
						
							|  |  |  | 		termHeight:   24, | 
					
						
							|  |  |  | 		echo:         true, | 
					
						
							|  |  |  | 		historyIndex: -1, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	keyCtrlD     = 4 | 
					
						
							|  |  |  | 	keyCtrlU     = 21 | 
					
						
							|  |  |  | 	keyEnter     = '\r' | 
					
						
							|  |  |  | 	keyEscape    = 27 | 
					
						
							|  |  |  | 	keyBackspace = 127 | 
					
						
							|  |  |  | 	keyUnknown   = 0xd800 /* UTF-16 surrogate area */ + iota | 
					
						
							|  |  |  | 	keyUp | 
					
						
							|  |  |  | 	keyDown | 
					
						
							|  |  |  | 	keyLeft | 
					
						
							|  |  |  | 	keyRight | 
					
						
							|  |  |  | 	keyAltLeft | 
					
						
							|  |  |  | 	keyAltRight | 
					
						
							|  |  |  | 	keyHome | 
					
						
							|  |  |  | 	keyEnd | 
					
						
							|  |  |  | 	keyDeleteWord | 
					
						
							|  |  |  | 	keyDeleteLine | 
					
						
							|  |  |  | 	keyClearScreen | 
					
						
							|  |  |  | 	keyPasteStart | 
					
						
							|  |  |  | 	keyPasteEnd | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	crlf       = []byte{'\r', '\n'} | 
					
						
							|  |  |  | 	pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} | 
					
						
							|  |  |  | 	pasteEnd   = []byte{keyEscape, '[', '2', '0', '1', '~'} | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // bytesToKey tries to parse a key sequence from b. If successful, it returns | 
					
						
							|  |  |  | // the key and the remainder of the input. Otherwise it returns utf8.RuneError. | 
					
						
							|  |  |  | func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { | 
					
						
							|  |  |  | 	if len(b) == 0 { | 
					
						
							|  |  |  | 		return utf8.RuneError, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !pasteActive { | 
					
						
							|  |  |  | 		switch b[0] { | 
					
						
							|  |  |  | 		case 1: // ^A | 
					
						
							|  |  |  | 			return keyHome, b[1:] | 
					
						
							|  |  |  | 		case 5: // ^E | 
					
						
							|  |  |  | 			return keyEnd, b[1:] | 
					
						
							|  |  |  | 		case 8: // ^H | 
					
						
							|  |  |  | 			return keyBackspace, b[1:] | 
					
						
							|  |  |  | 		case 11: // ^K | 
					
						
							|  |  |  | 			return keyDeleteLine, b[1:] | 
					
						
							|  |  |  | 		case 12: // ^L | 
					
						
							|  |  |  | 			return keyClearScreen, b[1:] | 
					
						
							|  |  |  | 		case 23: // ^W | 
					
						
							|  |  |  | 			return keyDeleteWord, b[1:] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if b[0] != keyEscape { | 
					
						
							|  |  |  | 		if !utf8.FullRune(b) { | 
					
						
							|  |  |  | 			return utf8.RuneError, b | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		r, l := utf8.DecodeRune(b) | 
					
						
							|  |  |  | 		return r, b[l:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { | 
					
						
							|  |  |  | 		switch b[2] { | 
					
						
							|  |  |  | 		case 'A': | 
					
						
							|  |  |  | 			return keyUp, b[3:] | 
					
						
							|  |  |  | 		case 'B': | 
					
						
							|  |  |  | 			return keyDown, b[3:] | 
					
						
							|  |  |  | 		case 'C': | 
					
						
							|  |  |  | 			return keyRight, b[3:] | 
					
						
							|  |  |  | 		case 'D': | 
					
						
							|  |  |  | 			return keyLeft, b[3:] | 
					
						
							|  |  |  | 		case 'H': | 
					
						
							|  |  |  | 			return keyHome, b[3:] | 
					
						
							|  |  |  | 		case 'F': | 
					
						
							|  |  |  | 			return keyEnd, b[3:] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { | 
					
						
							|  |  |  | 		switch b[5] { | 
					
						
							|  |  |  | 		case 'C': | 
					
						
							|  |  |  | 			return keyAltRight, b[6:] | 
					
						
							|  |  |  | 		case 'D': | 
					
						
							|  |  |  | 			return keyAltLeft, b[6:] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { | 
					
						
							|  |  |  | 		return keyPasteStart, b[6:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { | 
					
						
							|  |  |  | 		return keyPasteEnd, b[6:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If we get here then we have a key that we don't recognise, or a | 
					
						
							|  |  |  | 	// partial sequence. It's not clear how one should find the end of a | 
					
						
							|  |  |  | 	// sequence without knowing them all, but it seems that [a-zA-Z~] only | 
					
						
							|  |  |  | 	// appears at the end of a sequence. | 
					
						
							|  |  |  | 	for i, c := range b[0:] { | 
					
						
							|  |  |  | 		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { | 
					
						
							|  |  |  | 			return keyUnknown, b[i+1:] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return utf8.RuneError, b | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // queue appends data to the end of t.outBuf | 
					
						
							|  |  |  | func (t *Terminal) queue(data []rune) { | 
					
						
							|  |  |  | 	t.outBuf = append(t.outBuf, []byte(string(data))...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'} | 
					
						
							|  |  |  | var space = []rune{' '} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func isPrintable(key rune) bool { | 
					
						
							|  |  |  | 	isInSurrogateArea := key >= 0xd800 && key <= 0xdbff | 
					
						
							|  |  |  | 	return key >= 32 && !isInSurrogateArea | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // moveCursorToPos appends data to t.outBuf which will move the cursor to the | 
					
						
							|  |  |  | // given, logical position in the text. | 
					
						
							|  |  |  | func (t *Terminal) moveCursorToPos(pos int) { | 
					
						
							|  |  |  | 	if !t.echo { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	x := visualLength(t.prompt) + pos | 
					
						
							|  |  |  | 	y := x / t.termWidth | 
					
						
							|  |  |  | 	x = x % t.termWidth | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	up := 0 | 
					
						
							|  |  |  | 	if y < t.cursorY { | 
					
						
							|  |  |  | 		up = t.cursorY - y | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	down := 0 | 
					
						
							|  |  |  | 	if y > t.cursorY { | 
					
						
							|  |  |  | 		down = y - t.cursorY | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	left := 0 | 
					
						
							|  |  |  | 	if x < t.cursorX { | 
					
						
							|  |  |  | 		left = t.cursorX - x | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	right := 0 | 
					
						
							|  |  |  | 	if x > t.cursorX { | 
					
						
							|  |  |  | 		right = x - t.cursorX | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.cursorX = x | 
					
						
							|  |  |  | 	t.cursorY = y | 
					
						
							|  |  |  | 	t.move(up, down, left, right) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) move(up, down, left, right int) { | 
					
						
							|  |  |  | 	movement := make([]rune, 3*(up+down+left+right)) | 
					
						
							|  |  |  | 	m := movement | 
					
						
							|  |  |  | 	for i := 0; i < up; i++ { | 
					
						
							|  |  |  | 		m[0] = keyEscape | 
					
						
							|  |  |  | 		m[1] = '[' | 
					
						
							|  |  |  | 		m[2] = 'A' | 
					
						
							|  |  |  | 		m = m[3:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i := 0; i < down; i++ { | 
					
						
							|  |  |  | 		m[0] = keyEscape | 
					
						
							|  |  |  | 		m[1] = '[' | 
					
						
							|  |  |  | 		m[2] = 'B' | 
					
						
							|  |  |  | 		m = m[3:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i := 0; i < left; i++ { | 
					
						
							|  |  |  | 		m[0] = keyEscape | 
					
						
							|  |  |  | 		m[1] = '[' | 
					
						
							|  |  |  | 		m[2] = 'D' | 
					
						
							|  |  |  | 		m = m[3:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for i := 0; i < right; i++ { | 
					
						
							|  |  |  | 		m[0] = keyEscape | 
					
						
							|  |  |  | 		m[1] = '[' | 
					
						
							|  |  |  | 		m[2] = 'C' | 
					
						
							|  |  |  | 		m = m[3:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.queue(movement) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) clearLineToRight() { | 
					
						
							|  |  |  | 	op := []rune{keyEscape, '[', 'K'} | 
					
						
							|  |  |  | 	t.queue(op) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const maxLineLength = 4096 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) setLine(newLine []rune, newPos int) { | 
					
						
							|  |  |  | 	if t.echo { | 
					
						
							|  |  |  | 		t.moveCursorToPos(0) | 
					
						
							|  |  |  | 		t.writeLine(newLine) | 
					
						
							|  |  |  | 		for i := len(newLine); i < len(t.line); i++ { | 
					
						
							|  |  |  | 			t.writeLine(space) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.moveCursorToPos(newPos) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.line = newLine | 
					
						
							|  |  |  | 	t.pos = newPos | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) advanceCursor(places int) { | 
					
						
							|  |  |  | 	t.cursorX += places | 
					
						
							|  |  |  | 	t.cursorY += t.cursorX / t.termWidth | 
					
						
							|  |  |  | 	if t.cursorY > t.maxLine { | 
					
						
							|  |  |  | 		t.maxLine = t.cursorY | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.cursorX = t.cursorX % t.termWidth | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if places > 0 && t.cursorX == 0 { | 
					
						
							|  |  |  | 		// Normally terminals will advance the current position | 
					
						
							|  |  |  | 		// when writing a character. But that doesn't happen | 
					
						
							|  |  |  | 		// for the last character in a line. However, when | 
					
						
							|  |  |  | 		// writing a character (except a new line) that causes | 
					
						
							|  |  |  | 		// a line wrap, the position will be advanced two | 
					
						
							|  |  |  | 		// places. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// So, if we are stopping at the end of a line, we | 
					
						
							|  |  |  | 		// need to write a newline so that our cursor can be | 
					
						
							|  |  |  | 		// advanced to the next line. | 
					
						
							|  |  |  | 		t.outBuf = append(t.outBuf, '\r', '\n') | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) eraseNPreviousChars(n int) { | 
					
						
							|  |  |  | 	if n == 0 { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if t.pos < n { | 
					
						
							|  |  |  | 		n = t.pos | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.pos -= n | 
					
						
							|  |  |  | 	t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	copy(t.line[t.pos:], t.line[n+t.pos:]) | 
					
						
							|  |  |  | 	t.line = t.line[:len(t.line)-n] | 
					
						
							|  |  |  | 	if t.echo { | 
					
						
							|  |  |  | 		t.writeLine(t.line[t.pos:]) | 
					
						
							|  |  |  | 		for i := 0; i < n; i++ { | 
					
						
							|  |  |  | 			t.queue(space) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.advanceCursor(n) | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // countToLeftWord returns then number of characters from the cursor to the | 
					
						
							|  |  |  | // start of the previous word. | 
					
						
							|  |  |  | func (t *Terminal) countToLeftWord() int { | 
					
						
							|  |  |  | 	if t.pos == 0 { | 
					
						
							|  |  |  | 		return 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pos := t.pos - 1 | 
					
						
							|  |  |  | 	for pos > 0 { | 
					
						
							|  |  |  | 		if t.line[pos] != ' ' { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		pos-- | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for pos > 0 { | 
					
						
							|  |  |  | 		if t.line[pos] == ' ' { | 
					
						
							|  |  |  | 			pos++ | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		pos-- | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return t.pos - pos | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // countToRightWord returns then number of characters from the cursor to the | 
					
						
							|  |  |  | // start of the next word. | 
					
						
							|  |  |  | func (t *Terminal) countToRightWord() int { | 
					
						
							|  |  |  | 	pos := t.pos | 
					
						
							|  |  |  | 	for pos < len(t.line) { | 
					
						
							|  |  |  | 		if t.line[pos] == ' ' { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		pos++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for pos < len(t.line) { | 
					
						
							|  |  |  | 		if t.line[pos] != ' ' { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		pos++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return pos - t.pos | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // visualLength returns the number of visible glyphs in s. | 
					
						
							|  |  |  | func visualLength(runes []rune) int { | 
					
						
							|  |  |  | 	inEscapeSeq := false | 
					
						
							|  |  |  | 	length := 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, r := range runes { | 
					
						
							|  |  |  | 		switch { | 
					
						
							|  |  |  | 		case inEscapeSeq: | 
					
						
							|  |  |  | 			if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { | 
					
						
							|  |  |  | 				inEscapeSeq = false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case r == '\x1b': | 
					
						
							|  |  |  | 			inEscapeSeq = true | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			length++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return length | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // handleKey processes the given key and, optionally, returns a line of text | 
					
						
							|  |  |  | // that the user has entered. | 
					
						
							|  |  |  | func (t *Terminal) handleKey(key rune) (line string, ok bool) { | 
					
						
							|  |  |  | 	if t.pasteActive && key != keyEnter { | 
					
						
							|  |  |  | 		t.addKeyToLine(key) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch key { | 
					
						
							|  |  |  | 	case keyBackspace: | 
					
						
							|  |  |  | 		if t.pos == 0 { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.eraseNPreviousChars(1) | 
					
						
							|  |  |  | 	case keyAltLeft: | 
					
						
							|  |  |  | 		// move left by a word. | 
					
						
							|  |  |  | 		t.pos -= t.countToLeftWord() | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	case keyAltRight: | 
					
						
							|  |  |  | 		// move right by a word. | 
					
						
							|  |  |  | 		t.pos += t.countToRightWord() | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	case keyLeft: | 
					
						
							|  |  |  | 		if t.pos == 0 { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.pos-- | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	case keyRight: | 
					
						
							|  |  |  | 		if t.pos == len(t.line) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.pos++ | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	case keyHome: | 
					
						
							|  |  |  | 		if t.pos == 0 { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.pos = 0 | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	case keyEnd: | 
					
						
							|  |  |  | 		if t.pos == len(t.line) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.pos = len(t.line) | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	case keyUp: | 
					
						
							|  |  |  | 		entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			return "", false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if t.historyIndex == -1 { | 
					
						
							|  |  |  | 			t.historyPending = string(t.line) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.historyIndex++ | 
					
						
							|  |  |  | 		runes := []rune(entry) | 
					
						
							|  |  |  | 		t.setLine(runes, len(runes)) | 
					
						
							|  |  |  | 	case keyDown: | 
					
						
							|  |  |  | 		switch t.historyIndex { | 
					
						
							|  |  |  | 		case -1: | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		case 0: | 
					
						
							|  |  |  | 			runes := []rune(t.historyPending) | 
					
						
							|  |  |  | 			t.setLine(runes, len(runes)) | 
					
						
							|  |  |  | 			t.historyIndex-- | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) | 
					
						
							|  |  |  | 			if ok { | 
					
						
							|  |  |  | 				t.historyIndex-- | 
					
						
							|  |  |  | 				runes := []rune(entry) | 
					
						
							|  |  |  | 				t.setLine(runes, len(runes)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case keyEnter: | 
					
						
							|  |  |  | 		t.moveCursorToPos(len(t.line)) | 
					
						
							|  |  |  | 		t.queue([]rune("\r\n")) | 
					
						
							|  |  |  | 		line = string(t.line) | 
					
						
							|  |  |  | 		ok = true | 
					
						
							|  |  |  | 		t.line = t.line[:0] | 
					
						
							|  |  |  | 		t.pos = 0 | 
					
						
							|  |  |  | 		t.cursorX = 0 | 
					
						
							|  |  |  | 		t.cursorY = 0 | 
					
						
							|  |  |  | 		t.maxLine = 0 | 
					
						
							|  |  |  | 	case keyDeleteWord: | 
					
						
							|  |  |  | 		// Delete zero or more spaces and then one or more characters. | 
					
						
							|  |  |  | 		t.eraseNPreviousChars(t.countToLeftWord()) | 
					
						
							|  |  |  | 	case keyDeleteLine: | 
					
						
							|  |  |  | 		// Delete everything from the current cursor position to the | 
					
						
							|  |  |  | 		// end of line. | 
					
						
							|  |  |  | 		for i := t.pos; i < len(t.line); i++ { | 
					
						
							|  |  |  | 			t.queue(space) | 
					
						
							|  |  |  | 			t.advanceCursor(1) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.line = t.line[:t.pos] | 
					
						
							|  |  |  | 		t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 	case keyCtrlD: | 
					
						
							|  |  |  | 		// Erase the character under the current position. | 
					
						
							|  |  |  | 		// The EOF case when the line is empty is handled in | 
					
						
							|  |  |  | 		// readLine(). | 
					
						
							|  |  |  | 		if t.pos < len(t.line) { | 
					
						
							|  |  |  | 			t.pos++ | 
					
						
							|  |  |  | 			t.eraseNPreviousChars(1) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case keyCtrlU: | 
					
						
							|  |  |  | 		t.eraseNPreviousChars(t.pos) | 
					
						
							|  |  |  | 	case keyClearScreen: | 
					
						
							|  |  |  | 		// Erases the screen and moves the cursor to the home position. | 
					
						
							|  |  |  | 		t.queue([]rune("\x1b[2J\x1b[H")) | 
					
						
							|  |  |  | 		t.queue(t.prompt) | 
					
						
							|  |  |  | 		t.cursorX, t.cursorY = 0, 0 | 
					
						
							|  |  |  | 		t.advanceCursor(visualLength(t.prompt)) | 
					
						
							|  |  |  | 		t.setLine(t.line, t.pos) | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		if t.AutoCompleteCallback != nil { | 
					
						
							|  |  |  | 			prefix := string(t.line[:t.pos]) | 
					
						
							|  |  |  | 			suffix := string(t.line[t.pos:]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			t.lock.Unlock() | 
					
						
							|  |  |  | 			newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key) | 
					
						
							|  |  |  | 			t.lock.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if completeOk { | 
					
						
							|  |  |  | 				t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos])) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !isPrintable(key) { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if len(t.line) == maxLineLength { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.addKeyToLine(key) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // addKeyToLine inserts the given key at the current position in the current | 
					
						
							|  |  |  | // line. | 
					
						
							|  |  |  | func (t *Terminal) addKeyToLine(key rune) { | 
					
						
							|  |  |  | 	if len(t.line) == cap(t.line) { | 
					
						
							|  |  |  | 		newLine := make([]rune, len(t.line), 2*(1+len(t.line))) | 
					
						
							|  |  |  | 		copy(newLine, t.line) | 
					
						
							|  |  |  | 		t.line = newLine | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.line = t.line[:len(t.line)+1] | 
					
						
							|  |  |  | 	copy(t.line[t.pos+1:], t.line[t.pos:]) | 
					
						
							|  |  |  | 	t.line[t.pos] = key | 
					
						
							|  |  |  | 	if t.echo { | 
					
						
							|  |  |  | 		t.writeLine(t.line[t.pos:]) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.pos++ | 
					
						
							|  |  |  | 	t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) writeLine(line []rune) { | 
					
						
							|  |  |  | 	for len(line) != 0 { | 
					
						
							|  |  |  | 		remainingOnLine := t.termWidth - t.cursorX | 
					
						
							|  |  |  | 		todo := len(line) | 
					
						
							|  |  |  | 		if todo > remainingOnLine { | 
					
						
							|  |  |  | 			todo = remainingOnLine | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.queue(line[:todo]) | 
					
						
							|  |  |  | 		t.advanceCursor(visualLength(line[:todo])) | 
					
						
							|  |  |  | 		line = line[todo:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n. | 
					
						
							|  |  |  | func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) { | 
					
						
							|  |  |  | 	for len(buf) > 0 { | 
					
						
							|  |  |  | 		i := bytes.IndexByte(buf, '\n') | 
					
						
							|  |  |  | 		todo := len(buf) | 
					
						
							|  |  |  | 		if i >= 0 { | 
					
						
							|  |  |  | 			todo = i | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var nn int | 
					
						
							|  |  |  | 		nn, err = w.Write(buf[:todo]) | 
					
						
							|  |  |  | 		n += nn | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return n, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		buf = buf[todo:] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if i >= 0 { | 
					
						
							|  |  |  | 			if _, err = w.Write(crlf); err != nil { | 
					
						
							|  |  |  | 				return n, err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-01-04 09:22:59 +02:00
										 |  |  | 			n++ | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 			buf = buf[1:] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return n, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) Write(buf []byte) (n int, err error) { | 
					
						
							|  |  |  | 	t.lock.Lock() | 
					
						
							|  |  |  | 	defer t.lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if t.cursorX == 0 && t.cursorY == 0 { | 
					
						
							|  |  |  | 		// This is the easy case: there's nothing on the screen that we | 
					
						
							|  |  |  | 		// have to move out of the way. | 
					
						
							|  |  |  | 		return writeWithCRLF(t.c, buf) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// We have a prompt and possibly user input on the screen. We | 
					
						
							|  |  |  | 	// have to clear it first. | 
					
						
							|  |  |  | 	t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */) | 
					
						
							|  |  |  | 	t.cursorX = 0 | 
					
						
							|  |  |  | 	t.clearLineToRight() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for t.cursorY > 0 { | 
					
						
							|  |  |  | 		t.move(1 /* up */, 0, 0, 0) | 
					
						
							|  |  |  | 		t.cursorY-- | 
					
						
							|  |  |  | 		t.clearLineToRight() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, err = t.c.Write(t.outBuf); err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.outBuf = t.outBuf[:0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if n, err = writeWithCRLF(t.c, buf); err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.writeLine(t.prompt) | 
					
						
							|  |  |  | 	if t.echo { | 
					
						
							|  |  |  | 		t.writeLine(t.line) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, err = t.c.Write(t.outBuf); err != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.outBuf = t.outBuf[:0] | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ReadPassword temporarily changes the prompt and reads a password, without | 
					
						
							|  |  |  | // echo, from the terminal. | 
					
						
							|  |  |  | func (t *Terminal) ReadPassword(prompt string) (line string, err error) { | 
					
						
							|  |  |  | 	t.lock.Lock() | 
					
						
							|  |  |  | 	defer t.lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	oldPrompt := t.prompt | 
					
						
							|  |  |  | 	t.prompt = []rune(prompt) | 
					
						
							|  |  |  | 	t.echo = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	line, err = t.readLine() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.prompt = oldPrompt | 
					
						
							|  |  |  | 	t.echo = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ReadLine returns a line of input from the terminal. | 
					
						
							|  |  |  | func (t *Terminal) ReadLine() (line string, err error) { | 
					
						
							|  |  |  | 	t.lock.Lock() | 
					
						
							|  |  |  | 	defer t.lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return t.readLine() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) readLine() (line string, err error) { | 
					
						
							|  |  |  | 	// t.lock must be held at this point | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if t.cursorX == 0 && t.cursorY == 0 { | 
					
						
							|  |  |  | 		t.writeLine(t.prompt) | 
					
						
							|  |  |  | 		t.c.Write(t.outBuf) | 
					
						
							|  |  |  | 		t.outBuf = t.outBuf[:0] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lineIsPasted := t.pasteActive | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		rest := t.remainder | 
					
						
							|  |  |  | 		lineOk := false | 
					
						
							|  |  |  | 		for !lineOk { | 
					
						
							|  |  |  | 			var key rune | 
					
						
							|  |  |  | 			key, rest = bytesToKey(rest, t.pasteActive) | 
					
						
							|  |  |  | 			if key == utf8.RuneError { | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if !t.pasteActive { | 
					
						
							|  |  |  | 				if key == keyCtrlD { | 
					
						
							|  |  |  | 					if len(t.line) == 0 { | 
					
						
							|  |  |  | 						return "", io.EOF | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if key == keyPasteStart { | 
					
						
							|  |  |  | 					t.pasteActive = true | 
					
						
							|  |  |  | 					if len(t.line) == 0 { | 
					
						
							|  |  |  | 						lineIsPasted = true | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else if key == keyPasteEnd { | 
					
						
							|  |  |  | 				t.pasteActive = false | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if !t.pasteActive { | 
					
						
							|  |  |  | 				lineIsPasted = false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			line, lineOk = t.handleKey(key) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if len(rest) > 0 { | 
					
						
							|  |  |  | 			n := copy(t.inBuf[:], rest) | 
					
						
							|  |  |  | 			t.remainder = t.inBuf[:n] | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			t.remainder = nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.c.Write(t.outBuf) | 
					
						
							|  |  |  | 		t.outBuf = t.outBuf[:0] | 
					
						
							|  |  |  | 		if lineOk { | 
					
						
							|  |  |  | 			if t.echo { | 
					
						
							|  |  |  | 				t.historyIndex = -1 | 
					
						
							|  |  |  | 				t.history.Add(line) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if lineIsPasted { | 
					
						
							|  |  |  | 				err = ErrPasteIndicator | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// t.remainder is a slice at the beginning of t.inBuf | 
					
						
							|  |  |  | 		// containing a partial key sequence | 
					
						
							|  |  |  | 		readBuf := t.inBuf[len(t.remainder):] | 
					
						
							|  |  |  | 		var n int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.lock.Unlock() | 
					
						
							|  |  |  | 		n, err = t.c.Read(readBuf) | 
					
						
							|  |  |  | 		t.lock.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.remainder = t.inBuf[:n+len(t.remainder)] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetPrompt sets the prompt to be used when reading subsequent lines. | 
					
						
							|  |  |  | func (t *Terminal) SetPrompt(prompt string) { | 
					
						
							|  |  |  | 	t.lock.Lock() | 
					
						
							|  |  |  | 	defer t.lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.prompt = []rune(prompt) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { | 
					
						
							|  |  |  | 	// Move cursor to column zero at the start of the line. | 
					
						
							|  |  |  | 	t.move(t.cursorY, 0, t.cursorX, 0) | 
					
						
							|  |  |  | 	t.cursorX, t.cursorY = 0, 0 | 
					
						
							|  |  |  | 	t.clearLineToRight() | 
					
						
							|  |  |  | 	for t.cursorY < numPrevLines { | 
					
						
							|  |  |  | 		// Move down a line | 
					
						
							|  |  |  | 		t.move(0, 1, 0, 0) | 
					
						
							|  |  |  | 		t.cursorY++ | 
					
						
							|  |  |  | 		t.clearLineToRight() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Move back to beginning. | 
					
						
							|  |  |  | 	t.move(t.cursorY, 0, 0, 0) | 
					
						
							|  |  |  | 	t.cursorX, t.cursorY = 0, 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.queue(t.prompt) | 
					
						
							|  |  |  | 	t.advanceCursor(visualLength(t.prompt)) | 
					
						
							|  |  |  | 	t.writeLine(t.line) | 
					
						
							|  |  |  | 	t.moveCursorToPos(t.pos) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Terminal) SetSize(width, height int) error { | 
					
						
							|  |  |  | 	t.lock.Lock() | 
					
						
							|  |  |  | 	defer t.lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if width == 0 { | 
					
						
							|  |  |  | 		width = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	oldWidth := t.termWidth | 
					
						
							|  |  |  | 	t.termWidth, t.termHeight = width, height | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case width == oldWidth: | 
					
						
							|  |  |  | 		// If the width didn't change then nothing else needs to be | 
					
						
							|  |  |  | 		// done. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: | 
					
						
							|  |  |  | 		// If there is nothing on current line and no prompt printed, | 
					
						
							|  |  |  | 		// just do nothing | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	case width < oldWidth: | 
					
						
							|  |  |  | 		// Some terminals (e.g. xterm) will truncate lines that were | 
					
						
							|  |  |  | 		// too long when shinking. Others, (e.g. gnome-terminal) will | 
					
						
							|  |  |  | 		// attempt to wrap them. For the former, repainting t.maxLine | 
					
						
							|  |  |  | 		// works great, but that behaviour goes badly wrong in the case | 
					
						
							|  |  |  | 		// of the latter because they have doubled every full line. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We assume that we are working on a terminal that wraps lines | 
					
						
							|  |  |  | 		// and adjust the cursor position based on every previous line | 
					
						
							|  |  |  | 		// wrapping and turning into two. This causes the prompt on | 
					
						
							|  |  |  | 		// xterms to move upwards, which isn't great, but it avoids a | 
					
						
							|  |  |  | 		// huge mess with gnome-terminal. | 
					
						
							|  |  |  | 		if t.cursorX >= t.termWidth { | 
					
						
							|  |  |  | 			t.cursorX = t.termWidth - 1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.cursorY *= 2 | 
					
						
							|  |  |  | 		t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) | 
					
						
							|  |  |  | 	case width > oldWidth: | 
					
						
							|  |  |  | 		// If the terminal expands then our position calculations will | 
					
						
							|  |  |  | 		// be wrong in the future because we think the cursor is | 
					
						
							|  |  |  | 		// |t.pos| chars into the string, but there will be a gap at | 
					
						
							|  |  |  | 		// the end of any wrapped line. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// But the position will actually be correct until we move, so | 
					
						
							|  |  |  | 		// we can move back to the beginning and repaint everything. | 
					
						
							|  |  |  | 		t.clearAndRepaintLinePlusNPrevious(t.maxLine) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err := t.c.Write(t.outBuf) | 
					
						
							|  |  |  | 	t.outBuf = t.outBuf[:0] | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type pasteIndicatorError struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (pasteIndicatorError) Error() string { | 
					
						
							|  |  |  | 	return "terminal: ErrPasteIndicator not correctly handled" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ErrPasteIndicator may be returned from ReadLine as the error, in addition | 
					
						
							|  |  |  | // to valid line data. It indicates that bracketed paste mode is enabled and | 
					
						
							|  |  |  | // that the returned line consists only of pasted data. Programs may wish to | 
					
						
							|  |  |  | // interpret pasted data more literally than typed data. | 
					
						
							|  |  |  | var ErrPasteIndicator = pasteIndicatorError{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetBracketedPasteMode requests that the terminal bracket paste operations | 
					
						
							|  |  |  | // with markers. Not all terminals support this but, if it is supported, then | 
					
						
							|  |  |  | // enabling this mode will stop any autocomplete callback from running due to | 
					
						
							|  |  |  | // pastes. Additionally, any lines that are completely pasted will be returned | 
					
						
							|  |  |  | // from ReadLine with the error set to ErrPasteIndicator. | 
					
						
							|  |  |  | func (t *Terminal) SetBracketedPasteMode(on bool) { | 
					
						
							|  |  |  | 	if on { | 
					
						
							|  |  |  | 		io.WriteString(t.c, "\x1b[?2004h") | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		io.WriteString(t.c, "\x1b[?2004l") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // stRingBuffer is a ring buffer of strings. | 
					
						
							|  |  |  | type stRingBuffer struct { | 
					
						
							|  |  |  | 	// entries contains max elements. | 
					
						
							|  |  |  | 	entries []string | 
					
						
							|  |  |  | 	max     int | 
					
						
							|  |  |  | 	// head contains the index of the element most recently added to the ring. | 
					
						
							|  |  |  | 	head int | 
					
						
							|  |  |  | 	// size contains the number of elements in the ring. | 
					
						
							|  |  |  | 	size int | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *stRingBuffer) Add(a string) { | 
					
						
							|  |  |  | 	if s.entries == nil { | 
					
						
							|  |  |  | 		const defaultNumEntries = 100 | 
					
						
							|  |  |  | 		s.entries = make([]string, defaultNumEntries) | 
					
						
							|  |  |  | 		s.max = defaultNumEntries | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	s.head = (s.head + 1) % s.max | 
					
						
							|  |  |  | 	s.entries[s.head] = a | 
					
						
							|  |  |  | 	if s.size < s.max { | 
					
						
							|  |  |  | 		s.size++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NthPreviousEntry returns the value passed to the nth previous call to Add. | 
					
						
							|  |  |  | // If n is zero then the immediately prior value is returned, if one, then the | 
					
						
							|  |  |  | // next most recent, and so on. If such an element doesn't exist then ok is | 
					
						
							|  |  |  | // false. | 
					
						
							|  |  |  | func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { | 
					
						
							|  |  |  | 	if n >= s.size { | 
					
						
							|  |  |  | 		return "", false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	index := s.head - n | 
					
						
							|  |  |  | 	if index < 0 { | 
					
						
							|  |  |  | 		index += s.max | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return s.entries[index], true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readPasswordLine reads from reader until it finds \n or io.EOF. | 
					
						
							|  |  |  | // The slice returned does not include the \n. | 
					
						
							|  |  |  | // readPasswordLine also ignores any \r it finds. | 
					
						
							|  |  |  | func readPasswordLine(reader io.Reader) ([]byte, error) { | 
					
						
							|  |  |  | 	var buf [1]byte | 
					
						
							|  |  |  | 	var ret []byte | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		n, err := reader.Read(buf[:]) | 
					
						
							|  |  |  | 		if n > 0 { | 
					
						
							|  |  |  | 			switch buf[0] { | 
					
						
							|  |  |  | 			case '\n': | 
					
						
							|  |  |  | 				return ret, nil | 
					
						
							|  |  |  | 			case '\r': | 
					
						
							|  |  |  | 				// remove \r from passwords on Windows | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				ret = append(ret, buf[0]) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			if err == io.EOF && len(ret) > 0 { | 
					
						
							|  |  |  | 				return ret, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return ret, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |