refactor(writer): restructure terminal writer

This commit is contained in:
Jan De Dobbeleer 2024-07-01 07:37:54 +02:00 committed by Jan De Dobbeleer
parent 74aaa9b9a2
commit 8e2c8eb6b1
9 changed files with 256 additions and 226 deletions

View file

@ -3,10 +3,10 @@ package cli
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
)
@ -69,9 +69,9 @@ Exports the config to an image file using customized output options.`,
writerColors := cfg.MakeColors()
writer := &terminal.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
TrueColor: env.CmdFlags.TrueColor,
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
TrueColor: env.CmdFlags.TrueColor,
}
writer.Init(shell.GENERIC)
eng := &engine.Engine{

View file

@ -4,11 +4,11 @@ import (
"fmt"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/jandedobbeleer/oh-my-posh/src/build"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
)
@ -42,10 +42,10 @@ var debugCmd = &cobra.Command{
writerColors := cfg.MakeColors()
writer := &terminal.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
Plain: plain,
TrueColor: env.CmdFlags.TrueColor,
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
Plain: plain,
TrueColor: env.CmdFlags.TrueColor,
}
writer.Init(shell.GENERIC)

View file

@ -69,9 +69,9 @@ func (b *Block) Init(env platform.Environment, writer *terminal.Writer) {
func (b *Block) InitPlain(env platform.Environment, config *Config) {
b.writer = &terminal.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, config.TerminalBackground),
AnsiColors: config.MakeColors(),
TrueColor: env.Flags().TrueColor,
BackgroundColor: shell.ConsoleBackgroundColor(env, config.TerminalBackground),
AnsiColors: config.MakeColors(),
TrueColor: env.Flags().TrueColor,
}
b.writer.Init(shell.GENERIC)

View file

@ -116,9 +116,9 @@ func engineRender() {
writerColors := cfg.MakeColors()
writer := &terminal.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
TrueColor: env.CmdFlags.TrueColor,
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
TrueColor: env.CmdFlags.TrueColor,
}
writer.Init(shell.GENERIC)
engine := &Engine{

View file

@ -25,10 +25,10 @@ func New(flags *platform.Flags) *Engine {
flags.HasTransient = cfg.TransientPrompt != nil
ansiWriter := &terminal.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: cfg.MakeColors(),
Plain: flags.Plain,
TrueColor: env.CmdFlags.TrueColor,
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: cfg.MakeColors(),
Plain: flags.Plain,
TrueColor: env.CmdFlags.TrueColor,
}
ansiWriter.Init(env.Shell())

View file

@ -112,7 +112,7 @@ func (e *Engine) Primary() string {
prompt += strings.Repeat(" ", space)
prompt += e.rprompt
prompt += writer.RestoreCursorPosition()
prompt = e.Writer.FormatText(prompt)
prompt = e.Writer.EscapeText(prompt)
e.write(prompt)
}

View file

@ -39,11 +39,11 @@ func (w *Writer) RenderItermFeatures(features ITermFeatures, sh, pwd, user, host
continue
}
result.WriteString(w.iTermPromptMark)
result.WriteString(w.formats.iTermPromptMark)
case CurrentDir:
result.WriteString(fmt.Sprintf(w.iTermCurrentDir, pwd))
result.WriteString(fmt.Sprintf(w.formats.iTermCurrentDir, pwd))
case RemoteHost:
result.WriteString(fmt.Sprintf(w.iTermRemoteHost, user, host))
result.WriteString(fmt.Sprintf(w.formats.iTermRemoteHost, user, host))
}
}

View file

@ -13,6 +13,18 @@ func init() {
runewidth.DefaultCondition.EastAsianWidth = false
}
type Colors struct {
Background string `json:"background" toml:"background"`
Foreground string `json:"foreground" toml:"foreground"`
}
type style struct {
AnchorStart string
AnchorEnd string
Start string
End string
}
var (
knownStyles = []*style{
{AnchorStart: `<b>`, AnchorEnd: `</b>`, Start: "\x1b[1m", End: "\x1b[22m"},
@ -24,20 +36,36 @@ var (
{AnchorStart: `<f>`, AnchorEnd: `</f>`, Start: "\x1b[5m", End: "\x1b[25m"},
{AnchorStart: `<r>`, AnchorEnd: `</r>`, Start: "\x1b[7m", End: "\x1b[27m"},
}
resetStyle = &style{AnchorStart: "RESET", AnchorEnd: `</>`, End: "\x1b[0m"}
backgroundStyle = &style{AnchorStart: "BACKGROUND", AnchorEnd: `</>`, End: "\x1b[49m"}
)
type style struct {
AnchorStart string
AnchorEnd string
Start string
End string
}
type shellFormats struct {
escape string
left string
linechange string
clearBelow string
clearLine string
type Colors struct {
Background string `json:"background" toml:"background"`
Foreground string `json:"foreground" toml:"foreground"`
title string
saveCursorPosition string
restoreCursorPosition string
osc99 string
osc7 string
osc51 string
escapeSequences map[rune]rune
hyperlinkStart string
hyperlinkCenter string
hyperlinkEnd string
iTermPromptMark string
iTermCurrentDir string
iTermRemoteHost string
}
const (
@ -54,11 +82,11 @@ const (
// Foreground takes the current segment's foreground color
Foreground = "foreground"
AnchorRegex = `^(?P<ANCHOR><(?P<FG>[^,<>]+)?,?(?P<BG>[^<>]+)?>)`
colorise = "\x1b[%sm"
transparent = "\x1b[0m\x1b[%s;49m\x1b[7m"
transparentEnd = "\x1b[27m"
backgroundEnd = "\x1b[49m"
AnchorRegex = `^(?P<ANCHOR><(?P<FG>[^,<>]+)?,?(?P<BG>[^<>]+)?>)`
colorise = "\x1b[%sm"
transparentStart = "\x1b[0m\x1b[%s;49m\x1b[7m"
transparentEnd = "\x1b[27m"
backgroundEnd = "\x1b[49m"
AnsiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
@ -78,10 +106,10 @@ const (
// Writer writes colorized ANSI strings
type Writer struct {
TerminalBackground string
Colors *Colors
ParentColors []*Colors
AnsiColors ColorString
BackgroundColor string
CurrentColors *Colors
ParentColors []*Colors
AnsiColors ColorString
Plain bool
TrueColor bool
@ -90,112 +118,96 @@ type Writer struct {
builder strings.Builder
length int
foreground Color
background Color
current ColorHistory
runes []rune
transparent bool
invisible bool
hyperlink bool
foregroundColor Color
backgroundColor Color
currentColor ColorHistory
runes []rune
shell string
format string
left string
title string
linechange string
clearBelow string
clearLine string
saveCursorPosition string
restoreCursorPosition string
escapeLeft string
escapeRight string
osc99 string
osc7 string
osc51 string
isTransparent bool
isInvisible bool
isHyperlink bool
lastRune rune
escapeSequences map[rune]rune
lastRune rune
hyperlinkStart string
hyperlinkCenter string
hyperlinkEnd string
shellName string
iTermPromptMark string
iTermCurrentDir string
iTermRemoteHost string
formats *shellFormats
}
func (w *Writer) Init(shellName string) {
w.shell = shellName
w.format = "%s"
switch w.shell {
w.shellName = shellName
switch w.shellName {
case shell.BASH:
w.format = "\\[%s\\]"
w.linechange = "\\[\x1b[%d%s\\]"
w.left = "\\[\x1b[%dD\\]"
w.clearBelow = "\\[\x1b[0J\\]"
w.clearLine = "\\[\x1b[K\\]"
w.saveCursorPosition = "\\[\x1b7\\]"
w.restoreCursorPosition = "\\[\x1b8\\]"
w.title = "\\[\x1b]0;%s\007\\]"
w.escapeLeft = "\\["
w.escapeRight = "\\]"
w.hyperlinkStart = "\\[\x1b]8;;"
w.hyperlinkCenter = "\x1b\\\\\\]"
w.hyperlinkEnd = "\\[\x1b]8;;\x1b\\\\\\]"
w.osc99 = "\\[\x1b]9;9;%s\x1b\\\\\\]"
w.osc7 = "\\[\x1b]7;file://%s/%s\x1b\\\\\\]"
w.osc51 = "\\[\x1b]51;A;%s@%s:%s\x1b\\\\\\]"
w.iTermPromptMark = "\\[$(iterm2_prompt_mark)\\]"
w.iTermCurrentDir = "\\[\x1b]1337;CurrentDir=%s\x07\\]"
w.iTermRemoteHost = "\\[\x1b]1337;RemoteHost=%s@%s\x07\\]"
w.escapeSequences = map[rune]rune{
96: 92, // backtick
92: 92, // backslash
w.formats = &shellFormats{
escape: "\\[%s\\]",
linechange: "\\[\x1b[%d%s\\]",
left: "\\[\x1b[%dD\\]",
clearBelow: "\\[\x1b[0J\\]",
clearLine: "\\[\x1b[K\\]",
saveCursorPosition: "\\[\x1b7\\]",
restoreCursorPosition: "\\[\x1b8\\]",
title: "\\[\x1b]0;%s\007\\]",
hyperlinkStart: "\\[\x1b]8;;",
hyperlinkCenter: "\x1b\\\\\\]",
hyperlinkEnd: "\\[\x1b]8;;\x1b\\\\\\]",
osc99: "\\[\x1b]9;9;%s\x1b\\\\\\]",
osc7: "\\[\x1b]7;file://%s/%s\x1b\\\\\\]",
osc51: "\\[\x1b]51;A;%s@%s:%s\x1b\\\\\\]",
iTermPromptMark: "\\[$(iterm2_prompt_mark)\\]",
iTermCurrentDir: "\\[\x1b]1337;CurrentDir=%s\x07\\]",
iTermRemoteHost: "\\[\x1b]1337;RemoteHost=%s@%s\x07\\]",
escapeSequences: map[rune]rune{
96: 92, // backtick
92: 92, // backslash
},
}
case shell.ZSH, shell.TCSH:
w.format = "%%{%s%%}"
w.linechange = "%%{\x1b[%d%s%%}"
w.left = "%%{\x1b[%dD%%}"
w.clearBelow = "%{\x1b[0J%}"
w.clearLine = "%{\x1b[K%}"
w.saveCursorPosition = "%{\x1b7%}"
w.restoreCursorPosition = "%{\x1b8%}"
w.title = "%%{\x1b]0;%s\007%%}"
w.escapeLeft = "%{"
w.escapeRight = "%}"
w.hyperlinkStart = "%{\x1b]8;;"
w.hyperlinkCenter = "\x1b\\%}"
w.hyperlinkEnd = "%{\x1b]8;;\x1b\\%}"
w.osc99 = "%%{\x1b]9;9;%s\x1b\\%%}"
w.osc7 = "%%{\x1b]7;file://%s/%s\x1b\\%%}"
w.osc51 = "%%{\x1b]51;A%s@%s:%s\x1b\\%%}"
w.iTermPromptMark = "%{$(iterm2_prompt_mark)%}"
w.iTermCurrentDir = "%%{\x1b]1337;CurrentDir=%s\x07%%}"
w.iTermRemoteHost = "%%{\x1b]1337;RemoteHost=%s@%s\x07%%}"
w.formats = &shellFormats{
escape: "%%{%s%%}",
linechange: "%%{\x1b[%d%s%%}",
left: "%%{\x1b[%dD%%}",
clearBelow: "%{\x1b[0J%}",
clearLine: "%{\x1b[K%}",
saveCursorPosition: "%{\x1b7%}",
restoreCursorPosition: "%{\x1b8%}",
title: "%%{\x1b]0;%s\007%%}",
hyperlinkStart: "%{\x1b]8;;",
hyperlinkCenter: "\x1b\\%}",
hyperlinkEnd: "%{\x1b]8;;\x1b\\%}",
osc99: "%%{\x1b]9;9;%s\x1b\\%%}",
osc7: "%%{\x1b]7;file://%s/%s\x1b\\%%}",
osc51: "%%{\x1b]51;A%s@%s:%s\x1b\\%%}",
iTermPromptMark: "%{$(iterm2_prompt_mark)%}",
iTermCurrentDir: "%%{\x1b]1337;CurrentDir=%s\x07%%}",
iTermRemoteHost: "%%{\x1b]1337;RemoteHost=%s@%s\x07%%}",
}
default:
w.linechange = "\x1b[%d%s"
w.left = "\x1b[%dD"
w.clearBelow = "\x1b[0J"
w.clearLine = "\x1b[K"
w.saveCursorPosition = "\x1b7"
w.restoreCursorPosition = "\x1b8"
w.title = "\x1b]0;%s\007"
// when in fish on Linux, it seems hyperlinks ending with \\ print a \
// unlike on macOS. However, this is a fish bug, so do not try to fix it here:
// https://github.com/JanDeDobbeleer/oh-my-posh/pull/3288#issuecomment-1369137068
w.hyperlinkStart = "\x1b]8;;"
w.hyperlinkCenter = "\x1b\\"
w.hyperlinkEnd = "\x1b]8;;\x1b\\"
w.osc99 = "\x1b]9;9;%s\x1b\\"
w.osc7 = "\x1b]7;file://%s/%s\x1b\\"
w.osc51 = "\x1b]51;A%s@%s:%s\x1b\\"
w.iTermCurrentDir = "\x1b]1337;CurrentDir=%s\x07"
w.iTermRemoteHost = "\x1b]1337;RemoteHost=%s@%s\x07"
w.formats = &shellFormats{
escape: "%s",
linechange: "\x1b[%d%s",
left: "\x1b[%dD",
clearBelow: "\x1b[0J",
clearLine: "\x1b[K",
saveCursorPosition: "\x1b7",
restoreCursorPosition: "\x1b8",
title: "\x1b]0;%s\007",
// when in fish on Linux, it seems hyperlinks ending with \\ print a \
// unlike on macOS. However, this is a fish bug, so do not try to fix it here:
// https://github.com/JanDeDobbeleer/oh-my-posh/pull/3288#issuecomment-1369137068
hyperlinkStart: "\x1b]8;;",
hyperlinkCenter: "\x1b\\",
hyperlinkEnd: "\x1b]8;;\x1b\\",
osc99: "\x1b]9;9;%s\x1b\\",
osc7: "\x1b]7;file://%s/%s\x1b\\",
osc51: "\x1b]51;A%s@%s:%s\x1b\\",
iTermCurrentDir: "\x1b]1337;CurrentDir=%s\x07",
iTermRemoteHost: "\x1b]1337;RemoteHost=%s@%s\x07",
}
}
if shellName == shell.ZSH {
w.escapeSequences = map[rune]rune{
w.formats.escapeSequences = map[rune]rune{
96: 92, // backtick
37: 37, // %
}
@ -203,7 +215,7 @@ func (w *Writer) Init(shellName string) {
}
func (w *Writer) SetColors(background, foreground string) {
w.Colors = &Colors{
w.CurrentColors = &Colors{
Background: background,
Foreground: foreground,
}
@ -213,6 +225,7 @@ func (w *Writer) SetParentColors(background, foreground string) {
if w.ParentColors == nil {
w.ParentColors = make([]*Colors, 0)
}
w.ParentColors = append([]*Colors{{
Background: background,
Foreground: foreground,
@ -231,7 +244,7 @@ func (w *Writer) ChangeLine(numberOfLines int) string {
numberOfLines = -numberOfLines
}
return fmt.Sprintf(w.linechange, numberOfLines, position)
return fmt.Sprintf(w.formats.linechange, numberOfLines, position)
}
func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string {
@ -245,13 +258,13 @@ func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string {
switch pwdType {
case OSC7:
return fmt.Sprintf(w.osc7, hostName, pwd)
return fmt.Sprintf(w.formats.osc7, hostName, pwd)
case OSC51:
return fmt.Sprintf(w.osc51, userName, hostName, pwd)
return fmt.Sprintf(w.formats.osc51, userName, hostName, pwd)
case OSC99:
fallthrough
default:
return fmt.Sprintf(w.osc99, pwd)
return fmt.Sprintf(w.formats.osc99, pwd)
}
}
@ -260,17 +273,18 @@ func (w *Writer) ClearAfter() string {
return ""
}
return w.clearLine + w.clearBelow
return w.formats.clearLine + w.formats.clearBelow
}
func (w *Writer) FormatTitle(title string) string {
title = w.trimAnsi(title)
if w.Plain {
return title
}
// we have to do this to prevent bash/zsh from misidentifying escape sequences
switch w.shell {
switch w.shellName {
case shell.BASH:
title = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(title)
case shell.ZSH:
@ -280,40 +294,42 @@ func (w *Writer) FormatTitle(title string) string {
return ""
}
return fmt.Sprintf(w.title, title)
return fmt.Sprintf(w.formats.title, title)
}
func (w *Writer) FormatText(text string) string {
return fmt.Sprintf(w.format, text)
func (w *Writer) EscapeText(text string) string {
return fmt.Sprintf(w.formats.escape, text)
}
func (w *Writer) SaveCursorPosition() string {
return w.saveCursorPosition
return w.formats.saveCursorPosition
}
func (w *Writer) RestoreCursorPosition() string {
return w.restoreCursorPosition
return w.formats.restoreCursorPosition
}
func (w *Writer) PromptStart() string {
return fmt.Sprintf(w.format, "\x1b]133;A\007")
return fmt.Sprintf(w.formats.escape, "\x1b]133;A\007")
}
func (w *Writer) CommandStart() string {
return fmt.Sprintf(w.format, "\x1b]133;B\007")
return fmt.Sprintf(w.formats.escape, "\x1b]133;B\007")
}
func (w *Writer) CommandFinished(code int, ignore bool) string {
if ignore {
return fmt.Sprintf(w.format, "\x1b]133;D\007")
return fmt.Sprintf(w.formats.escape, "\x1b]133;D\007")
}
mark := fmt.Sprintf("\x1b]133;D;%d\007", code)
return fmt.Sprintf(w.format, mark)
return fmt.Sprintf(w.formats.escape, mark)
}
func (w *Writer) LineBreak() string {
cr := fmt.Sprintf(w.left, 1000)
lf := fmt.Sprintf(w.linechange, 1, "B")
cr := fmt.Sprintf(w.formats.left, 1000)
lf := fmt.Sprintf(w.formats.linechange, 1, "B")
return cr + lf
}
@ -322,11 +338,13 @@ func (w *Writer) Write(background, foreground, text string) {
return
}
w.background, w.foreground = w.asAnsiColors(background, foreground)
w.backgroundColor, w.foregroundColor = w.asAnsiColors(background, foreground)
// default to white foreground
if w.foreground.IsEmpty() {
w.foreground = w.AnsiColors.ToColor("white", false, w.TrueColor)
if w.foregroundColor.IsEmpty() {
w.foregroundColor = w.AnsiColors.ToColor("white", false, w.TrueColor)
}
// validate if we start with a color override
match := regex.FindNamedRegexMatch(AnchorRegex, text)
if len(match) != 0 && match[ANCHOR] != hyperLinkStart {
@ -335,12 +353,13 @@ func (w *Writer) Write(background, foreground, text string) {
if match[ANCHOR] != style.AnchorStart {
continue
}
w.writeEscapedAnsiString(style.Start)
colorOverride = false
}
if colorOverride {
w.current.Add(w.asAnsiColors(match[BG], match[FG]))
w.currentColor.Add(w.asAnsiColors(match[BG], match[FG]))
}
}
@ -348,8 +367,8 @@ func (w *Writer) Write(background, foreground, text string) {
// print the hyperlink part AFTER the coloring
if match[ANCHOR] == hyperLinkStart {
w.hyperlink = true
w.builder.WriteString(w.hyperlinkStart)
w.isHyperlink = true
w.builder.WriteString(w.formats.hyperlinkStart)
}
text = text[len(match[ANCHOR]):]
@ -371,15 +390,15 @@ func (w *Writer) Write(background, foreground, text string) {
// check for hyperlinks first
switch match[ANCHOR] {
case hyperLinkStart:
w.hyperlink = true
w.isHyperlink = true
i += len([]rune(match[ANCHOR])) - 1
w.builder.WriteString(w.hyperlinkStart)
w.builder.WriteString(w.formats.hyperlinkStart)
continue
case hyperLinkText:
w.hyperlink = false
w.isHyperlink = false
i += len([]rune(match[ANCHOR])) - 1
hyperlinkTextPosition = i
w.builder.WriteString(w.hyperlinkCenter)
w.builder.WriteString(w.formats.hyperlinkCenter)
continue
case hyperLinkTextEnd:
// this implies there's no text in the hyperlink
@ -391,7 +410,7 @@ func (w *Writer) Write(background, foreground, text string) {
continue
case hyperLinkEnd:
i += len([]rune(match[ANCHOR])) - 1
w.builder.WriteString(w.hyperlinkEnd)
w.builder.WriteString(w.formats.hyperlinkEnd)
continue
}
@ -407,7 +426,7 @@ func (w *Writer) Write(background, foreground, text string) {
w.writeEscapedAnsiString(resetStyle.End)
// pop last color from the stack
w.current.Pop()
w.currentColor.Pop()
}
func (w *Writer) String() (string, int) {
@ -424,8 +443,8 @@ func (w *Writer) writeEscapedAnsiString(text string) {
return
}
if len(w.format) != 0 {
text = fmt.Sprintf(w.format, text)
if len(w.formats.escape) != 0 {
text = fmt.Sprintf(w.formats.escape, text)
}
w.builder.WriteString(text)
@ -436,17 +455,17 @@ func (w *Writer) getAnsiFromColorString(colorString string, isBackground bool) C
}
func (w *Writer) write(s rune) {
if w.invisible {
if w.isInvisible {
return
}
if w.hyperlink {
if w.isHyperlink {
w.builder.WriteRune(s)
return
}
if !w.Interactive {
for special, escape := range w.escapeSequences {
for special, escape := range w.formats.escapeSequences {
if s == special && w.lastRune != escape {
w.builder.WriteRune(escape)
}
@ -460,28 +479,28 @@ func (w *Writer) write(s rune) {
func (w *Writer) writeSegmentColors() {
// use correct starting colors
bg := w.background
fg := w.foreground
if !w.current.Background().IsEmpty() {
bg = w.current.Background()
bg := w.backgroundColor
fg := w.foregroundColor
if !w.currentColor.Background().IsEmpty() {
bg = w.currentColor.Background()
}
if !w.current.Foreground().IsEmpty() {
fg = w.current.Foreground()
if !w.currentColor.Foreground().IsEmpty() {
fg = w.currentColor.Foreground()
}
// ignore processing fully tranparent colors
w.invisible = fg.IsTransparent() && bg.IsTransparent()
if w.invisible {
w.isInvisible = fg.IsTransparent() && bg.IsTransparent()
if w.isInvisible {
return
}
if fg.IsTransparent() && len(w.TerminalBackground) != 0 { //nolint: gocritic
background := w.getAnsiFromColorString(w.TerminalBackground, false)
if fg.IsTransparent() && len(w.BackgroundColor) != 0 { //nolint: gocritic
background := w.getAnsiFromColorString(w.BackgroundColor, false)
w.writeEscapedAnsiString(fmt.Sprintf(colorise, background))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground()))
} else if fg.IsTransparent() && !bg.IsEmpty() {
w.transparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparent, bg))
w.isTransparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparentStart, bg))
} else {
if !bg.IsEmpty() && !bg.IsTransparent() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg))
@ -492,7 +511,7 @@ func (w *Writer) writeSegmentColors() {
}
// set current colors
w.current.Add(bg, fg)
w.currentColor.Add(bg, fg)
}
func (w *Writer) writeArchorOverride(match map[string]string, background string, i int) int {
@ -522,45 +541,45 @@ func (w *Writer) writeArchorOverride(match map[string]string, background string,
bg, fg := w.asAnsiColors(match[BG], match[FG])
// ignore processing fully tranparent colors
w.invisible = fg.IsTransparent() && bg.IsTransparent()
if w.invisible {
w.isInvisible = fg.IsTransparent() && bg.IsTransparent()
if w.isInvisible {
return position
}
// make sure we have colors
if fg.IsEmpty() {
fg = w.foreground
fg = w.foregroundColor
}
if bg.IsEmpty() {
bg = w.background
bg = w.backgroundColor
}
w.current.Add(bg, fg)
w.currentColor.Add(bg, fg)
if w.current.Foreground().IsTransparent() && len(w.TerminalBackground) != 0 {
background := w.getAnsiFromColorString(w.TerminalBackground, false)
if w.currentColor.Foreground().IsTransparent() && len(w.BackgroundColor) != 0 {
background := w.getAnsiFromColorString(w.BackgroundColor, false)
w.writeEscapedAnsiString(fmt.Sprintf(colorise, background))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.current.Background().ToForeground()))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Background().ToForeground()))
return position
}
if w.current.Foreground().IsTransparent() && !w.current.Background().IsTransparent() {
w.transparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparent, w.current.Background()))
if w.currentColor.Foreground().IsTransparent() && !w.currentColor.Background().IsTransparent() {
w.isTransparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparentStart, w.currentColor.Background()))
return position
}
if w.current.Background() != w.background {
if w.currentColor.Background() != w.backgroundColor {
// end the colors in case we have a transparent background
if w.current.Background().IsTransparent() {
if w.currentColor.Background().IsTransparent() {
w.writeEscapedAnsiString(backgroundEnd)
} else {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.current.Background()))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Background()))
}
}
if w.current.Foreground() != w.foreground {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.current.Foreground()))
if w.currentColor.Foreground() != w.foregroundColor {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Foreground()))
}
return position
@ -572,23 +591,23 @@ func (w *Writer) endColorOverride(position int) int {
// do not restore colors at the end of the string, we print it anyways
if position == len(w.runes)-1 {
w.current.Pop()
w.currentColor.Pop()
return position
}
// reset colors to previous when we have more than 1 in stack
// as soon as we have more than 1, we can pop the last one
// and print the previous override as it wasn't ended yet
if w.current.Len() > 1 {
fg := w.current.Foreground()
bg := w.current.Background()
if w.currentColor.Len() > 1 {
fg := w.currentColor.Foreground()
bg := w.currentColor.Background()
w.current.Pop()
w.currentColor.Pop()
previousBg := w.current.Background()
previousFg := w.current.Foreground()
previousBg := w.currentColor.Background()
previousFg := w.currentColor.Foreground()
if w.transparent {
if w.isTransparent {
w.writeEscapedAnsiString(transparentEnd)
}
@ -609,30 +628,30 @@ func (w *Writer) endColorOverride(position int) int {
}
// pop the last colors from the stack
defer w.current.Pop()
defer w.currentColor.Pop()
// do not reset when colors are identical
if w.current.Background() == w.background && w.current.Foreground() == w.foreground {
if w.currentColor.Background() == w.backgroundColor && w.currentColor.Foreground() == w.foregroundColor {
return position
}
if w.transparent {
if w.isTransparent {
w.writeEscapedAnsiString(transparentEnd)
}
if w.background.IsClear() {
if w.backgroundColor.IsClear() {
w.writeEscapedAnsiString(backgroundStyle.End)
}
if w.current.Background() != w.background && !w.background.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.background))
if w.currentColor.Background() != w.backgroundColor && !w.backgroundColor.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.backgroundColor))
}
if (w.current.Foreground() != w.foreground || w.transparent) && !w.foreground.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.foreground))
if (w.currentColor.Foreground() != w.foregroundColor || w.isTransparent) && !w.foregroundColor.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.foregroundColor))
}
w.transparent = false
w.isTransparent = false
return position
}
@ -640,14 +659,19 @@ func (w *Writer) asAnsiColors(background, foreground string) (Color, Color) {
if len(background) == 0 {
background = Background
}
if len(foreground) == 0 {
foreground = Foreground
}
background = w.expandKeyword(background)
foreground = w.expandKeyword(foreground)
inverted := foreground == Transparent && len(background) != 0
backgroundAnsi := w.getAnsiFromColorString(background, !inverted)
foregroundAnsi := w.getAnsiFromColorString(foreground, false)
return backgroundAnsi, foregroundAnsi
}
@ -678,30 +702,36 @@ func (w *Writer) expandKeyword(keyword string) string {
return keyword
}
}
if len(keyword) == 0 {
return Transparent
}
return keyword
}
resolveKeyword := func(keyword string) string {
switch {
case keyword == Background && w.Colors != nil:
return w.Colors.Background
case keyword == Foreground && w.Colors != nil:
return w.Colors.Foreground
case keyword == Background && w.CurrentColors != nil:
return w.CurrentColors.Background
case keyword == Foreground && w.CurrentColors != nil:
return w.CurrentColors.Foreground
case (keyword == ParentBackground || keyword == ParentForeground) && w.ParentColors != nil:
return resolveParentColor(keyword)
default:
return Transparent
}
}
for ok := w.isKeyword(keyword); ok; ok = w.isKeyword(keyword) {
resolved := resolveKeyword(keyword)
if resolved == keyword {
break
}
keyword = resolved
}
return keyword
}

View file

@ -222,11 +222,11 @@ func TestWriteANSIColors(t *testing.T) {
for _, tc := range cases {
renderer := &Writer{
ParentColors: []*Colors{tc.Parent},
Colors: tc.Colors,
TerminalBackground: tc.TerminalBackground,
AnsiColors: &DefaultColors{},
TrueColor: true,
ParentColors: []*Colors{tc.Parent},
CurrentColors: tc.Colors,
BackgroundColor: tc.TerminalBackground,
AnsiColors: &DefaultColors{},
TrueColor: true,
}
renderer.Init(shell.GENERIC)
renderer.Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
@ -276,9 +276,9 @@ func TestWriteLength(t *testing.T) {
for _, tc := range cases {
renderer := &Writer{
ParentColors: []*Colors{},
Colors: tc.Colors,
AnsiColors: &DefaultColors{},
ParentColors: []*Colors{},
CurrentColors: tc.Colors,
AnsiColors: &DefaultColors{},
}
renderer.Init(shell.GENERIC)
renderer.Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)