oh-my-posh/src/color/ansi.go

237 lines
7 KiB
Go
Raw Normal View History

2022-01-26 04:09:21 -08:00
package color
2020-12-26 10:51:21 -08:00
import (
"fmt"
"oh-my-posh/regex"
2020-12-26 10:51:21 -08:00
"strings"
)
2020-12-26 10:51:21 -08:00
const (
2022-01-26 04:09:21 -08:00
zsh = "zsh"
bash = "bash"
pwsh = "pwsh"
AnsiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
2020-12-26 10:51:21 -08:00
)
2022-01-26 04:09:21 -08:00
type Ansi struct {
title string
2020-12-26 10:51:21 -08:00
shell string
linechange string
left string
right string
creset string
clearBelow string
clearLine string
2020-12-26 10:51:21 -08:00
saveCursorPosition string
restoreCursorPosition string
colorSingle string
colorFull string
colorTransparent string
escapeLeft string
escapeRight string
hyperlink string
2021-02-14 05:09:43 -08:00
osc99 string
2021-02-23 23:40:55 -08:00
bold string
italic string
underline string
strikethrough string
blink string
reverse string
dimmed string
2022-01-26 04:09:21 -08:00
format string
shellReservedKeywords []shellKeyWordReplacement
}
type shellKeyWordReplacement struct {
text string
replacement string
2020-12-26 10:51:21 -08:00
}
2022-01-26 04:09:21 -08:00
func (a *Ansi) Init(shell string) {
2020-12-26 10:51:21 -08:00
a.shell = shell
switch shell {
case zsh:
2022-01-26 04:09:21 -08:00
a.format = "%%{%s%%}"
2020-12-26 10:51:21 -08:00
a.linechange = "%%{\x1b[%d%s%%}"
2021-06-15 12:23:08 -07:00
a.right = "%%{\x1b[%dC%%}"
a.left = "%%{\x1b[%dD%%}"
2020-12-26 10:51:21 -08:00
a.creset = "%{\x1b[0m%}"
a.clearBelow = "%{\x1b[0J%}"
a.clearLine = "%{\x1b[K%}"
2020-12-26 10:51:21 -08:00
a.saveCursorPosition = "%{\x1b7%}"
a.restoreCursorPosition = "%{\x1b8%}"
a.title = "%%{\x1b]0;%s\007%%}"
2020-12-26 10:51:21 -08:00
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%%}"
2020-12-26 10:51:21 -08:00
a.escapeLeft = "%{"
a.escapeRight = "%}"
a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}"
a.osc99 = "%%{\x1b]9;9;\"%s\"\x1b\\%%}"
2021-02-23 23:40:55 -08:00
a.bold = "%%{\x1b[1m%%}%s%%{\x1b[22m%%}"
a.italic = "%%{\x1b[3m%%}%s%%{\x1b[23m%%}"
a.underline = "%%{\x1b[4m%%}%s%%{\x1b[24m%%}"
a.blink = "%%{\x1b[5m%%}%s%%{\x1b[25m%%}"
a.reverse = "%%{\x1b[7m%%}%s%%{\x1b[27m%%}"
a.dimmed = "%%{\x1b[2m%%}%s%%{\x1b[22m%%}"
2021-02-23 23:40:55 -08:00
a.strikethrough = "%%{\x1b[9m%%}%s%%{\x1b[29m%%}"
// escape double quotes and variable expansion
a.shellReservedKeywords = append(a.shellReservedKeywords, shellKeyWordReplacement{"\\", "\\\\"}, shellKeyWordReplacement{"%", "%%"})
2020-12-26 10:51:21 -08:00
case bash:
2022-01-26 04:09:21 -08:00
a.format = "\\[%s\\]"
2020-12-26 10:51:21 -08:00
a.linechange = "\\[\x1b[%d%s\\]"
2021-06-15 12:23:08 -07:00
a.right = "\\[\x1b[%dC\\]"
a.left = "\\[\x1b[%dD\\]"
2020-12-26 10:51:21 -08:00
a.creset = "\\[\x1b[0m\\]"
a.clearBelow = "\\[\x1b[0J\\]"
a.clearLine = "\\[\x1b[K\\]"
2020-12-26 10:51:21 -08:00
a.saveCursorPosition = "\\[\x1b7\\]"
a.restoreCursorPosition = "\\[\x1b8\\]"
a.title = "\\[\x1b]0;%s\007\\]"
2020-12-26 10:51:21 -08:00
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\\]"
2020-12-26 10:51:21 -08:00
a.escapeLeft = "\\["
a.escapeRight = "\\]"
a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]"
a.osc99 = "\\[\x1b]9;9;\"%s\"\x1b\\\\\\]"
2021-02-23 23:40:55 -08:00
a.bold = "\\[\x1b[1m\\]%s\\[\x1b[22m\\]"
a.italic = "\\[\x1b[3m\\]%s\\[\x1b[23m\\]"
a.underline = "\\[\x1b[4m\\]%s\\[\x1b[24m\\]"
a.blink = "\\[\x1b[5m%s\\[\x1b[25m\\]"
a.reverse = "\\[\x1b[7m\\]%s\\[\x1b[27m\\]"
a.dimmed = "\\[\x1b[2m\\]%s\\[\x1b[22m\\]"
2021-02-23 23:40:55 -08:00
a.strikethrough = "\\[\x1b[9m\\]%s\\[\x1b[29m\\]"
// escape backslashes to avoid replacements
// https://tldp.org/HOWTO/Bash-Prompt-HOWTO/bash-prompt-escape-sequences.html
a.shellReservedKeywords = append(a.shellReservedKeywords, shellKeyWordReplacement{"\\", "\\\\"})
2020-12-26 10:51:21 -08:00
default:
2022-01-26 04:09:21 -08:00
a.format = "%s"
2020-12-26 10:51:21 -08:00
a.linechange = "\x1b[%d%s"
2021-06-15 12:23:08 -07:00
a.right = "\x1b[%dC"
a.left = "\x1b[%dD"
2020-12-26 10:51:21 -08:00
a.creset = "\x1b[0m"
a.clearBelow = "\x1b[0J"
a.clearLine = "\x1b[K"
2020-12-26 10:51:21 -08:00
a.saveCursorPosition = "\x1b7"
a.restoreCursorPosition = "\x1b8"
a.title = "\x1b]0;%s\007"
2020-12-26 10:51:21 -08:00
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"
2020-12-26 10:51:21 -08:00
a.escapeLeft = ""
a.escapeRight = ""
a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\"
a.osc99 = "\x1b]9;9;\"%s\"\x1b\\"
2021-02-23 23:40:55 -08:00
a.bold = "\x1b[1m%s\x1b[22m"
a.italic = "\x1b[3m%s\x1b[23m"
a.underline = "\x1b[4m%s\x1b[24m"
a.blink = "\x1b[5m%s\x1b[25m"
a.reverse = "\x1b[7m%s\x1b[27m"
a.dimmed = "\x1b[2m%s\x1b[22m"
2021-02-23 23:40:55 -08:00
a.strikethrough = "\x1b[9m%s\x1b[29m"
2020-12-26 10:51:21 -08:00
}
// common replacement for all shells
a.shellReservedKeywords = append(a.shellReservedKeywords, shellKeyWordReplacement{"`", "'"})
2020-12-26 10:51:21 -08:00
}
2022-01-26 04:09:21 -08:00
func (a *Ansi) generateHyperlink(text string) string {
// hyperlink matching
results := regex.FindNamedRegexMatch("(?P<all>(?:\\[(?P<name>.+)\\])(?:\\((?P<url>.*)\\)))", text)
if len(results) != 3 {
return text
}
// build hyperlink ansi
hyperlink := fmt.Sprintf(a.hyperlink, results["url"], results["name"])
// replace original text by the new one
return strings.Replace(text, results["all"], hyperlink, 1)
}
2021-02-23 23:40:55 -08:00
2022-01-26 04:09:21 -08:00
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 "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)
2021-02-23 23:40:55 -08:00
}
}
rgx := "(?P<context><(?P<format>[buisrdf])>(?P<text>[^<]+)</[buisrdf]>)"
for results := regex.FindAllNamedRegexMatch(rgx, text); len(results) != 0; results = regex.FindAllNamedRegexMatch(rgx, text) {
replaceFormats(results)
2021-02-23 23:40:55 -08:00
}
return text
}
2021-04-20 12:30:46 -07:00
2022-01-26 04:09:21 -08:00
func (a *Ansi) CarriageForward() string {
2021-06-15 12:23:08 -07:00
return fmt.Sprintf(a.right, 1000)
}
func (a *Ansi) GetCursorForRightWrite(length, offset int) string {
strippedLen := length + (-offset)
2021-06-15 12:23:08 -07:00
return fmt.Sprintf(a.left, strippedLen)
2021-04-20 12:30:46 -07:00
}
2022-01-26 04:09:21 -08:00
func (a *Ansi) ChangeLine(numberOfLines int) string {
2021-04-20 12:30:46 -07:00
position := "B"
if numberOfLines < 0 {
position = "F"
numberOfLines = -numberOfLines
}
return fmt.Sprintf(a.linechange, numberOfLines, position)
}
2022-01-26 04:09:21 -08:00
func (a *Ansi) ConsolePwd(pwd string) string {
if strings.HasSuffix(pwd, ":") {
pwd += "\\"
}
2021-04-20 12:30:46 -07:00
return fmt.Sprintf(a.osc99, pwd)
}
2022-01-26 04:09:21 -08:00
func (a *Ansi) ClearAfter() string {
return a.clearLine + a.clearBelow
}
2022-01-26 04:09:21 -08:00
func (a *Ansi) EscapeText(text string) string {
// what to escape/replace is different per shell
for _, s := range a.shellReservedKeywords {
text = strings.ReplaceAll(text, s.text, s.replacement)
}
return text
}
2022-01-26 04:09:21 -08:00
func (a *Ansi) Title(title string) string {
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
}