Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: introduce color profiles and use term/ansi for styles #269

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
9 changes: 4 additions & 5 deletions align.go
Expand Up @@ -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 *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
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
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
}
}
}
12 changes: 6 additions & 6 deletions borders.go
Expand Up @@ -4,7 +4,6 @@ import (
"strings"

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

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

style := termenv.Style{}
p := s.r.ColorProfile()

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

return style.Styled(border)
Expand Down
99 changes: 24 additions & 75 deletions color.go
@@ -1,15 +1,15 @@
package lipgloss

import (
"strconv"
"image/color"

"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
RGBA() (r, g, b, a uint32)
color(*Renderer) ansi.Color
}

var noColor = NoColor{}
Expand All @@ -23,17 +23,15 @@ 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 color.Black
}

// 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
}
Expand All @@ -44,44 +42,20 @@ func (n NoColor) RGBA() (r, g, b, a uint32) {
// hexColor := lipgloss.Color("#0000ff")
type Color string

func (c Color) color(r *Renderer) termenv.Color {
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
// 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()
}

// 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.
// ANSIColor is a color specified by an ANSI256 color value.
//
// Example usage:
//
// // These two statements are equivalent.
// colorA := lipgloss.ANSIColor(21)
// colorB := lipgloss.Color("21")
type ANSIColor uint
// colorA := lipgloss.ANSIColor(8)
// colorB := lipgloss.ANSIColor(134)
type ANSIColor uint8

func (ac ANSIColor) color(r *Renderer) termenv.Color {
return Color(strconv.FormatUint(uint64(ac), 10)).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 (ac ANSIColor) RGBA() (r, g, b, a uint32) {
cf := Color(strconv.FormatUint(uint64(ac), 10))
return cf.RGBA()
func (ac ANSIColor) color(r *Renderer) ansi.Color {
return r.ColorProfile().Convert(ansi.ExtendedColor(ac))
}

// AdaptiveColor provides color options for light and dark backgrounds. The
Expand All @@ -96,23 +70,13 @@ 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)
}
return Color(ac.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 (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 {
Expand All @@ -121,31 +85,20 @@ 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:
case TrueColor:
return p.Color(c.TrueColor)
case termenv.ANSI256:
case ANSI256:
return p.Color(c.ANSI256)
case termenv.ANSI:
case ANSI:
return p.Color(c.ANSI)
default:
return termenv.NoColor{}
return 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:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color
//
// 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.
Expand All @@ -154,19 +107,15 @@ 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)
}
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()
// ConvertToRGB converts a Color to a colorful.Color.
func ConvertToRGB(c ansi.Color) colorful.Color {
ch, _ := colorful.MakeColor(c)
return ch
}