go.crypto/ssh/terminal: support bracketed paste mode.

Some terminals support a mode where pasted text is bracketed by escape sequences. This is very useful for terminal applications that otherwise have no good way to tell pastes and typed text apart.

This change allows applications to enable this mode and, if the terminal supports it, will suppress autocompletes during pastes and indicate to the caller that a line came entirely from pasted text.

LGTM=bradfitz
R=bradfitz
CC=golang-codereviews
https://golang.org/cl/171330043
diff --git a/terminal.go b/terminal.go
index fd97611..965f0cf 100644
--- a/terminal.go
+++ b/terminal.go
@@ -5,6 +5,7 @@
 package terminal
 
 import (
+	"bytes"
 	"io"
 	"sync"
 	"unicode/utf8"
@@ -61,6 +62,9 @@
 	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
@@ -124,28 +128,35 @@
 	keyDeleteWord
 	keyDeleteLine
 	keyClearScreen
+	keyPasteStart
+	keyPasteEnd
 )
 
+var pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
+var 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) (rune, []byte) {
+func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
 	if len(b) == 0 {
 		return utf8.RuneError, nil
 	}
 
-	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 !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 {
@@ -156,7 +167,7 @@
 		return r, b[l:]
 	}
 
-	if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
+	if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
 		switch b[2] {
 		case 'A':
 			return keyUp, b[3:]
@@ -173,7 +184,7 @@
 		}
 	}
 
-	if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '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:]
@@ -182,12 +193,20 @@
 		}
 	}
 
+	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
+	// 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' {
+		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
 			return keyUnknown, b[i+1:]
 		}
 	}
@@ -409,6 +428,11 @@
 // 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 {
@@ -533,23 +557,29 @@
 		if len(t.line) == maxLineLength {
 			return
 		}
-		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)
+		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
@@ -643,19 +673,36 @@
 		t.outBuf = t.outBuf[:0]
 	}
 
+	lineIsPasted := t.pasteActive
+
 	for {
 		rest := t.remainder
 		lineOk := false
 		for !lineOk {
 			var key rune
-			key, rest = bytesToKey(rest)
+			key, rest = bytesToKey(rest, t.pasteActive)
 			if key == utf8.RuneError {
 				break
 			}
-			if key == keyCtrlD {
-				if len(t.line) == 0 {
-					return "", io.EOF
+			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)
 		}
@@ -672,6 +719,9 @@
 				t.historyIndex = -1
 				t.history.Add(line)
 			}
+			if lineIsPasted {
+				err = ErrPasteIndicator
+			}
 			return
 		}
 
@@ -772,6 +822,31 @@
 	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.
diff --git a/terminal_test.go b/terminal_test.go
index fb42d76..6579801 100644
--- a/terminal_test.go
+++ b/terminal_test.go
@@ -179,6 +179,24 @@
 		in:   "abcd\x1b[D\x1b[D\025\r",
 		line: "cd",
 	},
+	{
+		// Bracketed paste mode: control sequences should be returned
+		// verbatim in paste mode.
+		in:   "abc\x1b[200~de\177f\x1b[201~\177\r",
+		line: "abcde\177",
+	},
+	{
+		// Enter in bracketed paste mode should still work.
+		in:             "abc\x1b[200~d\refg\x1b[201~h\r",
+		line:           "efgh",
+		throwAwayLines: 1,
+	},
+	{
+		// Lines consisting entirely of pasted data should be indicated as such.
+		in:   "\x1b[200~a\r",
+		line: "a",
+		err:  ErrPasteIndicator,
+	},
 }
 
 func TestKeyPresses(t *testing.T) {