mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-31 22:07:25 -08:00
f1e699f5b3
resolves #3082
350 lines
8.9 KiB
Go
350 lines
8.9 KiB
Go
package color
|
|
|
|
import (
|
|
"fmt"
|
|
"oh-my-posh/regex"
|
|
"oh-my-posh/shell"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
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"
|
|
)
|
|
|
|
type Ansi struct {
|
|
title string
|
|
shell string
|
|
linechange string
|
|
left string
|
|
right string
|
|
creset string
|
|
clearBelow string
|
|
clearLine string
|
|
saveCursorPosition string
|
|
restoreCursorPosition string
|
|
colorSingle string
|
|
colorFull string
|
|
colorTransparent string
|
|
escapeLeft string
|
|
escapeRight string
|
|
hyperlink string
|
|
hyperlinkRegex string
|
|
osc99 string
|
|
osc7 string
|
|
bold string
|
|
italic string
|
|
underline string
|
|
overline string
|
|
strikethrough string
|
|
blink string
|
|
reverse string
|
|
dimmed string
|
|
format string
|
|
}
|
|
|
|
func (a *Ansi) Init(shellName string) {
|
|
a.shell = shellName
|
|
switch shellName {
|
|
case shell.ZSH:
|
|
a.format = "%%{%s%%}"
|
|
a.linechange = "%%{\x1b[%d%s%%}"
|
|
a.right = "%%{\x1b[%dC%%}"
|
|
a.left = "%%{\x1b[%dD%%}"
|
|
a.creset = "%{\x1b[0m%}"
|
|
a.clearBelow = "%{\x1b[0J%}"
|
|
a.clearLine = "%{\x1b[K%}"
|
|
a.saveCursorPosition = "%{\x1b7%}"
|
|
a.restoreCursorPosition = "%{\x1b8%}"
|
|
a.title = "%%{\x1b]0;%s\007%%}"
|
|
a.colorSingle = "%%{\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
|
a.colorFull = "%%{\x1b[%sm\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
|
a.colorTransparent = "%%{\x1b[%s;49m\x1b[7m%%}%s%%{\x1b[0m%%}"
|
|
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.bold = "%%{\x1b[1m%%}%s%%{\x1b[22m%%}"
|
|
a.italic = "%%{\x1b[3m%%}%s%%{\x1b[23m%%}"
|
|
a.underline = "%%{\x1b[4m%%}%s%%{\x1b[24m%%}"
|
|
a.overline = "%%{\x1b[53m%%}%s%%{\x1b[55m%%}"
|
|
a.blink = "%%{\x1b[5m%%}%s%%{\x1b[25m%%}"
|
|
a.reverse = "%%{\x1b[7m%%}%s%%{\x1b[27m%%}"
|
|
a.dimmed = "%%{\x1b[2m%%}%s%%{\x1b[22m%%}"
|
|
a.strikethrough = "%%{\x1b[9m%%}%s%%{\x1b[29m%%}"
|
|
case shell.BASH:
|
|
a.format = "\\[%s\\]"
|
|
a.linechange = "\\[\x1b[%d%s\\]"
|
|
a.right = "\\[\x1b[%dC\\]"
|
|
a.left = "\\[\x1b[%dD\\]"
|
|
a.creset = "\\[\x1b[0m\\]"
|
|
a.clearBelow = "\\[\x1b[0J\\]"
|
|
a.clearLine = "\\[\x1b[K\\]"
|
|
a.saveCursorPosition = "\\[\x1b7\\]"
|
|
a.restoreCursorPosition = "\\[\x1b8\\]"
|
|
a.title = "\\[\x1b]0;%s\007\\]"
|
|
a.colorSingle = "\\[\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
|
a.colorFull = "\\[\x1b[%sm\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
|
a.colorTransparent = "\\[\x1b[%s;49m\x1b[7m\\]%s\\[\x1b[0m\\]"
|
|
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.bold = "\\[\x1b[1m\\]%s\\[\x1b[22m\\]"
|
|
a.italic = "\\[\x1b[3m\\]%s\\[\x1b[23m\\]"
|
|
a.underline = "\\[\x1b[4m\\]%s\\[\x1b[24m\\]"
|
|
a.overline = "\\[\x1b[53m\\]%s\\[\x1b[55m\\]"
|
|
a.blink = "\\[\x1b[5m\\]%s\\[\x1b[25m\\]"
|
|
a.reverse = "\\[\x1b[7m\\]%s\\[\x1b[27m\\]"
|
|
a.dimmed = "\\[\x1b[2m\\]%s\\[\x1b[22m\\]"
|
|
a.strikethrough = "\\[\x1b[9m\\]%s\\[\x1b[29m\\]"
|
|
default:
|
|
a.format = "%s"
|
|
a.linechange = "\x1b[%d%s"
|
|
a.right = "\x1b[%dC"
|
|
a.left = "\x1b[%dD"
|
|
a.creset = "\x1b[0m"
|
|
a.clearBelow = "\x1b[0J"
|
|
a.clearLine = "\x1b[K"
|
|
a.saveCursorPosition = "\x1b7"
|
|
a.restoreCursorPosition = "\x1b8"
|
|
a.title = "\x1b]0;%s\007"
|
|
a.colorSingle = "\x1b[%sm%s\x1b[0m"
|
|
a.colorFull = "\x1b[%sm\x1b[%sm%s\x1b[0m"
|
|
a.colorTransparent = "\x1b[%s;49m\x1b[7m%s\x1b[0m"
|
|
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.bold = "\x1b[1m%s\x1b[22m"
|
|
a.italic = "\x1b[3m%s\x1b[23m"
|
|
a.underline = "\x1b[4m%s\x1b[24m"
|
|
a.overline = "\x1b[53m%s\x1b[55m"
|
|
a.blink = "\x1b[5m%s\x1b[25m"
|
|
a.reverse = "\x1b[7m%s\x1b[27m"
|
|
a.dimmed = "\x1b[2m%s\x1b[22m"
|
|
a.strikethrough = "\x1b[9m%s\x1b[29m"
|
|
}
|
|
}
|
|
|
|
func (a *Ansi) InitPlain() {
|
|
a.Init(shell.PLAIN)
|
|
}
|
|
|
|
func (a *Ansi) GenerateHyperlink(text string) string {
|
|
const (
|
|
LINK = "link"
|
|
TEXT = "text"
|
|
OTHER = "plain"
|
|
)
|
|
|
|
var result, hyperlink strings.Builder
|
|
var squareIndex, roundCount int
|
|
state := OTHER
|
|
|
|
for i, s := range text {
|
|
if s == '[' && state == OTHER {
|
|
state = TEXT
|
|
hyperlink.WriteRune(s)
|
|
continue
|
|
}
|
|
|
|
if state == OTHER {
|
|
result.WriteRune(s)
|
|
continue
|
|
}
|
|
|
|
hyperlink.WriteRune(s)
|
|
|
|
switch s {
|
|
case ']':
|
|
// potential end of text part of hyperlink
|
|
squareIndex = i
|
|
case '(':
|
|
// split into link part
|
|
if squareIndex == i-1 {
|
|
state = LINK
|
|
}
|
|
if state == LINK {
|
|
roundCount++
|
|
}
|
|
case ')':
|
|
if state != LINK {
|
|
continue
|
|
}
|
|
roundCount--
|
|
if roundCount != 0 {
|
|
continue
|
|
}
|
|
// end of link part
|
|
result.WriteString(a.replaceHyperlink(hyperlink.String()))
|
|
hyperlink.Reset()
|
|
state = OTHER
|
|
}
|
|
}
|
|
|
|
result.WriteString(hyperlink.String())
|
|
return result.String()
|
|
}
|
|
|
|
func (a *Ansi) 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"])
|
|
// build hyperlink ansi
|
|
hyperlink := fmt.Sprintf(a.hyperlink, results["URL"], linkText)
|
|
// replace original text by the new onex
|
|
return strings.Replace(text, results["ALL"], hyperlink, 1)
|
|
}
|
|
|
|
func (a *Ansi) escapeLinkTextForFishShell(text string) string {
|
|
if a.shell != shell.FISH {
|
|
return text
|
|
}
|
|
escapeChars := map[string]string{
|
|
`c`: `\c`,
|
|
`a`: `\a`,
|
|
`b`: `\b`,
|
|
`e`: `\e`,
|
|
`f`: `\f`,
|
|
`n`: `\n`,
|
|
`r`: `\r`,
|
|
`t`: `\t`,
|
|
`v`: `\v`,
|
|
`$`: `\$`,
|
|
`*`: `\*`,
|
|
`?`: `\?`,
|
|
`~`: `\~`,
|
|
`%`: `\%`,
|
|
`#`: `\#`,
|
|
`(`: `\(`,
|
|
`)`: `\)`,
|
|
`{`: `\{`,
|
|
`}`: `\}`,
|
|
`[`: `\[`,
|
|
`]`: `\]`,
|
|
`<`: `\<`,
|
|
`>`: `\>`,
|
|
`^`: `\^`,
|
|
`&`: `\&`,
|
|
`;`: `\;`,
|
|
`"`: `\"`,
|
|
`'`: `\'`,
|
|
`x`: `\x`,
|
|
`X`: `\X`,
|
|
`0`: `\0`,
|
|
`u`: `\u`,
|
|
`U`: `\U`,
|
|
}
|
|
if val, ok := escapeChars[text[0:1]]; ok {
|
|
return val + text[1:]
|
|
}
|
|
return text
|
|
}
|
|
|
|
func (a *Ansi) formatText(text string) string {
|
|
replaceFormats := func(results []map[string]string) {
|
|
for _, result := range results {
|
|
var formatted string
|
|
switch result["format"] {
|
|
case "b":
|
|
formatted = fmt.Sprintf(a.bold, result["text"])
|
|
case "u":
|
|
formatted = fmt.Sprintf(a.underline, result["text"])
|
|
case "o":
|
|
formatted = fmt.Sprintf(a.overline, result["text"])
|
|
case "i":
|
|
formatted = fmt.Sprintf(a.italic, result["text"])
|
|
case "s":
|
|
formatted = fmt.Sprintf(a.strikethrough, result["text"])
|
|
case "d":
|
|
formatted = fmt.Sprintf(a.dimmed, result["text"])
|
|
case "f":
|
|
formatted = fmt.Sprintf(a.blink, result["text"])
|
|
case "r":
|
|
formatted = fmt.Sprintf(a.reverse, result["text"])
|
|
}
|
|
text = strings.Replace(text, result["context"], formatted, 1)
|
|
}
|
|
}
|
|
rgx := "(?P<context><(?P<format>[buisrdfo])>(?P<text>[^<]+)</[buisrdfo]>)"
|
|
for results := regex.FindAllNamedRegexMatch(rgx, text); len(results) != 0; results = regex.FindAllNamedRegexMatch(rgx, text) {
|
|
replaceFormats(results)
|
|
}
|
|
return text
|
|
}
|
|
|
|
func (a *Ansi) CarriageForward() string {
|
|
return fmt.Sprintf(a.right, 1000)
|
|
}
|
|
|
|
func (a *Ansi) GetCursorForRightWrite(length, offset int) string {
|
|
strippedLen := length + (-offset)
|
|
return fmt.Sprintf(a.left, strippedLen)
|
|
}
|
|
|
|
func (a *Ansi) ChangeLine(numberOfLines int) string {
|
|
position := "B"
|
|
if numberOfLines < 0 {
|
|
position = "F"
|
|
numberOfLines = -numberOfLines
|
|
}
|
|
return fmt.Sprintf(a.linechange, numberOfLines, position)
|
|
}
|
|
|
|
func (a *Ansi) ConsolePwd(pwdType, hostName, pwd string) string {
|
|
if strings.HasSuffix(pwd, ":") {
|
|
pwd += "\\"
|
|
}
|
|
switch pwdType {
|
|
case OSC7:
|
|
return fmt.Sprintf(a.osc7, hostName, pwd)
|
|
case OSC99:
|
|
fallthrough
|
|
default:
|
|
return fmt.Sprintf(a.osc99, pwd)
|
|
}
|
|
}
|
|
|
|
func (a *Ansi) ClearAfter() string {
|
|
return a.clearLine + a.clearBelow
|
|
}
|
|
|
|
func (a *Ansi) Title(title string) string {
|
|
// 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 *Ansi) ColorReset() string {
|
|
return a.creset
|
|
}
|
|
|
|
func (a *Ansi) FormatText(text string) string {
|
|
return fmt.Sprintf(a.format, text)
|
|
}
|
|
|
|
func (a *Ansi) SaveCursorPosition() string {
|
|
return a.saveCursorPosition
|
|
}
|
|
|
|
func (a *Ansi) RestoreCursorPosition() string {
|
|
return a.restoreCursorPosition
|
|
}
|