diff --git a/src/ansi/ansi_writer.go b/src/ansi/ansi_writer.go new file mode 100644 index 00000000..04e5d7d4 --- /dev/null +++ b/src/ansi/ansi_writer.go @@ -0,0 +1,501 @@ +package ansi + +import ( + "fmt" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/regex" + "github.com/jandedobbeleer/oh-my-posh/shell" + "github.com/mattn/go-runewidth" +) + +var ( + knownStyles = []*style{ + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[1m", End: "\x1b[22m"}, + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[4m", End: "\x1b[24m"}, + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[53m", End: "\x1b[55m"}, + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[3m", End: "\x1b[23m"}, + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[9m", End: "\x1b[29m"}, + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[2m", End: "\x1b[22m"}, + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[5m", End: "\x1b[25m"}, + {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[7m", End: "\x1b[27m"}, + } + colorStyle = &style{AnchorStart: "COLOR", AnchorEnd: ``, End: "\x1b[0m"} +) + +type style struct { + AnchorStart string + AnchorEnd string + Start string + End string +} + +type cachedColor struct { + Background string + Foreground string +} + +const ( + // Transparent implies a transparent color + Transparent = "transparent" + // 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" + + anchorRegex = `^(?P<(?P[^,>]+)?,?(?P[^>]+)?>)` + colorise = "\x1b[%sm" + transparent = "\x1b[%s;49m\x1b[7m" + + AnsiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" + + OSC99 string = "osc99" + OSC7 string = "osc7" + OSC51 string = "osc51" +) + +// Writer writes colorized ANSI strings +type Writer struct { + TerminalBackground string + Colors *cachedColor + ParentColors []*cachedColor + AnsiColors Colors + Plain bool + + builder strings.Builder + length int + + foreground Color + background Color + currentForeground Color + currentBackground Color + runes []rune + + shell string + format string + left string + right string + title string + linechange string + clearBelow string + clearLine string + saveCursorPosition string + restoreCursorPosition string + escapeLeft string + escapeRight string + hyperlink string + hyperlinkRegex string + osc99 string + osc7 string + osc51 string +} + +func (w *Writer) Init(shellName string) { + w.shell = shellName + switch w.shell { + case shell.BASH: + w.format = "\\[%s\\]" + w.linechange = "\\[\x1b[%d%s\\]" + w.right = "\\[\x1b[%dC\\]" + 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.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]" + w.hyperlinkRegex = `(?P\\\[\x1b\]8;;(.+)\x1b\\\\\\\](?P.+)\\\[\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\\\\\\]" + case "zsh": + w.format = "%%{%s%%}" + w.linechange = "%%{\x1b[%d%s%%}" + w.right = "%%{\x1b[%dC%%}" + 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.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}" + w.hyperlinkRegex = `(?P%{\x1b]8;;(.+)\x1b\\%}(?P.+)%{\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\\%%}" + default: + w.linechange = "\x1b[%d%s" + w.right = "\x1b[%dC" + 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.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\" + w.hyperlinkRegex = "(?P\x1b]8;;(.+)\x1b\\\\\\\\?(?P.+)\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\\" + } +} + +func (w *Writer) SetColors(background, foreground string) { + w.Colors = &cachedColor{ + Background: background, + Foreground: foreground, + } +} + +func (w *Writer) SetParentColors(background, foreground string) { + if w.ParentColors == nil { + w.ParentColors = make([]*cachedColor, 0) + } + w.ParentColors = append([]*cachedColor{{ + Background: background, + Foreground: foreground, + }}, w.ParentColors...) +} + +func (w *Writer) CarriageForward() string { + return fmt.Sprintf(w.right, 1000) +} + +func (w *Writer) GetCursorForRightWrite(length, offset int) string { + strippedLen := length + (-offset) + return fmt.Sprintf(w.left, strippedLen) +} + +func (w *Writer) ChangeLine(numberOfLines int) string { + if w.Plain { + return "" + } + position := "B" + if numberOfLines < 0 { + position = "F" + numberOfLines = -numberOfLines + } + return fmt.Sprintf(w.linechange, numberOfLines, position) +} + +func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string { + if w.Plain { + return "" + } + if strings.HasSuffix(pwd, ":") { + pwd += "\\" + } + switch pwdType { + case OSC7: + return fmt.Sprintf(w.osc7, hostName, pwd) + case OSC51: + return fmt.Sprintf(w.osc51, userName, hostName, pwd) + case OSC99: + fallthrough + default: + return fmt.Sprintf(w.osc99, pwd) + } +} + +func (w *Writer) ClearAfter() string { + if w.Plain { + return "" + } + return w.clearLine + w.clearBelow +} + +func (w *Writer) FormatTitle(title string) string { + title = w.trimAnsi(title) + // we have to do this to prevent bash/zsh from misidentifying escape sequences + switch w.shell { + case shell.BASH: + title = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(title) + case shell.ZSH: + title = strings.NewReplacer("`", "\\`", `%`, `%%`).Replace(title) + } + return fmt.Sprintf(w.title, title) +} + +func (w *Writer) FormatText(text string) string { + return fmt.Sprintf(w.format, text) +} + +func (w *Writer) SaveCursorPosition() string { + return w.saveCursorPosition +} + +func (w *Writer) RestoreCursorPosition() string { + return w.restoreCursorPosition +} + +func (w *Writer) LineBreak() string { + cr := fmt.Sprintf(w.left, 1000) + lf := fmt.Sprintf(w.linechange, 1, "B") + return cr + lf +} + +func (w *Writer) Write(background, foreground, text string) { + if len(text) == 0 { + return + } + + if !w.Plain { + text = w.GenerateHyperlink(text) + } + + w.background, w.foreground = w.asAnsiColors(background, foreground) + // default to white foreground + if w.foreground.IsEmpty() { + w.foreground = w.AnsiColors.AnsiColorFromString("white", false) + } + // validate if we start with a color override + match := regex.FindNamedRegexMatch(anchorRegex, text) + if len(match) != 0 { + colorOverride := true + for _, style := range knownStyles { + if match["ANCHOR"] != style.AnchorStart { + continue + } + w.printEscapedAnsiString(style.Start) + colorOverride = false + } + if colorOverride { + w.currentBackground, w.currentForeground = w.asAnsiColors(match["BG"], match["FG"]) + } + } + w.writeSegmentColors() + + text = text[len(match["ANCHOR"]):] + w.runes = []rune(text) + + for i := 0; i < len(w.runes); i++ { + s := w.runes[i] + // ignore everything which isn't overriding + if s != '<' { + w.length += runewidth.RuneWidth(s) + w.builder.WriteRune(s) + continue + } + + // color/end overrides first + text = string(w.runes[i:]) + match = regex.FindNamedRegexMatch(anchorRegex, text) + if len(match) > 0 { + i = w.writeColorOverrides(match, background, i) + continue + } + + w.length += runewidth.RuneWidth(s) + w.builder.WriteRune(s) + } + + w.printEscapedAnsiString(colorStyle.End) + + // reset current + w.currentBackground = "" + w.currentForeground = "" +} + +func (w *Writer) printEscapedAnsiString(text string) { + if w.Plain { + return + } + if len(w.format) == 0 { + w.builder.WriteString(text) + return + } + w.builder.WriteString(fmt.Sprintf(w.format, text)) +} + +func (w *Writer) getAnsiFromColorString(colorString string, isBackground bool) Color { + return w.AnsiColors.AnsiColorFromString(colorString, isBackground) +} + +func (w *Writer) writeSegmentColors() { + // use correct starting colors + bg := w.background + fg := w.foreground + if !w.currentBackground.IsEmpty() { + bg = w.currentBackground + } + if !w.currentForeground.IsEmpty() { + fg = w.currentForeground + } + + if fg.IsTransparent() && len(w.TerminalBackground) != 0 { + background := w.getAnsiFromColorString(w.TerminalBackground, false) + w.printEscapedAnsiString(fmt.Sprintf(colorise, background)) + w.printEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground())) + } else if fg.IsTransparent() && !bg.IsEmpty() { + w.printEscapedAnsiString(fmt.Sprintf(transparent, bg)) + } else { + if !bg.IsEmpty() && !bg.IsTransparent() { + w.printEscapedAnsiString(fmt.Sprintf(colorise, bg)) + } + if !fg.IsEmpty() { + w.printEscapedAnsiString(fmt.Sprintf(colorise, fg)) + } + } + + // set current colors + w.currentBackground = bg + w.currentForeground = fg +} + +func (w *Writer) writeColorOverrides(match map[string]string, background string, i int) (position int) { + position = i + // check color reset first + if match["ANCHOR"] == colorStyle.AnchorEnd { + // make sure to reset the colors if needed + position += len([]rune(colorStyle.AnchorEnd)) - 1 + // do not restore colors at the end of the string, we print it anyways + if position == len(w.runes)-1 { + return + } + if w.currentBackground != w.background { + w.printEscapedAnsiString(fmt.Sprintf(colorise, w.background)) + } + if w.currentForeground != w.foreground { + w.printEscapedAnsiString(fmt.Sprintf(colorise, w.foreground)) + } + return + } + + position += len([]rune(match["ANCHOR"])) - 1 + + for _, style := range knownStyles { + if style.AnchorEnd == match["ANCHOR"] { + w.printEscapedAnsiString(style.End) + return + } + if style.AnchorStart == match["ANCHOR"] { + w.printEscapedAnsiString(style.Start) + return + } + } + + if match["FG"] == Transparent && len(match["BG"]) == 0 { + match["BG"] = background + } + w.currentBackground, w.currentForeground = w.asAnsiColors(match["BG"], match["FG"]) + + // make sure we have colors + if w.currentForeground.IsEmpty() { + w.currentForeground = w.foreground + } + if w.currentBackground.IsEmpty() { + w.currentBackground = w.background + } + + if w.currentForeground.IsTransparent() && len(w.TerminalBackground) != 0 { + background := w.getAnsiFromColorString(w.TerminalBackground, false) + w.printEscapedAnsiString(fmt.Sprintf(colorise, background)) + w.printEscapedAnsiString(fmt.Sprintf(colorise, w.currentBackground.ToForeground())) + return + } + + if w.currentForeground.IsTransparent() && !w.currentBackground.IsTransparent() { + w.printEscapedAnsiString(fmt.Sprintf(transparent, w.currentBackground)) + return + } + + if w.currentBackground != w.background { + // end the colors in case we have a transparent background + if w.currentBackground.IsTransparent() { + w.printEscapedAnsiString(colorStyle.End) + } else { + w.printEscapedAnsiString(fmt.Sprintf(colorise, w.currentBackground)) + } + } + + if w.currentForeground != w.foreground || w.currentBackground.IsTransparent() { + w.printEscapedAnsiString(fmt.Sprintf(colorise, w.currentForeground)) + } + + return position +} + +func (w *Writer) asAnsiColors(background, foreground string) (Color, Color) { + 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 +} + +func (w *Writer) isKeyword(color string) bool { + switch color { + case Transparent, ParentBackground, ParentForeground, Background, Foreground: + return true + default: + return false + } +} + +func (w *Writer) expandKeyword(keyword string) string { + resolveParentColor := func(keyword string) string { + for _, color := range w.ParentColors { + if color == nil { + return Transparent + } + switch keyword { + case ParentBackground: + keyword = color.Background + case ParentForeground: + keyword = color.Foreground + default: + if len(keyword) == 0 { + return Transparent + } + 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 == 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 +} + +func (w *Writer) String() (string, int) { + defer func() { + w.length = 0 + w.builder.Reset() + }() + + return w.builder.String(), w.length +} diff --git a/src/color/ansi_writer_hyperlink.go b/src/ansi/ansi_writer_hyperlink.go similarity index 82% rename from src/color/ansi_writer_hyperlink.go rename to src/ansi/ansi_writer_hyperlink.go index bc47de15..e5b2d997 100644 --- a/src/color/ansi_writer_hyperlink.go +++ b/src/ansi/ansi_writer_hyperlink.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "fmt" @@ -8,7 +8,7 @@ import ( "github.com/jandedobbeleer/oh-my-posh/shell" ) -func (a *AnsiWriter) GenerateHyperlink(text string) string { +func (w *Writer) GenerateHyperlink(text string) string { const ( LINK = "link" TEXT = "text" @@ -60,7 +60,7 @@ func (a *AnsiWriter) GenerateHyperlink(text string) string { continue } // end of link part - result.WriteString(a.replaceHyperlink(hyperlink.String())) + result.WriteString(w.replaceHyperlink(hyperlink.String())) hyperlink.Reset() state = OTHER } @@ -70,21 +70,21 @@ func (a *AnsiWriter) GenerateHyperlink(text string) string { return result.String() } -func (a *AnsiWriter) replaceHyperlink(text string) string { +func (w *Writer) replaceHyperlink(text string) string { // hyperlink matching results := regex.FindNamedRegexMatch("(?P(?:\\[(?P.+)\\])(?:\\((?P.*)\\)))", text) if len(results) != 3 { return text } - linkText := a.escapeLinkTextForFishShell(results["TEXT"]) + linkText := w.escapeLinkTextForFishShell(results["TEXT"]) // build hyperlink ansi - hyperlink := fmt.Sprintf(a.hyperlink, results["URL"], linkText) + hyperlink := fmt.Sprintf(w.hyperlink, results["URL"], linkText) // replace original text by the new onex return strings.Replace(text, results["ALL"], hyperlink, 1) } -func (a *AnsiWriter) escapeLinkTextForFishShell(text string) string { - if a.shell != shell.FISH { +func (w *Writer) escapeLinkTextForFishShell(text string) string { + if w.shell != shell.FISH { return text } escapeChars := map[string]string{ diff --git a/src/color/ansi_writer_hyperlink_test.go b/src/ansi/ansi_writer_hyperlink_test.go similarity index 97% rename from src/color/ansi_writer_hyperlink_test.go rename to src/ansi/ansi_writer_hyperlink_test.go index e7f824a2..e81837f4 100644 --- a/src/color/ansi_writer_hyperlink_test.go +++ b/src/ansi/ansi_writer_hyperlink_test.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "testing" @@ -19,7 +19,7 @@ func TestGenerateHyperlinkNoUrl(t *testing.T) { {Text: "sample text with no url", ShellName: shell.BASH, Expected: "sample text with no url"}, } for _, tc := range cases { - a := AnsiWriter{} + a := Writer{} a.Init(tc.ShellName) hyperlinkText := a.GenerateHyperlink(tc.Text) assert.Equal(t, tc.Expected, hyperlinkText) @@ -52,7 +52,7 @@ func TestGenerateHyperlinkWithUrl(t *testing.T) { }, } for _, tc := range cases { - a := AnsiWriter{} + a := Writer{} a.Init(tc.ShellName) hyperlinkText := a.GenerateHyperlink(tc.Text) assert.Equal(t, tc.Expected, hyperlinkText) @@ -70,7 +70,7 @@ func TestGenerateHyperlinkWithUrlNoName(t *testing.T) { {Text: "[](http://www.google.be)", ShellName: shell.BASH, Expected: "[](http://www.google.be)"}, } for _, tc := range cases { - a := AnsiWriter{} + a := Writer{} a.Init(tc.ShellName) hyperlinkText := a.GenerateHyperlink(tc.Text) assert.Equal(t, tc.Expected, hyperlinkText) @@ -89,7 +89,7 @@ func TestGenerateFileLink(t *testing.T) { {Text: `[Windows](file:C:/Windows)`, Expected: "\x1b]8;;file:C:/Windows\x1b\\Windows\x1b]8;;\x1b\\"}, } for _, tc := range cases { - a := AnsiWriter{} + a := Writer{} a.Init(shell.PWSH) hyperlinkText := a.GenerateHyperlink(tc.Text) assert.Equal(t, tc.Expected, hyperlinkText) diff --git a/src/color/ansi_writer_test.go b/src/ansi/ansi_writer_test.go similarity index 65% rename from src/color/ansi_writer_test.go rename to src/ansi/ansi_writer_test.go index a1599c68..c6eccbd7 100644 --- a/src/color/ansi_writer_test.go +++ b/src/ansi/ansi_writer_test.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "testing" @@ -13,187 +13,187 @@ func TestWriteANSIColors(t *testing.T) { Case string Expected string Input string - Colors *Color - Parent *Color + Colors *cachedColor + Parent *cachedColor TerminalBackground string }{ { Case: "Bold", Input: "test", Expected: "\x1b[1m\x1b[30mtest\x1b[22m\x1b[0m", - Colors: &Color{Foreground: "black", Background: ParentBackground}, + Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, }, { Case: "Bold with color override", Input: "<#ffffff>test", Expected: "\x1b[1m\x1b[30m\x1b[38;2;255;255;255mtest\x1b[30m\x1b[22m\x1b[0m", - Colors: &Color{Foreground: "black", Background: ParentBackground}, + Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, }, { Case: "Bold with color override, flavor 2", Input: "<#ffffff>test", Expected: "\x1b[38;2;255;255;255m\x1b[1mtest\x1b[22m\x1b[0m", - Colors: &Color{Foreground: "black", Background: ParentBackground}, + Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, }, { Case: "Double override", Input: "<#ffffff>jan@<#ffffff>Jans-MBP", Expected: "\x1b[48;2;255;87;51m\x1b[38;2;255;255;255mjan\x1b[32m@\x1b[38;2;255;255;255mJans-MBP\x1b[0m", - Colors: &Color{Foreground: "green", Background: "#FF5733"}, + Colors: &cachedColor{Foreground: "green", Background: "#FF5733"}, }, { Case: "No color override", Input: "test", Expected: "\x1b[47m\x1b[30mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, - Parent: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, + Parent: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Inherit foreground", Input: "test", Expected: "\x1b[47m\x1b[33mtest\x1b[0m", - Colors: &Color{Foreground: ParentForeground, Background: "white"}, - Parent: &Color{Foreground: "yellow", Background: "white"}, + Colors: &cachedColor{Foreground: ParentForeground, Background: "white"}, + Parent: &cachedColor{Foreground: "yellow", Background: "white"}, }, { Case: "Inherit background", Input: "test", Expected: "\x1b[41m\x1b[30mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: ParentBackground}, - Parent: &Color{Foreground: "yellow", Background: "red"}, + Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Parent: &cachedColor{Foreground: "yellow", Background: "red"}, }, { Case: "No parent", Input: "test", Expected: "\x1b[30mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: ParentBackground}, + Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, }, { Case: "Inherit override foreground", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[33mworld\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, - Parent: &Color{Foreground: "yellow", Background: "red"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, + Parent: &cachedColor{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit override background", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, - Parent: &Color{Foreground: "yellow", Background: "red"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, + Parent: &cachedColor{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit override background, no foreground specified", Input: "hello <,parentBackground>world", Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, - Parent: &Color{Foreground: "yellow", Background: "red"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, + Parent: &cachedColor{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit no parent foreground", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[47;49m\x1b[7mworld\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Inherit no parent background", Input: "hello <,parentBackground>world", Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[30mworld\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Inherit override both", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[41m\x1b[33mworld\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, - Parent: &Color{Foreground: "yellow", Background: "red"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, + Parent: &cachedColor{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit override both inverted", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[43m\x1b[31mworld\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, - Parent: &Color{Foreground: "yellow", Background: "red"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, + Parent: &cachedColor{Foreground: "yellow", Background: "red"}, }, { Case: "Inline override", Input: "hello, world, rabbit", Expected: "\x1b[47m\x1b[30mhello, \x1b[31mworld\x1b[30m, rabbit\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Transparent background", Input: "hello world", Expected: "\x1b[37mhello world\x1b[0m", - Colors: &Color{Foreground: "white", Background: Transparent}, + Colors: &cachedColor{Foreground: "white", Background: Transparent}, }, { Case: "Transparent foreground override", Input: "hello <#ffffff>world", Expected: "\x1b[32mhello \x1b[38;2;255;255;255mworld\x1b[0m", - Colors: &Color{Foreground: "green", Background: Transparent}, + Colors: &cachedColor{Foreground: "green", Background: Transparent}, }, { Case: "No foreground", Input: "test", Expected: "\x1b[48;2;255;87;51m\x1b[37mtest\x1b[0m", - Colors: &Color{Foreground: "", Background: "#FF5733"}, + Colors: &cachedColor{Foreground: "", Background: "#FF5733"}, }, { Case: "Transparent foreground", Input: "test", Expected: "\x1b[38;2;255;87;51;49m\x1b[7mtest\x1b[0m", - Colors: &Color{Foreground: Transparent, Background: "#FF5733"}, + Colors: &cachedColor{Foreground: Transparent, Background: "#FF5733"}, }, { Case: "Transparent foreground, terminal background set", Input: "test", Expected: "\x1b[38;2;33;47;60m\x1b[48;2;255;87;51mtest\x1b[0m", - Colors: &Color{Foreground: Transparent, Background: "#FF5733"}, + Colors: &cachedColor{Foreground: Transparent, Background: "#FF5733"}, TerminalBackground: "#212F3C", }, { Case: "Foreground for foreground override", Input: "test", Expected: "\x1b[47m\x1b[30mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Background for background override", Input: "<,background>test", Expected: "\x1b[47m\x1b[30mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Google", Input: "Google", Expected: "\x1b[47m\x1b[34mG\x1b[40m\x1b[30m\x1b[47m\x1b[31mo\x1b[40m\x1b[30m\x1b[47m\x1b[33mo\x1b[40m\x1b[30m\x1b[47m\x1b[34mg\x1b[40m\x1b[30m\x1b[47m\x1b[32ml\x1b[40m\x1b[30m\x1b[47m\x1b[31me\x1b[0m", //nolint: lll - Colors: &Color{Foreground: "black", Background: "black"}, + Colors: &cachedColor{Foreground: "black", Background: "black"}, }, { Case: "Foreground for background override", Input: "test", Expected: "\x1b[47m\x1b[37mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Foreground for background vice versa override", Input: "test", Expected: "\x1b[40m\x1b[37mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, { Case: "Background for foreground override", Input: "<,foreground>test", Expected: "\x1b[40m\x1b[30mtest\x1b[0m", - Colors: &Color{Foreground: "black", Background: "white"}, + Colors: &cachedColor{Foreground: "black", Background: "white"}, }, } for _, tc := range cases { - renderer := &AnsiWriter{ - ParentColors: []*Color{tc.Parent}, + renderer := &Writer{ + ParentColors: []*cachedColor{tc.Parent}, Colors: tc.Colors, TerminalBackground: tc.TerminalBackground, AnsiColors: &DefaultColors{}, diff --git a/src/color/colors.go b/src/ansi/colors.go similarity index 75% rename from src/color/colors.go rename to src/ansi/colors.go index 95f7a2c6..e31f0d58 100644 --- a/src/color/colors.go +++ b/src/ansi/colors.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "fmt" @@ -10,41 +10,41 @@ import ( "github.com/gookit/color" ) -// AnsiColors is the interface that wraps AnsiColorFromString method. +// Colors 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 +type Colors interface { + AnsiColorFromString(colorString string, isBackground bool) Color } -// AnsiColor is an ANSI color code ready to be printed to the console. +// Color 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 +type Color string const ( - emptyAnsiColor = AnsiColor("") - transparentAnsiColor = AnsiColor(Transparent) + emptyColor = Color("") + transparentColor = Color(Transparent) ) -func (c AnsiColor) IsEmpty() bool { - return c == emptyAnsiColor +func (c Color) IsEmpty() bool { + return c == emptyColor } -func (c AnsiColor) IsTransparent() bool { - return c == transparentAnsiColor +func (c Color) IsTransparent() bool { + return c == transparentColor } -func (c AnsiColor) ToForeground() AnsiColor { +func (c Color) ToForeground() Color { colorString := string(c) if strings.HasPrefix(colorString, "38;") { - return AnsiColor(strings.Replace(colorString, "38;", "48;", 1)) + return Color(strings.Replace(colorString, "38;", "48;", 1)) } return c } -func MakeColors(palette Palette, cacheEnabled bool, accentColor string, env platform.Environment) (colors AnsiColors) { +func MakeColors(palette Palette, cacheEnabled bool, accentColor string, env platform.Environment) (colors Colors) { defaultColors := &DefaultColors{} defaultColors.SetAccentColor(env, accentColor) colors = defaultColors @@ -63,12 +63,12 @@ type RGB struct { // DefaultColors is the default AnsiColors implementation. type DefaultColors struct { - accent *Color + accent *cachedColor } var ( // Map for color names and their respective foreground [0] or background [1] color codes - ansiColorCodes = map[string][2]AnsiColor{ + ansiColorCodes = map[string][2]Color{ "black": {"30", "40"}, "red": {"31", "41"}, "green": {"32", "42"}, @@ -94,21 +94,21 @@ const ( backgroundIndex = 1 ) -func (d *DefaultColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor { +func (d *DefaultColors) AnsiColorFromString(colorString string, isBackground bool) Color { if len(colorString) == 0 { - return emptyAnsiColor + return emptyColor } if colorString == Transparent { - return transparentAnsiColor + return transparentColor } if colorString == Accent { if d.accent == nil { - return emptyAnsiColor + return emptyColor } if isBackground { - return AnsiColor(d.accent.Background) + return Color(d.accent.Background) } - return AnsiColor(d.accent.Foreground) + return Color(d.accent.Foreground) } colorFromName, err := getAnsiColorFromName(colorString, isBackground) if err == nil { @@ -117,25 +117,25 @@ func (d *DefaultColors) AnsiColorFromString(colorString string, isBackground boo if !strings.HasPrefix(colorString, "#") { val, err := strconv.ParseUint(colorString, 10, 64) if err != nil || val > 255 { - return emptyAnsiColor + return emptyColor } c256 := color.C256(uint8(val), isBackground) - return AnsiColor(c256.RGBColor().String()) + return Color(c256.RGBColor().String()) } style := color.HEX(colorString, isBackground) if !style.IsEmpty() { - return AnsiColor(style.String()) + return Color(style.String()) } if colorInt, err := strconv.ParseInt(colorString, 10, 8); err == nil { c := color.C256(uint8(colorInt), isBackground) - return AnsiColor(c.String()) + return Color(c.String()) } - return emptyAnsiColor + return emptyColor } // getAnsiColorFromName returns the color code for a given color name if the name is // known ANSI color name. -func getAnsiColorFromName(colorName string, isBackground bool) (AnsiColor, error) { +func getAnsiColorFromName(colorName string, isBackground bool) (Color, error) { if colorCodes, found := ansiColorCodes[colorName]; found { if isBackground { return colorCodes[backgroundIndex], nil @@ -153,14 +153,14 @@ func IsAnsiColorName(colorString string) bool { // PaletteColors is the AnsiColors Decorator that uses the Palette to do named color // lookups before ANSI color code generation. type PaletteColors struct { - ansiColors AnsiColors + ansiColors Colors palette Palette } -func (p *PaletteColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor { +func (p *PaletteColors) AnsiColorFromString(colorString string, isBackground bool) Color { paletteColor, err := p.palette.ResolveColor(colorString) if err != nil { - return emptyAnsiColor + return emptyColor } ansiColor := p.ansiColors.AnsiColorFromString(paletteColor, isBackground) return ansiColor @@ -170,8 +170,8 @@ func (p *PaletteColors) AnsiColorFromString(colorString string, isBackground boo // AnsiColorFromString calls are cheap, but not free, and having a simple cache in // has measurable positive effect on performance. type CachedColors struct { - ansiColors AnsiColors - colorCache map[cachedColorKey]AnsiColor + ansiColors Colors + colorCache map[cachedColorKey]Color } type cachedColorKey struct { @@ -179,9 +179,9 @@ type cachedColorKey struct { isBackground bool } -func (c *CachedColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor { +func (c *CachedColors) AnsiColorFromString(colorString string, isBackground bool) Color { if c.colorCache == nil { - c.colorCache = make(map[cachedColorKey]AnsiColor) + c.colorCache = make(map[cachedColorKey]Color) } key := cachedColorKey{colorString, isBackground} if ansiColor, hit := c.colorCache[key]; hit { diff --git a/src/color/colors_test.go b/src/ansi/colors_test.go similarity index 52% rename from src/color/colors_test.go rename to src/ansi/colors_test.go index b951c631..dae79950 100644 --- a/src/color/colors_test.go +++ b/src/ansi/colors_test.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "testing" @@ -9,20 +9,20 @@ import ( func TestGetAnsiFromColorString(t *testing.T) { cases := []struct { Case string - Expected AnsiColor + Expected Color Color string Background bool }{ - {Case: "256 color", Expected: AnsiColor("38;2;135;95;255"), Color: "99", Background: false}, - {Case: "256 color", Expected: AnsiColor("38;2;135;255;215"), Color: "122", Background: false}, - {Case: "Invalid background", Expected: emptyAnsiColor, Color: "invalid", Background: true}, - {Case: "Invalid background", Expected: emptyAnsiColor, Color: "invalid", Background: false}, - {Case: "Hex foreground", Expected: AnsiColor("38;2;170;187;204"), Color: "#AABBCC", Background: false}, - {Case: "Hex backgrond", Expected: AnsiColor("48;2;170;187;204"), Color: "#AABBCC", Background: true}, - {Case: "Base 8 foreground", Expected: AnsiColor("31"), Color: "red", Background: false}, - {Case: "Base 8 background", Expected: AnsiColor("41"), Color: "red", Background: true}, - {Case: "Base 16 foreground", Expected: AnsiColor("91"), Color: "lightRed", Background: false}, - {Case: "Base 16 backround", Expected: AnsiColor("101"), Color: "lightRed", Background: true}, + {Case: "256 color", Expected: Color("38;2;135;95;255"), Color: "99", Background: false}, + {Case: "256 color", Expected: Color("38;2;135;255;215"), Color: "122", Background: false}, + {Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: true}, + {Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: false}, + {Case: "Hex foreground", Expected: Color("38;2;170;187;204"), Color: "#AABBCC", Background: false}, + {Case: "Hex backgrond", Expected: Color("48;2;170;187;204"), Color: "#AABBCC", Background: true}, + {Case: "Base 8 foreground", Expected: Color("31"), Color: "red", Background: false}, + {Case: "Base 8 background", Expected: Color("41"), Color: "red", Background: true}, + {Case: "Base 16 foreground", Expected: Color("91"), Color: "lightRed", Background: false}, + {Case: "Base 16 backround", Expected: Color("101"), Color: "lightRed", Background: true}, } for _, tc := range cases { ansiColors := &DefaultColors{} diff --git a/src/color/colors_unix.go b/src/ansi/colors_unix.go similarity index 91% rename from src/color/colors_unix.go rename to src/ansi/colors_unix.go index 188a48e7..57407937 100644 --- a/src/color/colors_unix.go +++ b/src/ansi/colors_unix.go @@ -1,6 +1,6 @@ //go:build !windows -package color +package ansi import "github.com/jandedobbeleer/oh-my-posh/platform" @@ -12,7 +12,7 @@ func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor st if len(defaultColor) == 0 { return } - d.accent = &Color{ + d.accent = &cachedColor{ Foreground: string(d.AnsiColorFromString(defaultColor, false)), Background: string(d.AnsiColorFromString(defaultColor, true)), } diff --git a/src/color/colors_windows.go b/src/ansi/colors_windows.go similarity index 94% rename from src/color/colors_windows.go rename to src/ansi/colors_windows.go index b44e801b..744026d6 100644 --- a/src/color/colors_windows.go +++ b/src/ansi/colors_windows.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "errors" @@ -27,7 +27,7 @@ func GetAccentColor(env platform.Environment) (*RGB, error) { func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor string) { rgb, err := GetAccentColor(env) if err != nil { - d.accent = &Color{ + d.accent = &cachedColor{ Foreground: string(d.AnsiColorFromString(defaultColor, false)), Background: string(d.AnsiColorFromString(defaultColor, true)), } @@ -35,7 +35,7 @@ func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor st } foreground := color.RGB(rgb.R, rgb.G, rgb.B, false) background := color.RGB(rgb.R, rgb.G, rgb.B, true) - d.accent = &Color{ + d.accent = &cachedColor{ Foreground: foreground.String(), Background: background.String(), } diff --git a/src/color/palette.go b/src/ansi/palette.go similarity index 99% rename from src/color/palette.go rename to src/ansi/palette.go index 103864d5..1aa033f6 100644 --- a/src/color/palette.go +++ b/src/ansi/palette.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "fmt" diff --git a/src/color/palette_test.go b/src/ansi/palette_test.go similarity index 99% rename from src/color/palette_test.go rename to src/ansi/palette_test.go index 5a0b31e6..37e6ad83 100644 --- a/src/color/palette_test.go +++ b/src/ansi/palette_test.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "testing" diff --git a/src/color/palettes.go b/src/ansi/palettes.go similarity index 90% rename from src/color/palettes.go rename to src/ansi/palettes.go index a6019dc3..9cbd7fbb 100644 --- a/src/color/palettes.go +++ b/src/ansi/palettes.go @@ -1,4 +1,4 @@ -package color +package ansi type Palettes struct { Template string `json:"template,omitempty"` diff --git a/src/color/text.go b/src/ansi/text.go similarity index 82% rename from src/color/text.go rename to src/ansi/text.go index b47f68be..9b8fff85 100644 --- a/src/color/text.go +++ b/src/ansi/text.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "strings" @@ -12,7 +12,7 @@ func init() { //nolint:gochecknoinits runewidth.DefaultCondition.EastAsianWidth = false } -func (a *AnsiWriter) MeasureText(text string) int { +func (a *Writer) MeasureText(text string) int { // skip strings with ANSI if !strings.Contains(text, "\x1b") { text = a.TrimEscapeSequences(text) @@ -25,20 +25,20 @@ func (a *AnsiWriter) MeasureText(text string) int { text = strings.ReplaceAll(text, match["STR"], match["TEXT"]) } } - text = a.TrimAnsi(text) + text = a.trimAnsi(text) text = a.TrimEscapeSequences(text) length := runewidth.StringWidth(text) return length } -func (a *AnsiWriter) TrimAnsi(text string) string { +func (a *Writer) trimAnsi(text string) string { if len(text) == 0 || !strings.Contains(text, "\x1b") { return text } return regex.ReplaceAllString(AnsiRegex, text, "") } -func (a *AnsiWriter) TrimEscapeSequences(text string) string { +func (a *Writer) TrimEscapeSequences(text string) string { if len(text) == 0 { return text } diff --git a/src/color/text_test.go b/src/ansi/text_test.go similarity index 96% rename from src/color/text_test.go rename to src/ansi/text_test.go index 63935277..01e1e9c3 100644 --- a/src/color/text_test.go +++ b/src/ansi/text_test.go @@ -1,4 +1,4 @@ -package color +package ansi import ( "fmt" @@ -36,7 +36,7 @@ func TestMeasureText(t *testing.T) { shells := []string{shell.BASH, shell.ZSH, shell.GENERIC} for _, shell := range shells { for _, tc := range cases { - ansiWriter := &AnsiWriter{} + ansiWriter := &Writer{} ansiWriter.Init(shell) tmpl := &template.Text{ Template: tc.Template, diff --git a/src/cli/config_export_image.go b/src/cli/config_export_image.go index 6714deb5..0589e889 100644 --- a/src/cli/config_export_image.go +++ b/src/cli/config_export_image.go @@ -3,7 +3,7 @@ package cli import ( "fmt" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/engine" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/shell" @@ -58,7 +58,7 @@ Exports the config to an image file using customized output options.`, defer env.Close() cfg := engine.LoadConfig(env) writerColors := cfg.MakeColors() - writer := &color.AnsiWriter{ + writer := &ansi.Writer{ TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), AnsiColors: writerColors, } diff --git a/src/cli/debug.go b/src/cli/debug.go index 43b8e0f8..5ba40088 100644 --- a/src/cli/debug.go +++ b/src/cli/debug.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/engine" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/shell" @@ -33,7 +33,7 @@ var debugCmd = &cobra.Command{ defer env.Close() cfg := engine.LoadConfig(env) writerColors := cfg.MakeColors() - writer := &color.AnsiWriter{ + writer := &ansi.Writer{ TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), AnsiColors: writerColors, } diff --git a/src/cli/get.go b/src/cli/get.go index 913f624a..678be4e1 100644 --- a/src/cli/get.go +++ b/src/cli/get.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/platform" color2 "github.com/gookit/color" @@ -52,7 +52,7 @@ This command is used to get the value of the following variables: case "shell": fmt.Println(env.Shell()) case "accent": - rgb, err := color.GetAccentColor(env) + rgb, err := ansi.GetAccentColor(env) if err != nil { fmt.Println("error getting accent color:", err.Error()) return diff --git a/src/color/ansi_writer.go b/src/color/ansi_writer.go deleted file mode 100644 index 3c57b1cf..00000000 --- a/src/color/ansi_writer.go +++ /dev/null @@ -1,520 +0,0 @@ -package color - -import ( - "fmt" - "strings" - - "github.com/jandedobbeleer/oh-my-posh/regex" - "github.com/jandedobbeleer/oh-my-posh/shell" - "github.com/mattn/go-runewidth" -) - -type Writer interface { - Init(shellName string) - Write(background, foreground, text string) - String() (string, int) - SetColors(background, foreground string) - SetParentColors(background, foreground string) - CarriageForward() string - GetCursorForRightWrite(length, offset int) string - ChangeLine(numberOfLines int) string - ConsolePwd(pwdType, userName, hostName, pwd string) string - ClearAfter() string - FormatTitle(title string) string - FormatText(text string) string - SaveCursorPosition() string - RestoreCursorPosition() string - LineBreak() string - TrimAnsi(text string) string -} - -var ( - knownStyles = []*style{ - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[1m", End: "\x1b[22m"}, - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[4m", End: "\x1b[24m"}, - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[53m", End: "\x1b[55m"}, - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[3m", End: "\x1b[23m"}, - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[9m", End: "\x1b[29m"}, - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[2m", End: "\x1b[22m"}, - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[5m", End: "\x1b[25m"}, - {AnchorStart: ``, AnchorEnd: ``, Start: "\x1b[7m", End: "\x1b[27m"}, - } - colorStyle = &style{AnchorStart: "COLOR", AnchorEnd: ``, End: "\x1b[0m"} -) - -type style struct { - AnchorStart string - AnchorEnd string - Start string - End string -} - -type Color struct { - Background string - Foreground string -} - -const ( - // Transparent implies a transparent color - Transparent = "transparent" - // 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" - - anchorRegex = `^(?P<(?P[^,>]+)?,?(?P[^>]+)?>)` - colorise = "\x1b[%sm" - transparent = "\x1b[%s;49m\x1b[7m" - - AnsiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" - - OSC99 string = "osc99" - OSC7 string = "osc7" - OSC51 string = "osc51" -) - -// AnsiWriter writes colorized ANSI strings -type AnsiWriter struct { - TerminalBackground string - Colors *Color - ParentColors []*Color - AnsiColors AnsiColors - Plain bool - - builder strings.Builder - length int - - foreground AnsiColor - background AnsiColor - currentForeground AnsiColor - currentBackground AnsiColor - runes []rune - - shell string - format string - left string - right string - title string - linechange string - clearBelow string - clearLine string - saveCursorPosition string - restoreCursorPosition string - escapeLeft string - escapeRight string - hyperlink string - hyperlinkRegex string - osc99 string - osc7 string - osc51 string -} - -func (a *AnsiWriter) Init(shellName string) { - a.shell = shellName - switch a.shell { - case shell.BASH: - a.format = "\\[%s\\]" - a.linechange = "\\[\x1b[%d%s\\]" - a.right = "\\[\x1b[%dC\\]" - a.left = "\\[\x1b[%dD\\]" - a.clearBelow = "\\[\x1b[0J\\]" - a.clearLine = "\\[\x1b[K\\]" - a.saveCursorPosition = "\\[\x1b7\\]" - a.restoreCursorPosition = "\\[\x1b8\\]" - a.title = "\\[\x1b]0;%s\007\\]" - a.escapeLeft = "\\[" - a.escapeRight = "\\]" - a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]" - a.hyperlinkRegex = `(?P\\\[\x1b\]8;;(.+)\x1b\\\\\\\](?P.+)\\\[\x1b\]8;;\x1b\\\\\\\])` - a.osc99 = "\\[\x1b]9;9;\"%s\"\x1b\\\\\\]" - a.osc7 = "\\[\x1b]7;\"file://%s/%s\"\x1b\\\\\\]" - a.osc51 = "\\[\x1b]51;A;%s@%s:%s\x1b\\\\\\]" - case "zsh": - a.format = "%%{%s%%}" - a.linechange = "%%{\x1b[%d%s%%}" - a.right = "%%{\x1b[%dC%%}" - a.left = "%%{\x1b[%dD%%}" - a.clearBelow = "%{\x1b[0J%}" - a.clearLine = "%{\x1b[K%}" - a.saveCursorPosition = "%{\x1b7%}" - a.restoreCursorPosition = "%{\x1b8%}" - a.title = "%%{\x1b]0;%s\007%%}" - a.escapeLeft = "%{" - a.escapeRight = "%}" - a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}" - a.hyperlinkRegex = `(?P%{\x1b]8;;(.+)\x1b\\%}(?P.+)%{\x1b]8;;\x1b\\%})` - a.osc99 = "%%{\x1b]9;9;\"%s\"\x1b\\%%}" - a.osc7 = "%%{\x1b]7;file:\"//%s/%s\"\x1b\\%%}" - a.osc51 = "%%{\x1b]51;A%s@%s:%s\x1b\\%%}" - default: - a.linechange = "\x1b[%d%s" - a.right = "\x1b[%dC" - a.left = "\x1b[%dD" - a.clearBelow = "\x1b[0J" - a.clearLine = "\x1b[K" - a.saveCursorPosition = "\x1b7" - a.restoreCursorPosition = "\x1b8" - a.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 - a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\" - a.hyperlinkRegex = "(?P\x1b]8;;(.+)\x1b\\\\\\\\?(?P.+)\x1b]8;;\x1b\\\\)" - a.osc99 = "\x1b]9;9;\"%s\"\x1b\\" - a.osc7 = "\x1b]7;\"file://%s/%s\"\x1b\\" - a.osc51 = "\x1b]51;A%s@%s:%s\x1b\\" - } -} - -func (a *AnsiWriter) SetColors(background, foreground string) { - a.Colors = &Color{ - Background: background, - Foreground: foreground, - } -} - -func (a *AnsiWriter) SetParentColors(background, foreground string) { - if a.ParentColors == nil { - a.ParentColors = make([]*Color, 0) - } - a.ParentColors = append([]*Color{{ - Background: background, - Foreground: foreground, - }}, a.ParentColors...) -} - -func (a *AnsiWriter) CarriageForward() string { - return fmt.Sprintf(a.right, 1000) -} - -func (a *AnsiWriter) GetCursorForRightWrite(length, offset int) string { - strippedLen := length + (-offset) - return fmt.Sprintf(a.left, strippedLen) -} - -func (a *AnsiWriter) ChangeLine(numberOfLines int) string { - if a.Plain { - return "" - } - position := "B" - if numberOfLines < 0 { - position = "F" - numberOfLines = -numberOfLines - } - return fmt.Sprintf(a.linechange, numberOfLines, position) -} - -func (a *AnsiWriter) ConsolePwd(pwdType, userName, hostName, pwd string) string { - if a.Plain { - return "" - } - if strings.HasSuffix(pwd, ":") { - pwd += "\\" - } - switch pwdType { - case OSC7: - return fmt.Sprintf(a.osc7, hostName, pwd) - case OSC51: - return fmt.Sprintf(a.osc51, userName, hostName, pwd) - case OSC99: - fallthrough - default: - return fmt.Sprintf(a.osc99, pwd) - } -} - -func (a *AnsiWriter) ClearAfter() string { - if a.Plain { - return "" - } - return a.clearLine + a.clearBelow -} - -func (a *AnsiWriter) FormatTitle(title string) string { - title = a.TrimAnsi(title) - // we have to do this to prevent bash/zsh from misidentifying escape sequences - switch a.shell { - case shell.BASH: - title = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(title) - case shell.ZSH: - title = strings.NewReplacer("`", "\\`", `%`, `%%`).Replace(title) - } - return fmt.Sprintf(a.title, title) -} - -func (a *AnsiWriter) FormatText(text string) string { - return fmt.Sprintf(a.format, text) -} - -func (a *AnsiWriter) SaveCursorPosition() string { - return a.saveCursorPosition -} - -func (a *AnsiWriter) RestoreCursorPosition() string { - return a.restoreCursorPosition -} - -func (a *AnsiWriter) LineBreak() string { - cr := fmt.Sprintf(a.left, 1000) - lf := fmt.Sprintf(a.linechange, 1, "B") - return cr + lf -} - -func (a *AnsiWriter) Write(background, foreground, text string) { - if len(text) == 0 { - return - } - - if !a.Plain { - text = a.GenerateHyperlink(text) - } - - a.background, a.foreground = a.asAnsiColors(background, foreground) - // default to white foreground - if a.foreground.IsEmpty() { - a.foreground = a.AnsiColors.AnsiColorFromString("white", false) - } - // validate if we start with a color override - match := regex.FindNamedRegexMatch(anchorRegex, text) - if len(match) != 0 { - colorOverride := true - for _, style := range knownStyles { - if match["ANCHOR"] != style.AnchorStart { - continue - } - a.printEscapedAnsiString(style.Start) - colorOverride = false - } - if colorOverride { - a.currentBackground, a.currentForeground = a.asAnsiColors(match["BG"], match["FG"]) - } - } - a.writeSegmentColors() - - text = text[len(match["ANCHOR"]):] - a.runes = []rune(text) - - for i := 0; i < len(a.runes); i++ { - s := a.runes[i] - // ignore everything which isn't overriding - if s != '<' { - a.length += runewidth.RuneWidth(s) - a.builder.WriteRune(s) - continue - } - - // color/end overrides first - text = string(a.runes[i:]) - match = regex.FindNamedRegexMatch(anchorRegex, text) - if len(match) > 0 { - i = a.writeColorOverrides(match, background, i) - continue - } - - a.length += runewidth.RuneWidth(s) - a.builder.WriteRune(s) - } - - a.printEscapedAnsiString(colorStyle.End) - - // reset current - a.currentBackground = "" - a.currentForeground = "" -} - -func (a *AnsiWriter) printEscapedAnsiString(text string) { - if a.Plain { - return - } - if len(a.format) == 0 { - a.builder.WriteString(text) - return - } - a.builder.WriteString(fmt.Sprintf(a.format, text)) -} - -func (a *AnsiWriter) getAnsiFromColorString(colorString string, isBackground bool) AnsiColor { - return a.AnsiColors.AnsiColorFromString(colorString, isBackground) -} - -func (a *AnsiWriter) writeSegmentColors() { - // use correct starting colors - bg := a.background - fg := a.foreground - if !a.currentBackground.IsEmpty() { - bg = a.currentBackground - } - if !a.currentForeground.IsEmpty() { - fg = a.currentForeground - } - - if fg.IsTransparent() && len(a.TerminalBackground) != 0 { - background := a.getAnsiFromColorString(a.TerminalBackground, false) - a.printEscapedAnsiString(fmt.Sprintf(colorise, background)) - a.printEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground())) - } else if fg.IsTransparent() && !bg.IsEmpty() { - a.printEscapedAnsiString(fmt.Sprintf(transparent, bg)) - } else { - if !bg.IsEmpty() && !bg.IsTransparent() { - a.printEscapedAnsiString(fmt.Sprintf(colorise, bg)) - } - if !fg.IsEmpty() { - a.printEscapedAnsiString(fmt.Sprintf(colorise, fg)) - } - } - - // set current colors - a.currentBackground = bg - a.currentForeground = fg -} - -func (a *AnsiWriter) writeColorOverrides(match map[string]string, background string, i int) (position int) { - position = i - // check color reset first - if match["ANCHOR"] == colorStyle.AnchorEnd { - // make sure to reset the colors if needed - position += len([]rune(colorStyle.AnchorEnd)) - 1 - // do not restore colors at the end of the string, we print it anyways - if position == len(a.runes)-1 { - return - } - if a.currentBackground != a.background { - a.printEscapedAnsiString(fmt.Sprintf(colorise, a.background)) - } - if a.currentForeground != a.foreground { - a.printEscapedAnsiString(fmt.Sprintf(colorise, a.foreground)) - } - return - } - - position += len([]rune(match["ANCHOR"])) - 1 - - for _, style := range knownStyles { - if style.AnchorEnd == match["ANCHOR"] { - a.printEscapedAnsiString(style.End) - return - } - if style.AnchorStart == match["ANCHOR"] { - a.printEscapedAnsiString(style.Start) - return - } - } - - if match["FG"] == Transparent && len(match["BG"]) == 0 { - match["BG"] = background - } - a.currentBackground, a.currentForeground = a.asAnsiColors(match["BG"], match["FG"]) - - // make sure we have colors - if a.currentForeground.IsEmpty() { - a.currentForeground = a.foreground - } - if a.currentBackground.IsEmpty() { - a.currentBackground = a.background - } - - if a.currentForeground.IsTransparent() && len(a.TerminalBackground) != 0 { - background := a.getAnsiFromColorString(a.TerminalBackground, false) - a.printEscapedAnsiString(fmt.Sprintf(colorise, background)) - a.printEscapedAnsiString(fmt.Sprintf(colorise, a.currentBackground.ToForeground())) - return - } - - if a.currentForeground.IsTransparent() && !a.currentBackground.IsTransparent() { - a.printEscapedAnsiString(fmt.Sprintf(transparent, a.currentBackground)) - return - } - - if a.currentBackground != a.background { - // end the colors in case we have a transparent background - if a.currentBackground.IsTransparent() { - a.printEscapedAnsiString(colorStyle.End) - } else { - a.printEscapedAnsiString(fmt.Sprintf(colorise, a.currentBackground)) - } - } - - if a.currentForeground != a.foreground || a.currentBackground.IsTransparent() { - a.printEscapedAnsiString(fmt.Sprintf(colorise, a.currentForeground)) - } - - return position -} - -func (a *AnsiWriter) asAnsiColors(background, foreground string) (AnsiColor, AnsiColor) { - 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 -} - -func (a *AnsiWriter) isKeyword(color string) bool { - switch color { - case Transparent, ParentBackground, ParentForeground, Background, Foreground: - return true - default: - return false - } -} - -func (a *AnsiWriter) expandKeyword(keyword string) string { - 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: - if len(keyword) == 0 { - return Transparent - } - return keyword - } - } - if len(keyword) == 0 { - return Transparent - } - return keyword - } - 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 - case (keyword == ParentBackground || keyword == ParentForeground) && a.ParentColors != nil: - return resolveParentColor(keyword) - default: - return Transparent - } - } - for ok := a.isKeyword(keyword); ok; ok = a.isKeyword(keyword) { - resolved := resolveKeyword(keyword) - if resolved == keyword { - break - } - keyword = resolved - } - return keyword -} - -func (a *AnsiWriter) String() (string, int) { - defer func() { - a.length = 0 - a.builder.Reset() - }() - - return a.builder.String(), a.length -} diff --git a/src/engine/block.go b/src/engine/block.go index 96d345fb..10815177 100644 --- a/src/engine/block.go +++ b/src/engine/block.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/shell" ) @@ -52,19 +52,19 @@ type Block struct { MinWidth int `json:"min_width,omitempty"` env platform.Environment - writer color.Writer + writer *ansi.Writer activeSegment *Segment previousActiveSegment *Segment } -func (b *Block) Init(env platform.Environment, writer color.Writer) { +func (b *Block) Init(env platform.Environment, writer *ansi.Writer) { b.env = env b.writer = writer b.executeSegmentLogic() } func (b *Block) InitPlain(env platform.Environment, config *Config) { - b.writer = &color.AnsiWriter{ + b.writer = &ansi.Writer{ TerminalBackground: shell.ConsoleBackgroundColor(env, config.TerminalBackground), AnsiColors: config.MakeColors(), } @@ -141,14 +141,14 @@ func (b *Block) renderActiveSegment() { b.writePowerline(false) switch b.activeSegment.style() { case Plain, Powerline: - b.writer.Write(color.Background, color.Foreground, b.activeSegment.text) + b.writer.Write(ansi.Background, ansi.Foreground, b.activeSegment.text) case Diamond: - b.writer.Write(color.Transparent, color.Background, b.activeSegment.LeadingDiamond) - b.writer.Write(color.Background, color.Foreground, b.activeSegment.text) - b.writer.Write(color.Transparent, color.Background, b.activeSegment.TrailingDiamond) + b.writer.Write(ansi.Transparent, ansi.Background, b.activeSegment.LeadingDiamond) + b.writer.Write(ansi.Background, ansi.Foreground, b.activeSegment.text) + b.writer.Write(ansi.Transparent, ansi.Background, b.activeSegment.TrailingDiamond) case Accordion: if b.activeSegment.Enabled { - b.writer.Write(color.Background, color.Foreground, b.activeSegment.text) + b.writer.Write(ansi.Background, ansi.Foreground, b.activeSegment.text) } } b.previousActiveSegment = b.activeSegment @@ -169,12 +169,12 @@ func (b *Block) writePowerline(final bool) { if len(symbol) == 0 { return } - bgColor := color.Background + bgColor := ansi.Background if final || !b.activeSegment.isPowerline() { - bgColor = color.Transparent + bgColor = ansi.Transparent } if b.activeSegment.style() == Diamond && len(b.activeSegment.LeadingDiamond) == 0 { - bgColor = color.Background + bgColor = ansi.Background } if b.activeSegment.InvertPowerline { b.writer.Write(b.getPowerlineColor(), bgColor, symbol) @@ -185,7 +185,7 @@ func (b *Block) writePowerline(final bool) { func (b *Block) getPowerlineColor() string { if b.previousActiveSegment == nil { - return color.Transparent + return ansi.Transparent } if b.previousActiveSegment.style() == Diamond && len(b.previousActiveSegment.TrailingDiamond) == 0 { return b.previousActiveSegment.background() @@ -194,7 +194,7 @@ func (b *Block) getPowerlineColor() string { return b.previousActiveSegment.background() } if !b.previousActiveSegment.isPowerline() { - return color.Transparent + return ansi.Transparent } return b.previousActiveSegment.background() } diff --git a/src/engine/config.go b/src/engine/config.go index 93c7d118..a7ab48a7 100644 --- a/src/engine/config.go +++ b/src/engine/config.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/properties" "github.com/jandedobbeleer/oh-my-posh/segments" @@ -33,21 +33,21 @@ const ( // Config holds all the theme for rendering the prompt type Config struct { - Version int `json:"version"` - FinalSpace bool `json:"final_space,omitempty"` - ConsoleTitleTemplate string `json:"console_title_template,omitempty"` - TerminalBackground string `json:"terminal_background,omitempty"` - AccentColor string `json:"accent_color,omitempty"` - Blocks []*Block `json:"blocks,omitempty"` - Tooltips []*Segment `json:"tooltips,omitempty"` - TransientPrompt *Segment `json:"transient_prompt,omitempty"` - ValidLine *Segment `json:"valid_line,omitempty"` - ErrorLine *Segment `json:"error_line,omitempty"` - SecondaryPrompt *Segment `json:"secondary_prompt,omitempty"` - DebugPrompt *Segment `json:"debug_prompt,omitempty"` - Palette color.Palette `json:"palette,omitempty"` - Palettes *color.Palettes `json:"palettes,omitempty"` - PWD string `json:"pwd,omitempty"` + Version int `json:"version"` + FinalSpace bool `json:"final_space,omitempty"` + ConsoleTitleTemplate string `json:"console_title_template,omitempty"` + TerminalBackground string `json:"terminal_background,omitempty"` + AccentColor string `json:"accent_color,omitempty"` + Blocks []*Block `json:"blocks,omitempty"` + Tooltips []*Segment `json:"tooltips,omitempty"` + TransientPrompt *Segment `json:"transient_prompt,omitempty"` + ValidLine *Segment `json:"valid_line,omitempty"` + ErrorLine *Segment `json:"error_line,omitempty"` + SecondaryPrompt *Segment `json:"secondary_prompt,omitempty"` + DebugPrompt *Segment `json:"debug_prompt,omitempty"` + Palette ansi.Palette `json:"palette,omitempty"` + Palettes *ansi.Palettes `json:"palettes,omitempty"` + PWD string `json:"pwd,omitempty"` // Deprecated OSC99 bool `json:"osc99,omitempty"` @@ -63,12 +63,12 @@ type Config struct { // MakeColors creates instance of AnsiColors to use in AnsiWriter according to // environment and configuration. -func (cfg *Config) MakeColors() color.AnsiColors { +func (cfg *Config) MakeColors() ansi.Colors { cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1" - return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env) + return ansi.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env) } -func (cfg *Config) getPalette() color.Palette { +func (cfg *Config) getPalette() ansi.Palette { if cfg.Palettes == nil { return cfg.Palette } @@ -387,7 +387,7 @@ func defaultConfig(warning bool) *Config { }, }, ConsoleTitleTemplate: "{{ .Shell }} in {{ .Folder }}", - Palette: color.Palette{ + Palette: ansi.Palette{ "black": "#262B44", "blue": "#4B95E9", "green": "#59C9A5", diff --git a/src/engine/config_test.go b/src/engine/config_test.go index c21836a9..fcdf1c03 100644 --- a/src/engine/config_test.go +++ b/src/engine/config_test.go @@ -3,7 +3,7 @@ package engine import ( "testing" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/mock" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/segments" @@ -61,21 +61,21 @@ func TestEscapeGlyphs(t *testing.T) { } func TestGetPalette(t *testing.T) { - palette := color.Palette{ + palette := ansi.Palette{ "red": "#ff0000", "blue": "#0000ff", } cases := []struct { Case string - Palettes *color.Palettes - Palette color.Palette - ExpectedPalette color.Palette + Palettes *ansi.Palettes + Palette ansi.Palette + ExpectedPalette ansi.Palette }{ { Case: "match", - Palettes: &color.Palettes{ + Palettes: &ansi.Palettes{ Template: "{{ .Shell }}", - List: map[string]color.Palette{ + List: map[string]ansi.Palette{ "bash": palette, "zsh": { "red": "#ff0001", @@ -87,9 +87,9 @@ func TestGetPalette(t *testing.T) { }, { Case: "no match, no fallback", - Palettes: &color.Palettes{ + Palettes: &ansi.Palettes{ Template: "{{ .Shell }}", - List: map[string]color.Palette{ + List: map[string]ansi.Palette{ "fish": palette, "zsh": { "red": "#ff0001", @@ -101,9 +101,9 @@ func TestGetPalette(t *testing.T) { }, { Case: "no match, default", - Palettes: &color.Palettes{ + Palettes: &ansi.Palettes{ Template: "{{ .Shell }}", - List: map[string]color.Palette{ + List: map[string]ansi.Palette{ "zsh": { "red": "#ff0001", "blue": "#0000fb", diff --git a/src/engine/engine.go b/src/engine/engine.go index f4bf97b8..13559453 100644 --- a/src/engine/engine.go +++ b/src/engine/engine.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/shell" "github.com/jandedobbeleer/oh-my-posh/template" @@ -14,7 +14,7 @@ import ( type Engine struct { Config *Config Env platform.Environment - Writer color.Writer + Writer *ansi.Writer Plain bool console strings.Builder @@ -78,7 +78,7 @@ func (e *Engine) printPWD() { cwd := e.Env.Pwd() // Backwards compatibility for deprecated OSC99 if e.Config.OSC99 { - e.write(e.Writer.ConsolePwd(color.OSC99, "", "", cwd)) + e.write(e.Writer.ConsolePwd(ansi.OSC99, "", "", cwd)) return } // Allow template logic to define when to enable the PWD (when supported) @@ -304,7 +304,7 @@ func (e *Engine) print() string { } // in bash, the entire rprompt needs to be escaped for the prompt to be interpreted correctly // see https://github.com/jandedobbeleer/oh-my-posh/pull/2398 - writer := &color.AnsiWriter{} + writer := &ansi.Writer{} writer.Init(shell.GENERIC) prompt := writer.SaveCursorPosition() prompt += writer.CarriageForward() diff --git a/src/engine/engine_test.go b/src/engine/engine_test.go index 88189223..dc5e8c0e 100644 --- a/src/engine/engine_test.go +++ b/src/engine/engine_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/mock" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/shell" @@ -52,9 +52,9 @@ func TestPrintPWD(t *testing.T) { OSC99 bool }{ {Case: "Empty PWD"}, - {Case: "OSC99", PWD: color.OSC99, Expected: "\x1b]9;9;\"pwd\"\x1b\\"}, - {Case: "OSC7", PWD: color.OSC7, Expected: "\x1b]7;\"file://host/pwd\"\x1b\\"}, - {Case: "OSC51", PWD: color.OSC51, Expected: "\x1b]51;Auser@host:pwd\x1b\\"}, + {Case: "OSC99", PWD: ansi.OSC99, Expected: "\x1b]9;9;\"pwd\"\x1b\\"}, + {Case: "OSC7", PWD: ansi.OSC7, Expected: "\x1b]7;\"file://host/pwd\"\x1b\\"}, + {Case: "OSC51", PWD: ansi.OSC51, Expected: "\x1b]51;Auser@host:pwd\x1b\\"}, {Case: "Deprecated OSC99", OSC99: true, Expected: "\x1b]9;9;\"pwd\"\x1b\\"}, {Case: "Template (empty)", PWD: "{{ if eq .Shell \"pwsh\" }}osc7{{ end }}"}, {Case: "Template (non empty)", PWD: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;\"file://host/pwd\"\x1b\\"}, @@ -71,7 +71,7 @@ func TestPrintPWD(t *testing.T) { Shell: "shell", }) - writer := &color.AnsiWriter{} + writer := &ansi.Writer{} writer.Init(shell.GENERIC) engine := &Engine{ Env: env, @@ -102,7 +102,7 @@ func engineRender() { defer testClearDefaultConfig() writerColors := cfg.MakeColors() - writer := &color.AnsiWriter{ + writer := &ansi.Writer{ TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), AnsiColors: writerColors, } @@ -173,17 +173,17 @@ func TestGetTitle(t *testing.T) { PWD: tc.Cwd, Folder: "vagrant", }) - ansi := &color.AnsiWriter{} - ansi.Init(shell.GENERIC) + writer := &ansi.Writer{} + writer.Init(shell.GENERIC) engine := &Engine{ Config: &Config{ ConsoleTitleTemplate: tc.Template, }, - Writer: ansi, + Writer: writer, Env: env, } title := engine.getTitleTemplateText() - got := ansi.FormatTitle(title) + got := writer.FormatTitle(title) assert.Equal(t, tc.Expected, got) } } @@ -231,17 +231,17 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) { Root: tc.Root, HostName: "", }) - ansi := &color.AnsiWriter{} - ansi.Init(shell.GENERIC) + writer := &ansi.Writer{} + writer.Init(shell.GENERIC) engine := &Engine{ Config: &Config{ ConsoleTitleTemplate: tc.Template, }, - Writer: ansi, + Writer: writer, Env: env, } title := engine.getTitleTemplateText() - got := ansi.FormatTitle(title) + got := writer.FormatTitle(title) assert.Equal(t, tc.Expected, got) } } diff --git a/src/engine/image.go b/src/engine/image.go index 16b5d983..cccaaad3 100644 --- a/src/engine/image.go +++ b/src/engine/image.go @@ -30,7 +30,7 @@ import ( "strings" "unicode/utf8" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/regex" "github.com/esimov/stackblur-go" @@ -110,7 +110,7 @@ type ImageRenderer struct { CursorPadding int RPromptOffset int BgColor string - Ansi *color.AnsiWriter + Ansi *ansi.Writer Path string @@ -278,7 +278,7 @@ func (ir *ImageRenderer) lenWithoutANSI(text string) int { for _, match := range matches { text = strings.ReplaceAll(text, match[str], "") } - stripped := regex.ReplaceAllString(color.AnsiRegex, text, "") + stripped := regex.ReplaceAllString(ansi.AnsiRegex, text, "") length := utf8.RuneCountInString(stripped) for _, rune := range stripped { length += ir.runeAdditionalWidth(rune) diff --git a/src/engine/image_test.go b/src/engine/image_test.go index f2831d19..b989b192 100644 --- a/src/engine/image_test.go +++ b/src/engine/image_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/shell" "github.com/stretchr/testify/assert" @@ -33,11 +33,11 @@ func runImageTest(config, content string) (string, error) { return "", err } defer os.Remove(file.Name()) - ansi := &color.AnsiWriter{} - ansi.Init(shell.GENERIC) + writer := &ansi.Writer{} + writer.Init(shell.GENERIC) image := &ImageRenderer{ AnsiString: content, - Ansi: ansi, + Ansi: writer, } image.Init(config) err = image.SavePNG() diff --git a/src/engine/new.go b/src/engine/new.go index 5dcc10c1..4ba2fd92 100644 --- a/src/engine/new.go +++ b/src/engine/new.go @@ -1,7 +1,7 @@ package engine import ( - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/platform" "github.com/jandedobbeleer/oh-my-posh/shell" ) @@ -17,7 +17,7 @@ func New(flags *platform.Flags) *Engine { env.Init() cfg := LoadConfig(env) - ansiWriter := &color.AnsiWriter{ + ansiWriter := &ansi.Writer{ TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), AnsiColors: cfg.MakeColors(), Plain: flags.Plain, diff --git a/src/properties/properties.go b/src/properties/properties.go index 1793a83e..579c313b 100644 --- a/src/properties/properties.go +++ b/src/properties/properties.go @@ -3,7 +3,7 @@ package properties import ( "fmt" - "github.com/jandedobbeleer/oh-my-posh/color" + "github.com/jandedobbeleer/oh-my-posh/ansi" "github.com/jandedobbeleer/oh-my-posh/regex" ) @@ -70,7 +70,7 @@ func (m Map) GetColor(property Property, defaultValue string) string { return defaultValue } colorString := fmt.Sprint(val) - if color.IsAnsiColorName(colorString) { + if ansi.IsAnsiColorName(colorString) { return colorString } values := regex.FindNamedRegexMatch(`(?P#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|p:.*)`, colorString)