Browse code

bump sirupsen/logrus v1.4.1

Full diff: https://github.com/sirupsen/logrus/compare/v1.3.0...v1.4.1

Fixes:

- Remove dependency on golang.org/x/crypto
- Fix wrong method calls Logger.Print and Logger.Warningln
- Update Entry.Logf to not do string formatting unless the log level is enabled
- Fix infinite recursion on unknown Level.String()
- Fix race condition in getCaller
- Fix Entry.WithContext method to return a copy of the initial entry

New:

- Add DeferExitHandler, similar to RegisterExitHandler but prepending the handler to the list of handlers (semantically like defer)
- Add CallerPrettyfier to JSONFormatter and `TextFormatter`
- Add Entry.WithContext() and Entry.Context, to set a context on entries to be used e.g. in hooks
- Enhance TextFormatter to not print caller information when they are empty

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2019/04/04 04:52:58
Showing 21 changed files
... ...
@@ -11,7 +11,7 @@ github.com/Microsoft/opengcs a10967154e143a36014584a6f664344e3bb0aa64
11 11
 github.com/konsorten/go-windows-terminal-sequences v1.0.1
12 12
 github.com/kr/pty 5cf931ef8f
13 13
 github.com/mattn/go-shellwords v1.0.3
14
-github.com/sirupsen/logrus v1.3.0
14
+github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1
15 15
 github.com/tchap/go-patricia v2.2.6
16 16
 github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
17 17
 golang.org/x/net a680a1efc54dd51c040b3b5ce4939ea3cf2ea0d1
... ...
@@ -365,6 +365,7 @@ Third party logging formatters:
365 365
 * [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
366 366
 * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
367 367
 * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
368
+* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
368 369
 
369 370
 You can define your formatter by implementing the `Formatter` interface,
370 371
 requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
... ...
@@ -51,9 +51,9 @@ func Exit(code int) {
51 51
 	os.Exit(code)
52 52
 }
53 53
 
54
-// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
55
-// all handlers. The handlers will also be invoked when any Fatal log entry is
56
-// made.
54
+// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
55
+// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
56
+// any Fatal log entry is made.
57 57
 //
58 58
 // This method is useful when a caller wishes to use logrus to log a fatal
59 59
 // message but also needs to gracefully shutdown. An example usecase could be
... ...
@@ -62,3 +62,15 @@ func Exit(code int) {
62 62
 func RegisterExitHandler(handler func()) {
63 63
 	handlers = append(handlers, handler)
64 64
 }
65
+
66
+// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
67
+// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
68
+// any Fatal log entry is made.
69
+//
70
+// This method is useful when a caller wishes to use logrus to log a fatal
71
+// message but also needs to gracefully shutdown. An example usecase could be
72
+// closing database connections, or sending a alert that the application is
73
+// closing.
74
+func DeferExitHandler(handler func()) {
75
+	handlers = append([]func(){handler}, handlers...)
76
+}
... ...
@@ -2,6 +2,7 @@ package logrus
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"context"
5 6
 	"fmt"
6 7
 	"os"
7 8
 	"reflect"
... ...
@@ -69,6 +70,9 @@ type Entry struct {
69 69
 	// When formatter is called in entry.log(), a Buffer may be set to entry
70 70
 	Buffer *bytes.Buffer
71 71
 
72
+	// Contains the context set by the user. Useful for hook processing etc.
73
+	Context context.Context
74
+
72 75
 	// err may contain a field formatting error
73 76
 	err string
74 77
 }
... ...
@@ -97,6 +101,11 @@ func (entry *Entry) WithError(err error) *Entry {
97 97
 	return entry.WithField(ErrorKey, err)
98 98
 }
99 99
 
100
+// Add a context to the Entry.
101
+func (entry *Entry) WithContext(ctx context.Context) *Entry {
102
+	return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx}
103
+}
104
+
100 105
 // Add a single field to the Entry.
101 106
 func (entry *Entry) WithField(key string, value interface{}) *Entry {
102 107
 	return entry.WithFields(Fields{key: value})
... ...
@@ -130,12 +139,12 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
130 130
 			data[k] = v
131 131
 		}
132 132
 	}
133
-	return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr}
133
+	return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
134 134
 }
135 135
 
136 136
 // Overrides the time of the Entry.
137 137
 func (entry *Entry) WithTime(t time.Time) *Entry {
138
-	return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err}
138
+	return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context}
139 139
 }
140 140
 
141 141
 // getPackageName reduces a fully qualified function name to the package name
... ...
@@ -156,20 +165,23 @@ func getPackageName(f string) string {
156 156
 
157 157
 // getCaller retrieves the name of the first non-logrus calling function
158 158
 func getCaller() *runtime.Frame {
159
-	// Restrict the lookback frames to avoid runaway lookups
160
-	pcs := make([]uintptr, maximumCallerDepth)
161
-	depth := runtime.Callers(minimumCallerDepth, pcs)
162
-	frames := runtime.CallersFrames(pcs[:depth])
163 159
 
164 160
 	// cache this package's fully-qualified name
165 161
 	callerInitOnce.Do(func() {
166
-		logrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name())
162
+		pcs := make([]uintptr, 2)
163
+		_ = runtime.Callers(0, pcs)
164
+		logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name())
167 165
 
168 166
 		// now that we have the cache, we can skip a minimum count of known-logrus functions
169
-		// XXX this is dubious, the number of frames may vary store an entry in a logger interface
167
+		// XXX this is dubious, the number of frames may vary
170 168
 		minimumCallerDepth = knownLogrusFrames
171 169
 	})
172 170
 
171
+	// Restrict the lookback frames to avoid runaway lookups
172
+	pcs := make([]uintptr, maximumCallerDepth)
173
+	depth := runtime.Callers(minimumCallerDepth, pcs)
174
+	frames := runtime.CallersFrames(pcs[:depth])
175
+
173 176
 	for f, again := frames.Next(); again; f, again = frames.Next() {
174 177
 		pkg := getPackageName(f.Function)
175 178
 
... ...
@@ -298,7 +310,9 @@ func (entry *Entry) Panic(args ...interface{}) {
298 298
 // Entry Printf family functions
299 299
 
300 300
 func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
301
-	entry.Log(level, fmt.Sprintf(format, args...))
301
+	if entry.Logger.IsLevelEnabled(level) {
302
+		entry.Log(level, fmt.Sprintf(format, args...))
303
+	}
302 304
 }
303 305
 
304 306
 func (entry *Entry) Tracef(format string, args ...interface{}) {
... ...
@@ -1,6 +1,7 @@
1 1
 package logrus
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"io"
5 6
 	"time"
6 7
 )
... ...
@@ -55,6 +56,11 @@ func WithError(err error) *Entry {
55 55
 	return std.WithField(ErrorKey, err)
56 56
 }
57 57
 
58
+// WithContext creates an entry from the standard logger and adds a context to it.
59
+func WithContext(ctx context.Context) *Entry {
60
+	return std.WithContext(ctx)
61
+}
62
+
58 63
 // WithField creates an entry from the standard logger and adds a field to
59 64
 // it. If you want multiple fields, use `WithFields`.
60 65
 //
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"runtime"
7 8
 )
8 9
 
9 10
 type fieldKey string
... ...
@@ -42,6 +43,12 @@ type JSONFormatter struct {
42 42
 	// }
43 43
 	FieldMap FieldMap
44 44
 
45
+	// CallerPrettyfier can be set by the user to modify the content
46
+	// of the function and file keys in the json data when ReportCaller is
47
+	// activated. If any of the returned value is the empty string the
48
+	// corresponding key will be removed from json fields.
49
+	CallerPrettyfier func(*runtime.Frame) (function string, file string)
50
+
45 51
 	// PrettyPrint will indent all json logs
46 52
 	PrettyPrint bool
47 53
 }
