Skip to content

Commit

Permalink
feat!: simplify color interface and style
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Jul 22, 2024
1 parent 87dd58d commit 2390dea
Show file tree
Hide file tree
Showing 17 changed files with 246 additions and 402 deletions.
9 changes: 4 additions & 5 deletions align.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import (
"strings"

"github.com/charmbracelet/x/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 *ansi.Style) string {
lines, widestLine := getLines(str)
var b strings.Builder

Expand Down Expand Up @@ -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, _ *ansi.Style) string {
strHeight := strings.Count(str, "\n") + 1
if height < strHeight {
return str
Expand Down
6 changes: 4 additions & 2 deletions ansi_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@

package lipgloss

// enableLegacyWindowsANSI is only needed on Windows.
func enableLegacyWindowsANSI() {}
import "os"

// EnableLegacyWindowsANSI is only needed on Windows.
func EnableLegacyWindowsANSI(*os.File) {}
31 changes: 20 additions & 11 deletions ansi_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@
package lipgloss

import (
"sync"
"os"

"github.com/muesli/termenv"
"golang.org/x/sys/windows"
)

var enableANSI sync.Once
// EnableLegacyWindowsANSI enables support for ANSI color sequences in the
// Windows default console (cmd.exe and the PowerShell application). Note that
// this only works with Windows 10 and greater. Also note that Windows Terminal
// supports colors by default.
func EnableLegacyWindowsANSI(f *os.File) {
var mode uint32
handle := windows.Handle(f.Fd())
err := windows.GetConsoleMode(handle, &mode)
if err != nil {
return
}

// enableANSIColors enables support for ANSI color sequences in the Windows
// default console (cmd.exe and the PowerShell application). Note that this
// only works with Windows 10. Also note that Windows Terminal supports colors
// by default.
func enableLegacyWindowsANSI() {
enableANSI.Do(func() {
_, _ = termenv.EnableWindowsANSIConsole()
})
// 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
}
}
}
8 changes: 3 additions & 5 deletions borders.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"strings"

"github.com/charmbracelet/x/ansi"
"github.com/muesli/termenv"
"github.com/rivo/uniseg"
)

Expand Down Expand Up @@ -407,13 +406,12 @@ func (s Style) styleBorder(border string, fg, bg TerminalColor) string {
return border
}

style := termenv.Style{}

var style ansi.Style
if fg != noColor {
style = style.Foreground(fg.color(s.r))
style = style.ForegroundColor(fg)
}
if bg != noColor {
style = style.Background(bg.color(s.r))
style = style.BackgroundColor(bg)
}

return style.Styled(border)
Expand Down
206 changes: 83 additions & 123 deletions color.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,34 @@ package lipgloss
import (
"strconv"

"github.com/muesli/termenv"
"github.com/charmbracelet/x/ansi"
"github.com/lucasb-eyer/go-colorful"
)

// 4-bit color constants.
const (
Black ansi.BasicColor = iota
Red
Green
Yellow
Blue
Magenta
Cyan
White

BrightBlack
BrightRed
BrightGreen
BrightYellow
BrightBlue
BrightMagenta
BrightCyan
BrightWhite
)

// TerminalColor is a color intended to be rendered in the terminal.
type TerminalColor interface {
color(*Renderer) termenv.Color
RGBA() (r, g, b, a uint32)
ansi.Color
}

var noColor = NoColor{}
Expand All @@ -23,150 +44,89 @@ var noColor = NoColor{}
// var style = someStyle.Background(lipgloss.NoColor{})
type NoColor struct{}

func (NoColor) color(*Renderer) termenv.Color {
return termenv.NoColor{}
}

// RGBA returns the RGBA value of this color. Because we have to return
// something, despite this color being the absence of color, we're returning
// black with 100% opacity.
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
//
// Deprecated.
func (n NoColor) RGBA() (r, g, b, a uint32) {
return 0x0, 0x0, 0x0, 0xFFFF //nolint:gomnd
}

// Color specifies a color by hex or ANSI value. For example:
// Color specifies a color by hex or ANSI256 value. For example:
//
// ansiColor := lipgloss.Color("21")
// ansiColor := lipgloss.Color(21)
// hexColor := lipgloss.Color("#0000ff")
type Color string

