oh-my-posh/src/color/writer.go

248 lines
7.1 KiB
Go
Raw Normal View History

2022-01-26 04:09:21 -08:00
package color
2020-12-17 23:59:45 -08:00
import (
"fmt"
"oh-my-posh/regex"
2020-12-17 23:59:45 -08:00
"strings"
)
2021-11-02 06:39:51 -07:00
const (
colorRegex = `<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`
)
2022-01-26 04:09:21 -08:00
type Writer interface {
Write(background, foreground, text string)
String() (string, int)
2022-01-26 04:09:21 -08:00
Reset()
SetColors(background, foreground string)
SetParentColors(background, foreground string)
ClearParentColors()
2021-04-20 12:30:46 -07:00
}
2022-01-26 04:09:21 -08:00
// AnsiWriter writes colorized ANSI strings
2021-11-02 06:39:51 -07:00
type AnsiWriter struct {
2022-01-26 04:09:21 -08:00
Ansi *Ansi
TerminalBackground string
Colors *Color
2021-12-16 01:57:01 -08:00
ParentColors []*Color
2022-01-26 04:09:21 -08:00
AnsiColors AnsiColors
builder strings.Builder
length int
2021-09-25 11:49:32 -07:00
}
type Color struct {
Background string
Foreground string
2020-12-17 23:59:45 -08:00
}
// AnsiColors is the interface that wraps AnsiColorFromString method.
//
// AnsiColorFromString gets the ANSI color code for a given color string.
// This can include a valid hex color in the format `#FFFFFF`,
// but also a name of one of the first 16 ANSI colors like `lightBlue`.
type AnsiColors interface {
AnsiColorFromString(colorString string, isBackground bool) AnsiColor
}
// AnsiColor is an ANSI color code ready to be printed to the console.
// Example: "38;2;255;255;255", "48;2;255;255;255", "31", "95".
type AnsiColor string
const (
emptyAnsiColor = AnsiColor("")
transparentAnsiColor = AnsiColor(Transparent)
)
func (c AnsiColor) IsEmpty() bool {
return c == emptyAnsiColor
}
func (c AnsiColor) IsTransparent() bool {
return c == transparentAnsiColor
}
func (c AnsiColor) ToForeground() AnsiColor {
colorString := string(c)
if strings.HasPrefix(colorString, "38;") {
return AnsiColor(strings.Replace(colorString, "38;", "48;", 1))
}
return c
}
2020-12-17 23:59:45 -08:00
const (
// Transparent implies a transparent color
Transparent = "transparent"
2022-05-22 00:11:16 -07:00
// Accent is the OS accent color
Accent = "accent"
// ParentBackground takes the previous segment's background color
ParentBackground = "parentBackground"
// ParentForeground takes the previous segment's color
ParentForeground = "parentForeground"
// Background takes the current segment's background color
Background = "background"
// Foreground takes the current segment's foreground color
Foreground = "foreground"
2020-12-17 23:59:45 -08:00
)
2022-01-26 04:09:21 -08:00
func (a *AnsiWriter) SetColors(background, foreground string) {
a.Colors = &Color{
Background: background,
Foreground: foreground,
}
}
2022-01-26 04:09:21 -08:00
func (a *AnsiWriter) SetParentColors(background, foreground string) {
2021-12-16 01:57:01 -08:00
if a.ParentColors == nil {
a.ParentColors = make([]*Color, 0)
}
a.ParentColors = append([]*Color{{
2021-09-25 11:49:32 -07:00
Background: background,
Foreground: foreground,
2021-12-16 01:57:01 -08:00
}}, a.ParentColors...)
2021-09-25 11:49:32 -07:00
}
2022-01-26 04:09:21 -08:00
func (a *AnsiWriter) ClearParentColors() {
a.ParentColors = nil
}
func (a *AnsiWriter) getAnsiFromColorString(colorString string, isBackground bool) AnsiColor {
2022-01-26 04:09:21 -08:00
return a.AnsiColors.AnsiColorFromString(colorString, isBackground)
2020-12-17 23:59:45 -08:00
}
func (a *AnsiWriter) writeColoredText(background, foreground AnsiColor, text string) {
// Avoid emitting empty strings with color codes
if text == "" || (foreground.IsTransparent() && background.IsTransparent()) {
return
}
a.length += a.Ansi.MeasureText(text)
2021-09-25 01:52:12 -07:00
// default to white fg if empty, empty backgrond is supported
if foreground.IsEmpty() {
2021-09-25 01:52:12 -07:00
foreground = a.getAnsiFromColorString("white", false)
}
2022-01-26 04:09:21 -08:00
if foreground.IsTransparent() && !background.IsEmpty() && len(a.TerminalBackground) != 0 {
bgAnsiColor := a.getAnsiFromColorString(a.TerminalBackground, false)
coloredText := fmt.Sprintf(a.Ansi.colorFull, background.ToForeground(), bgAnsiColor, text)
a.builder.WriteString(coloredText)
return
}
if foreground.IsTransparent() && !background.IsEmpty() {
2022-01-26 04:09:21 -08:00
coloredText := fmt.Sprintf(a.Ansi.colorTransparent, background, text)
a.builder.WriteString(coloredText)
return
} else if background.IsEmpty() || background.IsTransparent() {
2022-01-26 04:09:21 -08:00
coloredText := fmt.Sprintf(a.Ansi.colorSingle, foreground, text)
a.builder.WriteString(coloredText)
return
2020-12-17 23:59:45 -08:00
}
2022-01-26 04:09:21 -08:00
coloredText := fmt.Sprintf(a.Ansi.colorFull, background, foreground, text)
a.builder.WriteString(coloredText)
2020-12-17 23:59:45 -08:00
}
func (a *AnsiWriter) writeAndRemoveText(background, foreground AnsiColor, text, textToRemove, parentText string) string {
2020-12-17 23:59:45 -08:00
a.writeColoredText(background, foreground, text)
return strings.Replace(parentText, textToRemove, "", 1)
}
2022-01-26 04:09:21 -08:00
func (a *AnsiWriter) Write(background, foreground, text string) {
2021-06-08 10:29:09 -07:00
if len(text) == 0 {
return
}
2021-09-25 01:52:12 -07:00
bgAnsi, fgAnsi := a.asAnsiColors(background, foreground)
2022-01-26 04:09:21 -08:00
text = a.Ansi.formatText(text)
2022-04-03 11:38:02 -07:00
text = a.Ansi.GenerateHyperlink(text)
2020-12-17 23:59:45 -08:00
// first we match for any potentially valid colors enclosed in <>
// i.e., find color overrides
overrides := regex.FindAllNamedRegexMatch(colorRegex, text)
for _, override := range overrides {
fgOverride := override["foreground"]
bgOverride := override["background"]
if fgOverride == Transparent && len(bgOverride) == 0 {
bgOverride = background
2020-12-17 23:59:45 -08:00
}
bgOverrideAnsi, fgOverrideAnsi := a.asAnsiColors(bgOverride, fgOverride)
2021-09-25 01:52:12 -07:00
// set colors if they are empty
if bgOverrideAnsi.IsEmpty() {
bgOverrideAnsi = bgAnsi
2020-12-17 23:59:45 -08:00
}
if fgOverrideAnsi.IsEmpty() {
fgOverrideAnsi = fgAnsi
2020-12-17 23:59:45 -08:00
}
escapedTextSegment := override["text"]
innerText := override["content"]
2020-12-17 23:59:45 -08:00
textBeforeColorOverride := strings.Split(text, escapedTextSegment)[0]
2021-09-25 01:52:12 -07:00
text = a.writeAndRemoveText(bgAnsi, fgAnsi, textBeforeColorOverride, textBeforeColorOverride, text)
text = a.writeAndRemoveText(bgOverrideAnsi, fgOverrideAnsi, innerText, escapedTextSegment, text)
2020-12-17 23:59:45 -08:00
}
// color the remaining part of text with background and foreground
2021-09-25 01:52:12 -07:00
a.writeColoredText(bgAnsi, fgAnsi, text)
2020-12-17 23:59:45 -08:00
}
func (a *AnsiWriter) asAnsiColors(background, foreground string) (AnsiColor, AnsiColor) {
2021-12-15 11:09:17 -08:00
background = a.expandKeyword(background)
foreground = a.expandKeyword(foreground)
inverted := foreground == Transparent && len(background) != 0
backgroundAnsi := a.getAnsiFromColorString(background, !inverted)
foregroundAnsi := a.getAnsiFromColorString(foreground, false)
return backgroundAnsi, foregroundAnsi
}
2021-12-15 11:09:17 -08:00
func (a *AnsiWriter) isKeyword(color string) bool {
switch color {
case Transparent, ParentBackground, ParentForeground, Background, Foreground:
return true
default:
2021-12-15 11:09:17 -08:00
return false
}
}
func (a *AnsiWriter) expandKeyword(keyword string) string {
2021-12-16 01:57:01 -08:00
resolveParentColor := func(keyword string) string {
for _, color := range a.ParentColors {
if color == nil {
return Transparent
}
switch keyword {
case ParentBackground:
keyword = color.Background
case ParentForeground:
keyword = color.Foreground
default:
return keyword
}
}
return keyword
}
2021-12-15 11:09:17 -08:00
resolveKeyword := func(keyword string) string {
switch {
case keyword == Background && a.Colors != nil:
return a.Colors.Background
case keyword == Foreground && a.Colors != nil:
return a.Colors.Foreground
2021-12-16 01:57:01 -08:00
case (keyword == ParentBackground || keyword == ParentForeground) && a.ParentColors != nil:
return resolveParentColor(keyword)
2021-12-15 11:09:17 -08:00
default:
return Transparent
}
}
for ok := a.isKeyword(keyword); ok; ok = a.isKeyword(keyword) {
resolved := resolveKeyword(keyword)
if resolved == keyword {
break
}
keyword = resolved
}
2021-12-15 11:09:17 -08:00
return keyword
}
func (a *AnsiWriter) String() (string, int) {
return a.builder.String(), a.length
2020-12-17 23:59:45 -08:00
}
2022-01-26 04:09:21 -08:00
func (a *AnsiWriter) Reset() {
a.length = 0
a.builder.Reset()
2020-12-17 23:59:45 -08:00
}