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