2020-12-17 23:59:45 -08:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2021-11-02 06:39:51 -07:00
|
|
|
const (
|
|
|
|
colorRegex = `<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`
|
|
|
|
)
|
|
|
|
|
|
|
|
type promptWriter interface {
|
2021-04-20 12:30:46 -07:00
|
|
|
write(background, foreground, text string)
|
|
|
|
string() string
|
|
|
|
reset()
|
2021-10-25 10:22:58 -07:00
|
|
|
setColors(background, foreground string)
|
2021-09-25 11:49:32 -07:00
|
|
|
setParentColors(background, foreground string)
|
2021-10-30 02:39:08 -07:00
|
|
|
clearParentColors()
|
2021-04-20 12:30:46 -07:00
|
|
|
}
|
|
|
|
|
2021-11-02 06:39:51 -07:00
|
|
|
// AnsiWriter writes colorized strings
|
|
|
|
type AnsiWriter struct {
|
2021-03-14 06:14:08 -07:00
|
|
|
builder strings.Builder
|
2021-04-20 12:30:46 -07:00
|
|
|
ansi *ansiUtils
|
2021-03-14 06:14:08 -07:00
|
|
|
terminalBackground string
|
2021-10-25 10:22:58 -07:00
|
|
|
Colors *Color
|
|
|
|
ParentColors *Color
|
2021-11-22 06:25:56 -08:00
|
|
|
ansiColors AnsiColors
|
2021-09-25 11:49:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type Color struct {
|
|
|
|
Background string
|
|
|
|
Foreground string
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
|
|
|
|
2021-11-22 06:25:56 -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
|
|
|
|
}
|
|
|
|
|
2020-12-17 23:59:45 -08:00
|
|
|
const (
|
|
|
|
// Transparent implies a transparent color
|
|
|
|
Transparent = "transparent"
|
2021-11-06 05:13:45 -07:00
|
|
|
// ParentBackground takes the previous segment's background color
|
|
|
|
ParentBackground = "parentBackground"
|
|
|
|
// ParentForeground takes the previous segment's color
|
|
|
|
ParentForeground = "parentForeground"
|
2021-10-25 10:22:58 -07:00
|
|
|
// 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
|
|
|
)
|
|
|
|
|
2021-11-02 06:39:51 -07:00
|
|
|
func (a *AnsiWriter) setColors(background, foreground string) {
|
2021-10-25 10:22:58 -07:00
|
|
|
a.Colors = &Color{
|
|
|
|
Background: background,
|
|
|
|
Foreground: foreground,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-02 06:39:51 -07:00
|
|
|
func (a *AnsiWriter) setParentColors(background, foreground string) {
|
2021-10-25 10:22:58 -07:00
|
|
|
a.ParentColors = &Color{
|
2021-09-25 11:49:32 -07:00
|
|
|
Background: background,
|
|
|
|
Foreground: foreground,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-02 06:39:51 -07:00
|
|
|
func (a *AnsiWriter) clearParentColors() {
|
2021-10-30 02:39:08 -07:00
|
|
|
a.ParentColors = nil
|
|
|
|
}
|
|
|
|
|
2021-11-22 06:25:56 -08:00
|
|
|
func (a *AnsiWriter) getAnsiFromColorString(colorString string, isBackground bool) AnsiColor {
|
|
|
|
return a.ansiColors.AnsiColorFromString(colorString, isBackground)
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
|
|
|
|
2021-11-22 06:25:56 -08:00
|
|
|
func (a *AnsiWriter) writeColoredText(background, foreground AnsiColor, text string) {
|
2020-12-27 02:21:03 -08:00
|
|
|
// Avoid emitting empty strings with color codes
|
2021-11-22 06:25:56 -08:00
|
|
|
if text == "" || (foreground.IsTransparent() && background.IsTransparent()) {
|
2020-12-27 02:21:03 -08:00
|
|
|
return
|
|
|
|
}
|
2021-09-25 01:52:12 -07:00
|
|
|
// default to white fg if empty, empty backgrond is supported
|
2021-11-22 06:25:56 -08:00
|
|
|
if foreground.IsEmpty() {
|
2021-09-25 01:52:12 -07:00
|
|
|
foreground = a.getAnsiFromColorString("white", false)
|
|
|
|
}
|
2021-11-22 06:25:56 -08:00
|
|
|
if foreground.IsTransparent() && !background.IsEmpty() && len(a.terminalBackground) != 0 {
|
2021-03-14 06:14:08 -07:00
|
|
|
fgAnsiColor := a.getAnsiFromColorString(a.terminalBackground, false)
|
2021-09-25 01:52:12 -07:00
|
|
|
coloredText := fmt.Sprintf(a.ansi.colorFull, background, fgAnsiColor, text)
|
2021-03-14 06:14:08 -07:00
|
|
|
a.builder.WriteString(coloredText)
|
|
|
|
return
|
|
|
|
}
|
2021-11-22 06:25:56 -08:00
|
|
|
if foreground.IsTransparent() && !background.IsEmpty() {
|
2021-09-25 01:52:12 -07:00
|
|
|
coloredText := fmt.Sprintf(a.ansi.colorTransparent, background, text)
|
2021-03-14 06:14:08 -07:00
|
|
|
a.builder.WriteString(coloredText)
|
|
|
|
return
|
2021-11-22 06:25:56 -08:00
|
|
|
} else if background.IsEmpty() || background.IsTransparent() {
|
2021-09-25 01:52:12 -07:00
|
|
|
coloredText := fmt.Sprintf(a.ansi.colorSingle, foreground, text)
|
2021-03-14 06:14:08 -07:00
|
|
|
a.builder.WriteString(coloredText)
|
|
|
|
return
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
2021-09-25 01:52:12 -07:00
|
|
|
coloredText := fmt.Sprintf(a.ansi.colorFull, background, foreground, text)
|
2021-01-05 23:52:43 -08:00
|
|
|
a.builder.WriteString(coloredText)
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
|
|
|
|
2021-11-22 06:25:56 -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)
|
|
|
|
}
|
|
|
|
|
2021-11-02 06:39:51 -07: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
|
|
|
|
2021-11-22 06:25:56 -08:00
|
|
|
bgAnsi, fgAnsi := a.asAnsiColors(background, foreground)
|
2021-05-26 03:03:11 -07:00
|
|
|
text = a.ansi.escapeText(text)
|
2021-04-20 12:30:46 -07:00
|
|
|
text = a.ansi.formatText(text)
|
2021-05-26 03:03:11 -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 <>
|
2021-11-22 06:25:56 -08:00
|
|
|
// i.e., find color overrides
|
|
|
|
overrides := 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
|
|
|
}
|
2021-11-22 06:25:56 -08:00
|
|
|
bgOverrideAnsi, fgOverrideAnsi := a.asAnsiColors(bgOverride, fgOverride)
|
2021-09-25 01:52:12 -07:00
|
|
|
// set colors if they are empty
|
2021-11-22 06:25:56 -08:00
|
|
|
if bgOverrideAnsi.IsEmpty() {
|
|
|
|
bgOverrideAnsi = bgAnsi
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
2021-11-22 06:25:56 -08:00
|
|
|
if fgOverrideAnsi.IsEmpty() {
|
|
|
|
fgOverrideAnsi = fgAnsi
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
2021-11-22 06:25:56 -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)
|
2021-11-22 06:25:56 -08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-11-22 06:25:56 -08:00
|
|
|
func (a *AnsiWriter) asAnsiColors(background, foreground string) (AnsiColor, AnsiColor) {
|
|
|
|
if backgroundValue, ok := a.isKeyword(background); ok {
|
|
|
|
background = backgroundValue
|
|
|
|
}
|
|
|
|
if foregroundValue, ok := a.isKeyword(foreground); ok {
|
|
|
|
foreground = foregroundValue
|
|
|
|
}
|
|
|
|
inverted := foreground == Transparent && len(background) != 0
|
|
|
|
backgroundAnsi := a.getAnsiFromColorString(background, !inverted)
|
|
|
|
foregroundAnsi := a.getAnsiFromColorString(foreground, false)
|
|
|
|
return backgroundAnsi, foregroundAnsi
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *AnsiWriter) isKeyword(color string) (string, bool) {
|
|
|
|
switch {
|
2021-12-08 03:06:54 -08:00
|
|
|
case color == Background && a.Colors != nil:
|
2021-11-22 06:25:56 -08:00
|
|
|
return a.Colors.Background, true
|
2021-12-08 03:06:54 -08:00
|
|
|
case color == Foreground && a.Colors != nil:
|
2021-11-22 06:25:56 -08:00
|
|
|
return a.Colors.Foreground, true
|
|
|
|
case color == ParentBackground && a.ParentColors != nil:
|
|
|
|
return a.ParentColors.Background, true
|
|
|
|
case color == ParentForeground && a.ParentColors != nil:
|
|
|
|
return a.ParentColors.Foreground, true
|
|
|
|
case (color == ParentBackground || color == ParentForeground) && a.ParentColors == nil:
|
|
|
|
return Transparent, true
|
|
|
|
default:
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-02 06:39:51 -07:00
|
|
|
func (a *AnsiWriter) string() string {
|
2021-01-05 23:52:43 -08:00
|
|
|
return a.builder.String()
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
|
|
|
|
2021-11-02 06:39:51 -07:00
|
|
|
func (a *AnsiWriter) reset() {
|
2021-01-05 23:52:43 -08:00
|
|
|
a.builder.Reset()
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|