// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains an driver.UI implementation
// that provides the readline functionality if possible.
// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
// +build !appengine
// +build !android
package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/google/pprof/driver"
"golang.org/x/crypto/ssh/terminal"
)
func init() {
newUI = newReadlineUI
}
// readlineUI implements driver.UI interface using the
// golang.org/x/crypto/ssh/terminal package.
// The upstream pprof command implements the same functionality
// using the github.com/chzyer/readline package.
type readlineUI struct {
term *terminal.Terminal
}
func newReadlineUI() driver.UI {
// disable readline UI in dumb terminal. (golang.org/issue/26254)
if v := strings.ToLower(os.Getenv("TERM")); v == "" || v == "dumb" {
return nil
}
// test if we can use terminal.ReadLine
// that assumes operation in the raw mode.
oldState, err := terminal.MakeRaw(0)
if err != nil {
return nil
}
terminal.Restore(0, oldState)
rw := struct {
io.Reader
io.Writer
}{os.Stdin, os.Stderr}
return &readlineUI{term: terminal.NewTerminal(rw, "")}
}
// Read returns a line of text (a command) read from the user.
// prompt is printed before reading the command.
func (r *readlineUI) ReadLine(prompt string) (string, error) {
r.term.SetPrompt(prompt)
// skip error checking because we tested it
// when creating this readlineUI initially.
oldState, _ := terminal.MakeRaw(0)
defer terminal.Restore(0, oldState)
s, err := r.term.ReadLine()
return s, err
}
// Print shows a message to the user.
// It formats the text as fmt.Print would and adds a final \n if not already present.
// For line-based UI, Print writes to standard error.
// (Standard output is reserved for report data.)
func (r *readlineUI) Print(args ...interface{}) {
r.print(false, args...)
}
// PrintErr shows an error message to the user.
// It formats the text as fmt.Print would and adds a final \n if not already present.
// For line-based UI, PrintErr writes to standard error.
func (r *readlineUI) PrintErr(args ...interface{}) {
r.print(true, args...)
}
func (r *readlineUI) print(withColor bool, args ...interface{}) {
text := fmt.Sprint(args...)
if !strings.HasSuffix(text, "\n") {
text += "\n"
}
if withColor {
text = colorize(text)
}
fmt.Fprint(r.term, text)
}
// colorize prints the msg in red using ANSI color escapes.
func colorize(msg string) string {
const red = 31
var colorEscape = fmt.Sprintf("\033[0;%dm", red)
var colorResetEscape = "\033[0m"
return colorEscape + msg + colorResetEscape
}
// IsTerminal reports whether the UI is known to be tied to an
// interactive terminal (as opposed to being redirected to a file).
func (r *readlineUI) IsTerminal() bool {
const stdout = 1
return terminal.IsTerminal(stdout)
}
// WantBrowser indicates whether browser should be opened with the -http option.
func (r *readlineUI) WantBrowser() bool {
return r.IsTerminal()
}
// SetAutoComplete instructs the UI to call complete(cmd) to obtain
// the auto-completion of cmd, if the UI supports auto-completion at all.
func (r *readlineUI) SetAutoComplete(complete func(string) string) {
// TODO: Implement auto-completion support.
}