func (c Color) color(r *Renderer) termenv.Color {
return r.ColorProfile().Color(string(c))
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface. Note that on error we return black with 100% opacity, or:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
//
// Deprecated.
func (c Color) RGBA() (r, g, b, a uint32) {
return termenv.ConvertToRGB(c.color(renderer)).RGBA()
// uint32Color := lipgloss.Color(0xff0000)
func Color[T string | int](c T) TerminalColor {
var col TerminalColor = noColor
switch c := any(c).(type) {
case string:
if len(c) == 0 {
return col
}
if h, err := colorful.Hex(c); err == nil {
return h
} else if i, err := strconv.Atoi(c); err == nil {
if i < 16 {

Check failure on line 71 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 16, in <condition> detected (gomnd)
return ansi.BasicColor(i)
} else if i < 256 {

Check failure on line 73 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 256, in <condition> detected (gomnd)
return ansi.ExtendedColor(i)
}
return ansi.TrueColor(i)
}
case int:
if c < 16 {

Check failure on line 79 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 16, in <condition> detected (gomnd)
return ansi.BasicColor(c)
} else if c < 256 {

Check failure on line 81 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 256, in <condition> detected (gomnd)
return ansi.ExtendedColor(c)
}
return ansi.TrueColor(c)
}
return col
}

// ANSIColor is a color specified by an ANSI color value. It's merely syntactic
// sugar for the more general Color function. Invalid colors will render as
// black.
//
// Example usage:
//
// // These two statements are equivalent.
// colorA := lipgloss.ANSIColor(21)
// colorB := lipgloss.Color("21")
type ANSIColor uint

func (ac ANSIColor) color(r *Renderer) termenv.Color {
return Color(strconv.FormatUint(uint64(ac), 10)).color(r)
// RGBColor is a color specified by red, green, and blue values.
type RGBColor struct {
R uint8
G uint8
B uint8
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface. Note that on error we return black with 100% opacity, or:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
//
// Deprecated.
func (ac ANSIColor) RGBA() (r, g, b, a uint32) {
cf := Color(strconv.FormatUint(uint64(ac), 10))
return cf.RGBA()
// interface.
func (c RGBColor) RGBA() (r, g, b, a uint32) {
r |= uint32(c.R) << 8

Check failure on line 99 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 8, in <operation> detected (gomnd)
g |= uint32(c.G) << 8

Check failure on line 100 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 8, in <operation> detected (gomnd)
b |= uint32(c.B) << 8

Check failure on line 101 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 8, in <operation> detected (gomnd)
a = 0xFFFF //nolint:gomnd

Check failure on line 102 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

directive `//nolint:gomnd` is unused for linter "gomnd" (nolintlint)
return
}

// AdaptiveColor provides color options for light and dark backgrounds. The
// appropriate color will be returned at runtime based on the darkness of the
// terminal background color.
// ANSIColor is a color specified by an ANSI256 color value.
//
// Example usage:
//
// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
type AdaptiveColor struct {
Light string
Dark string
}

func (ac AdaptiveColor) color(r *Renderer) termenv.Color {
if r.HasDarkBackground() {
return Color(ac.Dark).color(r)
}
return Color(ac.Light).color(r)
}
// colorA := lipgloss.ANSIColor(8)
// colorB := lipgloss.ANSIColor(134)
type ANSIColor = ansi.ExtendedColor

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface. Note that on error we return black with 100% opacity, or:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
//
// Deprecated.
func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) {
return termenv.ConvertToRGB(ac.color(renderer)).RGBA()
}

// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color
// profiles. Automatic color degradation will not be performed.
type CompleteColor struct {
TrueColor string
ANSI256 string
ANSI string
}

func (c CompleteColor) color(r *Renderer) termenv.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)
default:
return termenv.NoColor{}
}
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface. Note that on error we return black with 100% opacity, or:
// IsDarkColor returns whether the given color is dark.
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
// Example usage:
//
// Deprecated.
func (c CompleteColor) RGBA() (r, g, b, a uint32) {
return termenv.ConvertToRGB(c.color(renderer)).RGBA()
}

// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
// profiles, with separate options for light and dark backgrounds. Automatic
// color degradation will not be performed.
type CompleteAdaptiveColor struct {
Light CompleteColor
Dark CompleteColor
}

func (cac CompleteAdaptiveColor) color(r *Renderer) termenv.Color {
if r.HasDarkBackground() {
return cac.Dark.color(r)
// color := lipgloss.Color("#0000ff")
// if lipgloss.IsDarkColor(color) {
// fmt.Println("It's dark!")
// } else {
// fmt.Println("It's light!")
// }
func IsDarkColor(c TerminalColor) bool {
col, ok := colorful.MakeColor(c)
if !ok {
return true
}
return cac.Light.color(r)
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface. Note that on error we return black with 100% opacity, or:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
//
// Deprecated.
func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) {
return termenv.ConvertToRGB(cac.color(renderer)).RGBA()
_, _, l := col.Hsl()
return l < 0.5

Check failure on line 131 in color.go

View workflow job for this annotation

GitHub Actions / lint-soft

Magic number: 0.5, in <return> detected (gomnd)
}
Loading

0 comments on commit 2390dea

Please sign in to comment.