Skip to content

Commit

Permalink
refactor: introduce NoTTY profile and export Sequence
Browse files Browse the repository at this point in the history
The NoTTY profile generates no sequences while the Ascii profile
generates sequences with no colors (bold, italic, underline, etc)
  • Loading branch information
aymanbagabas committed Apr 23, 2024
1 parent ce80acd commit fd0951e
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 100 deletions.
4 changes: 2 additions & 2 deletions align.go
Expand Up @@ -9,7 +9,7 @@ import (
// Perform text alignment. If the string is multi-lined, we also make all lines
// 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 {
func alignTextHorizontal(str string, pos Position, width int, style *Sequence) string {
lines, widestLine := getLines(str)
var b strings.Builder

Expand Down Expand Up @@ -58,7 +58,7 @@ func alignTextHorizontal(str string, pos Position, width int, style *style) stri
return b.String()
}

func alignTextVertical(str string, pos Position, height int, _ *style) string {
func alignTextVertical(str string, pos Position, height int, _ *Sequence) string {
strHeight := strings.Count(str, "\n") + 1
if height < strHeight {
return str
Expand Down
2 changes: 1 addition & 1 deletion borders.go
Expand Up @@ -406,7 +406,7 @@ func (s Style) styleBorder(border string, fg, bg TerminalColor) string {
return border
}

style := s.r.ColorProfile().string()
style := s.r.ColorProfile().Sequence()

if fg != noColor {
style = style.ForegroundColor(fg.color(s.r))
Expand Down
21 changes: 9 additions & 12 deletions color.go
Expand Up @@ -46,7 +46,7 @@ func (n NoColor) RGBA() (r, g, b, a uint32) {
type Color string

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

// RGBA returns the RGBA value of this color. This satisfies the Go Color
Expand All @@ -59,19 +59,16 @@ func (c Color) RGBA() (r, g, b, a uint32) {
return c.color(DefaultRenderer()).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) ansi.Color {
return Color(strconv.FormatUint(uint64(ac), 10)).color(r)
return r.ColorProfile().Convert(ansi.ExtendedColor(ac))
}

// RGBA returns the RGBA value of this color. This satisfies the Go Color
Expand Down Expand Up @@ -126,11 +123,11 @@ func (c CompleteColor) color(r *Renderer) ansi.Color {
p := r.ColorProfile()
switch p { //nolint:exhaustive
case TrueColor:
return p.color(c.TrueColor)
return p.Color(c.TrueColor)
case ANSI256:
return p.color(c.ANSI256)
return p.Color(c.ANSI256)
case ANSI:
return p.color(c.ANSI)
return p.Color(c.ANSI)
default:
return NoColor{}
}
Expand Down
14 changes: 7 additions & 7 deletions env.go
Expand Up @@ -25,15 +25,19 @@ func envNoColor(env map[string]string) bool {
// 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 EnvColorProfile(stdout *os.File, environ []string) Profile {
if stdout == nil || !term.IsTerminal(stdout.Fd()) {
return NoTTY
}
if environ == nil {
environ = os.Environ()
}

env := environMap(environ)
if envNoColor(env) {
return Ascii
}
p := detectColorProfile(stdout, env)
if cliColorForced(env) && p == Ascii {
p := detectColorProfile(env)
if cliColorForced(env) && p <= NoTTY {
return ANSI
}
return p
Expand All @@ -48,11 +52,7 @@ func cliColorForced(env map[string]string) bool {

// detectColorProfile returns the supported color profile:
// Ascii, ANSI, ANSI256, or TrueColor.
func detectColorProfile(stdout *os.File, env map[string]string) (p Profile) {
if stdout == nil || !term.IsTerminal(stdout.Fd()) {
return Ascii
}

func detectColorProfile(env map[string]string) (p Profile) {
setProfile := func(profile Profile) {
if profile > p {
p = profile
Expand Down
101 changes: 87 additions & 14 deletions profile.go
Expand Up @@ -14,8 +14,10 @@ import (
type Profile int

const (
// NoTTY, no terminal profile
NoTTY Profile = iota
// Ascii, uncolored profile
Ascii Profile = iota //nolint:revive
Ascii // nolint: revive
// ANSI, 4-bit color profile
ANSI
// ANSI256, 8-bit color profile
Expand All @@ -24,14 +26,85 @@ const (
TrueColor
)

func (p Profile) string() style {
return style{Profile: p}
// Sequence returns a style sequence for the profile.
func (p Profile) Sequence() Sequence {
return Sequence{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{}
// Sequence represents a text ANSI sequence style.
type Sequence struct {
ansi.Style
Profile
}

// Styled returns a styled string.
func (s Sequence) Styled(str string) string {
if s.Profile <= NoTTY {
return str
}
return s.Style.Styled(str)
}

// Bold returns a sequence with bold enabled.
func (s Sequence) Bold() Sequence {
return Sequence{s.Style.Bold(), s.Profile}
}

// Italic returns a sequence with italic enabled.
func (s Sequence) Italic() Sequence {
return Sequence{s.Style.Italic(), s.Profile}
}

// Underline returns a sequence with underline enabled.
func (s Sequence) Underline() Sequence {
return Sequence{s.Style.Underline(), s.Profile}
}

// Strikethrough returns a sequence with strikethrough enabled.
func (s Sequence) Strikethrough() Sequence {
return Sequence{s.Style.Strikethrough(), s.Profile}
}

// Inverse returns a sequence with inverse enabled.
func (s Sequence) Reverse() Sequence {
return Sequence{s.Style.Reverse(), s.Profile}
}

// SlowBlink returns a sequence with slow blink enabled.
func (s Sequence) SlowBlink() Sequence {
return Sequence{s.Style.SlowBlink(), s.Profile}
}

// RapidBlink returns a sequence with rapid blink enabled.
func (s Sequence) RapidBlink() Sequence {
return Sequence{s.Style.RapidBlink(), s.Profile}
}

// Faint returns a sequence with faint enabled.
func (s Sequence) Faint() Sequence {
return Sequence{s.Style.Faint(), s.Profile}
}

// ForegroundColor returns a sequence with the foreground color set.
func (s Sequence) ForegroundColor(c ansi.Color) Sequence {
if s.Profile <= Ascii {
return s
}
return Sequence{s.Style.ForegroundColor(c), s.Profile}
}

// BackgroundColor returns a sequence with the background color set.
func (s Sequence) BackgroundColor(c ansi.Color) Sequence {
if s.Profile <= Ascii {
return s
}
return Sequence{s.Style.BackgroundColor(c), s.Profile}
}

// 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) {
Expand Down Expand Up @@ -62,25 +135,25 @@ func (p Profile) convert(c ansi.Color) ansi.Color {
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 {
// 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
return noColor
}

var c ansi.Color
if strings.HasPrefix(s, "#") {
h, err := colorful.Hex(s)
if err != nil {
return nil
return noColor
}
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
return noColor
}

if i < 16 {
Expand All @@ -90,7 +163,7 @@ func (p Profile) color(s string) ansi.Color {
}
}

return p.convert(c)
return p.Convert(c)
}

func hexToANSI256Color(c colorful.Color) ansi.ExtendedColor {
Expand Down
72 changes: 10 additions & 62 deletions style.go
Expand Up @@ -185,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.Sequence()
teSpace = p.Sequence()
teWhitespace = p.Sequence()

bold = s.getAsBool(boldKey, false)
italic = s.getAsBool(italicKey, false)
Expand Down Expand Up @@ -335,15 +335,15 @@ func (s Style) Render(strs ...string) string {
// Padding
if !inline {
if leftPadding > 0 {
var st *style
var st *Sequence
if colorWhitespace || styleWhitespace {
st = &teWhitespace
}
str = padLeft(str, leftPadding, st)
}

if rightPadding > 0 {
var st *style
var st *Sequence
if colorWhitespace || styleWhitespace {
st = &teWhitespace
}
Expand Down Expand Up @@ -371,7 +371,7 @@ func (s Style) Render(strs ...string) string {
numLines := strings.Count(str, "\n")

if !(numLines == 0 && width == 0) {
var st *style
var st *Sequence
if colorWhitespace || styleWhitespace {
st = &teWhitespace
}
Expand Down Expand Up @@ -429,7 +429,7 @@ func (s Style) applyMargins(str string, inline bool) string {
bottomMargin = s.getAsInt(marginBottomKey)
leftMargin = s.getAsInt(marginLeftKey)

styler = s.r.ColorProfile().string()
styler = s.r.ColorProfile().Sequence()
)

bgc := s.getAsColor(marginBackgroundKey)
Expand Down Expand Up @@ -458,19 +458,19 @@ func (s Style) applyMargins(str string, inline bool) string {
}

// Apply left padding.
func padLeft(str string, n int, style *style) string {
func padLeft(str string, n int, style *Sequence) string {
return pad(str, -n, style)
}

// Apply right padding.
func padRight(str string, n int, style *style) string {
func padRight(str string, n int, style *Sequence) 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 *style) string {
func pad(str string, n int, style *Sequence) string {
if n == 0 {
return str
}
Expand Down Expand Up @@ -524,55 +524,3 @@ 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}
}

0 comments on commit fd0951e

Please sign in to comment.