Skip to content

Commit f11d5ce

Browse files
committed
chore(cli): replace lipgloss with coder/pretty
Should improve performance and eliminate round-trip risk of using AdaptiveColor. The performance of lipgloss affects us particularly bad because we generate all help text for all commands when any is invoked.
1 parent 2dae600 commit f11d5ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+238
-153
lines changed

cli/cliui/cliui.go

+49-35
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ package cliui
33
import (
44
"os"
55

6-
"github.com/charmbracelet/charm/ui/common"
7-
"github.com/charmbracelet/lipgloss"
6+
"github.com/coder/pretty"
87
"github.com/muesli/termenv"
98
"golang.org/x/xerrors"
109
)
@@ -15,55 +14,70 @@ var Canceled = xerrors.New("canceled")
1514
var DefaultStyles Styles
1615

1716
type Styles struct {
18-
Bold,
19-
Checkmark,
2017
Code,
21-
Crossmark,
2218
DateTimeStamp,
2319
Error,
2420
Field,
2521
Keyword,
26-
Paragraph,
2722
Placeholder,
2823
Prompt,
2924
FocusedPrompt,
3025
Fuchsia,
31-
Logo,
3226
Warn,
33-
Wrap lipgloss.Style
27+
Wrap pretty.Style
3428
}
3529

36-
func init() {
37-
lipgloss.SetDefaultRenderer(
38-
lipgloss.NewRenderer(os.Stdout, termenv.WithColorCache(true)),
39-
)
30+
var color = termenv.NewOutput(os.Stdout).ColorProfile()
4031

41-
// All Styles are set after we change the DefaultRenderer so that the ColorCache
42-
// is in effect, mitigating the severe performance issue seen here:
43-
// https://github.com/coder/coder/issues/7884.
44-
45-
charmStyles := common.DefaultStyles()
32+
var (
33+
Green = color.Color("#04B575")
34+
Red = color.Color("#ED567A")
35+
Fuschia = color.Color("#EE6FF8")
36+
Yellow = color.Color("#ECFD65")
37+
)
4638

39+
func init() {
40+
// We do not adapt the color based on whether the terminal is light or dark.
41+
// Doing so would require a round-trip between the program and the terminal
42+
// due to the OSC query and response.
4743
DefaultStyles = Styles{
48-
Bold: lipgloss.NewStyle().Bold(true),
49-
Checkmark: charmStyles.Checkmark,
50-
Code: charmStyles.Code,
51-
Crossmark: charmStyles.Error.Copy().SetString("✘"),
52-
DateTimeStamp: charmStyles.LabelDim,
53-
Error: charmStyles.Error,
54-
Field: charmStyles.Code.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}),
55-
Keyword: charmStyles.Keyword,
56-
Paragraph: charmStyles.Paragraph,
57-
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#585858", Dark: "#4d46b3"}),
58-
Prompt: charmStyles.Prompt.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"}),
59-
FocusedPrompt: charmStyles.FocusedPrompt.Copy().Foreground(lipgloss.Color("#651fff")),
60-
Fuchsia: charmStyles.SelectedMenuItem.Copy(),
61-
Logo: charmStyles.Logo.Copy().SetString("Coder"),
62-
Warn: lipgloss.NewStyle().Foreground(
63-
lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"},
64-
),
65-
Wrap: lipgloss.NewStyle().Width(80),
44+
Code: pretty.Style{
45+
pretty.XPad(1, 1),
46+
pretty.FgColor(Red),
47+
},
48+
DateTimeStamp: pretty.Style{
49+
pretty.FgColor(color.Color("#7571F9")),
50+
},
51+
Error: pretty.Style{
52+
pretty.FgColor(Red),
53+
},
54+
Field: pretty.Style{
55+
pretty.XPad(1, 1),
56+
pretty.FgColor(color.Color("#FFFFFF")),
57+
pretty.BgColor(color.Color("#2b2a2a")),
58+
},
59+
Keyword: pretty.Style{
60+
pretty.FgColor(Green),
61+
},
62+
Placeholder: pretty.Style{
63+
pretty.FgColor(color.Color("#4d46b3")),
64+
},
65+
Prompt: pretty.Style{
66+
pretty.FgColor(color.Color("#5C5C5C")),
67+
pretty.Wrap(">", ""),
68+
},
69+
Warn: pretty.Style{
70+
pretty.FgColor(Yellow),
71+
},
72+
Wrap: pretty.Style{
73+
pretty.LineWrap(80),
74+
},
6675
}
76+
77+
DefaultStyles.FocusedPrompt = append(
78+
DefaultStyles.Prompt,
79+
pretty.FgColor(Fuschia),
80+
)
6781
}
6882

