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// This file contains a driver.UI implementation 6// that provides the readline functionality if possible. 7 8//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows) && !appengine && !android 9 10package main 11 12import ( 13 "fmt" 14 "io" 15 "os" 16 "strings" 17 18 "github.com/google/pprof/driver" 19 "golang.org/x/term" 20) 21 22func init() { 23 newUI = newReadlineUI 24} 25 26// readlineUI implements driver.UI interface using the 27// golang.org/x/term package. 28// The upstream pprof command implements the same functionality 29// using the github.com/chzyer/readline package. 30type readlineUI struct { 31 term *term.Terminal 32} 33 34func newReadlineUI() driver.UI { 35 // disable readline UI in dumb terminal. (golang.org/issue/26254) 36 if v := strings.ToLower(os.Getenv("TERM")); v == "" || v == "dumb" { 37 return nil 38 } 39 // test if we can use term.ReadLine 40 // that assumes operation in the raw mode. 41 oldState, err := term.MakeRaw(0) 42 if err != nil { 43 return nil 44 } 45 term.Restore(0, oldState) 46 47 rw := struct { 48 io.Reader 49 io.Writer 50 }{os.Stdin, os.Stderr} 51 return &readlineUI{term: term.NewTerminal(rw, "")} 52} 53 54// ReadLine returns a line of text (a command) read from the user. 55// prompt is printed before reading the command. 56func (r *readlineUI) ReadLine(prompt string) (string, error) { 57 r.term.SetPrompt(prompt) 58 59 // skip error checking because we tested it 60 // when creating this readlineUI initially. 61 oldState, _ := term.MakeRaw(0) 62 defer term.Restore(0, oldState) 63 64 s, err := r.term.ReadLine() 65 return s, err 66} 67 68// Print shows a message to the user. 69// It formats the text as fmt.Print would and adds a final \n if not already present. 70// For line-based UI, Print writes to standard error. 71// (Standard output is reserved for report data.) 72func (r *readlineUI) Print(args ...any) { 73 r.print(false, args...) 74} 75 76// PrintErr shows an error message to the user. 77// It formats the text as fmt.Print would and adds a final \n if not already present. 78// For line-based UI, PrintErr writes to standard error. 79func (r *readlineUI) PrintErr(args ...any) { 80 r.print(true, args...) 81} 82 83func (r *readlineUI) print(withColor bool, args ...any) { 84 text := fmt.Sprint(args...) 85 if !strings.HasSuffix(text, "\n") { 86 text += "\n" 87 } 88 if withColor { 89 text = colorize(text) 90 } 91 fmt.Fprint(r.term, text) 92} 93 94// colorize prints the msg in red using ANSI color escapes. 95func colorize(msg string) string { 96 const red = 31 97 var colorEscape = fmt.Sprintf("\033[0;%dm", red) 98 var colorResetEscape = "\033[0m" 99 return colorEscape + msg + colorResetEscape 100} 101 102// IsTerminal reports whether the UI is known to be tied to an 103// interactive terminal (as opposed to being redirected to a file). 104func (r *readlineUI) IsTerminal() bool { 105 const stdout = 1 106 return term.IsTerminal(stdout) 107} 108 109// WantBrowser indicates whether browser should be opened with the -http option. 110func (r *readlineUI) WantBrowser() bool { 111 return r.IsTerminal() 112} 113 114// SetAutoComplete instructs the UI to call complete(cmd) to obtain 115// the auto-completion of cmd, if the UI supports auto-completion at all. 116func (r *readlineUI) SetAutoComplete(complete func(string) string) { 117 // TODO: Implement auto-completion support. 118} 119