Skip to content

Commit

Permalink
Merge branch 'v2-exp' into v2-godoc
Browse files Browse the repository at this point in the history
  • Loading branch information
bashbunni authored Dec 4, 2024
2 parents 26498df + 8f4aab7 commit 5877605
Show file tree
Hide file tree
Showing 34 changed files with 592 additions and 147 deletions.
123 changes: 85 additions & 38 deletions color.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package lipgloss

import (
"fmt"
"image/color"
"strconv"
"strings"

"github.com/charmbracelet/colorprofile"
"github.com/charmbracelet/x/ansi"
"github.com/lucasb-eyer/go-colorful"
)
Expand Down Expand Up @@ -52,39 +53,37 @@ func (n NoColor) RGBA() (r, g, b, a uint32) {

// Color specifies a color by hex or ANSI256 value. For example:
//
// ansiColor := lipgloss.Color(21)
// ansiColor := lipgloss.Color("1") // The same as lipgloss.Red
// ansi256Color := lipgloss.Color("21")
// hexColor := lipgloss.Color("#0000ff")
// uint32Color := lipgloss.Color(0xff0000)
func Color(c any) color.Color {
switch c := c.(type) {
case nil:
return noColor
case string:
if len(c) == 0 {
return noColor
func Color(s string) color.Color {
var c color.Color = noColor
if strings.HasPrefix(s, "#") {

Check failure on line 61 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

`if strings.HasPrefix(s, "#")` has complex nested blocks (complexity: 7) (nestif)

Check failure on line 61 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

`if strings.HasPrefix(s, "#")` has complex nested blocks (complexity: 7) (nestif)

Check failure on line 61 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

`if strings.HasPrefix(s, "#")` has complex nested blocks (complexity: 7) (nestif)

Check failure on line 61 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

`if strings.HasPrefix(s, "#")` has complex nested blocks (complexity: 7) (nestif)

Check failure on line 61 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

`if strings.HasPrefix(s, "#")` has complex nested blocks (complexity: 7) (nestif)

Check failure on line 61 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

`if strings.HasPrefix(s, "#")` has complex nested blocks (complexity: 7) (nestif)
hex, err := colorful.Hex(s)
if err != nil {
return c
}
if h, err := colorful.Hex(c); err == nil {
return h
} else if i, err := strconv.Atoi(c); err == nil {
if i < 16 { //nolint:mnd
return ansi.BasicColor(i) //nolint:gosec
} else if i < 256 { //nolint:mnd
return ansi.ExtendedColor(i) //nolint:gosec
}
return ansi.TrueColor(i) //nolint:gosec
c = hex
} else {
i, err := strconv.Atoi(s)
if err != nil {
return c
}
return noColor
case int:
if c < 16 { //nolint:mnd
return ansi.BasicColor(c) //nolint:gosec
} else if c < 256 { //nolint:mnd
return ansi.ExtendedColor(c) //nolint:gosec

if i < 0 {
// Only positive numbers
i = -i
}

if i < 16 {

Check failure on line 78 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 16, in <condition> detected (mnd)

Check failure on line 78 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 16, in <condition> detected (mnd)

Check failure on line 78 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 16, in <condition> detected (mnd)

Check failure on line 78 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 16, in <condition> detected (mnd)

Check failure on line 78 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 16, in <condition> detected (mnd)

Check failure on line 78 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 16, in <condition> detected (mnd)
c = ansi.BasicColor(i) //nolint:gosec
} else if i < 256 {

Check failure on line 80 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 256, in <condition> detected (mnd)

Check failure on line 80 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 256, in <condition> detected (mnd)

Check failure on line 80 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

Magic number: 256, in <condition> detected (mnd)

Check failure on line 80 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

Magic number: 256, in <condition> detected (mnd)

Check failure on line 80 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 256, in <condition> detected (mnd)

Check failure on line 80 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

Magic number: 256, in <condition> detected (mnd)
c = ANSIColor(i) //nolint:gosec
} else {
c = ansi.TrueColor(i) //nolint:gosec
}
return ansi.TrueColor(c) //nolint:gosec
case color.Color:
return c
}
return Color(fmt.Sprint(c))
return c
}

// RGBColor is a color specified by red, green, and blue values.
Expand Down Expand Up @@ -120,16 +119,18 @@ type ANSIColor = ansi.ExtendedColor
// Example:
//
// lightDark := lipgloss.LightDark(hasDarkBackground)
// myHotColor := lightDark("#ff0000", "#0000ff")
// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff")
// myHotColor := lightDark(red, blue)
//
// For more info see [LightDark].
type LightDarkFunc func(light, dark any) color.Color
type LightDarkFunc func(light, dark color.Color) color.Color

// LightDark is a simple helper type that can be used to choose the appropriate
// color based on whether the terminal has a light or dark background.
//
// lightDark := lipgloss.LightDark(hasDarkBackground)
// theRightColor := lightDark("#0000ff", "#ff0000")
// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff")
// myHotColor := lightDark(red, blue)
//
// In practice, there are slightly different workflows between Bubble Tea and
// Lip Gloss standalone.
Expand All @@ -144,19 +145,21 @@ type LightDarkFunc func(light, dark any) color.Color
// Later, when you're rendering use:
//
// lightDark := lipgloss.LightDark(m.hasDarkBackground)
// myHotColor := lightDark("#ff0000", "#0000ff")
// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff")
// myHotColor := lightDark(red, blue)
//
// In standalone Lip Gloss, the workflow is simpler:
//
// hasDarkBG, _ := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
// hasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
// lightDark := lipgloss.LightDark(hasDarkBG)
// myHotColor := lightDark("#ff0000", "#0000ff")
// red, blue := lipgloss.Color("#ff0000"), lipgloss.Color("#0000ff")
// myHotColor := lightDark(red, blue)
func LightDark(isDark bool) LightDarkFunc {
return func(light, dark any) color.Color {
return func(light, dark color.Color) color.Color {
if isDark {
return Color(dark)
return dark
}
return Color(light)
return light
}
}

Expand All @@ -180,3 +183,47 @@ func isDarkColor(c color.Color) bool {
_, _, l := col.Hsl()
return l < 0.5 //nolint:mnd
}

// CompleteFunc is a function that returns the appropriate color based on the
// given color profile.
//
// Example usage:
//
// p := colorprofile.Detect(os.Stderr, os.Environ())
// complete := lipgloss.Complete(p)
// color := complete(
// lipgloss.Color(1), // ANSI
// lipgloss.Color(124), // ANSI256
// lipgloss.Color("#ff34ac"), // TrueColor
// )
// fmt.Println("Ooh, pretty color: ", color)
//
// For more info see [Complete].
type CompleteFunc func(ansi, ansi256, truecolor color.Color) color.Color

// Complete returns a function that will return the appropriate color based on
// the given color profile.
//
// Example usage:
//
// p := colorprofile.Detect(os.Stderr, os.Environ())
// complete := lipgloss.Complete(p)
// color := complete(
// lipgloss.Color(1), // ANSI
// lipgloss.Color(124), // ANSI256
// lipgloss.Color("#ff34ac"), // TrueColor
// )
// fmt.Println("Ooh, pretty color: ", color)
func Complete(p colorprofile.Profile) CompleteFunc {
return func(ansi, ansi256, truecolor color.Color) color.Color {
switch p {

Check failure on line 219 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 219 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 219 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 219 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 219 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 219 in color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)
case colorprofile.ANSI:
return ansi
case colorprofile.ANSI256:
return ansi256
case colorprofile.TrueColor:
return truecolor
}
return noColor
}
}
77 changes: 77 additions & 0 deletions compat/color.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package compat

import (
"image/color"
"os"

"github.com/charmbracelet/colorprofile"
"github.com/charmbracelet/lipgloss/v2"
)

var (
// HasDarkBackground is true if the terminal has a dark background.
HasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)

// Profile is the color profile of the terminal.
Profile = colorprofile.Detect(os.Stdout, os.Environ())
)

// 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.
//
// Example usage:
//
// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
type AdaptiveColor struct {
Light color.Color
Dark color.Color
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface.
func (c AdaptiveColor) RGBA() (uint32, uint32, uint32, uint32) {
if HasDarkBackground {
return c.Dark.RGBA()
}
return c.Light.RGBA()
}

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

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface.
func (c CompleteColor) RGBA() (uint32, uint32, uint32, uint32) {
switch Profile {

Check failure on line 51 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 51 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 51 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (ubuntu-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 51 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (macos-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 51 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)

Check failure on line 51 in compat/color.go

View workflow job for this annotation

GitHub Actions / lint / lint-soft (windows-latest)

missing cases in switch of type colorprofile.Profile: colorprofile.Ascii, colorprofile.NoTTY (exhaustive)
case colorprofile.TrueColor:
return c.TrueColor.RGBA()
case colorprofile.ANSI256:
return c.ANSI256.RGBA()
case colorprofile.ANSI:
return c.ANSI.RGBA()
}
return lipgloss.NoColor{}.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
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface.
func (c CompleteAdaptiveColor) RGBA() (uint32, uint32, uint32, uint32) {
if HasDarkBackground {
return c.Dark.RGBA()
}
return c.Light.RGBA()
}
21 changes: 21 additions & 0 deletions compat/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Package compat is a compatibility layer for Lip Gloss that provides a way to
// deal with the hassle of setting up a writer. It's impure because it uses
// global variables, is not thread-safe, and only works with the default
// standard I/O streams.
//
// In case you want [os.Stderr] to be used as the default writer, you can set
// both [Writer] and [HasDarkBackground] to use [os.Stderr] with
// the following code:
//
// import (
// "os"
//
// "github.com/charmbracelet/colorprofile"
// "github.com/charmbracelet/lipgloss/v2/impure"
// )
//
// func init() {
// impure.Writer = colorprofile.NewWriter(os.Stderr, os.Environ())
// impure.HasDarkBackground, _ = lipgloss.HasDarkBackground(os.Stdin, os.Stderr)
// }
package compat
4 changes: 2 additions & 2 deletions examples/color/bubbletea/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"

tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/v2"
)

// Style definitions.
Expand Down Expand Up @@ -63,7 +63,7 @@ type model struct {
func (m model) Init() (tea.Model, tea.Cmd) {
// Query for the background color on start.
m.yes = true
return m, tea.BackgroundColor
return m, tea.RequestBackgroundColor
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
Expand Down
2 changes: 1 addition & 1 deletion examples/color/standalone/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"fmt"
"os"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/v2"
)

func main() {
Expand Down
Loading

0 comments on commit 5877605

Please sign in to comment.