mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
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.
This commit is contained in:
@ -2,11 +2,13 @@ package cliui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/charm/ui/common"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
var Canceled = xerrors.New("canceled")
|
||||
@ -15,55 +17,142 @@ var Canceled = xerrors.New("canceled")
|
||||
var DefaultStyles Styles
|
||||
|
||||
type Styles struct {
|
||||
Bold,
|
||||
Checkmark,
|
||||
Code,
|
||||
Crossmark,
|
||||
DateTimeStamp,
|
||||
Error,
|
||||
Field,
|
||||
Keyword,
|
||||
Paragraph,
|
||||
Placeholder,
|
||||
Prompt,
|
||||
FocusedPrompt,
|
||||
Fuchsia,
|
||||
Logo,
|
||||
Warn,
|
||||
Wrap lipgloss.Style
|
||||
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() {
|
||||
lipgloss.SetDefaultRenderer(
|
||||
lipgloss.NewRenderer(os.Stdout, termenv.WithColorCache(true)),
|
||||
)
|
||||
|
||||
// All Styles are set after we change the DefaultRenderer so that the ColorCache
|
||||
// is in effect, mitigating the severe performance issue seen here:
|
||||
// https://github.com/coder/coder/issues/7884.
|
||||
|
||||
charmStyles := common.DefaultStyles()
|
||||
|
||||
// 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{
|
||||
Bold: lipgloss.NewStyle().Bold(true),
|
||||
Checkmark: charmStyles.Checkmark,
|
||||
Code: charmStyles.Code,
|
||||
Crossmark: charmStyles.Error.Copy().SetString("✘"),
|
||||
DateTimeStamp: charmStyles.LabelDim,
|
||||
Error: charmStyles.Error,
|
||||
Field: charmStyles.Code.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}),
|
||||
Keyword: charmStyles.Keyword,
|
||||
Paragraph: charmStyles.Paragraph,
|
||||
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#585858", Dark: "#4d46b3"}),
|
||||
Prompt: charmStyles.Prompt.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"}),
|
||||
FocusedPrompt: charmStyles.FocusedPrompt.Copy().Foreground(lipgloss.Color("#651fff")),
|
||||
Fuchsia: charmStyles.SelectedMenuItem.Copy(),
|
||||
Logo: charmStyles.Logo.Copy().SetString("Coder"),
|
||||
Warn: lipgloss.NewStyle().Foreground(
|
||||
lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"},
|
||||
),
|
||||
Wrap: lipgloss.NewStyle().Width(80),
|
||||
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!
|
||||
|
Reference in New Issue
Block a user