6983
// ValidateNotEmpty is a helper function to disallow empty inputs!

cli/cliui/log.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import (
55
"io"
66
"strings"
77

8-
"github.com/charmbracelet/lipgloss"
8+
"github.com/coder/pretty"
99
)
1010

1111
// cliMessage provides a human-readable message for CLI errors and messages.
1212
type cliMessage struct {
13-
Style lipgloss.Style
13+
Style pretty.Style
1414
Header string
1515
Prefix string
1616
Lines []string
@@ -21,21 +21,21 @@ func (m cliMessage) String() string {
2121
var str strings.Builder
2222

2323
if m.Prefix != "" {
24-
_, _ = str.WriteString(m.Style.Bold(true).Render(m.Prefix))
24+
pretty.Fprint(&str, m.Style.With(pretty.Bold()), "%s", m.Prefix)
2525
}
2626

27-
_, _ = str.WriteString(m.Style.Bold(false).Render(m.Header))
27+
pretty.Fprint(&str, m.Style, m.Header)
2828
_, _ = str.WriteString("\r\n")
2929
for _, line := range m.Lines {
30-
_, _ = fmt.Fprintf(&str, " %s %s\r\n", m.Style.Render("|"), line)
30+
_, _ = fmt.Fprintf(&str, " %s %s\r\n", pretty.Sprint(m.Style, "|"), line)
3131
}
3232
return str.String()
3333
}
3434

3535
// Warn writes a log to the writer provided.
3636
func Warn(wtr io.Writer, header string, lines ...string) {
3737
_, _ = fmt.Fprint(wtr, cliMessage{
38-
Style: DefaultStyles.Warn.Copy(),
38+
Style: DefaultStyles.Warn,
3939
Prefix: "WARN: ",
4040
Header: header,
4141
Lines: lines,
@@ -63,7 +63,7 @@ func Infof(wtr io.Writer, fmtStr string, args ...interface{}) {
6363
// Error writes a log to the writer provided.
6464
func Error(wtr io.Writer, header string, lines ...string) {
6565
_, _ = fmt.Fprint(wtr, cliMessage{
66-
Style: DefaultStyles.Error.Copy(),
66+
Style: DefaultStyles.Error,
6767
Prefix: "ERROR: ",
6868
Header: header,
6969
Lines: lines,

cli/cliui/parameter.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/coder/coder/v2/cli/clibase"
99
"github.com/coder/coder/v2/codersdk"
10+
"github.com/coder/pretty"
1011
)
1112

1213
func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.TemplateVersionParameter) (string, error) {
@@ -16,10 +17,10 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
1617
}
1718

1819
if templateVersionParameter.Ephemeral {
19-
label += DefaultStyles.Warn.Render(" (build option)")
20+
label += pretty.Sprint(DefaultStyles.Warn, " (build option)")
2021
}
2122

22-
_, _ = fmt.Fprintln(inv.Stdout, DefaultStyles.Bold.Render(label))
23+
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(pretty.Bold(), label))
2324

2425
if templateVersionParameter.DescriptionPlaintext != "" {
2526
_, _ = fmt.Fprintln(inv.Stdout, " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n")
@@ -45,7 +46,7 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
4546
}
4647

4748
_, _ = fmt.Fprintln(inv.Stdout)
48-
_, _ = fmt.Fprintln(inv.Stdout, " "+DefaultStyles.Prompt.String()+DefaultStyles.Field.Render(strings.Join(values, ", ")))
49+
_, _ = fmt.Fprintln(inv.Stdout, " "+pretty.Sprint(DefaultStyles.Prompt.String()+DefaultStyles.Field, strings.Join(values, ", ")))
4950
value = string(v)
5051
}
5152
} else if len(templateVersionParameter.Options) > 0 {
@@ -59,7 +60,7 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
5960
})
6061
if err == nil {
6162
_, _ = fmt.Fprintln(inv.Stdout)
62-
_, _ = fmt.Fprintln(inv.Stdout, " "+DefaultStyles.Prompt.String()+DefaultStyles.Field.Render(richParameterOption.Name))
63+
_, _ = fmt.Fprintln(inv.Stdout, " "+pretty.Sprint(DefaultStyles.Prompt.String()+DefaultStyles.Field, richParameterOption.Name))
6364
value = richParameterOption.Value
6465
}
6566
} else {
@@ -70,7 +71,7 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
7071
text += ":"
7172

7273
value, err = Prompt(inv, PromptOptions{
73-
Text: DefaultStyles.Bold.Render(text),
74+
Text: pretty.Sprint(DefaultStyles.Bold, text),
7475
Validate: func(value string) error {
7576
return validateRichPrompt(value, templateVersionParameter)
7677
},

cli/cliui/prompt.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"golang.org/x/xerrors"
1515

1616
"github.com/coder/coder/v2/cli/clibase"
17+
"github.com/coder/pretty"
1718
)
1819

1920
// PromptOptions supply a set of options to the prompt.
@@ -60,16 +61,16 @@ func Prompt(inv *clibase.Invocation, opts PromptOptions) (string, error) {
6061
if len(opts.Default) == 0 {
6162
opts.Default = ConfirmYes
6263
}
63-
renderedYes := DefaultStyles.Placeholder.Render(ConfirmYes)
64-
renderedNo := DefaultStyles.Placeholder.Render(ConfirmNo)
64+
renderedYes := pretty.Sprint(DefaultStyles.Placeholder, ConfirmYes)
65+
renderedNo := pretty.Sprint(DefaultStyles.Placeholder, ConfirmNo)
6566
if opts.Default == ConfirmYes {
66-
renderedYes = DefaultStyles.Bold.Render(ConfirmYes)
67+
renderedYes = pretty.Sprint(DefaultStyles.Bold, ConfirmYes)
6768
} else {
68-
renderedNo = DefaultStyles.Bold.Render(ConfirmNo)
69+
renderedNo = pretty.Sprint(DefaultStyles.Bold, ConfirmNo)
6970
}
70-
_, _ = fmt.Fprint(inv.Stdout, DefaultStyles.Placeholder.Render("("+renderedYes+DefaultStyles.Placeholder.Render("/"+renderedNo+DefaultStyles.Placeholder.Render(") "))))
71+
_, _ = fmt.Fprint(inv.Stdout, pretty.Sprint(DefaultStyles.Placeholder.Render("("+renderedYes+DefaultStyles.Placeholder.Render("/"+renderedNo+DefaultStyles.Placeholder, ") "))))
7172
} else if opts.Default != "" {
72-
_, _ = fmt.Fprint(inv.Stdout, DefaultStyles.Placeholder.Render("("+opts.Default+") "))
73+
_, _ = fmt.Fprint(inv.Stdout, pretty.Sprint(DefaultStyles.Placeholder, "("+opts.Default+") "))
7374
}
7475
interrupt := make(chan os.Signal, 1)
7576

@@ -126,7 +127,7 @@ func Prompt(inv *clibase.Invocation, opts PromptOptions) (string, error) {
126127
if opts.Validate != nil {
127128
err := opts.Validate(line)
128129
if err != nil {
129-
_, _ = fmt.Fprintln(inv.Stdout, DefaultStyles.Error.Render(err.Error()))
130+
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(DefaultStyles.Error, err.Error()))
130131
return Prompt(inv, opts)
131132
}
132133
}

cli/cliui/provisionerjob.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"golang.org/x/xerrors"
1616

1717
"github.com/coder/coder/v2/codersdk"
18+
"github.com/coder/pretty"
1819
)
1920

2021
func WorkspaceBuild(ctx context.Context, writer io.Writer, client *codersdk.Client, build uuid.UUID) error {
@@ -127,7 +128,7 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
127128
return
128129
}
129130
}
130-
_, _ = fmt.Fprintf(writer, DefaultStyles.FocusedPrompt.String()+DefaultStyles.Bold.Render("Gracefully canceling...")+"\n\n")
131+
_, _ = fmt.Fprintf(writer, pretty.Sprint(DefaultStyles.FocusedPrompt.String()+DefaultStyles.Bold, "Gracefully canceling...")+"\n\n")
131132
err := opts.Cancel()
132133
if err != nil {
133134
errChan <- xerrors.Errorf("cancel: %w", err)

cli/cliui/resources.go

+16-15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/coder/coder/v2/coderd/database/dbtime"
1313
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/pretty"
1415
)
1516

1617
type WorkspaceResourcesOptions struct {
@@ -78,7 +79,7 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
7879

7980
// Display a line for the resource.
8081
tableWriter.AppendRow(table.Row{
81-
DefaultStyles.Bold.Render(resourceAddress),
82+
pretty.Sprint(DefaultStyles.Bold, resourceAddress),
8283
"",
8384
"",
8485
"",
@@ -107,7 +108,7 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
107108
if totalAgents > 1 {
108109
sshCommand += "." + agent.Name
109110
}
110-
sshCommand = DefaultStyles.Code.Render(sshCommand)
111+
sshCommand = pretty.Sprint(DefaultStyles.Code, sshCommand)
111112
row = append(row, sshCommand)
112113
}
113114
tableWriter.AppendRow(row)
@@ -122,43 +123,43 @@ func renderAgentStatus(agent codersdk.WorkspaceAgent) string {
122123
switch agent.Status {
123124
case codersdk.WorkspaceAgentConnecting:
124125
since := dbtime.Now().Sub(agent.CreatedAt)
125-
return DefaultStyles.Warn.Render("⦾ connecting") + " " +
126-
DefaultStyles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
126+
return pretty.Sprint(DefaultStyles.Warn, "⦾ connecting") + " " +
127+
pretty.Sprint(DefaultStyles.Placeholder, "["+strconv.Itoa(int(since.Seconds()))+"s]")
127128
case codersdk.WorkspaceAgentDisconnected:
128129
since := dbtime.Now().Sub(*agent.DisconnectedAt)
129-
return DefaultStyles.Error.Render("⦾ disconnected") + " " +
130-
DefaultStyles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
130+
return pretty.Sprint(DefaultStyles.Error, "⦾ disconnected") + " " +
131+
pretty.Sprint(DefaultStyles.Placeholder, "["+strconv.Itoa(int(since.Seconds()))+"s]")
131132
case codersdk.WorkspaceAgentTimeout:
132133
since := dbtime.Now().Sub(agent.CreatedAt)
133134
return fmt.Sprintf(
134135
"%s %s",
135-
DefaultStyles.Warn.Render("⦾ timeout"),
136-
DefaultStyles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]"),
136+
pretty.Sprint(DefaultStyles.Warn, "⦾ timeout"),
137+
pretty.Sprint(DefaultStyles.Placeholder, "["+strconv.Itoa(int(since.Seconds()))+"s]"),
137138
)
138139
case codersdk.WorkspaceAgentConnected:
139-
return DefaultStyles.Keyword.Render("⦿ connected")
140+
return pretty.Sprint(DefaultStyles.Keyword, "⦿ connected")
140141
default:
141-
return DefaultStyles.Warn.Render("○ unknown")
142+
return pretty.Sprint(DefaultStyles.Warn, "○ unknown")
142143
}
143144
}
144145

145146
func renderAgentHealth(agent codersdk.WorkspaceAgent) string {
146147
if agent.Health.Healthy {
147-
return DefaultStyles.Keyword.Render("✔ healthy")
148+
return pretty.Sprint(DefaultStyles.Keyword, "✔ healthy")
148149
}
149-
return DefaultStyles.Error.Render("✘ " + agent.Health.Reason)
150+
return pretty.Sprint(DefaultStyles.Error, "✘ "+agent.Health.Reason)
150151
}
151152

152153
func renderAgentVersion(agentVersion, serverVersion string) string {
153154
if agentVersion == "" {
154155
agentVersion = "(unknown)"
155156
}
156157
if !semver.IsValid(serverVersion) || !semver.IsValid(agentVersion) {
157-
return DefaultStyles.Placeholder.Render(agentVersion)
158+
return pretty.Sprint(DefaultStyles.Placeholder, agentVersion)
158159
}
159160
outdated := semver.Compare(agentVersion, serverVersion) < 0
160161
if outdated {
161-
return DefaultStyles.Warn.Render(agentVersion + " (outdated)")
162+
return pretty.Sprint(DefaultStyles.Warn, agentVersion+" (outdated)")
162163
}
163-
return DefaultStyles.Keyword.Render(agentVersion)
164+
return pretty.Sprint(DefaultStyles.Keyword, agentVersion)
164165
}

cli/create.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/google/uuid"
10+
"github.com/kr/pretty"
1011
"golang.org/x/exp/slices"
1112
"golang.org/x/xerrors"
1213

@@ -75,7 +76,7 @@ func (r *RootCmd) create() *clibase.Cmd {
7576

7677
var template codersdk.Template
7778
if templateName == "" {
78-
_, _ = fmt.Fprintln(inv.Stdout, cliui.DefaultStyles.Wrap.Render("Select a template below to preview the provisioned infrastructure:"))
79+
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "Select a template below to preview the provisioned infrastructure:"))
7980

8081
templates, err := client.TemplatesByOrganization(inv.Context(), organization.ID)
8182
if err != nil {
@@ -177,7 +178,7 @@ func (r *RootCmd) create() *clibase.Cmd {
177178
return xerrors.Errorf("watch build: %w", err)
178179
}
179180

180-
_, _ = fmt.Fprintf(inv.Stdout, "\nThe %s workspace has been created at %s!\n", cliui.DefaultStyles.Keyword.Render(workspace.Name), cliui.DefaultStyles.DateTimeStamp.Render(time.Now().Format(time.Stamp)))
181+
_, _ = fmt.Fprintf(inv.Stdout, "\nThe %s workspace has been created at %s!\n", pretty.Sprint(cliui.DefaultStyles.Keyword.Render(workspace.Name), cliui.DefaultStyles.DateTimeStamp, time.Now().Format(time.Stamp)))
181182
return nil
182183
},
183184
}

cli/delete.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/coder/coder/v2/cli/clibase"
88
"github.com/coder/coder/v2/cli/cliui"
99
"github.com/coder/coder/v2/codersdk"
10+
"github.com/kr/pretty"
1011
)
1112

1213
// nolint
@@ -54,7 +55,7 @@ func (r *RootCmd) deleteWorkspace() *clibase.Cmd {
5455
return err
5556
}
5657

57-
_, _ = fmt.Fprintf(inv.Stdout, "\n%s has been deleted at %s!\n", cliui.DefaultStyles.Keyword.Render(workspace.FullName()), cliui.DefaultStyles.DateTimeStamp.Render(time.Now().Format(time.Stamp)))
58+
_, _ = fmt.Fprintf(inv.Stdout, "\n%s has been deleted at %s!\n", pretty.Sprint(cliui.DefaultStyles.Keyword.Render(workspace.FullName()), cliui.DefaultStyles.DateTimeStamp, time.Now().Format(time.Stamp)))
5859
return nil
5960
},
6061
}

0 commit comments

Comments
 (0)