Files
coder/cli/cliui/cliui.go
T
Ammar Bandukwala dd97fe2bce chore(cli): replace lipgloss with coder/pretty (#9564)
This change will improve over CLI performance and "snappiness" as well as
substantially reduce our test times. Preliminary benchmarks show
`coder server --help` times cut from 300ms to 120ms on my dogfood
instance.

The inefficiency of lipgloss disproportionately impacts our system, as all help
text for every command is generated whenever any command is invoked.

The `pretty` API could clean up a lot of the code (e.g., by replacing
complex string concatenations with Printf), but this commit is too
expansive as is so that work will be done in a follow up.
2023-09-07 16:28:22 -05:00

165 lines
3.4 KiB
Go

package cliui
import (
"os"
"testing"
"time"
"github.com/muesli/termenv"
"golang.org/x/xerrors"
"github.com/coder/pretty"
)
var Canceled = xerrors.New("canceled")
// DefaultStyles compose visual elements of the UI.
var DefaultStyles Styles
type Styles struct {
Code,
DateTimeStamp,
Error,
Field,
Keyword,
Placeholder,
Prompt,
FocusedPrompt,
Fuchsia,
Warn,
Wrap pretty.Style
}
var color = termenv.NewOutput(os.Stdout).ColorProfile()
// TestColor sets the color profile to the given profile for the duration of the
// test.
// WARN: Must not be used in parallel tests.
func TestColor(t *testing.T, tprofile termenv.Profile) {
old := color
color = tprofile
t.Cleanup(func() {
color = old
})
}
var (
Green = color.Color("#04B575")
Red = color.Color("#ED567A")
Fuchsia = color.Color("#EE6FF8")
Yellow = color.Color("#ECFD65")
Blue = color.Color("#5000ff")
)
func isTerm() bool {
return color != termenv.Ascii
}
// Bold returns a formatter that renders text in bold
// if the terminal supports it.
func Bold(s string) string {
if !isTerm() {
return s
}
return pretty.Sprint(pretty.Bold(), s)
}
// BoldFmt returns a formatter that renders text in bold
// if the terminal supports it.
func BoldFmt() pretty.Formatter {
if !isTerm() {
return pretty.Style{}
}
return pretty.Bold()
}
// Timestamp formats a timestamp for display.
func Timestamp(t time.Time) string {
return pretty.Sprint(DefaultStyles.DateTimeStamp, t.Format(time.Stamp))
}
// Keyword formats a keyword for display.
func Keyword(s string) string {
return pretty.Sprint(DefaultStyles.Keyword, s)
}
// Placeholder formats a placeholder for display.
func Placeholder(s string) string {
return pretty.Sprint(DefaultStyles.Placeholder, s)
}
// Wrap prevents the text from overflowing the terminal.
func Wrap(s string) string {
return pretty.Sprint(DefaultStyles.Wrap, s)
}
// Code formats code for display.
func Code(s string) string {
return pretty.Sprint(DefaultStyles.Code, s)
}
// Field formats a field for display.
func Field(s string) string {
return pretty.Sprint(DefaultStyles.Field, s)
}
func ifTerm(fmt pretty.Formatter) pretty.Formatter {
if !isTerm() {
return pretty.Nop
}
return fmt
}
func init() {
// We do not adapt the color based on whether the terminal is light or dark.
// Doing so would require a round-trip between the program and the terminal
// due to the OSC query and response.
DefaultStyles = Styles{
Code: pretty.Style{
ifTerm(pretty.XPad(1, 1)),
pretty.FgColor(Red),
pretty.BgColor(color.Color("#2c2c2c")),
},
DateTimeStamp: pretty.Style{
pretty.FgColor(color.Color("#7571F9")),
},
Error: pretty.Style{
pretty.FgColor(Red),
},
Field: pretty.Style{
pretty.XPad(1, 1),
pretty.FgColor(color.Color("#FFFFFF")),
pretty.BgColor(color.Color("#2b2a2a")),
},
Keyword: pretty.Style{
pretty.FgColor(Green),
},
Placeholder: pretty.Style{
pretty.FgColor(color.Color("#4d46b3")),
},
Prompt: pretty.Style{
pretty.FgColor(color.Color("#5C5C5C")),
pretty.Wrap("> ", ""),
},
Warn: pretty.Style{
pretty.FgColor(Yellow),
},
Wrap: pretty.Style{
pretty.LineWrap(80),
},
}
DefaultStyles.FocusedPrompt = append(
DefaultStyles.Prompt,
pretty.FgColor(Blue),
)
}
// ValidateNotEmpty is a helper function to disallow empty inputs!
func ValidateNotEmpty(s string) error {
if s == "" {
return xerrors.New("Must be provided!")
}
return nil
}