oh-my-posh/src/ansi.go

208 lines
6.5 KiB
Go
Raw Normal View History

2020-12-26 10:51:21 -08:00
package main
import (
"fmt"
2020-12-26 10:51:21 -08:00
"strings"
)
2020-12-26 10:51:21 -08:00
const (
2021-05-21 11:01:08 -07:00
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
)
2021-04-20 12:30:46 -07:00
type ansiUtils struct {
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
title 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
bashFormat string
shellReservedKeywords []shellKeyWordReplacement
}
type shellKeyWordReplacement struct {
text string
replacement string
2020-12-26 10:51:21 -08:00
}
2021-04-20 12:30:46 -07:00
func (a *ansiUtils) init(shell string) {
2020-12-26 10:51:21 -08:00
a.shell = shell
a.bashFormat = "\\[%s\\]"
2020-12-26 10:51:21 -08:00
switch shell {
case zsh:
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.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:
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.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:
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.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
}
2021-04-20 12:30:46 -07:00
func (a *ansiUtils) lenWithoutANSI(text string) int {
if len(text) == 0 {
return 0
}
// replace hyperlinks(file/http/https)
matches := findAllNamedRegexMatch(`(?P<STR>\x1b]8;;(file|http|https):\/\/(.+?)\x1b\\(?P<URL>.+?)\x1b]8;;\x1b\\)`, text)
for _, match := range matches {
2021-05-21 11:01:08 -07:00
text = strings.ReplaceAll(text, match[str], match[url])
}
// replace console title
matches = findAllNamedRegexMatch(`(?P<STR>\x1b\]0;(.+)\007)`, text)
for _, match := range matches {
2021-05-21 11:01:08 -07:00
text = strings.ReplaceAll(text, match[str], "")
}
2021-05-21 11:01:08 -07:00
stripped := replaceAllString(ansiRegex, text, "")
2020-12-26 10:51:21 -08:00
stripped = strings.ReplaceAll(stripped, a.escapeLeft, "")
stripped = strings.ReplaceAll(stripped, a.escapeRight, "")
runeText := []rune(stripped)
return len(runeText)
2020-12-26 10:51:21 -08:00
}
2021-04-20 12:30:46 -07:00
func (a *ansiUtils) generateHyperlink(text string) string {
// hyperlink matching
results := 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
2021-04-20 12:30:46 -07:00
func (a *ansiUtils) formatText(text string) string {
2021-02-23 23:40:55 -08:00
results := findAllNamedRegexMatch("(?P<context><(?P<format>[buis])>(?P<text>[^<]+)</[buis]>)", text)
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"])
}
text = strings.Replace(text, result["context"], formatted, 1)
}
return text
}
2021-04-20 12:30:46 -07:00
func (a *ansiUtils) carriageForward() string {
2021-06-15 12:23:08 -07:00
return fmt.Sprintf(a.right, 1000)
}
2021-04-20 12:30:46 -07:00
func (a *ansiUtils) getCursorForRightWrite(text string, offset int) string {
strippedLen := a.lenWithoutANSI(text) + -offset
2021-06-15 12:23:08 -07:00
return fmt.Sprintf(a.left, strippedLen)
2021-04-20 12:30:46 -07:00
}
func (a *ansiUtils) changeLine(numberOfLines int) string {
position := "B"
if numberOfLines < 0 {
position = "F"
numberOfLines = -numberOfLines
}
return fmt.Sprintf(a.linechange, numberOfLines, position)
}
func (a *ansiUtils) consolePwd(pwd string) string {
if strings.HasSuffix(pwd, ":") {
pwd += "\\"
}
2021-04-20 12:30:46 -07:00
return fmt.Sprintf(a.osc99, pwd)
}
func (a *ansiUtils) clearAfter() string {
return a.clearLine + a.clearBelow
}
func (a *ansiUtils) 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
}