... ...
@@ -82,8 +89,17 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
82 82
 	data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
83 83
 	data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
84 84
 	if entry.HasCaller() {
85
-		data[f.FieldMap.resolve(FieldKeyFunc)] = entry.Caller.Function
86
-		data[f.FieldMap.resolve(FieldKeyFile)] = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
85
+		funcVal := entry.Caller.Function
86
+		fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
87
+		if f.CallerPrettyfier != nil {
88
+			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
89
+		}
90
+		if funcVal != "" {
91
+			data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
92
+		}
93
+		if fileVal != "" {
94
+			data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
95
+		}
87 96
 	}
88 97
 
89 98
 	var b *bytes.Buffer
... ...
@@ -98,7 +114,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
98 98
 		encoder.SetIndent("", "  ")
99 99
 	}
100 100
 	if err := encoder.Encode(data); err != nil {
101
-		return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
101
+		return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
102 102
 	}
103 103
 
104 104
 	return b.Bytes(), nil
... ...
@@ -1,6 +1,7 @@
1 1
 package logrus
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"io"
5 6
 	"os"
6 7
 	"sync"
... ...
@@ -124,6 +125,13 @@ func (logger *Logger) WithError(err error) *Entry {
124 124
 	return entry.WithError(err)
125 125
 }
126 126
 
127
+// Add a context to the log entry.
128
+func (logger *Logger) WithContext(ctx context.Context) *Entry {
129
+	entry := logger.newEntry()
130
+	defer logger.releaseEntry(entry)
131
+	return entry.WithContext(ctx)
132
+}
133
+
127 134
 // Overrides the time of the log entry.
