feat: ansi text formats

resolves #440
This commit is contained in:
Jan De Dobbeleer 2021-02-24 08:40:55 +01:00 committed by Jan De Dobbeleer
parent 2fd823c464
commit 3fbf785bd9
4 changed files with 68 additions and 0 deletions

View file

@ -335,6 +335,17 @@ Oh my Posh mainly supports three different color types being
`darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite` `darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite`
### Text decorations
You can make use of the following syntax to decorate text:
- `<b>bold</b>`: renders `bold` as bold text
- `<u>underline</u>`: renders `underline` as underlined text
- `<i>italic</i>`: renders `italic` as italic text
- `<s>strikethrough</s>`: renders `strikethrough` as strikethrough text
This can be used in templates and icons/text inside your config.
### Hyperlinks ### Hyperlinks
The engine has the ability to render hyperlinks. Your terminal has to support it and the option The engine has the ability to render hyperlinks. Your terminal has to support it and the option

View file

@ -92,6 +92,7 @@ func (a *AnsiColor) writeAndRemoveText(background, foreground, text, textToRemov
} }
func (a *AnsiColor) write(background, foreground, text string) { func (a *AnsiColor) write(background, foreground, text string) {
text = a.formats.formatText(text)
// first we match for any potentially valid colors enclosed in <> // first we match for any potentially valid colors enclosed in <>
match := findAllNamedRegexMatch(`<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`, text) match := findAllNamedRegexMatch(`<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`, text)
for i := range match { for i := range match {

View file

@ -24,6 +24,10 @@ type ansiFormats struct {
escapeRight string escapeRight string
hyperlink string hyperlink string
osc99 string osc99 string
bold string
italic string
underline string
strikethrough string
} }
func (a *ansiFormats) init(shell string) { func (a *ansiFormats) init(shell string) {
@ -45,6 +49,10 @@ func (a *ansiFormats) init(shell string) {
a.escapeRight = "%}" a.escapeRight = "%}"
a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}" a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}"
a.osc99 = "%%{\x1b]9;9;%s\x1b\\%%}" a.osc99 = "%%{\x1b]9;9;%s\x1b\\%%}"
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%%}"
case bash: case bash:
a.linechange = "\\[\x1b[%d%s\\]" a.linechange = "\\[\x1b[%d%s\\]"
a.left = "\\[\x1b[%dC\\]" a.left = "\\[\x1b[%dC\\]"
@ -61,6 +69,10 @@ func (a *ansiFormats) init(shell string) {
a.escapeRight = "\\]" a.escapeRight = "\\]"
a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]" a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]"
a.osc99 = "\\[\x1b]9;9;%s\x1b\\\\\\]" a.osc99 = "\\[\x1b]9;9;%s\x1b\\\\\\]"
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\\]"
default: default:
a.linechange = "\x1b[%d%s" a.linechange = "\x1b[%d%s"
a.left = "\x1b[%dC" a.left = "\x1b[%dC"
@ -77,6 +89,11 @@ func (a *ansiFormats) init(shell string) {
a.escapeRight = "" a.escapeRight = ""
a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\" a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\"
a.osc99 = "\x1b]9;9;%s\x1b\\" a.osc99 = "\x1b]9;9;%s\x1b\\"
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"
a.strikethrough = "\x1b[9m%s\x1b[29m"
} }
} }
@ -106,3 +123,22 @@ func (a *ansiFormats) generateHyperlink(text string) string {
// replace original text by the new one // replace original text by the new one
return strings.Replace(text, results["all"], hyperlink, 1) return strings.Replace(text, results["all"], hyperlink, 1)
} }
func (a *ansiFormats) formatText(text string) string {
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
}

View file

@ -77,3 +77,23 @@ func TestGenerateHyperlinkWithUrlNoName(t *testing.T) {
assert.Equal(t, tc.Expected, hyperlinkText) assert.Equal(t, tc.Expected, hyperlinkText)
} }
} }
func TestFormatText(t *testing.T) {
cases := []struct {
Case string
Text string
Expected string
}{
{Case: "single format", Text: "This <b>is</b> white", Expected: "This \x1b[1mis\x1b[22m white"},
{Case: "double format", Text: "This <b>is</b> white, this <b>is</b> orange", Expected: "This \x1b[1mis\x1b[22m white, this \x1b[1mis\x1b[22m orange"},
{Case: "underline", Text: "This <u>is</u> white", Expected: "This \x1b[4mis\x1b[24m white"},
{Case: "italic", Text: "This <i>is</i> white", Expected: "This \x1b[3mis\x1b[23m white"},
{Case: "strikethrough", Text: "This <s>is</s> white", Expected: "This \x1b[9mis\x1b[29m white"},
}
for _, tc := range cases {
a := ansiFormats{}
a.init("")
formattedText := a.formatText(tc.Text)
assert.Equal(t, tc.Expected, formattedText, tc.Case)
}
}