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>
| ... | ... |
@@ -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 |
| 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 |
-} |