refactor: rename color module to ansi

This commit is contained in:
Jan De Dobbeleer 2023-01-04 20:44:29 +01:00 committed by Jan De Dobbeleer
parent 005445b9fe
commit 48d8a522bf
26 changed files with 697 additions and 716 deletions

501
src/ansi/ansi_writer.go Normal file
View file

@ -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: `<b>`, AnchorEnd: `</b>`, Start: "\x1b[1m", End: "\x1b[22m"},
{AnchorStart: `<u>`, AnchorEnd: `</u>`, Start: "\x1b[4m", End: "\x1b[24m"},
{AnchorStart: `<o>`, AnchorEnd: `</o>`, Start: "\x1b[53m", End: "\x1b[55m"},
{AnchorStart: `<i>`, AnchorEnd: `</i>`, Start: "\x1b[3m", End: "\x1b[23m"},
{AnchorStart: `<s>`, AnchorEnd: `</s>`, Start: "\x1b[9m", End: "\x1b[29m"},
{AnchorStart: `<d>`, AnchorEnd: `</d>`, Start: "\x1b[2m", End: "\x1b[22m"},
{AnchorStart: `<f>`, AnchorEnd: `</f>`, Start: "\x1b[5m", End: "\x1b[25m"},
{AnchorStart: `<r>`, AnchorEnd: `</r>`, 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<ANCHOR><(?P<FG>[^,>]+)?,?(?P<BG>[^>]+)?>)`
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<STR>\\\[\x1b\]8;;(.+)\x1b\\\\\\\](?P<TEXT>.+)\\\[\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<STR>%{\x1b]8;;(.+)\x1b\\%}(?P<TEXT>.+)%{\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<STR>\x1b]8;;(.+)\x1b\\\\\\\\?(?P<TEXT>.+)\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
}

View file

@ -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<ALL>(?:\\[(?P<TEXT>.+)\\])(?:\\((?P<URL>.*)\\)))", 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{

View file

@ -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)

View file

@ -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: "<b>test</b>",
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: "<b><#ffffff>test</></b>",
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><b>test</b></>",
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 <parentForeground>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 <black,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 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 <parentForeground>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 <parentForeground,parentBackground>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 <parentBackground,parentForeground>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, <red>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: "<foreground>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: "<blue,white>G</><red,white>o</><yellow,white>o</><blue,white>g</><green,white>l</><red,white>e</>",
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: "<background>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: "<background,foreground>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{},

View file

@ -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 {

View file

@ -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{}

View file

@ -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)),
}

View file

@ -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(),
}

View file

@ -1,4 +1,4 @@
package color
package ansi
import (
"fmt"

View file

@ -1,4 +1,4 @@
package color
package ansi
import (
"testing"

View file

@ -1,4 +1,4 @@
package color
package ansi
type Palettes struct {
Template string `json:"template,omitempty"`

View file

@ -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
}

View file

@ -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,

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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

View file

@ -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: `<b>`, AnchorEnd: `</b>`, Start: "\x1b[1m", End: "\x1b[22m"},
{AnchorStart: `<u>`, AnchorEnd: `</u>`, Start: "\x1b[4m", End: "\x1b[24m"},
{AnchorStart: `<o>`, AnchorEnd: `</o>`, Start: "\x1b[53m", End: "\x1b[55m"},
{AnchorStart: `<i>`, AnchorEnd: `</i>`, Start: "\x1b[3m", End: "\x1b[23m"},
{AnchorStart: `<s>`, AnchorEnd: `</s>`, Start: "\x1b[9m", End: "\x1b[29m"},
{AnchorStart: `<d>`, AnchorEnd: `</d>`, Start: "\x1b[2m", End: "\x1b[22m"},
{AnchorStart: `<f>`, AnchorEnd: `</f>`, Start: "\x1b[5m", End: "\x1b[25m"},
{AnchorStart: `<r>`, AnchorEnd: `</r>`, 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<ANCHOR><(?P<FG>[^,>]+)?,?(?P<BG>[^>]+)?>)`
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<STR>\\\[\x1b\]8;;(.+)\x1b\\\\\\\](?P<TEXT>.+)\\\[\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<STR>%{\x1b]8;;(.+)\x1b\\%}(?P<TEXT>.+)%{\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<STR>\x1b]8;;(.+)\x1b\\\\\\\\?(?P<TEXT>.+)\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
}

View file

@ -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()
}

View file

@ -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",

View file

@ -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",

View file

@ -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()

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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()

View file

@ -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,

View file

@ -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<color>#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|p:.*)`, colorString)