mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-10-04 01:37:49 +03:00
feat!: introduce color profiles and use term/ansi for styles
This introduces color profiles similar to Termenv color profiles. Plus, it switches to using Charmbracelet term & term/ansi to query terminal background color for the default renderer, and construct styles. However, it breaks the renderer API since it doesn't depend on Termenv anymore.
This commit is contained in:
parent
e87acc3c4a
commit
05affa7bd0
9
align.go
9
align.go
@ -4,13 +4,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/x/exp/term/ansi"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
// Perform text alignment. If the string is multi-lined, we also make all lines
|
||||
// the same width by padding them with spaces. If a termenv style is passed,
|
||||
// use that to style the spaces added.
|
||||
func alignTextHorizontal(str string, pos Position, width int, style *termenv.Style) string {
|
||||
// the same width by padding them with spaces. If a style is passed, use that
|
||||
// to style the spaces added.
|
||||
func alignTextHorizontal(str string, pos Position, width int, style *style) string {
|
||||
lines, widestLine := getLines(str)
|
||||
var b strings.Builder
|
||||
|
||||
@ -59,7 +58,7 @@ func alignTextHorizontal(str string, pos Position, width int, style *termenv.Sty
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func alignTextVertical(str string, pos Position, height int, _ *termenv.Style) string {
|
||||
func alignTextVertical(str string, pos Position, height int, _ *style) string {
|
||||
strHeight := strings.Count(str, "\n") + 1
|
||||
if height < strHeight {
|
||||
return str
|
||||
|
@ -6,7 +6,7 @@ package lipgloss
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var enableANSI sync.Once
|
||||
@ -17,6 +17,23 @@ var enableANSI sync.Once
|
||||
// by default.
|
||||
func enableLegacyWindowsANSI() {
|
||||
enableANSI.Do(func() {
|
||||
_, _ = termenv.EnableWindowsANSIConsole()
|
||||
handle, err := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var mode uint32
|
||||
err = windows.GetConsoleMode(handle, &mode)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// See https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
|
||||
if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
|
||||
vtpmode := mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
if err := windows.SetConsoleMode(handle, vtpmode); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/x/exp/term/ansi"
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
@ -407,13 +406,13 @@ func (s Style) styleBorder(border string, fg, bg TerminalColor) string {
|
||||
return border
|
||||
}
|
||||
|
||||
style := termenv.Style{}
|
||||
style := s.r.ColorProfile().string()
|
||||
|
||||
if fg != noColor {
|
||||
style = style.Foreground(fg.color(s.r))
|
||||
style = style.ForegroundColor(fg.color(s.r))
|
||||
}
|
||||
if bg != noColor {
|
||||
style = style.Background(bg.color(s.r))
|
||||
style = style.BackgroundColor(bg.color(s.r))
|
||||
}
|
||||
|
||||
return style.Styled(border)
|
||||
|
49
color.go
49
color.go
@ -3,12 +3,13 @@ package lipgloss
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/charmbracelet/x/exp/term/ansi"
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
)
|
||||
|
||||
// TerminalColor is a color intended to be rendered in the terminal.
|
||||
type TerminalColor interface {
|
||||
color(*Renderer) termenv.Color
|
||||
color(*Renderer) ansi.Color
|
||||
RGBA() (r, g, b, a uint32)
|
||||
}
|
||||
|
||||
@ -23,8 +24,8 @@ var noColor = NoColor{}
|
||||
// var style = someStyle.Copy().Background(lipgloss.NoColor{})
|
||||
type NoColor struct{}
|
||||
|
||||
func (NoColor) color(*Renderer) termenv.Color {
|
||||
return termenv.NoColor{}
|
||||
func (NoColor) color(*Renderer) ansi.Color {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RGBA returns the RGBA value of this color. Because we have to return
|
||||
@ -44,8 +45,8 @@ func (n NoColor) RGBA() (r, g, b, a uint32) {
|
||||
// hexColor := lipgloss.Color("#0000ff")
|
||||
type Color string
|
||||
|
||||
func (c Color) color(r *Renderer) termenv.Color {
|
||||
return r.ColorProfile().Color(string(c))
|
||||
func (c Color) color(r *Renderer) ansi.Color {
|
||||
return r.ColorProfile().color(string(c))
|
||||
}
|
||||
|
||||
// RGBA returns the RGBA value of this color. This satisfies the Go Color
|
||||
@ -55,7 +56,7 @@ func (c Color) color(r *Renderer) termenv.Color {
|
||||
//
|
||||
// Deprecated.
|
||||
func (c Color) RGBA() (r, g, b, a uint32) {
|
||||
return termenv.ConvertToRGB(c.color(renderer)).RGBA()
|
||||
return c.color(DefaultRenderer()).RGBA()
|
||||
}
|
||||
|
||||
// ANSIColor is a color specified by an ANSI color value. It's merely syntactic
|
||||
@ -69,7 +70,7 @@ func (c Color) RGBA() (r, g, b, a uint32) {
|
||||
// colorB := lipgloss.Color("21")
|
||||
type ANSIColor uint
|
||||
|
||||
func (ac ANSIColor) color(r *Renderer) termenv.Color {
|
||||
func (ac ANSIColor) color(r *Renderer) ansi.Color {
|
||||
return Color(strconv.FormatUint(uint64(ac), 10)).color(r)
|
||||
}
|
||||
|
||||
@ -96,7 +97,7 @@ type AdaptiveColor struct {
|
||||
Dark string
|
||||
}
|
||||
|
||||
func (ac AdaptiveColor) color(r *Renderer) termenv.Color {
|
||||
func (ac AdaptiveColor) color(r *Renderer) ansi.Color {
|
||||
if r.HasDarkBackground() {
|
||||
return Color(ac.Dark).color(r)
|
||||
}
|
||||
@ -110,7 +111,7 @@ func (ac AdaptiveColor) color(r *Renderer) termenv.Color {
|
||||
//
|
||||
// Deprecated.
|
||||
func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) {
|
||||
return termenv.ConvertToRGB(ac.color(renderer)).RGBA()
|
||||
return ac.color(DefaultRenderer()).RGBA()
|
||||
}
|
||||
|
||||
// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color
|
||||
@ -121,17 +122,17 @@ type CompleteColor struct {
|
||||
ANSI string
|
||||
}
|
||||
|
||||
func (c CompleteColor) color(r *Renderer) termenv.Color {
|
||||
func (c CompleteColor) color(r *Renderer) ansi.Color {
|
||||
p := r.ColorProfile()
|
||||
switch p { //nolint:exhaustive
|
||||
case termenv.TrueColor:
|
||||
return p.Color(c.TrueColor)
|
||||
case termenv.ANSI256:
|
||||
return p.Color(c.ANSI256)
|
||||
case termenv.ANSI:
|
||||
return p.Color(c.ANSI)
|
||||
case TrueColor:
|
||||
return p.color(c.TrueColor)
|
||||
case ANSI256:
|
||||
return p.color(c.ANSI256)
|
||||
case ANSI:
|
||||
return p.color(c.ANSI)
|
||||
default:
|
||||
return termenv.NoColor{}
|
||||
return NoColor{}
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +144,7 @@ func (c CompleteColor) color(r *Renderer) termenv.Color {
|
||||
//
|
||||
// Deprecated.
|
||||
func (c CompleteColor) RGBA() (r, g, b, a uint32) {
|
||||
return termenv.ConvertToRGB(c.color(renderer)).RGBA()
|
||||
return c.color(DefaultRenderer()).RGBA()
|
||||
}
|
||||
|
||||
// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
|
||||
@ -154,7 +155,7 @@ type CompleteAdaptiveColor struct {
|
||||
Dark CompleteColor
|
||||
}
|
||||
|
||||
func (cac CompleteAdaptiveColor) color(r *Renderer) termenv.Color {
|
||||
func (cac CompleteAdaptiveColor) color(r *Renderer) ansi.Color {
|
||||
if r.HasDarkBackground() {
|
||||
return cac.Dark.color(r)
|
||||
}
|
||||
@ -168,5 +169,11 @@ func (cac CompleteAdaptiveColor) color(r *Renderer) termenv.Color {
|
||||
//
|
||||
// Deprecated.
|
||||
func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) {
|
||||
return termenv.ConvertToRGB(cac.color(renderer)).RGBA()
|
||||
return cac.color(DefaultRenderer()).RGBA()
|
||||
}
|
||||
|
||||
// ConvertToRGB converts a Color to a colorful.Color.
|
||||
func ConvertToRGB(c ansi.Color) colorful.Color {
|
||||
ch, _ := colorful.MakeColor(c)
|
||||
return ch
|
||||
}
|
||||
|
@ -3,38 +3,36 @@ package lipgloss
|
||||
import (
|
||||
"image/color"
|
||||
"testing"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
func TestSetColorProfile(t *testing.T) {
|
||||
r := renderer
|
||||
r := DefaultRenderer()
|
||||
input := "hello"
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
profile termenv.Profile
|
||||
profile Profile
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"ascii",
|
||||
termenv.Ascii,
|
||||
Ascii,
|
||||
"hello",
|
||||
},
|
||||
{
|
||||
"ansi",
|
||||
termenv.ANSI,
|
||||
"\x1b[94mhello\x1b[0m",
|
||||
ANSI,
|
||||
"\x1b[94mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
"ansi256",
|
||||
termenv.ANSI256,
|
||||
"\x1b[38;5;62mhello\x1b[0m",
|
||||
ANSI256,
|
||||
"\x1b[38;5;62mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
"truecolor",
|
||||
termenv.TrueColor,
|
||||
"\x1b[38;2;89;86;224mhello\x1b[0m",
|
||||
TrueColor,
|
||||
"\x1b[38;2;89;86;224mhello\x1b[m",
|
||||
},
|
||||
}
|
||||
|
||||
@ -89,76 +87,76 @@ func TestHexToColor(t *testing.T) {
|
||||
|
||||
func TestRGBA(t *testing.T) {
|
||||
tt := []struct {
|
||||
profile termenv.Profile
|
||||
profile Profile
|
||||
darkBg bool
|
||||
input TerminalColor
|
||||
expected uint
|
||||
}{
|
||||
// lipgloss.Color
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
Color("#FF0000"),
|
||||
0xFF0000,
|
||||
},
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
Color("9"),
|
||||
0xFF0000,
|
||||
},
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
Color("21"),
|
||||
0x0000FF,
|
||||
},
|
||||
// lipgloss.AdaptiveColor
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
AdaptiveColor{Light: "#0000FF", Dark: "#FF0000"},
|
||||
0xFF0000,
|
||||
},
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
false,
|
||||
AdaptiveColor{Light: "#0000FF", Dark: "#FF0000"},
|
||||
0x0000FF,
|
||||
},
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
AdaptiveColor{Light: "21", Dark: "9"},
|
||||
0xFF0000,
|
||||
},
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
false,
|
||||
AdaptiveColor{Light: "21", Dark: "9"},
|
||||
0x0000FF,
|
||||
},
|
||||
// lipgloss.CompleteColor
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"},
|
||||
0xFF0000,
|
||||
},
|
||||
{
|
||||
termenv.ANSI256,
|
||||
ANSI256,
|
||||
true,
|
||||
CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"},
|
||||
0xFFFFFF,
|
||||
},
|
||||
{
|
||||
termenv.ANSI,
|
||||
ANSI,
|
||||
true,
|
||||
CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"},
|
||||
0x0000FF,
|
||||
},
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
CompleteColor{TrueColor: "", ANSI256: "231", ANSI: "12"},
|
||||
0x000000,
|
||||
@ -166,7 +164,7 @@ func TestRGBA(t *testing.T) {
|
||||
// lipgloss.CompleteAdaptiveColor
|
||||
// dark
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
true,
|
||||
CompleteAdaptiveColor{
|
||||
Light: CompleteColor{TrueColor: "#0000FF", ANSI256: "231", ANSI: "12"},
|
||||
@ -175,7 +173,7 @@ func TestRGBA(t *testing.T) {
|
||||
0xFF0000,
|
||||
},
|
||||
{
|
||||
termenv.ANSI256,
|
||||
ANSI256,
|
||||
true,
|
||||
CompleteAdaptiveColor{
|
||||
Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "21", ANSI: "12"},
|
||||
@ -184,7 +182,7 @@ func TestRGBA(t *testing.T) {
|
||||
0xFFFFFF,
|
||||
},
|
||||
{
|
||||
termenv.ANSI,
|
||||
ANSI,
|
||||
true,
|
||||
CompleteAdaptiveColor{
|
||||
Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "9"},
|
||||
@ -194,7 +192,7 @@ func TestRGBA(t *testing.T) {
|
||||
},
|
||||
// light
|
||||
{
|
||||
termenv.TrueColor,
|
||||
TrueColor,
|
||||
false,
|
||||
CompleteAdaptiveColor{
|
||||
Light: CompleteColor{TrueColor: "#0000FF", ANSI256: "231", ANSI: "12"},
|
||||
@ -203,7 +201,7 @@ func TestRGBA(t *testing.T) {
|
||||
0x0000FF,
|
||||
},
|
||||
{
|
||||
termenv.ANSI256,
|
||||
ANSI256,
|
||||
false,
|
||||
CompleteAdaptiveColor{
|
||||
Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "21", ANSI: "12"},
|
||||
@ -212,7 +210,7 @@ func TestRGBA(t *testing.T) {
|
||||
0x0000FF,
|
||||
},
|
||||
{
|
||||
termenv.ANSI,
|
||||
ANSI,
|
||||
false,
|
||||
CompleteAdaptiveColor{
|
||||
Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "9"},
|
||||
|
120
env.go
Normal file
120
env.go
Normal file
@ -0,0 +1,120 @@
|
||||
package lipgloss
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// envNoColor returns true if the environment variables explicitly disable color output
|
||||
// by setting NO_COLOR (https://no-color.org/)
|
||||
// or CLICOLOR/CLICOLOR_FORCE (https://bixense.com/clicolors/)
|
||||
// If NO_COLOR is set, this will return true, ignoring CLICOLOR/CLICOLOR_FORCE
|
||||
// If CLICOLOR=="0", it will be true only if CLICOLOR_FORCE is also "0" or is unset.
|
||||
func (o *Renderer) envNoColor() bool {
|
||||
return o.environ["NO_COLOR"] != "" || (o.environ["CLICOLOR"] == "0" && !o.cliColorForced())
|
||||
}
|
||||
|
||||
// envColorProfile returns the color profile based on environment variables set
|
||||
// Supports NO_COLOR (https://no-color.org/)
|
||||
// and CLICOLOR/CLICOLOR_FORCE (https://bixense.com/clicolors/)
|
||||
// If none of these environment variables are set, this behaves the same as ColorProfile()
|
||||
// It will return the Ascii color profile if EnvNoColor() returns true
|
||||
// If the terminal does not support any colors, but CLICOLOR_FORCE is set and not "0"
|
||||
// then the ANSI color profile will be returned.
|
||||
func (o *Renderer) envColorProfile() Profile {
|
||||
if o.envNoColor() {
|
||||
return Ascii
|
||||
}
|
||||
p := o.detectColorProfile()
|
||||
if o.cliColorForced() && p == Ascii {
|
||||
return ANSI
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (o *Renderer) cliColorForced() bool {
|
||||
if forced := o.environ["CLICOLOR_FORCE"]; forced != "" {
|
||||
return !isTrue(forced)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// detectColorProfile returns the supported color profile:
|
||||
// Ascii, ANSI, ANSI256, or TrueColor.
|
||||
func (o *Renderer) detectColorProfile() (p Profile) {
|
||||
if !o.isatty {
|
||||
return Ascii
|
||||
}
|
||||
|
||||
setProfile := func(profile Profile) {
|
||||
if profile > p {
|
||||
p = profile
|
||||
}
|
||||
}
|
||||
|
||||
if isTrue(o.environ["GOOGLE_CLOUD_SHELL"]) {
|
||||
setProfile(TrueColor)
|
||||
}
|
||||
|
||||
term := o.environ["TERM"]
|
||||
colorTerm := o.environ["COLORTERM"]
|
||||
|
||||
switch strings.ToLower(colorTerm) {
|
||||
case "24bit":
|
||||
fallthrough
|
||||
case "truecolor":
|
||||
if strings.HasPrefix(term, "screen") {
|
||||
// tmux supports TrueColor, screen only ANSI256
|
||||
if o.environ["TERM_PROGRAM"] != "tmux" {
|
||||
setProfile(ANSI256)
|
||||
}
|
||||
}
|
||||
setProfile(TrueColor)
|
||||
case "yes":
|
||||
fallthrough
|
||||
case "true":
|
||||
setProfile(TrueColor)
|
||||
}
|
||||
|
||||
switch term {
|
||||
case "xterm-kitty", "wezterm", "xterm-ghostty":
|
||||
setProfile(TrueColor)
|
||||
case "linux":
|
||||
setProfile(ANSI)
|
||||
}
|
||||
|
||||
if strings.Contains(term, "256color") {
|
||||
setProfile(ANSI256)
|
||||
}
|
||||
if strings.Contains(term, "color") {
|
||||
setProfile(ANSI)
|
||||
}
|
||||
if strings.Contains(term, "ansi") {
|
||||
setProfile(ANSI)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// isTrue returns true if the string is a truthy value.
|
||||
func isTrue(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
v, _ := strconv.ParseBool(strings.ToLower(s))
|
||||
return v
|
||||
}
|
||||
|
||||
// environMap converts an environment slice to a map.
|
||||
func environMap(environ []string) map[string]string {
|
||||
m := make(map[string]string, len(environ))
|
||||
for _, e := range environ {
|
||||
parts := strings.SplitN(e, "=", 2)
|
||||
var value string
|
||||
if len(parts) == 2 {
|
||||
value = parts[1]
|
||||
}
|
||||
m[parts[0]] = value
|
||||
}
|
||||
return m
|
||||
}
|
@ -20,10 +20,13 @@ require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/caarlos0/sshmarshal v0.1.0 // indirect
|
||||
github.com/charmbracelet/keygen v0.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
)
|
||||
|
@ -17,6 +17,7 @@ github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBER
|
||||
github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg=
|
||||
github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240422203001-5cc5941b761c h1:MF4XzYBazvaM6g2IlOwgnsIuteW5q8tRfldetAHk2yg=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240422203001-5cc5941b761c/go.mod h1:yQqGHmheaQfkqiJWjklPHVAq1dKbk8uGbcoS/lcKCJ0=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
@ -24,6 +25,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
@ -54,17 +56,16 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
@ -86,6 +87,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@ -94,23 +96,19 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU=
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -129,32 +127,25 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
13
go.mod
13
go.mod
@ -5,15 +5,14 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze.
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240422203001-5cc5941b761c
|
||||
github.com/muesli/termenv v0.15.2
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/rivo/uniseg v0.4.7
|
||||
golang.org/x/sys v0.18.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
)
|
||||
|
26
go.sum
26
go.sum
@ -1,18 +1,16 @@
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240422203001-5cc5941b761c h1:MF4XzYBazvaM6g2IlOwgnsIuteW5q8tRfldetAHk2yg=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240422203001-5cc5941b761c/go.mod h1:yQqGHmheaQfkqiJWjklPHVAq1dKbk8uGbcoS/lcKCJ0=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd h1:HqBjkSFXXfW4IgX3TMKipWoPEN08T3Pi4SA/3DLss/U=
|
||||
github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
@ -34,7 +34,7 @@ const (
|
||||
// Place places a string or text block vertically in an unstyled box of a given
|
||||
// width or height.
|
||||
func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
|
||||
return renderer.Place(width, height, hPos, vPos, str, opts...)
|
||||
return DefaultRenderer().Place(width, height, hPos, vPos, str, opts...)
|
||||
}
|
||||
|
||||
// Place places a string or text block vertically in an unstyled box of a given
|
||||
@ -47,7 +47,7 @@ func (r *Renderer) Place(width, height int, hPos, vPos Position, str string, opt
|
||||
// block of a given width. If the given width is shorter than the max width of
|
||||
// the string (measured by its longest line) this will be a noop.
|
||||
func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
|
||||
return renderer.PlaceHorizontal(width, pos, str, opts...)
|
||||
return DefaultRenderer().PlaceHorizontal(width, pos, str, opts...)
|
||||
}
|
||||
|
||||
// PlaceHorizontal places a string or text block horizontally in an unstyled
|
||||
@ -101,7 +101,7 @@ func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...
|
||||
// of a given height. If the given height is shorter than the height of the
|
||||
// string (measured by its newlines) then this will be a noop.
|
||||
func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
|
||||
return renderer.PlaceVertical(height, pos, str, opts...)
|
||||
return DefaultRenderer().PlaceVertical(height, pos, str, opts...)
|
||||
}
|
||||
|
||||
// PlaceVertical places a string or text block vertically in an unstyled block
|
||||
|
417
profile.go
Normal file
417
profile.go
Normal file
@ -0,0 +1,417 @@
|
||||
package lipgloss
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/x/exp/term/ansi"
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
)
|
||||
|
||||
// Profile is a color profile: Ascii, ANSI, ANSI256, or TrueColor.
|
||||
type Profile int
|
||||
|
||||
const (
|
||||
// Ascii, uncolored profile
|
||||
Ascii Profile = iota //nolint:revive
|
||||
// ANSI, 4-bit color profile
|
||||
ANSI
|
||||
// ANSI256, 8-bit color profile
|
||||
ANSI256
|
||||
// TrueColor, 24-bit color profile
|
||||
TrueColor
|
||||
)
|
||||
|
||||
func (p Profile) string() style {
|
||||
return style{Profile: p}
|
||||
}
|
||||
|
||||
// convert transforms a given Color to a Color supported within the Profile.
|
||||
func (p Profile) convert(c ansi.Color) ansi.Color {
|
||||
if p == Ascii {
|
||||
return NoColor{}
|
||||
}
|
||||
|
||||
switch v := c.(type) {
|
||||
case ansi.BasicColor:
|
||||
return v
|
||||
|
||||
case ansi.ExtendedColor:
|
||||
if p == ANSI {
|
||||
return ansi256ToANSIColor(v)
|
||||
}
|
||||
return v
|
||||
|
||||
case ansi.TrueColor, color.Color:
|
||||
h, ok := colorful.MakeColor(v)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if p != TrueColor {
|
||||
ac := hexToANSI256Color(h)
|
||||
if p == ANSI {
|
||||
return ansi256ToANSIColor(ac)
|
||||
}
|
||||
return ac
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// color creates a color from a string. Valid inputs are hex colors, as well as
|
||||
// ANSI color codes (0-15, 16-255).
|
||||
func (p Profile) color(s string) ansi.Color {
|
||||
if len(s) == 0 {
|
||||
return color.Black
|
||||
}
|
||||
|
||||
var c ansi.Color
|
||||
if strings.HasPrefix(s, "#") {
|
||||
h, err := colorful.Hex(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
tc := uint32(h.R*255)<<16 + uint32(h.G*255)<<8 + uint32(h.B*255)
|
||||
c = ansi.TrueColor(tc)
|
||||
} else {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i < 16 {
|
||||
c = ansi.BasicColor(i)
|
||||
} else {
|
||||
c = ansi.ExtendedColor(i)
|
||||
}
|
||||
}
|
||||
|
||||
return p.convert(c)
|
||||
}
|
||||
|
||||
func hexToANSI256Color(c colorful.Color) ansi.ExtendedColor {
|
||||
v2ci := func(v float64) int {
|
||||
if v < 48 {
|
||||
return 0
|
||||
}
|
||||
if v < 115 {
|
||||
return 1
|
||||
}
|
||||
return int((v - 35) / 40)
|
||||
}
|
||||
|
||||
// Calculate the nearest 0-based color index at 16..231
|
||||
r := v2ci(c.R * 255.0) // 0..5 each
|
||||
g := v2ci(c.G * 255.0)
|
||||
b := v2ci(c.B * 255.0)
|
||||
ci := 36*r + 6*g + b /* 0..215 */
|
||||
|
||||
// Calculate the represented colors back from the index
|
||||
i2cv := [6]int{0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
|
||||
cr := i2cv[r] // r/g/b, 0..255 each
|
||||
cg := i2cv[g]
|
||||
cb := i2cv[b]
|
||||
|
||||
// Calculate the nearest 0-based gray index at 232..255
|
||||
var grayIdx int
|
||||
average := (r + g + b) / 3
|
||||
if average > 238 {
|
||||
grayIdx = 23
|
||||
} else {
|
||||
grayIdx = (average - 3) / 10 // 0..23
|
||||
}
|
||||
gv := 8 + 10*grayIdx // same value for r/g/b, 0..255
|
||||
|
||||
// Return the one which is nearer to the original input rgb value
|
||||
c2 := colorful.Color{R: float64(cr) / 255.0, G: float64(cg) / 255.0, B: float64(cb) / 255.0}
|
||||
g2 := colorful.Color{R: float64(gv) / 255.0, G: float64(gv) / 255.0, B: float64(gv) / 255.0}
|
||||
colorDist := c.DistanceHSLuv(c2)
|
||||
grayDist := c.DistanceHSLuv(g2)
|
||||
|
||||
if colorDist <= grayDist {
|
||||
return ansi.ExtendedColor(16 + ci)
|
||||
}
|
||||
return ansi.ExtendedColor(232 + grayIdx)
|
||||
}
|
||||
|
||||
func ansi256ToANSIColor(c ansi.ExtendedColor) ansi.BasicColor {
|
||||
var r int
|
||||
md := math.MaxFloat64
|
||||
|
||||
h, _ := colorful.Hex(ansiHex[c])
|
||||
for i := 0; i <= 15; i++ {
|
||||
hb, _ := colorful.Hex(ansiHex[i])
|
||||
d := h.DistanceHSLuv(hb)
|
||||
|
||||
if d < md {
|
||||
md = d
|
||||
r = i
|
||||
}
|
||||
}
|
||||
|
||||
return ansi.BasicColor(r)
|
||||
}
|
||||
|
||||
// RGB values of ANSI colors (0-255).
|
||||
var ansiHex = []string{
|
||||
"#000000",
|
||||
"#800000",
|
||||
"#008000",
|
||||
"#808000",
|
||||
"#000080",
|
||||
"#800080",
|
||||
"#008080",
|
||||
"#c0c0c0",
|
||||
"#808080",
|
||||
"#ff0000",
|
||||
"#00ff00",
|
||||
"#ffff00",
|
||||
"#0000ff",
|
||||
"#ff00ff",
|
||||
"#00ffff",
|
||||
"#ffffff",
|
||||
"#000000",
|
||||
"#00005f",
|
||||
"#000087",
|
||||
"#0000af",
|
||||
"#0000d7",
|
||||
"#0000ff",
|
||||
"#005f00",
|
||||
"#005f5f",
|
||||
"#005f87",
|
||||
"#005faf",
|
||||
"#005fd7",
|
||||
"#005fff",
|
||||
"#008700",
|
||||
"#00875f",
|
||||
"#008787",
|
||||
"#0087af",
|
||||
"#0087d7",
|
||||
"#0087ff",
|
||||
"#00af00",
|
||||
"#00af5f",
|
||||
"#00af87",
|
||||
"#00afaf",
|
||||
"#00afd7",
|
||||
"#00afff",
|
||||
"#00d700",
|
||||
"#00d75f",
|
||||
"#00d787",
|
||||
"#00d7af",
|
||||
"#00d7d7",
|
||||
"#00d7ff",
|
||||
"#00ff00",
|
||||
"#00ff5f",
|
||||
"#00ff87",
|
||||
"#00ffaf",
|
||||
"#00ffd7",
|
||||
"#00ffff",
|
||||
"#5f0000",
|
||||
"#5f005f",
|
||||
"#5f0087",
|
||||
"#5f00af",
|
||||
"#5f00d7",
|
||||
"#5f00ff",
|
||||
"#5f5f00",
|
||||
"#5f5f5f",
|
||||
"#5f5f87",
|
||||
"#5f5faf",
|
||||
"#5f5fd7",
|
||||
"#5f5fff",
|
||||
"#5f8700",
|
||||
"#5f875f",
|
||||
"#5f8787",
|
||||
"#5f87af",
|
||||
"#5f87d7",
|
||||
"#5f87ff",
|
||||
"#5faf00",
|
||||
"#5faf5f",
|
||||
"#5faf87",
|
||||
"#5fafaf",
|
||||
"#5fafd7",
|
||||
"#5fafff",
|
||||
"#5fd700",
|
||||
"#5fd75f",
|
||||
"#5fd787",
|
||||
"#5fd7af",
|
||||
"#5fd7d7",
|
||||
"#5fd7ff",
|
||||
"#5fff00",
|
||||
"#5fff5f",
|
||||
"#5fff87",
|
||||
"#5fffaf",
|
||||
"#5fffd7",
|
||||
"#5fffff",
|
||||
"#870000",
|
||||
"#87005f",
|
||||
"#870087",
|
||||
"#8700af",
|
||||
"#8700d7",
|
||||
"#8700ff",
|
||||
"#875f00",
|
||||
"#875f5f",
|
||||
"#875f87",
|
||||
"#875faf",
|
||||
"#875fd7",
|
||||
"#875fff",
|
||||
"#878700",
|
||||
"#87875f",
|
||||
"#878787",
|
||||
"#8787af",
|
||||
"#8787d7",
|
||||
"#8787ff",
|
||||
"#87af00",
|
||||
"#87af5f",
|
||||
"#87af87",
|
||||
"#87afaf",
|
||||
"#87afd7",
|
||||
"#87afff",
|
||||
"#87d700",
|
||||
"#87d75f",
|
||||
"#87d787",
|
||||
"#87d7af",
|
||||
"#87d7d7",
|
||||
"#87d7ff",
|
||||
"#87ff00",
|
||||
"#87ff5f",
|
||||
"#87ff87",
|
||||
"#87ffaf",
|
||||
"#87ffd7",
|
||||
"#87ffff",
|
||||
"#af0000",
|
||||
"#af005f",
|
||||
"#af0087",
|
||||
"#af00af",
|
||||
"#af00d7",
|
||||
"#af00ff",
|
||||
"#af5f00",
|
||||
"#af5f5f",
|
||||
"#af5f87",
|
||||
"#af5faf",
|
||||
"#af5fd7",
|
||||
"#af5fff",
|
||||
"#af8700",
|
||||
"#af875f",
|
||||
"#af8787",
|
||||
"#af87af",
|
||||
"#af87d7",
|
||||
"#af87ff",
|
||||
"#afaf00",
|
||||
"#afaf5f",
|
||||
"#afaf87",
|
||||
"#afafaf",
|
||||
"#afafd7",
|
||||
"#afafff",
|
||||
"#afd700",
|
||||
"#afd75f",
|
||||
"#afd787",
|
||||
"#afd7af",
|
||||
"#afd7d7",
|
||||
"#afd7ff",
|
||||
"#afff00",
|
||||
"#afff5f",
|
||||
"#afff87",
|
||||
"#afffaf",
|
||||
"#afffd7",
|
||||
"#afffff",
|
||||
"#d70000",
|
||||
"#d7005f",
|
||||
"#d70087",
|
||||
"#d700af",
|
||||
"#d700d7",
|
||||
"#d700ff",
|
||||
"#d75f00",
|
||||
"#d75f5f",
|
||||
"#d75f87",
|
||||
"#d75faf",
|
||||
"#d75fd7",
|
||||
"#d75fff",
|
||||
"#d78700",
|
||||
"#d7875f",
|
||||
"#d78787",
|
||||
"#d787af",
|
||||
"#d787d7",
|
||||
"#d787ff",
|
||||
"#d7af00",
|
||||
"#d7af5f",
|
||||
"#d7af87",
|
||||
"#d7afaf",
|
||||
"#d7afd7",
|
||||
"#d7afff",
|
||||
"#d7d700",
|
||||
"#d7d75f",
|
||||
"#d7d787",
|
||||
"#d7d7af",
|
||||
"#d7d7d7",
|
||||
"#d7d7ff",
|
||||
"#d7ff00",
|
||||
"#d7ff5f",
|
||||
"#d7ff87",
|
||||
"#d7ffaf",
|
||||
"#d7ffd7",
|
||||
"#d7ffff",
|
||||
"#ff0000",
|
||||
"#ff005f",
|
||||
"#ff0087",
|
||||
"#ff00af",
|
||||
"#ff00d7",
|
||||
"#ff00ff",
|
||||
"#ff5f00",
|
||||
"#ff5f5f",
|
||||
"#ff5f87",
|
||||
"#ff5faf",
|
||||
"#ff5fd7",
|
||||
"#ff5fff",
|
||||
"#ff8700",
|
||||
"#ff875f",
|
||||
"#ff8787",
|
||||
"#ff87af",
|
||||
"#ff87d7",
|
||||
"#ff87ff",
|
||||
"#ffaf00",
|
||||
"#ffaf5f",
|
||||
"#ffaf87",
|
||||
"#ffafaf",
|
||||
"#ffafd7",
|
||||
"#ffafff",
|
||||
"#ffd700",
|
||||
"#ffd75f",
|
||||
"#ffd787",
|
||||
"#ffd7af",
|
||||
"#ffd7d7",
|
||||
"#ffd7ff",
|
||||
"#ffff00",
|
||||
"#ffff5f",
|
||||
"#ffff87",
|
||||
"#ffffaf",
|
||||
"#ffffd7",
|
||||
"#ffffff",
|
||||
"#080808",
|
||||
"#121212",
|
||||
"#1c1c1c",
|
||||
"#262626",
|
||||
"#303030",
|
||||
"#3a3a3a",
|
||||
"#444444",
|
||||
"#4e4e4e",
|
||||
"#585858",
|
||||
"#626262",
|
||||
"#6c6c6c",
|
||||
"#767676",
|
||||
"#808080",
|
||||
"#8a8a8a",
|
||||
"#949494",
|
||||
"#9e9e9e",
|
||||
"#a8a8a8",
|
||||
"#b2b2b2",
|
||||
"#bcbcbc",
|
||||
"#c6c6c6",
|
||||
"#d0d0d0",
|
||||
"#dadada",
|
||||
"#e4e4e4",
|
||||
"#eeeeee",
|
||||
}
|
164
renderer.go
164
renderer.go
@ -1,31 +1,27 @@
|
||||
package lipgloss
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/charmbracelet/x/exp/term"
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
)
|
||||
|
||||
// We're manually creating the struct here to avoid initializing the output and
|
||||
// query the terminal multiple times.
|
||||
var renderer = &Renderer{
|
||||
output: termenv.DefaultOutput(),
|
||||
}
|
||||
var (
|
||||
renderer *Renderer
|
||||
rendererOnce sync.Once
|
||||
)
|
||||
|
||||
// Renderer is a lipgloss terminal renderer.
|
||||
type Renderer struct {
|
||||
output *termenv.Output
|
||||
colorProfile termenv.Profile
|
||||
environ map[string]string
|
||||
colorProfile Profile
|
||||
mtx sync.RWMutex
|
||||
hasDarkBackground bool
|
||||
|
||||
getColorProfile sync.Once
|
||||
explicitColorProfile bool
|
||||
|
||||
getBackgroundColor sync.Once
|
||||
explicitBackgroundColor bool
|
||||
|
||||
mtx sync.RWMutex
|
||||
isatty bool
|
||||
}
|
||||
|
||||
// RendererOption is a function that can be used to configure a [Renderer].
|
||||
@ -33,6 +29,32 @@ type RendererOption func(r *Renderer)
|
||||
|
||||
// DefaultRenderer returns the default renderer.
|
||||
func DefaultRenderer() *Renderer {
|
||||
rendererOnce.Do(func() {
|
||||
if renderer != nil {
|
||||
// Alredy set by SetDefaultRenderer
|
||||
return
|
||||
}
|
||||
hasDarkBackground := true // Assume dark background by default
|
||||
isatty := term.IsTerminal(os.Stdout.Fd())
|
||||
if isatty {
|
||||
if bg := term.BackgroundColor(os.Stdin, os.Stdout); bg != nil {
|
||||
c, ok := colorful.MakeColor(bg)
|
||||
if ok {
|
||||
_, _, l := c.Hsl()
|
||||
hasDarkBackground = l < 0.5
|
||||
}
|
||||
}
|
||||
|
||||
// Enable support for ANSI on the legacy Windows cmd.exe console. This is a
|
||||
// no-op on non-Windows systems and on Windows runs only once.
|
||||
// When using a custom renderer, this should be called manually.
|
||||
enableLegacyWindowsANSI()
|
||||
}
|
||||
// we already know whether the terminal isatty and we want to use
|
||||
// os.Environ() by default
|
||||
renderer = NewRenderer(nil, nil, hasDarkBackground)
|
||||
renderer.SetIsTerminal(isatty)
|
||||
})
|
||||
return renderer
|
||||
}
|
||||
|
||||
@ -43,47 +65,63 @@ func SetDefaultRenderer(r *Renderer) {
|
||||
|
||||
// NewRenderer creates a new Renderer.
|
||||
//
|
||||
// w will be used to determine the terminal's color capabilities.
|
||||
func NewRenderer(w io.Writer, opts ...termenv.OutputOption) *Renderer {
|
||||
// The stdout argument is used to detect if the renderer is writing to a
|
||||
// terminal. If it is nil, the renderer will assume it's not writing to a
|
||||
// terminal.
|
||||
// The environ argument is used to detect the color profile based on the
|
||||
// environment variables. If it's nil, os.Environ() will be used.
|
||||
// Set hasDarkBackground to true if the terminal has a dark background.
|
||||
func NewRenderer(stdout *os.File, environ []string, hasDarkBackground bool) *Renderer {
|
||||
r := &Renderer{
|
||||
output: termenv.NewOutput(w, opts...),
|
||||
hasDarkBackground: hasDarkBackground,
|
||||
}
|
||||
r.isatty = stdout != nil && term.IsTerminal(stdout.Fd())
|
||||
if environ == nil {
|
||||
environ = os.Environ()
|
||||
}
|
||||
r.environ = environMap(environ)
|
||||
r.colorProfile = r.envColorProfile()
|
||||
return r
|
||||
}
|
||||
|
||||
// Output returns the termenv output.
|
||||
func (r *Renderer) Output() *termenv.Output {
|
||||
// ColorProfile returns the detected color profile.
|
||||
func (r *Renderer) ColorProfile() Profile {
|
||||
r.mtx.RLock()
|
||||
defer r.mtx.RUnlock()
|
||||
return r.output
|
||||
}
|
||||
|
||||
// SetOutput sets the termenv output.
|
||||
func (r *Renderer) SetOutput(o *termenv.Output) {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
r.output = o
|
||||
}
|
||||
|
||||
// ColorProfile returns the detected termenv color profile.
|
||||
func (r *Renderer) ColorProfile() termenv.Profile {
|
||||
r.mtx.RLock()
|
||||
defer r.mtx.RUnlock()
|
||||
|
||||
if !r.explicitColorProfile {
|
||||
r.getColorProfile.Do(func() {
|
||||
// NOTE: we don't need to lock here because sync.Once provides its
|
||||
// own locking mechanism.
|
||||
r.colorProfile = r.output.EnvColorProfile()
|
||||
})
|
||||
}
|
||||
|
||||
return r.colorProfile
|
||||
}
|
||||
|
||||
// ColorProfile returns the detected termenv color profile.
|
||||
func ColorProfile() termenv.Profile {
|
||||
return renderer.ColorProfile()
|
||||
// ColorProfile returns the detected color profile.
|
||||
func ColorProfile() Profile {
|
||||
return DefaultRenderer().ColorProfile()
|
||||
}
|
||||
|
||||
// IsTerminal returns whether or not the renderer is thinking it's writing to a
|
||||
// terminal.
|
||||
func (r *Renderer) IsTerminal() bool {
|
||||
r.mtx.RLock()
|
||||
defer r.mtx.RUnlock()
|
||||
|
||||
return r.isatty
|
||||
}
|
||||
|
||||
// IsTerminal returns whether or not the default renderer is thinking it's
|
||||
// writing to a terminal.
|
||||
func IsTerminal() bool {
|
||||
return DefaultRenderer().IsTerminal()
|
||||
}
|
||||
|
||||
// SetIsTerminal sets whether or not the renderer is writing to a terminal.
|
||||
func (r *Renderer) SetIsTerminal(b bool) {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
r.isatty = b
|
||||
}
|
||||
|
||||
// SetIsTerminal sets whether or not the renderer is writing to a terminal.
|
||||
func SetIsTerminal(b bool) {
|
||||
DefaultRenderer().SetIsTerminal(b)
|
||||
}
|
||||
|
||||
// SetColorProfile sets the color profile on the renderer. This function exists
|
||||
@ -96,18 +134,17 @@ func ColorProfile() termenv.Profile {
|
||||
//
|
||||
// Available color profiles are:
|
||||
//
|
||||
// termenv.Ascii // no color, 1-bit
|
||||
// termenv.ANSI //16 colors, 4-bit
|
||||
// termenv.ANSI256 // 256 colors, 8-bit
|
||||
// termenv.TrueColor // 16,777,216 colors, 24-bit
|
||||
// Ascii // no color, 1-bit
|
||||
// ANSI //16 colors, 4-bit
|
||||
// ANSI256 // 256 colors, 8-bit
|
||||
// TrueColor // 16,777,216 colors, 24-bit
|
||||
//
|
||||
// This function is thread-safe.
|
||||
func (r *Renderer) SetColorProfile(p termenv.Profile) {
|
||||
func (r *Renderer) SetColorProfile(p Profile) {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.colorProfile = p
|
||||
r.explicitColorProfile = true
|
||||
}
|
||||
|
||||
// SetColorProfile sets the color profile on the default renderer. This
|
||||
@ -120,19 +157,19 @@ func (r *Renderer) SetColorProfile(p termenv.Profile) {
|
||||
//
|
||||
// Available color profiles are:
|
||||
//
|
||||
// termenv.Ascii // no color, 1-bit
|
||||
// termenv.ANSI //16 colors, 4-bit
|
||||
// termenv.ANSI256 // 256 colors, 8-bit
|
||||
// termenv.TrueColor // 16,777,216 colors, 24-bit
|
||||
// Ascii // no color, 1-bit
|
||||
// ANSI //16 colors, 4-bit
|
||||
// ANSI256 // 256 colors, 8-bit
|
||||
// TrueColor // 16,777,216 colors, 24-bit
|
||||
//
|
||||
// This function is thread-safe.
|
||||
func SetColorProfile(p termenv.Profile) {
|
||||
renderer.SetColorProfile(p)
|
||||
func SetColorProfile(p Profile) {
|
||||
DefaultRenderer().SetColorProfile(p)
|
||||
}
|
||||
|
||||
// HasDarkBackground returns whether or not the terminal has a dark background.
|
||||
func HasDarkBackground() bool {
|
||||
return renderer.HasDarkBackground()
|
||||
return DefaultRenderer().HasDarkBackground()
|
||||
}
|
||||
|
||||
// HasDarkBackground returns whether or not the renderer will render to a dark
|
||||
@ -142,14 +179,6 @@ func (r *Renderer) HasDarkBackground() bool {
|
||||
r.mtx.RLock()
|
||||
defer r.mtx.RUnlock()
|
||||
|
||||
if !r.explicitBackgroundColor {
|
||||
r.getBackgroundColor.Do(func() {
|
||||
// NOTE: we don't need to lock here because sync.Once provides its
|
||||
// own locking mechanism.
|
||||
r.hasDarkBackground = r.output.HasDarkBackground()
|
||||
})
|
||||
}
|
||||
|
||||
return r.hasDarkBackground
|
||||
}
|
||||
|
||||
@ -163,7 +192,7 @@ func (r *Renderer) HasDarkBackground() bool {
|
||||
//
|
||||
// This function is thread-safe.
|
||||
func SetHasDarkBackground(b bool) {
|
||||
renderer.SetHasDarkBackground(b)
|
||||
DefaultRenderer().SetHasDarkBackground(b)
|
||||
}
|
||||
|
||||
// SetHasDarkBackground sets the background color detection value on the
|
||||
@ -180,5 +209,4 @@ func (r *Renderer) SetHasDarkBackground(b bool) {
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.hasDarkBackground = b
|
||||
r.explicitBackgroundColor = true
|
||||
}
|
||||
|
@ -1,20 +1,17 @@
|
||||
package lipgloss
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
func TestRendererHasDarkBackground(t *testing.T) {
|
||||
r1 := NewRenderer(os.Stdout)
|
||||
r1 := NewRenderer(os.Stdout, nil, true)
|
||||
r1.SetHasDarkBackground(false)
|
||||
if r1.HasDarkBackground() {
|
||||
t.Error("Expected renderer to have light background")
|
||||
}
|
||||
r2 := NewRenderer(os.Stdout)
|
||||
r2 := NewRenderer(os.Stdout, nil, false)
|
||||
r2.SetHasDarkBackground(true)
|
||||
if !r2.HasDarkBackground() {
|
||||
t.Error("Expected renderer to have dark background")
|
||||
@ -28,26 +25,23 @@ func TestRendererWithOutput(t *testing.T) {
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
r := NewRenderer(f)
|
||||
r.SetColorProfile(termenv.TrueColor)
|
||||
if r.ColorProfile() != termenv.TrueColor {
|
||||
r := NewRenderer(f, nil, true)
|
||||
r.SetColorProfile(TrueColor)
|
||||
if r.ColorProfile() != TrueColor {
|
||||
t.Error("Expected renderer to use true color")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRace(t *testing.T) {
|
||||
r := NewRenderer(io.Discard)
|
||||
o := r.Output()
|
||||
r := NewRenderer(os.Stdout, nil, true)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Run("SetColorProfile", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
r.SetHasDarkBackground(false)
|
||||
r.HasDarkBackground()
|
||||
r.SetOutput(o)
|
||||
r.SetColorProfile(termenv.ANSI256)
|
||||
r.SetColorProfile(ANSI256)
|
||||
r.SetHasDarkBackground(true)
|
||||
r.Output()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -19,31 +19,31 @@ func TestStyleRunes(t *testing.T) {
|
||||
"hello 0",
|
||||
"hello",
|
||||
[]int{0},
|
||||
"\x1b[7mh\x1b[0mello",
|
||||
"\x1b[7mh\x1b[mello",
|
||||
},
|
||||
{
|
||||
"你好 1",
|
||||
"你好",
|
||||
[]int{1},
|
||||
"你\x1b[7m好\x1b[0m",
|
||||
"你\x1b[7m好\x1b[m",
|
||||
},
|
||||
{
|
||||
"hello 你好 6,7",
|
||||
"hello 你好",
|
||||
[]int{6, 7},
|
||||
"hello \x1b[7m你好\x1b[0m",
|
||||
"hello \x1b[7m你好\x1b[m",
|
||||
},
|
||||
{
|
||||
"hello 1,3",
|
||||
"hello",
|
||||
[]int{1, 3},
|
||||
"h\x1b[7me\x1b[0ml\x1b[7ml\x1b[0mo",
|
||||
"h\x1b[7me\x1b[ml\x1b[7ml\x1b[mo",
|
||||
},
|
||||
{
|
||||
"你好 0,1",
|
||||
"你好",
|
||||
[]int{0, 1},
|
||||
"\x1b[7m你好\x1b[0m",
|
||||
"\x1b[7m你好\x1b[m",
|
||||
},
|
||||
}
|
||||
|
||||
|
101
style.go
101
style.go
@ -5,7 +5,6 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/charmbracelet/x/exp/term/ansi"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
const tabWidthDefault = 4
|
||||
@ -83,7 +82,7 @@ type rules map[propKey]interface{}
|
||||
// in case the underlying implementation changes. It takes an optional string
|
||||
// value to be set as the underlying string value for this style.
|
||||
func NewStyle() Style {
|
||||
return renderer.NewStyle()
|
||||
return DefaultRenderer().NewStyle()
|
||||
}
|
||||
|
||||
// NewStyle returns a new, empty Style. While it's syntactic sugar for the
|
||||
@ -176,7 +175,7 @@ func (s Style) Inherit(i Style) Style {
|
||||
// Render applies the defined style formatting to a given string.
|
||||
func (s Style) Render(strs ...string) string {
|
||||
if s.r == nil {
|
||||
s.r = renderer
|
||||
s.r = DefaultRenderer()
|
||||
}
|
||||
if s.value != "" {
|
||||
strs = append([]string{s.value}, strs...)
|
||||
@ -186,9 +185,9 @@ func (s Style) Render(strs ...string) string {
|
||||
str = joinString(strs...)
|
||||
|
||||
p = s.r.ColorProfile()
|
||||
te = p.String()
|
||||
teSpace = p.String()
|
||||
teWhitespace = p.String()
|
||||
te = p.string()
|
||||
teSpace = p.string()
|
||||
teWhitespace = p.string()
|
||||
|
||||
bold = s.getAsBool(boldKey, false)
|
||||
italic = s.getAsBool(italicKey, false)
|
||||
@ -237,10 +236,6 @@ func (s Style) Render(strs ...string) string {
|
||||
return s.maybeConvertTabs(str)
|
||||
}
|
||||
|
||||
// Enable support for ANSI on the legacy Windows cmd.exe console. This is a
|
||||
// no-op on non-Windows systems and on Windows runs only once.
|
||||
enableLegacyWindowsANSI()
|
||||
|
||||
if bold {
|
||||
te = te.Bold()
|
||||
}
|
||||
@ -257,29 +252,29 @@ func (s Style) Render(strs ...string) string {
|
||||
te = te.Reverse()
|
||||
}
|
||||
if blink {
|
||||
te = te.Blink()
|
||||
te = te.SlowBlink()
|
||||
}
|
||||
if faint {
|
||||
te = te.Faint()
|
||||
}
|
||||
|
||||
if fg != noColor {
|
||||
te = te.Foreground(fg.color(s.r))
|
||||
te = te.ForegroundColor(fg.color(s.r))
|
||||
if styleWhitespace {
|
||||
teWhitespace = teWhitespace.Foreground(fg.color(s.r))
|
||||
teWhitespace = teWhitespace.ForegroundColor(fg.color(s.r))
|
||||
}
|
||||
if useSpaceStyler {
|
||||
teSpace = teSpace.Foreground(fg.color(s.r))
|
||||
teSpace = teSpace.ForegroundColor(fg.color(s.r))
|
||||
}
|
||||
}
|
||||
|
||||
if bg != noColor {
|
||||
te = te.Background(bg.color(s.r))
|
||||
te = te.BackgroundColor(bg.color(s.r))
|
||||
if colorWhitespace {
|
||||
teWhitespace = teWhitespace.Background(bg.color(s.r))
|
||||
teWhitespace = teWhitespace.BackgroundColor(bg.color(s.r))
|
||||
}
|
||||
if useSpaceStyler {
|
||||
teSpace = teSpace.Background(bg.color(s.r))
|
||||
teSpace = teSpace.BackgroundColor(bg.color(s.r))
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,14 +282,14 @@ func (s Style) Render(strs ...string) string {
|
||||
te = te.Underline()
|
||||
}
|
||||
if strikethrough {
|
||||
te = te.CrossOut()
|
||||
te = te.Strikethrough()
|
||||
}
|
||||
|
||||
if underlineSpaces {
|
||||
teSpace = teSpace.Underline()
|
||||
}
|
||||
if strikethroughSpaces {
|
||||
teSpace = teSpace.CrossOut()
|
||||
teSpace = teSpace.Strikethrough()
|
||||
}
|
||||
|
||||
// Potentially convert tabs to spaces
|
||||
@ -340,7 +335,7 @@ func (s Style) Render(strs ...string) string {
|
||||
// Padding
|
||||
if !inline {
|
||||
if leftPadding > 0 {
|
||||
var st *termenv.Style
|
||||
var st *style
|
||||
if colorWhitespace || styleWhitespace {
|
||||
st = &teWhitespace
|
||||
}
|
||||
@ -348,7 +343,7 @@ func (s Style) Render(strs ...string) string {
|
||||
}
|
||||
|
||||
if rightPadding > 0 {
|
||||
var st *termenv.Style
|
||||
var st *style
|
||||
if colorWhitespace || styleWhitespace {
|
||||
st = &teWhitespace
|
||||
}
|
||||
@ -376,7 +371,7 @@ func (s Style) Render(strs ...string) string {
|
||||
numLines := strings.Count(str, "\n")
|
||||
|
||||
if !(numLines == 0 && width == 0) {
|
||||
var st *termenv.Style
|
||||
var st *style
|
||||
if colorWhitespace || styleWhitespace {
|
||||
st = &teWhitespace
|
||||
}
|
||||
@ -434,12 +429,12 @@ func (s Style) applyMargins(str string, inline bool) string {
|
||||
bottomMargin = s.getAsInt(marginBottomKey)
|
||||
leftMargin = s.getAsInt(marginLeftKey)
|
||||
|
||||
styler termenv.Style
|
||||
styler = s.r.ColorProfile().string()
|
||||
)
|
||||
|
||||
bgc := s.getAsColor(marginBackgroundKey)
|
||||
if bgc != noColor {
|
||||
styler = styler.Background(bgc.color(s.r))
|
||||
styler = styler.BackgroundColor(bgc.color(s.r))
|
||||
}
|
||||
|
||||
// Add left and right margin
|
||||
@ -463,19 +458,19 @@ func (s Style) applyMargins(str string, inline bool) string {
|
||||
}
|
||||
|
||||
// Apply left padding.
|
||||
func padLeft(str string, n int, style *termenv.Style) string {
|
||||
func padLeft(str string, n int, style *style) string {
|
||||
return pad(str, -n, style)
|
||||
}
|
||||
|
||||
// Apply right padding.
|
||||
func padRight(str string, n int, style *termenv.Style) string {
|
||||
func padRight(str string, n int, style *style) string {
|
||||
return pad(str, n, style)
|
||||
}
|
||||
|
||||
// pad adds padding to either the left or right side of a string.
|
||||
// Positive values add to the right side while negative values
|
||||
// add to the left side.
|
||||
func pad(str string, n int, style *termenv.Style) string {
|
||||
func pad(str string, n int, style *style) string {
|
||||
if n == 0 {
|
||||
return str
|
||||
}
|
||||
@ -529,3 +524,55 @@ func abs(a int) int {
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
type style struct {
|
||||
ansi.Style
|
||||
Profile
|
||||
}
|
||||
|
||||
func (s style) Styled(str string) string {
|
||||
if s.Profile == Ascii {
|
||||
return str
|
||||
}
|
||||
return s.Style.Styled(str)
|
||||
}
|
||||
|
||||
func (s style) Bold() style {
|
||||
return style{s.Style.Bold(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) Italic() style {
|
||||
return style{s.Style.Italic(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) Underline() style {
|
||||
return style{s.Style.Underline(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) Strikethrough() style {
|
||||
return style{s.Style.Strikethrough(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) Reverse() style {
|
||||
return style{s.Style.Reverse(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) SlowBlink() style {
|
||||
return style{s.Style.SlowBlink(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) RapidBlink() style {
|
||||
return style{s.Style.RapidBlink(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) Faint() style {
|
||||
return style{s.Style.Faint(), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) ForegroundColor(c ansi.Color) style {
|
||||
return style{s.Style.ForegroundColor(c), s.Profile}
|
||||
}
|
||||
|
||||
func (s style) BackgroundColor(c ansi.Color) style {
|
||||
return style{s.Style.BackgroundColor(c), s.Profile}
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
package lipgloss
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
func TestStyleRender(t *testing.T) {
|
||||
r := NewRenderer(io.Discard)
|
||||
r.SetColorProfile(termenv.TrueColor)
|
||||
r := NewRenderer(os.Stdout, nil, true)
|
||||
r.SetColorProfile(TrueColor)
|
||||
r.SetHasDarkBackground(true)
|
||||
t.Parallel()
|
||||
|
||||
@ -21,31 +19,31 @@ func TestStyleRender(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
r.NewStyle().Foreground(Color("#5A56E0")),
|
||||
"\x1b[38;2;89;86;224mhello\x1b[0m",
|
||||
"\x1b[38;2;89;86;224mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}),
|
||||
"\x1b[38;2;89;86;224mhello\x1b[0m",
|
||||
"\x1b[38;2;89;86;224mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Bold(true),
|
||||
"\x1b[1mhello\x1b[0m",
|
||||
"\x1b[1mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Italic(true),
|
||||
"\x1b[3mhello\x1b[0m",
|
||||
"\x1b[3mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Underline(true),
|
||||
"\x1b[4;4mh\x1b[0m\x1b[4;4me\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4mo\x1b[0m",
|
||||
"\x1b[4;4mh\x1b[m\x1b[4;4me\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4mo\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Blink(true),
|
||||
"\x1b[5mhello\x1b[0m",
|
||||
"\x1b[5mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Faint(true),
|
||||
"\x1b[2mhello\x1b[0m",
|
||||
"\x1b[2mhello\x1b[m",
|
||||
},
|
||||
}
|
||||
|
||||
@ -61,44 +59,44 @@ func TestStyleRender(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStyleCustomRender(t *testing.T) {
|
||||
r := NewRenderer(io.Discard)
|
||||
r := NewRenderer(os.Stdout, nil, true)
|
||||
r.SetHasDarkBackground(false)
|
||||
r.SetColorProfile(termenv.TrueColor)
|
||||
r.SetColorProfile(TrueColor)
|
||||
tt := []struct {
|
||||
style Style
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
r.NewStyle().Foreground(Color("#5A56E0")),
|
||||
"\x1b[38;2;89;86;224mhello\x1b[0m",
|
||||
"\x1b[38;2;89;86;224mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}),
|
||||
"\x1b[38;2;255;254;18mhello\x1b[0m",
|
||||
"\x1b[38;2;255;254;18mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Bold(true),
|
||||
"\x1b[1mhello\x1b[0m",
|
||||
"\x1b[1mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Italic(true),
|
||||
"\x1b[3mhello\x1b[0m",
|
||||
"\x1b[3mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Underline(true),
|
||||
"\x1b[4;4mh\x1b[0m\x1b[4;4me\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4mo\x1b[0m",
|
||||
"\x1b[4;4mh\x1b[m\x1b[4;4me\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4mo\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Blink(true),
|
||||
"\x1b[5mhello\x1b[0m",
|
||||
"\x1b[5mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
r.NewStyle().Faint(true),
|
||||
"\x1b[2mhello\x1b[0m",
|
||||
"\x1b[2mhello\x1b[m",
|
||||
},
|
||||
{
|
||||
NewStyle().Faint(true).Renderer(r),
|
||||
"\x1b[2mhello\x1b[0m",
|
||||
"\x1b[2mhello\x1b[m",
|
||||
},
|
||||
}
|
||||
|
||||
@ -114,7 +112,7 @@ func TestStyleCustomRender(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStyleRenderer(t *testing.T) {
|
||||
r := NewRenderer(io.Discard)
|
||||
r := NewRenderer(os.Stdout, nil, true)
|
||||
s1 := NewStyle().Bold(true)
|
||||
s2 := s1.Renderer(r)
|
||||
if s1.r == s2.r {
|
||||
@ -349,7 +347,7 @@ func TestStyleValue(t *testing.T) {
|
||||
name: "set string with bold",
|
||||
text: "foo",
|
||||
style: NewStyle().SetString("bar").Bold(true),
|
||||
expected: "\x1b[1mbar foo\x1b[0m",
|
||||
expected: "\x1b[1mbar foo\x1b[m",
|
||||
},
|
||||
{
|
||||
name: "new style with string",
|
||||
@ -442,7 +440,7 @@ func TestStringTransform(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
res := NewStyle().Bold(true).Transform(tc.fn).Render(tc.input)
|
||||
expected := "\x1b[1m" + tc.expected + "\x1b[0m"
|
||||
expected := "\x1b[1m" + tc.expected + "\x1b[m"
|
||||
if res != expected {
|
||||
t.Errorf("Test #%d:\nExpected: %q\nGot: %q", i+1, expected, res)
|
||||
}
|
||||
|
@ -4,14 +4,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/x/exp/term/ansi"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
// whitespace is a whitespace renderer.
|
||||
type whitespace struct {
|
||||
re *Renderer
|
||||
style termenv.Style
|
||||
chars string
|
||||
style style
|
||||
}
|
||||
|
||||
// newWhitespace creates a new whitespace renderer. The order of the options
|
||||
@ -20,7 +19,7 @@ type whitespace struct {
|
||||
func newWhitespace(r *Renderer, opts ...WhitespaceOption) *whitespace {
|
||||
w := &whitespace{
|
||||
re: r,
|
||||
style: r.ColorProfile().String(),
|
||||
style: r.ColorProfile().string(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(w)
|
||||
@ -64,14 +63,14 @@ type WhitespaceOption func(*whitespace)
|
||||
// WithWhitespaceForeground sets the color of the characters in the whitespace.
|
||||
func WithWhitespaceForeground(c TerminalColor) WhitespaceOption {
|
||||
return func(w *whitespace) {
|
||||
w.style = w.style.Foreground(c.color(w.re))
|
||||
w.style = w.style.ForegroundColor(c.color(w.re))
|
||||
}
|
||||
}
|
||||
|
||||
// WithWhitespaceBackground sets the background color of the whitespace.
|
||||
func WithWhitespaceBackground(c TerminalColor) WhitespaceOption {
|
||||
return func(w *whitespace) {
|
||||
w.style = w.style.Background(c.color(w.re))
|
||||
w.style = w.style.BackgroundColor(c.color(w.re))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user