mirror of
https://github.com/coder/coder.git
synced 2026-06-05 05:58:20 +00:00
14d3e300d3
* chore: add command for showing colors * fix: use ANSI color codes instead of RGB * feat: add '--no-color' flag * fix: revert colors * chore: change colors * fix: update golden files * fix: replace blue with brightBlue * fix: drop '> ' for unfocused prompts * fix: run 'make fmt' * chore: allow disabling color with env flags * fix: apply fixes from feedback * fix: run 'make gen' * fix: refactor janky code * fix: re-add public function * fix: re-add init for non-color tests * fix: move styles to 'init' that can be * fix: stop overwriting entire DefaultStyles * fix: make code and field obey --no-color * fix: rip out '--no-color' due to race condition We still support `NO_COLOR` env variable through termenv's `EnvColorProfile`. The reason for the race condition is that `DefaultStyles` is a global that we shouldn't mutate after `init` is called, but we have to mutate it after `init` has ran to have serpent collect the cli flags and env vars for us. * fix: apply nit * fix: simplify code && hide command * fix: newline shouldn't be themed * fix: appease the linter
178 lines
3.6 KiB
Go
178 lines
3.6 KiB
Go
package cliui
|
|
|
|
import (
|
|
"flag"
|
|
"os"
|
|
"sync"
|
|
"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.Profile
|
|
colorOnce sync.Once
|
|
)
|
|
|
|
var (
|
|
// ANSI color codes
|
|
red = Color("1")
|
|
green = Color("2")
|
|
yellow = Color("3")
|
|
magenta = Color("5")
|
|
white = Color("7")
|
|
brightBlue = Color("12")
|
|
brightMagenta = Color("13")
|
|
)
|
|
|
|
// Color returns a color for the given string.
|
|
func Color(s string) termenv.Color {
|
|
colorOnce.Do(func() {
|
|
color = termenv.NewOutput(os.Stdout).EnvColorProfile()
|
|
|
|
if flag.Lookup("test.v") != nil {
|
|
// Use a consistent colorless profile in tests so that results
|
|
// are deterministic.
|
|
color = termenv.Ascii
|
|
}
|
|
})
|
|
return color.Color(s)
|
|
}
|
|
|
|
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(Color("#ED567A")),
|
|
pretty.BgColor(Color("#2C2C2C")),
|
|
},
|
|
DateTimeStamp: pretty.Style{
|
|
pretty.FgColor(brightBlue),
|
|
},
|
|
Error: pretty.Style{
|
|
pretty.FgColor(red),
|
|
},
|
|
Field: pretty.Style{
|
|
pretty.XPad(1, 1),
|
|
pretty.FgColor(Color("#FFFFFF")),
|
|
pretty.BgColor(Color("#2B2A2A")),
|
|
},
|
|
Fuchsia: pretty.Style{
|
|
pretty.FgColor(brightMagenta),
|
|
},
|
|
FocusedPrompt: pretty.Style{
|
|
pretty.FgColor(white),
|
|
pretty.Wrap("> ", ""),
|
|
pretty.FgColor(brightBlue),
|
|
},
|
|
Keyword: pretty.Style{
|
|
pretty.FgColor(green),
|
|
},
|
|
Placeholder: pretty.Style{
|
|
pretty.FgColor(magenta),
|
|
},
|
|
Prompt: pretty.Style{
|
|
pretty.FgColor(white),
|
|
pretty.Wrap(" ", ""),
|
|
},
|
|
Warn: pretty.Style{
|
|
pretty.FgColor(yellow),
|
|
},
|
|
Wrap: pretty.Style{
|
|
pretty.LineWrap(80),
|
|
},
|
|
}
|
|
}
|
|
|
|
// ValidateNotEmpty is a helper function to disallow empty inputs!
|
|
func ValidateNotEmpty(s string) error {
|
|
if s == "" {
|
|
return xerrors.New("Must be provided!")
|
|
}
|
|
return nil
|
|
}
|