From 3fbf785bd96354d9863640c35e0000bb1a3cab00 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Wed, 24 Feb 2021 08:40:55 +0100 Subject: [PATCH] feat: ansi text formats resolves #440 --- docs/docs/configuration.md | 11 +++++++++++ src/ansi_color.go | 1 + src/ansi_formats.go | 36 ++++++++++++++++++++++++++++++++++++ src/ansi_formats_test.go | 20 ++++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 89401262c087..03199046f28f 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -335,6 +335,17 @@ Oh my Posh mainly supports three different color types being `darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite` +### Text decorations + +You can make use of the following syntax to decorate text: + +- `bold`: renders `bold` as bold text +- `underline`: renders `underline` as underlined text +- `italic`: renders `italic` as italic text +- `strikethrough`: renders `strikethrough` as strikethrough text + +This can be used in templates and icons/text inside your config. + ### Hyperlinks The engine has the ability to render hyperlinks. Your terminal has to support it and the option diff --git a/src/ansi_color.go b/src/ansi_color.go index 9608afaf6d28..f24189820ed7 100644 --- a/src/ansi_color.go +++ b/src/ansi_color.go @@ -92,6 +92,7 @@ func (a *AnsiColor) writeAndRemoveText(background, foreground, text, textToRemov } func (a *AnsiColor) write(background, foreground, text string) { + text = a.formats.formatText(text) // first we match for any potentially valid colors enclosed in <> match := findAllNamedRegexMatch(`<(?P[^,>]+)?,?(?P[^>]+)?>(?P[^<]*)<\/>`, text) for i := range match { diff --git a/src/ansi_formats.go b/src/ansi_formats.go index 4f205295b9ea..e43f5c0bbac8 100644 --- a/src/ansi_formats.go +++ b/src/ansi_formats.go @@ -24,6 +24,10 @@ type ansiFormats struct { escapeRight string hyperlink string osc99 string + bold string + italic string + underline string + strikethrough string } func (a *ansiFormats) init(shell string) { @@ -45,6 +49,10 @@ func (a *ansiFormats) init(shell string) { a.escapeRight = "%}" a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\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: a.linechange = "\\[\x1b[%d%s\\]" a.left = "\\[\x1b[%dC\\]" @@ -61,6 +69,10 @@ func (a *ansiFormats) init(shell string) { a.escapeRight = "\\]" a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\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: a.linechange = "\x1b[%d%s" a.left = "\x1b[%dC" @@ -77,6 +89,11 @@ func (a *ansiFormats) init(shell string) { a.escapeRight = "" a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\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 return strings.Replace(text, results["all"], hyperlink, 1) } + +func (a *ansiFormats) formatText(text string) string { + results := findAllNamedRegexMatch("(?P<(?P[buis])>(?P[^<]+))", 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 +} diff --git a/src/ansi_formats_test.go b/src/ansi_formats_test.go index 1a200d7d9a40..d033004f0d4a 100644 --- a/src/ansi_formats_test.go +++ b/src/ansi_formats_test.go @@ -77,3 +77,23 @@ func TestGenerateHyperlinkWithUrlNoName(t *testing.T) { assert.Equal(t, tc.Expected, hyperlinkText) } } + +func TestFormatText(t *testing.T) { + cases := []struct { + Case string + Text string + Expected string + }{ + {Case: "single format", Text: "This is white", Expected: "This \x1b[1mis\x1b[22m white"}, + {Case: "double format", Text: "This is white, this is orange", Expected: "This \x1b[1mis\x1b[22m white, this \x1b[1mis\x1b[22m orange"}, + {Case: "underline", Text: "This is white", Expected: "This \x1b[4mis\x1b[24m white"}, + {Case: "italic", Text: "This is white", Expected: "This \x1b[3mis\x1b[23m white"}, + {Case: "strikethrough", Text: "This is 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) + } +}