Proposal: pure NewStyle, Lip Gloss v2
aymanbagabas opened this issue · 0 comments
The issue
Currently, *Renderer
type and name are misleading. It does not render styles, rather, it's a factory for new styles. The question becomes, do we need a style factory?
On a lower level, type Renderer
does two things
- Detect the color profile of the terminal
- Detect the background color to determine whether it's light or dark
Some context before getting into the issue. A color profile is the terminal profile used to decide the terminal color capability i.e. whether it supports NoTTY (no colors or text decorations), Ascii (no colors), ANSI (4-bit), ANSI256 (16-bit), and TrueColor(RGB 24-bit).
The way Lip Gloss works right now is by having a global *Renderer
for os.Stdout
that is used for all new global styles i.e. lipgloss.NewStyle()
. This may produce different styles on different environments. For example, Apple Terminal only supports the ANSI256 profile and defining a style such as lipgloss.NewStyle().Foreground(lipgloss.Color("#874BFD"))
won't produce an RGB color ANSI sequence on Apple Terminal. Instead, Lip Gloss degrades the color to ANSI256 before generating the sequence.
The same goes for HasDarkBackground()
, a style needs to know the terminal background color to decide between a light or a dark color for the AdaptiveColor
type.
Why this is a problem?
This is not a problem for the average user. However, users who want to use Lip Gloss with Wish, or use it on a different output like os.Stderr
might face issues detecting the appropriate profile and background color.
- Name confusion, particularly
type Renderer
- Difficulty debugging things since detection is happening in the background auto-magically
- Code complexity using Lip Gloss Renderers
How can we solve this?
// Profile is a color profile: NoTTY, Ascii, ANSI, ANSI256, or TrueColor.
type Profile int
const (
// TrueColor, 24-bit color profile, produce RGB colors
TrueColor Profile = iota
// ANSI256, 8-bit color profile, degrades higher profile colors to 8-bit
ANSI256
// ANSI, 4-bit color profile, degrades higher profile colors to 4-bit
ANSI
// Ascii, disables style colors
Ascii // nolint: revive
// NoTTY, disables style colors and decorations
NoTTY
)
Given the introduction of color profiles in Lip Gloss, here's a proposal that might simplify this issue and make Lip Gloss "pure".
Embed the color profile and background color in type Style
:
type Style struct {
colorProfile Profile // Defaults to `TrueColor`
hasLightBackground bool // Terminals default to dark background i.e. hasLightBackground = false
... // other props
}
func (s Style) ColorProfile(p Profile) Style {
s.colorProfile = p
return s
}
func (s Style) HasLightBackground(v bool) Style {
s.hasLightBackground = v
return s
}
Then we would move the color profile and background detection to their functions
func DetectColorProfile(output io.Writer, environ []string) Profile
func QueryHasLightBackground(in term.File, out term.File) bool
We can also introduce a global default color profile that new styles inherit from and can be overwritten by the above functions and helpers
var (
// ColorProfile is the color profile used by lipgloss.
// This is the default color profile used to create new styles.
// By default, it allows for 24-bit color (TrueColor), decorations, and
// doesn't do color conversion.
ColorProfile Profile
// HasLightBackground is true if the terminal has a light background.
// This is the default value used to create new styles.
HasLightBackground bool
onceStdDefaults sync.Once
)
// UseDefault will set the default color profile and background color detection
// from the given terminal file descriptors and environment variables.
func UseDefault(in term.File, out term.File, env []string) {
ColorProfile = DetectColorProfile(out, env)
HasLightBackground = QueryHasLightBackground(in, out)
}
// UseStdDefaults will set the default color profile and background color
// detection from the standard input, output, and OS environment variables.
func UseStdDefaults() {
UseDefault(os.Stdin, os.Stdout, os.Environ())
}
Then NewStyle
can be defined as
// NewStyle returns a new, empty Style. While it's syntactic sugar for the
// Style{} primitive, it's recommended to use this function for creating styles
// in case the underlying implementation changes.
func NewStyle() Style {
onceStdDefaults.Do(UseStdDefaults)
return Style{
profile: ColorProfile,
hasLightBackground: HasLightBackground,
}
}
This users who wants to use a "pure" style can do lipgloss.Style{}
.
For more details checkout the proposal-v2-exp branch