Files
coder/cli/cliui/cliui.go
2024-09-24 13:16:36 +10:00

183 lines
3.7 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,
Hyperlink,
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),
},
Hyperlink: pretty.Style{
pretty.FgColor(magenta),
pretty.Underline(),
},
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
}