128 135
 func (logger *Logger) WithTime(t time.Time) *Entry {
129 136
 	entry := logger.newEntry()
... ...
@@ -200,7 +208,7 @@ func (logger *Logger) Info(args ...interface{}) {
200 200
 
201 201
 func (logger *Logger) Print(args ...interface{}) {
202 202
 	entry := logger.newEntry()
203
-	entry.Info(args...)
203
+	entry.Print(args...)
204 204
 	logger.releaseEntry(entry)
205 205
 }
206 206
 
... ...
@@ -256,7 +264,7 @@ func (logger *Logger) Warnln(args ...interface{}) {
256 256
 }
257 257
 
258 258
 func (logger *Logger) Warningln(args ...interface{}) {
259
-	logger.Warn(args...)
259
+	logger.Warnln(args...)
260 260
 }
261 261
 
262 262
 func (logger *Logger) Errorln(args ...interface{}) {
... ...
@@ -74,7 +74,7 @@ func (level Level) MarshalText() ([]byte, error) {
74 74
 		return []byte("panic"), nil
75 75
 	}
76 76
 
77
-	return nil, fmt.Errorf("not a valid lorus level %q", level)
77
+	return nil, fmt.Errorf("not a valid logrus level %d", level)
78 78
 }
79 79
 
80 80
 // A constant exposing all logging levels
81 81
deleted file mode 100644
... ...
@@ -1,9 +0,0 @@
1
-// +build !appengine,!js,!windows,aix
2
-
3
-package logrus
4
-
5
-import "io"
6
-
7
-func checkIfTerminal(w io.Writer) bool {
8
-	return false
9
-}
10 1
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+// +build darwin dragonfly freebsd netbsd openbsd
1
+
2
+package logrus
3
+
4
+import "golang.org/x/sys/unix"
5
+
6
+const ioctlReadTermios = unix.TIOCGETA
7
+
8
+func isTerminal(fd int) bool {
9
+	_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
10
+	return err == nil
11
+}
12
+
... ...
@@ -1,18 +1,16 @@
1
-// +build !appengine,!js,!windows,!aix
1
+// +build !appengine,!js,!windows
2 2
 
3 3
 package logrus
4 4
 
5 5
 import (
6 6
 	"io"
7 7
 	"os"
8
-
9
-	"golang.org/x/crypto/ssh/terminal"
10 8
 )
11 9
 
12 10
 func checkIfTerminal(w io.Writer) bool {
13 11
 	switch v := w.(type) {
14 12
 	case *os.File:
15
-		return terminal.IsTerminal(int(v.Fd()))
13
+		return isTerminal(int(v.Fd()))
16 14
 	default:
17 15
 		return false
18 16
 	}
19 17
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+// +build linux aix
1
+
2
+package logrus
3
+
4
+import "golang.org/x/sys/unix"
5
+
6
+const ioctlReadTermios = unix.TCGETS
7
+
8
+func isTerminal(fd int) bool {
9
+	_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
10
+	return err == nil
11
+}
12
+
... ...
@@ -12,18 +12,13 @@ import (
12 12
 )
13 13
 
14 14
 const (
15
-	nocolor = 0
16
-	red     = 31
17
-	green   = 32
18
-	yellow  = 33
19
-	blue    = 36
20
-	gray    = 37
15
+	red    = 31
16
+	yellow = 33
17
+	blue   = 36
18
+	gray   = 37
21 19
 )
22 20
 
23
-var (
24
-	baseTimestamp time.Time
25
-	emptyFieldMap FieldMap
26
-)
21
+var baseTimestamp time.Time
27 22
 
28 23
 func init() {
29 24
 	baseTimestamp = time.Now()
... ...
@@ -77,6 +72,12 @@ type TextFormatter struct {
77 77
 	//         FieldKeyMsg:   "@message"}}
78 78
 	FieldMap FieldMap
79 79
 
80
+	// CallerPrettyfier can be set by the user to modify the content
81
+	// of the function and file keys in the data when ReportCaller is
82
+	// activated. If any of the returned value is the empty string the
83
+	// corresponding key will be removed from fields.
84
+	CallerPrettyfier func(*runtime.Frame) (function string, file string)
85
+
80 86
 	terminalInitOnce sync.Once
81 87
 }
82 88
 
... ...
@@ -118,6 +119,8 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
118 118
 		keys = append(keys, k)
119 119
 	}
120 120
 
121
+	var funcVal, fileVal string
122
+
121 123
 	fixedKeys := make([]string, 0, 4+len(data))
122 124
 	if !f.DisableTimestamp {
123 125
 		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
... ...
@@ -130,8 +133,19 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
130 130
 		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
131 131
 	}
132 132
 	if entry.HasCaller() {
133
-		fixedKeys = append(fixedKeys,
134
-			f.FieldMap.resolve(FieldKeyFunc), f.FieldMap.resolve(FieldKeyFile))
133
+		if f.CallerPrettyfier != nil {
134
+			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
135
+		} else {
136
+			funcVal = entry.Caller.Function
137
+			fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
138
+		}
139
+
140
+		if funcVal != "" {
141
+			fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
142
+		}
143
+		if fileVal != "" {
144
+			fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
145
+		}
135 146
 	}
136 147
 
137 148
 	if !f.DisableSorting {
... ...
@@ -166,6 +180,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
166 166
 	if f.isColored() {
167 167
 		f.printColored(b, entry, keys, data, timestampFormat)
168 168
 	} else {
169
+
169 170
 		for _, key := range fixedKeys {
170 171
 			var value interface{}
171 172
 			switch {
... ...
@@ -178,9 +193,9 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
178 178
 			case key == f.FieldMap.resolve(FieldKeyLogrusError):
179 179
 				value = entry.err
180 180
 			case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
181
-				value = entry.Caller.Function
181
+				value = funcVal
182 182
 			case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
183
-				value = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
183
+				value = fileVal
184 184
 			default:
185 185
 				value = data[key]
186 186
 			}
... ...
@@ -215,10 +230,21 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
215 215
 	entry.Message = strings.TrimSuffix(entry.Message, "\n")
216 216
 
217 217
 	caller := ""
218
-
219 218
 	if entry.HasCaller() {
220
-		caller = fmt.Sprintf("%s:%d %s()",
221
-			entry.Caller.File, entry.Caller.Line, entry.Caller.Function)
219
+		funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
220
+		fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
221
+
222
+		if f.CallerPrettyfier != nil {
223
+			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
224
+		}
225
+
226
+		if fileVal == "" {
227
+			caller = funcVal
228
+		} else if funcVal == "" {
229
+			caller = fileVal
230
+		} else {
231
+			caller = fileVal + " " + funcVal
232
+		}
222 233
 	}
223 234
 
224 235
 	if f.DisableTimestamp {
225 236
deleted file mode 100644
... ...
@@ -1,955 +0,0 @@
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
-
5
-package terminal
6
-
7
-import (
8
-	"bytes"
9
-	"io"
10
-	"sync"
11
-	"unicode/utf8"
12
-)
13
-
14
-// EscapeCodes contains escape sequences that can be written to the terminal in
15
-// order to achieve different styles of text.
16
-type EscapeCodes struct {
17
-	// Foreground colors
18
-	Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
19
-
20
-	// Reset all attributes
21
-	Reset []byte
22
-}
23
-
24
-var vt100EscapeCodes = EscapeCodes{
25
-	Black:   []byte{keyEscape, '[', '3', '0', 'm'},
26
-	Red:     []byte{keyEscape, '[', '3', '1', 'm'},
27
-	Green:   []byte{keyEscape, '[', '3', '2', 'm'},
28
-	Yellow:  []byte{keyEscape, '[', '3', '3', 'm'},
29
-	Blue:    []byte{keyEscape, '[', '3', '4', 'm'},
30
-	Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
31
-	Cyan:    []byte{keyEscape, '[', '3', '6', 'm'},
32
-	White:   []byte{keyEscape, '[', '3', '7', 'm'},
33
-
34
-	Reset: []byte{keyEscape, '[', '0', 'm'},
35
-}
36
-
37
-// Terminal contains the state for running a VT100 terminal that is capable of
38
-// reading lines of input.
39
-type Terminal struct {
40
-	// AutoCompleteCallback, if non-null, is called for each keypress with
41
-	// the full input line and the current position of the cursor (in
42
-	// bytes, as an index into |line|). If it returns ok=false, the key
43
-	// press is processed normally. Otherwise it returns a replacement line
44
-	// and the new cursor position.
45
-	AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
46
-
47
-	// Escape contains a pointer to the escape codes for this terminal.
48
-	// It's always a valid pointer, although the escape codes themselves
49
-	// may be empty if the terminal doesn't support them.
50
-	Escape *EscapeCodes
51
-
52
-	// lock protects the terminal and the state in this object from
53
-	// concurrent processing of a key press and a Write() call.
54
-	lock sync.Mutex
55
-
56
-	c      io.ReadWriter
57
-	prompt []rune
58
-
59
-	// line is the current line being entered.
60
-	line []rune
61
-	// pos is the logical position of the cursor in line
62
-	pos int
63
-	// echo is true if local echo is enabled
64
-	echo bool
65
-	// pasteActive is true iff there is a bracketed paste operation in
66
-	// progress.
67
-	pasteActive bool
68
-
69
-	// cursorX contains the current X value of the cursor where the left
70
-	// edge is 0. cursorY contains the row number where the first row of
71
-	// the current line is 0.
72
-	cursorX, cursorY int
73
-	// maxLine is the greatest value of cursorY so far.
74
-	maxLine int
75
-
76
-	termWidth, termHeight int
77
-
78
-	// outBuf contains the terminal data to be sent.
79
-	outBuf []byte
80
-	// remainder contains the remainder of any partial key sequences after
81
-	// a read. It aliases into inBuf.
82
-	remainder []byte
83
-	inBuf     [256]byte
84
-
85
-	// history contains previously entered commands so that they can be
86
-	// accessed with the up and down keys.
87
-	history stRingBuffer
88
-	// historyIndex stores the currently accessed history entry, where zero
89
-	// means the immediately previous entry.
90
-	historyIndex int
91
-	// When navigating up and down the history it's possible to return to
92
-	// the incomplete, initial line. That value is stored in
93
-	// historyPending.
94
-	historyPending string
95
-}
96
-
97
-// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
98
-// a local terminal, that terminal must first have been put into raw mode.
99
-// prompt is a string that is written at the start of each input line (i.e.
100
-// "> ").
101
-func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
102
-	return &Terminal{
103
-		Escape:       &vt100EscapeCodes,
104
-		c:            c,
105
-		prompt:       []rune(prompt),
106
-		termWidth:    80,
107
-		termHeight:   24,
108
-		echo:         true,
109
-		historyIndex: -1,
110
-	}
111
-}
112
-
113
-const (
114
-	keyCtrlD     = 4
115
-	keyCtrlU     = 21
116
-	keyEnter     = '\r'
117
-	keyEscape    = 27
118
-	keyBackspace = 127
119
-	keyUnknown   = 0xd800 /* UTF-16 surrogate area */ + iota
120
-	keyUp
121
-	keyDown
122
-	keyLeft
123
-	keyRight
124
-	keyAltLeft
125
-	keyAltRight
126
-	keyHome
127
-	keyEnd
128
-	keyDeleteWord
129
-	keyDeleteLine
130
-	keyClearScreen
131
-	keyPasteStart
132
-	keyPasteEnd
133
-)
134
-
135
-var (
136
-	crlf       = []byte{'\r', '\n'}
137
-	pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
138
-	pasteEnd   = []byte{keyEscape, '[', '2', '0', '1', '~'}
139
-)
140
-
141
-// bytesToKey tries to parse a key sequence from b. If successful, it returns
142
-// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
143
-func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
144
-	if len(b) == 0 {
145
-		return utf8.RuneError, nil
146
-	}
147
-
148
-	if !pasteActive {
149
-		switch b[0] {
150
-		case 1: // ^A
151
-			return keyHome, b[1:]
152
-		case 5: // ^E
153
-			return keyEnd, b[1:]
154
-		case 8: // ^H
155
-			return keyBackspace, b[1:]
156
-		case 11: // ^K
157
-			return keyDeleteLine, b[1:]
158
-		case 12: // ^L
159
-			return keyClearScreen, b[1:]
160
-		case 23: // ^W
161
-			return keyDeleteWord, b[1:]
162
-		case 14: // ^N
163
-			return keyDown, b[1:]
164
-		case 16: // ^P
165
-			return keyUp, b[1:]
166
-		}
167
-	}
168
-
169
-	if b[0] != keyEscape {
170
-		if !utf8.FullRune(b) {
171
-			return utf8.RuneError, b
172
-		}
173
-		r, l := utf8.DecodeRune(b)
174
-		return r, b[l:]
175
-	}
176
-
177
-	if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
178
-		switch b[2] {
179
-		case 'A':
180
-			return keyUp, b[3:]
181
-		case 'B':
182
-			return keyDown, b[3:]
183
-		case 'C':
184
-			return keyRight, b[3:]
185
-		case 'D':
186
-			return keyLeft, b[3:]
187
-		case 'H':
188
-			return keyHome, b[3:]
189
-		case 'F':
190
-			return keyEnd, b[3:]
191
-		}
192
-	}
193
-
194
-	if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
195
-		switch b[5] {
196
-		case 'C':
197
-			return keyAltRight, b[6:]
198
-		case 'D':
199
-			return keyAltLeft, b[6:]
200
-		}
201
-	}
202
-
203
-	if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
204
-		return keyPasteStart, b[6:]
205
-	}
206
-
207
-	if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
208
-		return keyPasteEnd, b[6:]
209
-	}
210
-
211
-	// If we get here then we have a key that we don't recognise, or a
212
-	// partial sequence. It's not clear how one should find the end of a
213
-	// sequence without knowing them all, but it seems that [a-zA-Z~] only
214
-	// appears at the end of a sequence.
215
-	for i, c := range b[0:] {
216
-		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
217
-			return keyUnknown, b[i+1:]
218
-		}
219
-	}
220
-
221
-	return utf8.RuneError, b
222
-}
223
-
224
-// queue appends data to the end of t.outBuf
225
-func (t *Terminal) queue(data []rune) {
226
-	t.outBuf = append(t.outBuf, []byte(string(data))...)
227
-}
228
-
229
-var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
230
-var space = []rune{' '}
231
-
232
-func isPrintable(key rune) bool {
233
-	isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
234
-	return key >= 32 && !isInSurrogateArea
235
-}
236
-
237
-// moveCursorToPos appends data to t.outBuf which will move the cursor to the
238
-// given, logical position in the text.
239
-func (t *Terminal) moveCursorToPos(pos int) {
240
-	if !t.echo {
241
-		return
242
-	}
243
-
244
-	x := visualLength(t.prompt) + pos
245
-	y := x / t.termWidth
246
-	x = x % t.termWidth
247
-
248
-	up := 0
249
-	if y < t.cursorY {
250
-		up = t.cursorY - y
251
-	}
252
-
253
-	down := 0
254
-	if y > t.cursorY {
255
-		down = y - t.cursorY
256
-	}
257
-
258
-	left := 0
259
-	if x < t.cursorX {
260
-		left = t.cursorX - x
261
-	}
262
-
263
-	right := 0
264
-	if x > t.cursorX {
265
-		right = x - t.cursorX
266
-	}
267
-
268
-	t.cursorX = x
269
-	t.cursorY = y
270
-	t.move(up, down, left, right)
271
-}
272
-
273
-func (t *Terminal) move(up, down, left, right int) {
274
-	movement := make([]rune, 3*(up+down+left+right))
275
-	m := movement
276
-	for i := 0; i < up; i++ {
277
-		m[0] = keyEscape
278
-		m[1] = '['
279
-		m[2] = 'A'
280
-		m = m[3:]
281
-	}
282
-	for i := 0; i < down; i++ {
283
-		m[0] = keyEscape
284
-		m[1] = '['
285
-		m[2] = 'B'
286
-		m = m[3:]
287
-	}
288
-	for i := 0; i < left; i++ {
289
-		m[0] = keyEscape
290
-		m[1] = '['
291
-		m[2] = 'D'
292
-		m = m[3:]
293
-	}
294
-	for i := 0; i < right; i++ {
295
-		m[0] = keyEscape
296
-		m[1] = '['
297
-		m[2] = 'C'
298
-		m = m[3:]
299
-	}
300
-
301
-	t.queue(movement)
302
-}
303
-
304
-func (t *Terminal) clearLineToRight() {
305
-	op := []rune{keyEscape, '[', 'K'}
306
-	t.queue(op)
307
-}
308
-
309
-const maxLineLength = 4096
310
-
311
-func (t *Terminal) setLine(newLine []rune, newPos int) {
312
-	if t.echo {
313
-		t.moveCursorToPos(0)
314
-		t.writeLine(newLine)
315
-		for i := len(newLine); i < len(t.line); i++ {
316
-			t.writeLine(space)
317
-		}
318
-		t.moveCursorToPos(newPos)
319
-	}
320
-	t.line = newLine
321
-	t.pos = newPos
322
-}
323
-
324
-func (t *Terminal) advanceCursor(places int) {
325
-	t.cursorX += places
326
-	t.cursorY += t.cursorX / t.termWidth
327
-	if t.cursorY > t.maxLine {
328
-		t.maxLine = t.cursorY
329
-	}
330
-	t.cursorX = t.cursorX % t.termWidth
331
-
332
-	if places > 0 && t.cursorX == 0 {
333
-		// Normally terminals will advance the current position
334
-		// when writing a character. But that doesn't happen
335
-		// for the last character in a line. However, when
336
-		// writing a character (except a new line) that causes
337
-		// a line wrap, the position will be advanced two
338
-		// places.
339
-		//
340
-		// So, if we are stopping at the end of a line, we
341
-		// need to write a newline so that our cursor can be
342
-		// advanced to the next line.
343
-		t.outBuf = append(t.outBuf, '\r', '\n')
344
-	}
345
-}
346
-
347
-func (t *Terminal) eraseNPreviousChars(n int) {
348
-	if n == 0 {
349
-		return
350
-	}
351
-
352
-	if t.pos < n {
353
-		n = t.pos
354
-	}
355
-	t.pos -= n
356
-	t.moveCursorToPos(t.pos)
357
-
358
-	copy(t.line[t.pos:], t.line[n+t.pos:])
359
-	t.line = t.line[:len(t.line)-n]
360
-	if t.echo {
361
-		t.writeLine(t.line[t.pos:])
362
-		for i := 0; i < n; i++ {
363
-			t.queue(space)
364
-		}
365
-		t.advanceCursor(n)
366
-		t.moveCursorToPos(t.pos)
367
-	}
368
-}
369
-
370
-// countToLeftWord returns then number of characters from the cursor to the
371
-// start of the previous word.
372
-func (t *Terminal) countToLeftWord() int {
373
-	if t.pos == 0 {
374
-		return 0
375
-	}
376
-
377
-	pos := t.pos - 1
378
-	for pos > 0 {
379
-		if t.line[pos] != ' ' {
380
-			break
381
-		}
382
-		pos--
383
-	}
384
-	for pos > 0 {
385
-		if t.line[pos] == ' ' {
386
-			pos++
387
-			break
388
-		}
389
-		pos--
390
-	}
391
-
392
-	return t.pos - pos
393
-}
394
-
395
-// countToRightWord returns then number of characters from the cursor to the
396
-// start of the next word.
397
-func (t *Terminal) countToRightWord() int {
398
-	pos := t.pos
399
-	for pos < len(t.line) {
400
-		if t.line[pos] == ' ' {
401
-			break
402
-		}
403
-		pos++
404
-	}
405
-	for pos < len(t.line) {
406
-		if t.line[pos] != ' ' {
407
-			break
408
-		}
409
-		pos++
410
-	}
411
-	return pos - t.pos
412
-}
413
-
414
-// visualLength returns the number of visible glyphs in s.
415
-func visualLength(runes []rune) int {
416
-	inEscapeSeq := false
417
-	length := 0
418
-
419
-	for _, r := range runes {
420
-		switch {
421
-		case inEscapeSeq:
422
-			if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
423
-				inEscapeSeq = false
424
-			}
425
-		case r == '\x1b':
426
-			inEscapeSeq = true
427
-		default:
428
-			length++
429
-		}
430
-	}
431
-
432
-	return length
433
-}
434
-
435
-// handleKey processes the given key and, optionally, returns a line of text
436
-// that the user has entered.
437
-func (t *Terminal) handleKey(key rune) (line string, ok bool) {
438
-	if t.pasteActive && key != keyEnter {
439
-		t.addKeyToLine(key)
440
-		return
441
-	}
442
-
443
-	switch key {
444
-	case keyBackspace:
445
-		if t.pos == 0 {
446
-			return
447
-		}
448
-		t.eraseNPreviousChars(1)
449
-	case keyAltLeft:
450
-		// move left by a word.
451
-		t.pos -= t.countToLeftWord()
452
-		t.moveCursorToPos(t.pos)
453
-	case keyAltRight:
454
-		// move right by a word.
455
-		t.pos += t.countToRightWord()
456
-		t.moveCursorToPos(t.pos)
457
-	case keyLeft:
458
-		if t.pos == 0 {
459
-			return
460
-		}
461
-		t.pos--
462
-		t.moveCursorToPos(t.pos)
463
-	case keyRight:
464
-		if t.pos == len(t.line) {
465
-			return
466
-		}
467
-		t.pos++
468
-		t.moveCursorToPos(t.pos)
469
-	case keyHome:
470
-		if t.pos == 0 {
471
-			return
472
-		}
473
-		t.pos = 0
474
-		t.moveCursorToPos(t.pos)
475
-	case keyEnd:
476
-		if t.pos == len(t.line) {
477
-			return
478
-		}
479
-		t.pos = len(t.line)
480
-		t.moveCursorToPos(t.pos)
481
-	case keyUp:
482
-		entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
483
-		if !ok {
484
-			return "", false
485
-		}
486
-		if t.historyIndex == -1 {
487
-			t.historyPending = string(t.line)
488
-		}
489
-		t.historyIndex++
490
-		runes := []rune(entry)
491
-		t.setLine(runes, len(runes))
492
-	case keyDown:
493
-		switch t.historyIndex {
494
-		case -1:
495
-			return
496
-		case 0:
497
-			runes := []rune(t.historyPending)
498
-			t.setLine(runes, len(runes))
499
-			t.historyIndex--
500
-		default:
501
-			entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
502
-			if ok {
503
-				t.historyIndex--
504
-				runes := []rune(entry)
505
-				t.setLine(runes, len(runes))
506
-			}
507
-		}
508
-	case keyEnter:
509
-		t.moveCursorToPos(len(t.line))
510
-		t.queue([]rune("\r\n"))
511
-		line = string(t.line)
512
-		ok = true
513
-		t.line = t.line[:0]
514
-		t.pos = 0
515
-		t.cursorX = 0
516
-		t.cursorY = 0
517
-		t.maxLine = 0
518
-	case keyDeleteWord:
519
-		// Delete zero or more spaces and then one or more characters.
520
-		t.eraseNPreviousChars(t.countToLeftWord())
521
-	case keyDeleteLine:
522
-		// Delete everything from the current cursor position to the
523
-		// end of line.
524
-		for i := t.pos; i < len(t.line); i++ {
525
-			t.queue(space)
526
-			t.advanceCursor(1)
527
-		}
528
-		t.line = t.line[:t.pos]
529
-		t.moveCursorToPos(t.pos)
530
-	case keyCtrlD:
531
-		// Erase the character under the current position.
532
-		// The EOF case when the line is empty is handled in
533
-		// readLine().
534
-		if t.pos < len(t.line) {
535
-			t.pos++
536
-			t.eraseNPreviousChars(1)
537
-		}
538
-	case keyCtrlU:
539
-		t.eraseNPreviousChars(t.pos)
540
-	case keyClearScreen:
541
-		// Erases the screen and moves the cursor to the home position.
542
-		t.queue([]rune("\x1b[2J\x1b[H"))
543
-		t.queue(t.prompt)
544
-		t.cursorX, t.cursorY = 0, 0
545
-		t.advanceCursor(visualLength(t.prompt))
546
-		t.setLine(t.line, t.pos)
547
-	default:
548
-		if t.AutoCompleteCallback != nil {
549
-			prefix := string(t.line[:t.pos])
550
-			suffix := string(t.line[t.pos:])
551
-
552
-			t.lock.Unlock()
553
-			newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
554
-			t.lock.Lock()
555
-
556
-			if completeOk {
557
-				t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
558
-				return
559
-			}
560
-		}
561
-		if !isPrintable(key) {
562
-			return
563
-		}
564
-		if len(t.line) == maxLineLength {
565
-			return
566
-		}
567
-		t.addKeyToLine(key)
568
-	}
569
-	return
570
-}
571
-
572
-// addKeyToLine inserts the given key at the current position in the current
573
-// line.
574
-func (t *Terminal) addKeyToLine(key rune) {
575
-	if len(t.line) == cap(t.line) {
576
-		newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
577
-		copy(newLine, t.line)
578
-		t.line = newLine
579
-	}
580
-	t.line = t.line[:len(t.line)+1]
581
-	copy(t.line[t.pos+1:], t.line[t.pos:])
582
-	t.line[t.pos] = key
583
-	if t.echo {
584
-		t.writeLine(t.line[t.pos:])
585
-	}
586
-	t.pos++
587
-	t.moveCursorToPos(t.pos)
588
-}
589
-
590
-func (t *Terminal) writeLine(line []rune) {
591
-	for len(line) != 0 {
592
-		remainingOnLine := t.termWidth - t.cursorX
593
-		todo := len(line)
594
-		if todo > remainingOnLine {
595
-			todo = remainingOnLine
596
-		}
597
-		t.queue(line[:todo])
598
-		t.advanceCursor(visualLength(line[:todo]))
599
-		line = line[todo:]
600
-	}
601
-}
602
-
603
-// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
604
-func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
605
-	for len(buf) > 0 {
606
-		i := bytes.IndexByte(buf, '\n')
607
-		todo := len(buf)
608
-		if i >= 0 {
609
-			todo = i
610
-		}
611
-
612
-		var nn int
613
-		nn, err = w.Write(buf[:todo])
614
-		n += nn
615
-		if err != nil {
616
-			return n, err
617
-		}
618
-		buf = buf[todo:]
619
-
620
-		if i >= 0 {
621
-			if _, err = w.Write(crlf); err != nil {
622
-				return n, err
623
-			}
624
-			n++
625
-			buf = buf[1:]
626
-		}
627
-	}
628
-
629
-	return n, nil
630
-}
631
-
632
-func (t *Terminal) Write(buf []byte) (n int, err error) {
633
-	t.lock.Lock()
634
-	defer t.lock.Unlock()
635
-
636
-	if t.cursorX == 0 && t.cursorY == 0 {
637
-		// This is the easy case: there's nothing on the screen that we
638
-		// have to move out of the way.
639
-		return writeWithCRLF(t.c, buf)
640
-	}
641
-
642
-	// We have a prompt and possibly user input on the screen. We
643
-	// have to clear it first.
644
-	t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
645
-	t.cursorX = 0
646
-	t.clearLineToRight()
647
-
648
-	for t.cursorY > 0 {
649
-		t.move(1 /* up */, 0, 0, 0)
650
-		t.cursorY--
651
-		t.clearLineToRight()
652
-	}
653
-
654
-	if _, err = t.c.Write(t.outBuf); err != nil {
655
-		return
656
-	}
657
-	t.outBuf = t.outBuf[:0]
658
-
659
-	if n, err = writeWithCRLF(t.c, buf); err != nil {
660
-		return
661
-	}
662
-
663
-	t.writeLine(t.prompt)
664
-	if t.echo {
665
-		t.writeLine(t.line)
666
-	}
667
-
668
-	t.moveCursorToPos(t.pos)
669
-
670
-	if _, err = t.c.Write(t.outBuf); err != nil {
671
-		return
672
-	}
673
-	t.outBuf = t.outBuf[:0]
674
-	return
675
-}
676
-
677
-// ReadPassword temporarily changes the prompt and reads a password, without
678
-// echo, from the terminal.
679
-func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
680
-	t.lock.Lock()
681
-	defer t.lock.Unlock()
682
-
683
-	oldPrompt := t.prompt
684
-	t.prompt = []rune(prompt)
685
-	t.echo = false
686
-
687
-	line, err = t.readLine()
688
-
689
-	t.prompt = oldPrompt
690
-	t.echo = true
691
-
692
-	return
693
-}
694
-
695
-// ReadLine returns a line of input from the terminal.
696
-func (t *Terminal) ReadLine() (line string, err error) {
697
-	t.lock.Lock()
698
-	defer t.lock.Unlock()
699
-
700
-	return t.readLine()
701
-}
702
-
703
-func (t *Terminal) readLine() (line string, err error) {
704
-	// t.lock must be held at this point
705
-
706
-	if t.cursorX == 0 && t.cursorY == 0 {
707
-		t.writeLine(t.prompt)
708
-		t.c.Write(t.outBuf)
709
-		t.outBuf = t.outBuf[:0]
710
-	}
711
-
712
-	lineIsPasted := t.pasteActive
713
-
714
-	for {
715
-		rest := t.remainder
716
-		lineOk := false
717
-		for !lineOk {
718
-			var key rune
719
-			key, rest = bytesToKey(rest, t.pasteActive)
720
-			if key == utf8.RuneError {
721
-				break
722
-			}
723
-			if !t.pasteActive {
724
-				if key == keyCtrlD {
725
-					if len(t.line) == 0 {
726
-						return "", io.EOF
727
-					}
728
-				}
729
-				if key == keyPasteStart {
730
-					t.pasteActive = true
731
-					if len(t.line) == 0 {
732
-						lineIsPasted = true
733
-					}
734
-					continue
735
-				}
736
-			} else if key == keyPasteEnd {
737
-				t.pasteActive = false
738
-				continue
739
-			}
740
-			if !t.pasteActive {
741
-				lineIsPasted = false
742
-			}
743
-			line, lineOk = t.handleKey(key)
744
-		}
745
-		if len(rest) > 0 {
746
-			n := copy(t.inBuf[:], rest)
747
-			t.remainder = t.inBuf[:n]
748
-		} else {
749
-			t.remainder = nil
750
-		}
751
-		t.c.Write(t.outBuf)
752
-		t.outBuf = t.outBuf[:0]
753
-		if lineOk {
754
-			if t.echo {
755
-				t.historyIndex = -1
756
-				t.history.Add(line)
757
-			}
758
-			if lineIsPasted {
759
-				err = ErrPasteIndicator
760
-			}
761
-			return
762
-		}
763
-
764
-		// t.remainder is a slice at the beginning of t.inBuf
765
-		// containing a partial key sequence
766
-		readBuf := t.inBuf[len(t.remainder):]
767
-		var n int
768
-
769
-		t.lock.Unlock()
770
-		n, err = t.c.Read(readBuf)
771
-		t.lock.Lock()
772
-
773
-		if err != nil {
774
-			return
775
-		}
776
-
777
-		t.remainder = t.inBuf[:n+len(t.remainder)]
778
-	}
779
-}
780
-
781
-// SetPrompt sets the prompt to be used when reading subsequent lines.
782
-func (t *Terminal) SetPrompt(prompt string) {
783
-	t.lock.Lock()
784
-	defer t.lock.Unlock()
785
-
786
-	t.prompt = []rune(prompt)
787
-}
788
-
789
-func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
790
-	// Move cursor to column zero at the start of the line.
791
-	t.move(t.cursorY, 0, t.cursorX, 0)
792
-	t.cursorX, t.cursorY = 0, 0
793
-	t.clearLineToRight()
794
-	for t.cursorY < numPrevLines {
795
-		// Move down a line
796
-		t.move(0, 1, 0, 0)
797
-		t.cursorY++
798
-		t.clearLineToRight()
799
-	}
800
-	// Move back to beginning.
801
-	t.move(t.cursorY, 0, 0, 0)
802
-	t.cursorX, t.cursorY = 0, 0
803
-
804
-	t.queue(t.prompt)
805
-	t.advanceCursor(visualLength(t.prompt))
806
-	t.writeLine(t.line)
807
-	t.moveCursorToPos(t.pos)
808
-}
809
-
810
-func (t *Terminal) SetSize(width, height int) error {
811
-	t.lock.Lock()
812
-	defer t.lock.Unlock()
813
-
814
-	if width == 0 {
815
-		width = 1
816
-	}
817
-
818
-	oldWidth := t.termWidth
819
-	t.termWidth, t.termHeight = width, height
820
-
821
-	switch {
822
-	case width == oldWidth:
823
-		// If the width didn't change then nothing else needs to be
824
-		// done.
825
-		return nil
826
-	case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
827
-		// If there is nothing on current line and no prompt printed,
828
-		// just do nothing
829
-		return nil
830
-	case width < oldWidth:
831
-		// Some terminals (e.g. xterm) will truncate lines that were
832
-		// too long when shinking. Others, (e.g. gnome-terminal) will
833
-		// attempt to wrap them. For the former, repainting t.maxLine
834
-		// works great, but that behaviour goes badly wrong in the case
835
-		// of the latter because they have doubled every full line.
836
-
837
-		// We assume that we are working on a terminal that wraps lines
838
-		// and adjust the cursor position based on every previous line
839
-		// wrapping and turning into two. This causes the prompt on
840
-		// xterms to move upwards, which isn't great, but it avoids a
841
-		// huge mess with gnome-terminal.
842
-		if t.cursorX >= t.termWidth {
843
-			t.cursorX = t.termWidth - 1
844
-		}
845
-		t.cursorY *= 2
846
-		t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
847
-	case width > oldWidth:
848
-		// If the terminal expands then our position calculations will
849
-		// be wrong in the future because we think the cursor is
850
-		// |t.pos| chars into the string, but there will be a gap at
851
-		// the end of any wrapped line.
852
-		//
853
-		// But the position will actually be correct until we move, so
854
-		// we can move back to the beginning and repaint everything.
855
-		t.clearAndRepaintLinePlusNPrevious(t.maxLine)
856
-	}
857
-
858
-	_, err := t.c.Write(t.outBuf)
859
-	t.outBuf = t.outBuf[:0]
860
-	return err
861
-}
862
-
863
-type pasteIndicatorError struct{}
864
-
865
-func (pasteIndicatorError) Error() string {
866
-	return "terminal: ErrPasteIndicator not correctly handled"
867
-}
868
-
869
-// ErrPasteIndicator may be returned from ReadLine as the error, in addition
870
-// to valid line data. It indicates that bracketed paste mode is enabled and
871
-// that the returned line consists only of pasted data. Programs may wish to
872
-// interpret pasted data more literally than typed data.
873
-var ErrPasteIndicator = pasteIndicatorError{}
874
-
875
-// SetBracketedPasteMode requests that the terminal bracket paste operations
876
-// with markers. Not all terminals support this but, if it is supported, then
877
-// enabling this mode will stop any autocomplete callback from running due to
878
-// pastes. Additionally, any lines that are completely pasted will be returned
879
-// from ReadLine with the error set to ErrPasteIndicator.
880
-func (t *Terminal) SetBracketedPasteMode(on bool) {
881
-	if on {
882
-		io.WriteString(t.c, "\x1b[?2004h")
883
-	} else {
884
-		io.WriteString(t.c, "\x1b[?2004l")
885
-	}
886
-}
887
-
888
-// stRingBuffer is a ring buffer of strings.
889
-type stRingBuffer struct {
890
-	// entries contains max elements.
891
-	entries []string
892
-	max     int
893
-	// head contains the index of the element most recently added to the ring.
894
-	head int
895
-	// size contains the number of elements in the ring.
896
-	size int
897
-}
898
-
899
-func (s *stRingBuffer) Add(a string) {
900
-	if s.entries == nil {
901
-		const defaultNumEntries = 100
902
-		s.entries = make([]string, defaultNumEntries)
903
-		s.max = defaultNumEntries
904
-	}
905
-
906
-	s.head = (s.head + 1) % s.max
907
-	s.entries[s.head] = a
908
-	if s.size < s.max {
909
-		s.size++
910
-	}
911
-}
912
-
913
-// NthPreviousEntry returns the value passed to the nth previous call to Add.
914
-// If n is zero then the immediately prior value is returned, if one, then the
915
-// next most recent, and so on. If such an element doesn't exist then ok is
916
-// false.
917
-func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
918
-	if n >= s.size {
919
-		return "", false
920
-	}
921
-	index := s.head - n
922
-	if index < 0 {
923
-		index += s.max
924
-	}
925
-	return s.entries[index], true
926
-}
927
-
928
-// readPasswordLine reads from reader until it finds \n or io.EOF.
929
-// The slice returned does not include the \n.
930
-// readPasswordLine also ignores any \r it finds.
931
-func readPasswordLine(reader io.Reader) ([]byte, error) {
932
-	var buf [1]byte
933
-	var ret []byte
934
-
935
-	for {
936
-		n, err := reader.Read(buf[:])
937
-		if n > 0 {
938
-			switch buf[0] {
939
-			case '\n':
940
-				return ret, nil
941
-			case '\r':
942
-				// remove \r from passwords on Windows
943
-			default:
944
-				ret = append(ret, buf[0])
945
-			}
946
-			continue
947
-		}
948
-		if err != nil {
949
-			if err == io.EOF && len(ret) > 0 {
950
-				return ret, nil
951
-			}
952
-			return ret, err
953
-		}
954
-	}
955
-}
956 1
deleted file mode 100644
... ...
@@ -1,114 +0,0 @@
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
-
5
-// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd
6
-
7
-// Package terminal provides support functions for dealing with terminals, as
8
-// commonly found on UNIX systems.
9
-//
10
-// Putting a terminal into raw mode is the most common requirement:
11
-//
12
-// 	oldState, err := terminal.MakeRaw(0)
13
-// 	if err != nil {
14
-// 	        panic(err)
15
-// 	}
16
-// 	defer terminal.Restore(0, oldState)
17
-package terminal // import "golang.org/x/crypto/ssh/terminal"
18
-
19
-import (
20
-	"golang.org/x/sys/unix"
21
-)
22
-
23
-// State contains the state of a terminal.
24
-type State struct {
25
-	termios unix.Termios
26
-}
27
-
28
-// IsTerminal returns whether the given file descriptor is a terminal.
29
-func IsTerminal(fd int) bool {
30
-	_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
31
-	return err == nil
32
-}
33
-
34
-// MakeRaw put the terminal connected to the given file descriptor into raw
35
-// mode and returns the previous state of the terminal so that it can be
36
-// restored.
37
-func MakeRaw(fd int) (*State, error) {
38
-	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
39
-	if err != nil {
40
-		return nil, err
41
-	}
42
-
43
-	oldState := State{termios: *termios}
44
-
45
-	// This attempts to replicate the behaviour documented for cfmakeraw in
46
-	// the termios(3) manpage.
47
-	termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
48
-	termios.Oflag &^= unix.OPOST
49
-	termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
50
-	termios.Cflag &^= unix.CSIZE | unix.PARENB
51
-	termios.Cflag |= unix.CS8
52
-	termios.Cc[unix.VMIN] = 1
53
-	termios.Cc[unix.VTIME] = 0
54
-	if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
55
-		return nil, err
56
-	}
57
-
58
-	return &oldState, nil
59
-}
60
-
61
-// GetState returns the current state of a terminal which may be useful to
62
-// restore the terminal after a signal.
63
-func GetState(fd int) (*State, error) {
64
-	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
65
-	if err != nil {
66
-		return nil, err
67
-	}
68
-
69
-	return &State{termios: *termios}, nil
70
-}
71
-
72
-// Restore restores the terminal connected to the given file descriptor to a
73
-// previous state.
74
-func Restore(fd int, state *State) error {
75
-	return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
76
-}
77
-
78
-// GetSize returns the dimensions of the given terminal.
79
-func GetSize(fd int) (width, height int, err error) {
80
-	ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
81
-	if err != nil {
82
-		return -1, -1, err
83
-	}
84
-	return int(ws.Col), int(ws.Row), nil
85
-}
86
-
87
-// passwordReader is an io.Reader that reads from a specific file descriptor.
88
-type passwordReader int
89
-
90
-func (r passwordReader) Read(buf []byte) (int, error) {
91
-	return unix.Read(int(r), buf)
92
-}
93
-
94
-// ReadPassword reads a line of input from a terminal without local echo.  This
95
-// is commonly used for inputting passwords and other sensitive data. The slice
96
-// returned does not include the \n.
97
-func ReadPassword(fd int) ([]byte, error) {
98
-	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
99
-	if err != nil {
100
-		return nil, err
101
-	}
102
-
103
-	newState := *termios
104
-	newState.Lflag &^= unix.ECHO
105
-	newState.Lflag |= unix.ICANON | unix.ISIG
106
-	newState.Iflag |= unix.ICRNL
107
-	if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
108
-		return nil, err
109
-	}
110
-
111
-	defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
112
-
113
-	return readPasswordLine(passwordReader(fd))
114
-}
115 1
deleted file mode 100644
... ...
@@ -1,12 +0,0 @@
1
-// Copyright 2018 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
-
5
-// +build aix
6
-
7
-package terminal
8
-
9
-import "golang.org/x/sys/unix"
10
-
11
-const ioctlReadTermios = unix.TCGETS
12
-const ioctlWriteTermios = unix.TCSETS
13 1
deleted file mode 100644
... ...
@@ -1,12 +0,0 @@
1
-// Copyright 2013 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
-
5
-// +build darwin dragonfly freebsd netbsd openbsd
6
-
7
-package terminal
8
-
9
-import "golang.org/x/sys/unix"
10
-
11
-const ioctlReadTermios = unix.TIOCGETA
12
-const ioctlWriteTermios = unix.TIOCSETA
13 1
deleted file mode 100644
... ...
@@ -1,10 +0,0 @@
1
-// Copyright 2013 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
-
5
-package terminal
6
-
7
-import "golang.org/x/sys/unix"
8
-
9
-const ioctlReadTermios = unix.TCGETS
10
-const ioctlWriteTermios = unix.TCSETS
11 1
deleted file mode 100644
... ...
@@ -1,58 +0,0 @@
1
-// Copyright 2016 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
-
5
-// Package terminal provides support functions for dealing with terminals, as
6
-// commonly found on UNIX systems.
7
-//
8
-// Putting a terminal into raw mode is the most common requirement:
9
-//
10
-// 	oldState, err := terminal.MakeRaw(0)
11
-// 	if err != nil {
12
-// 	        panic(err)
13
-// 	}
14
-// 	defer terminal.Restore(0, oldState)
15
-package terminal
16
-
17
-import (
18
-	"fmt"
19
-	"runtime"
20
-)
21
-
22
-type State struct{}
23
-
24
-// IsTerminal returns whether the given file descriptor is a terminal.
25
-func IsTerminal(fd int) bool {
26
-	return false
27
-}
28
-
29
-// MakeRaw put the terminal connected to the given file descriptor into raw
30
-// mode and returns the previous state of the terminal so that it can be
31
-// restored.
32
-func MakeRaw(fd int) (*State, error) {
33
-	return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
34
-}
35
-
36
-// GetState returns the current state of a terminal which may be useful to
37
-// restore the terminal after a signal.
38
-func GetState(fd int) (*State, error) {
39
-	return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
40
-}
41
-
42
-// Restore restores the terminal connected to the given file descriptor to a
43
-// previous state.
44
-func Restore(fd int, state *State) error {
45
-	return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
46
-}
47
-
48
-// GetSize returns the dimensions of the given terminal.
49
-func GetSize(fd int) (width, height int, err error) {
50
-	return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
51
-}
52
-
53
-// ReadPassword reads a line of input from a terminal without local echo.  This
54
-// is commonly used for inputting passwords and other sensitive data. The slice
55
-// returned does not include the \n.
56
-func ReadPassword(fd int) ([]byte, error) {
57
-	return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
58
-}
59 1
deleted file mode 100644
... ...
@@ -1,124 +0,0 @@
1
-// Copyright 2015 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
-
5
-// +build solaris
6
-
7
-package terminal // import "golang.org/x/crypto/ssh/terminal"
8
-
9
-import (
10
-	"golang.org/x/sys/unix"
11
-	"io"
12
-	"syscall"
13
-)
14
-
15
-// State contains the state of a terminal.
16
-type State struct {
17
-	termios unix.Termios
18
-}
19
-
20
-// IsTerminal returns whether the given file descriptor is a terminal.
21
-func IsTerminal(fd int) bool {
22
-	_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
23
-	return err == nil
24
-}
25
-
26
-// ReadPassword reads a line of input from a terminal without local echo.  This
27
-// is commonly used for inputting passwords and other sensitive data. The slice
28
-// returned does not include the \n.
29
-func ReadPassword(fd int) ([]byte, error) {
30
-	// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
31
-	val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
32
-	if err != nil {
33
-		return nil, err
34
-	}
35
-	oldState := *val
36
-
37
-	newState := oldState
38
-	newState.Lflag &^= syscall.ECHO
39
-	newState.Lflag |= syscall.ICANON | syscall.ISIG
40
-	newState.Iflag |= syscall.ICRNL
41
-	err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
42
-	if err != nil {
43
-		return nil, err
44
-	}
45
-
46
-	defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
47
-
48
-	var buf [16]byte
49
-	var ret []byte
50
-	for {
51
-		n, err := syscall.Read(fd, buf[:])
52
-		if err != nil {
53
-			return nil, err
54
-		}
55
-		if n == 0 {
56
-			if len(ret) == 0 {
57
-				return nil, io.EOF
58
-			}
59
-			break
60
-		}
61
-		if buf[n-1] == '\n' {
62
-			n--
63
-		}
64
-		ret = append(ret, buf[:n]...)
65
-		if n < len(buf) {
66
-			break
67
-		}
68
-	}
69
-
70
-	return ret, nil
71
-}
72
-
73
-// MakeRaw puts the terminal connected to the given file descriptor into raw
74
-// mode and returns the previous state of the terminal so that it can be
75
-// restored.
76
-// see http://cr.illumos.org/~webrev/andy_js/1060/
77
-func MakeRaw(fd int) (*State, error) {
78
-	termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
79
-	if err != nil {
80
-		return nil, err
81
-	}
82
-
83
-	oldState := State{termios: *termios}
84
-
85
-	termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
86
-	termios.Oflag &^= unix.OPOST
87
-	termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
88
-	termios.Cflag &^= unix.CSIZE | unix.PARENB
89
-	termios.Cflag |= unix.CS8
90
-	termios.Cc[unix.VMIN] = 1
91
-	termios.Cc[unix.VTIME] = 0
92
-
93
-	if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil {
94
-		return nil, err
95
-	}
96
-
97
-	return &oldState, nil
98
-}
99
-
100
-// Restore restores the terminal connected to the given file descriptor to a
101
-// previous state.
102
-func Restore(fd int, oldState *State) error {
103
-	return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios)
104
-}
105
-
106
-// GetState returns the current state of a terminal which may be useful to
107
-// restore the terminal after a signal.
108
-func GetState(fd int) (*State, error) {
109
-	termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
110
-	if err != nil {
111
-		return nil, err
112
-	}
113
-
114
-	return &State{termios: *termios}, nil
115
-}
116
-
117
-// GetSize returns the dimensions of the given terminal.
118
-func GetSize(fd int) (width, height int, err error) {
119
-	ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
120
-	if err != nil {
121
-		return 0, 0, err
122
-	}
123
-	return int(ws.Col), int(ws.Row), nil
124
-}
125 1
deleted file mode 100644
... ...
@@ -1,105 +0,0 @@
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
-
5
-// +build windows
6
-
7
-// Package terminal provides support functions for dealing with terminals, as
8
-// commonly found on UNIX systems.
9
-//
10
-// Putting a terminal into raw mode is the most common requirement:
11
-//
12
-// 	oldState, err := terminal.MakeRaw(0)
13
-// 	if err != nil {
14
-// 	        panic(err)
15
-// 	}
16
-// 	defer terminal.Restore(0, oldState)
17
-package terminal
18
-
19
-import (
20
-	"os"
21
-
22
-	"golang.org/x/sys/windows"
23
-)
24
-
25
-type State struct {
26
-	mode uint32
27
-}
28
-
29
-// IsTerminal returns whether the given file descriptor is a terminal.
30
-func IsTerminal(fd int) bool {
31
-	var st uint32
32
-	err := windows.GetConsoleMode(windows.Handle(fd), &st)
33
-	return err == nil
34
-}
35
-
36
-// MakeRaw put the terminal connected to the given file descriptor into raw
37
-// mode and returns the previous state of the terminal so that it can be
38
-// restored.
39
-func MakeRaw(fd int) (*State, error) {
40
-	var st uint32
41
-	if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
42
-		return nil, err
43
-	}
44
-	raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
45
-	if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
46
-		return nil, err
47
-	}
48
-	return &State{st}, nil
49
-}
50
-
51
-// GetState returns the current state of a terminal which may be useful to
52
-// restore the terminal after a signal.
53
-func GetState(fd int) (*State, error) {
54
-	var st uint32
55
-	if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
56
-		return nil, err
57
-	}
58
-	return &State{st}, nil
59
-}
60
-
61
-// Restore restores the terminal connected to the given file descriptor to a
62
-// previous state.
63
-func Restore(fd int, state *State) error {
64
-	return windows.SetConsoleMode(windows.Handle(fd), state.mode)
65
-}
66
-
67
-// GetSize returns the visible dimensions of the given terminal.
68
-//
69
-// These dimensions don't include any scrollback buffer height.
70
-func GetSize(fd int) (width, height int, err error) {
71
-	var info windows.ConsoleScreenBufferInfo
72
-	if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
73
-		return 0, 0, err
74
-	}
75
-	return int(info.Window.Right - info.Window.Left + 1), int(info.Window.Bottom - info.Window.Top + 1), nil
76
-}
77
-
78
-// ReadPassword reads a line of input from a terminal without local echo.  This
79
-// is commonly used for inputting passwords and other sensitive data. The slice
80
-// returned does not include the \n.
81
-func ReadPassword(fd int) ([]byte, error) {
82
-	var st uint32
83
-	if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
84
-		return nil, err
85
-	}
86
-	old := st
87
-
88
-	st &^= (windows.ENABLE_ECHO_INPUT)
89
-	st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
90
-	if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil {
91
-		return nil, err
92
-	}
93
-
94
-	defer windows.SetConsoleMode(windows.Handle(fd), old)
95
-
96
-	var h windows.Handle
97
-	p, _ := windows.GetCurrentProcess()
98
-	if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil {
99
-		return nil, err
100
-	}
101
-
102
-	f := os.NewFile(uintptr(h), "stdin")
103
-	defer f.Close()
104
-	return readPasswordLine(f)
105
-}