feat: support color names for basic 16 ANSI colors

Closes #127
This commit is contained in:
Samuel 2020-11-08 15:52:10 +01:00 committed by Jan De Dobbeleer
parent aa36303962
commit 00e62e4810
8 changed files with 192 additions and 32 deletions

View file

@ -103,8 +103,8 @@ understand how to configure a segment.
- invert_powerline: `boolean`
- leading_diamond: `string`
- trailing_diamond: `string`
- foreground: `string` [hex color code][colors]
- background: `string` [hex color code][colors]
- foreground: `string` [color][colors]
- background: `string` [color][colors]
- properties: `array` of `Property`: `string`
### Type
@ -205,8 +205,21 @@ do so like this:
"prefix": "<#CB4B16>┏[</>",
```
Oh my Posh offers support for hex [colors][colors] as well as the `transparent` keyword to create either a transparent foreground
override or transparent background color using the segement's [foreground][fg] property.
Oh my Posh mainly supports three different color types being
* Typical [hex colors][hexcolors] (for example `#CB4B16`).
* The `transparent` keyword which can be used to create either a transparent foreground override
or transparent background color using the segement's foreground property.
* 16 [ANSI color names][ansicolors].
These include 8 basic ANSI colors and `default`:
`black` `red` `green` `yellow` `blue` `magenta` `cyan` `white` `default`
as well as 8 extended ANSI colors:
`darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite`
## Full Sample
@ -294,5 +307,7 @@ override or transparent background color using the segement's [foreground][fg] p
[releases]: https://github.com/JanDeDobbeleer/oh-my-posh3/releases/latest
[nf]: https://www.nerdfonts.com/
[segments]: /docs/battery
[colors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
[colors]: #colors
[hexcolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
[ansicolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
[fg]: /docs/configure#foreground

View file

@ -39,8 +39,8 @@ Battery displays the remaining power percentage for your battery.
- discharging_icon: `string` - icon to display on the left when discharging - defaults to empty
- charged_icon: `string` - icon to display on the left when fully charged - defaults to empty
- color_background: `boolean` - color the background or foreground for properties below - defaults to `false`
- charged_color: `string` [hex color code][colors] - color to use when fully charged - defaults to segment color
- charging_color: `string` [hex color code][colors] - color to use when charging - defaults to segment color
- discharging_color: `string` [hex color code][colors] - color to use when discharging - defaults to segment color
- charged_color: `string` [color][colors] - color to use when fully charged - defaults to segment color
- charging_color: `string` [color][colors] - color to use when charging - defaults to segment color
- discharging_color: `string` [color][colors] - color to use when discharging - defaults to segment color
[colors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
[colors]: /docs/configure#colors

View file

@ -33,6 +33,6 @@ Displays the last exit code or that the last command failed based on the configu
- display_exit_code: `boolean` - show or hide the exit code - defaults to `true`
- always_enabled: `boolean` - always show the status - defaults to `false`
- color_background: `boolean` - color the background or foreground when an error occurs - defaults to `false`
- error_color: `string` [hex color code][colors] - color to use when an error occured
- error_color: `string` [color][colors] - color to use when an error occured
[colors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
[colors]: /docs/configure#colors

View file

@ -67,17 +67,17 @@ Local changes can also shown by default using the following syntax for both the
### Colors
- working_color: `string` [hex color code][colors] - foreground color for the working area status - defaults to segment foreground
- staging_color: `string` [hex color code][colors] - foreground color for the staging area status - defaults to segment foreground
- working_color: `string` [color][colors] - foreground color for the working area status - defaults to segment foreground
- staging_color: `string` [color][colors] - foreground color for the staging area status - defaults to segment foreground
- status_colors_enabled: `boolean` - color the segment based on the repository status - defaults to `false`
- color_background: `boolean` - color background or foreground - defaults to `true`
- local_changes_color: `string` [hex color code][colors] - segment color when there are local changes - defaults to segment
- local_changes_color: `string` [color][colors] - segment color when there are local changes - defaults to segment
foreground/background (see `color_background`)
- ahead_and_behind_color: `string` [hex color code][colors] - segment color when the branch is ahead and behind -
- ahead_and_behind_color: `string` [color][colors] - segment color when the branch is ahead and behind -
defaults to segment foreground/background (see `color_background`)
- behind_color: `string` [hex color code][colors] - segment color when the branch is behind - defaults to segment
- behind_color: `string` [color][colors] - segment color when the branch is behind - defaults to segment
foreground/background (see `color_background`)
- ahead_color: `string` [hex color code][colors] - segment color when the branch is ahead - defaults to segment
- ahead_color: `string` [color][colors] - segment color when the branch is ahead - defaults to segment
foreground/background (see `color_background`)
[colors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
[colors]: /docs/configure#colors

View file

@ -26,9 +26,9 @@ Show the current user and host name.
- user_info_separator: `string` - text/icon to put in between the user and host name - defaults to `@`
- ssh_icon: `string` - text/icon to display first when in an active SSH session - defaults
to `\uF817 `
- user_color: `string` [hex color code][colors] - override the foreground color of the user name
- host_color: `string` [hex color code][colors] - override the foreground color of the host name
- user_color: `string` [color][colors] - override the foreground color of the user name
- host_color: `string` [color][colors] - override the foreground color of the host name
- display_user: `boolean` - display the user name or not - defaults to `true`
- display_host: `boolean` - display the host name or not - defaults to `true`
[colors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
[colors]: /docs/configure#colors

View file

@ -12,7 +12,7 @@ const features = [
description: (
<>
Oh my Posh enables you to use the full color set of your terminal
by using hex colors to define and render the prompt.
by using colors to define and render the prompt.
</>
),
},

View file

@ -2,6 +2,7 @@ package main
import (
"bytes"
"errors"
"fmt"
"regexp"
"strings"
@ -23,6 +24,41 @@ type formats struct {
clearOEL string
}
var (
// Map for color names and their respective foreground [0] or background [1] color codes
ColorMap map[string][2]string = map[string][2]string {
"black": [2]string { "30", "40" },
"red": [2]string { "31", "41" },
"green": [2]string { "32", "42" },
"yellow": [2]string { "33", "43" },
"blue": [2]string { "34", "44" },
"magenta": [2]string { "35", "45" },
"cyan": [2]string { "36", "46" },
"white": [2]string { "37", "47" },
"default": [2]string { "39", "49" },
"darkGray": [2]string { "90", "100" },
"lightRed": [2]string { "91", "101" },
"lightGreen": [2]string { "92", "102" },
"lightYellow": [2]string { "93", "103" },
"lightBlue": [2]string { "94", "104" },
"lightMagenta": [2]string { "95", "105" },
"lightCyan": [2]string { "96", "106" },
"lightWhite": [2]string { "97", "107" },
}
)
// Returns the color code for a given color name
func getColorFromName(colorName string, isBackground bool) (string, error) {
colorMapOffset := 0
if isBackground {
colorMapOffset = 1
}
if colorCodes, found := ColorMap[colorName]; found {
return colorCodes[colorMapOffset], nil
}
return "", errors.New("This color name does not exist.")
}
//Renderer writes colorized strings
type Renderer struct {
Buffer *bytes.Buffer
@ -74,22 +110,29 @@ func (r *Renderer) init(shell string) {
}
}
func (r *Renderer) getAnsiFromHex(hexColor string, isBackground bool) string {
style := color.HEX(hexColor, isBackground)
// Gets the ANSI color code for a given color string.
// This can include a valid hex color in the format `#FFFFFF`,
// but also a name of one of the first 16 ANSI colors like `lightBlue`.
func (r *Renderer) getAnsiFromColorString(colorString string, isBackground bool) string {
colorFromName, err := getColorFromName(colorString, isBackground)
if err == nil {
return colorFromName
}
style := color.HEX(colorString, isBackground)
return style.Code()
}
func (r *Renderer) writeColoredText(background string, foreground string, text string) {
var coloredText string
if foreground == Transparent && background != "" {
ansiColor := r.getAnsiFromHex(background, false)
ansiColor := r.getAnsiFromColorString(background, false)
coloredText = fmt.Sprintf(r.formats.transparent, ansiColor, text)
} else if background == "" || background == Transparent {
ansiColor := r.getAnsiFromHex(foreground, false)
ansiColor := r.getAnsiFromColorString(foreground, false)
coloredText = fmt.Sprintf(r.formats.single, ansiColor, text)
} else if foreground != "" && background != "" {
bgAnsiColor := r.getAnsiFromHex(background, true)
fgAnsiColor := r.getAnsiFromHex(foreground, false)
bgAnsiColor := r.getAnsiFromColorString(background, true)
fgAnsiColor := r.getAnsiFromColorString(foreground, false)
coloredText = fmt.Sprintf(r.formats.full, bgAnsiColor, fgAnsiColor, text)
}
r.Buffer.WriteString(coloredText)
@ -101,13 +144,19 @@ func (r *Renderer) writeAndRemoveText(background string, foreground string, text
}
func (r *Renderer) write(background string, foreground string, text string) {
rex := regexp.MustCompile(`<((#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|transparent))>(.*?)</>`)
// first we match for any potentially valid color enclosed in <>
rex := regexp.MustCompile(`<([#A-Za-z0-9]+)>(.*?)<\/>`)
match := rex.FindAllStringSubmatch(text, -1)
for i := range match {
// get the text before the color override and write that first
textBeforeColorOverride := strings.Split(text, match[i][0])[0]
extractedColor := match[i][1]
if col := r.getAnsiFromColorString(extractedColor, false); col == "" && extractedColor != Transparent {
continue // we skip invalid colors
}
escapedTextSegment := match[i][0]
innerText := match[i][2]
textBeforeColorOverride := strings.Split(text, escapedTextSegment)[0]
text = r.writeAndRemoveText(background, foreground, textBeforeColorOverride, textBeforeColorOverride, text)
text = r.writeAndRemoveText(background, match[i][1], match[i][4], match[i][0], text)
text = r.writeAndRemoveText(background, extractedColor, innerText, escapedTextSegment, text)
}
// color the remaining part of text with background and foreground
r.writeColoredText(background, foreground, text)

View file

@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/gookit/color"
)
func TestWriteAndRemoveText(t *testing.T) {
@ -49,6 +50,36 @@ func TestWriteColorTransparent(t *testing.T) {
t.Log(renderer.string())
}
func TestWriteColorName(t *testing.T) {
// given
renderer := &Renderer{
Buffer: new(bytes.Buffer),
}
renderer.init("pwsh")
text := "This is white, <red>this is red</>, white again"
// when
renderer.write("#193549", "red", text)
// then
assert.NotContains(t, renderer.string(), "<red>")
}
func TestWriteColorInvalid(t *testing.T) {
// given
renderer := &Renderer{
Buffer: new(bytes.Buffer),
}
renderer.init("pwsh")
text := "This is white, <invalid>this is orange</>, white again"
// when
renderer.write("#193549", "invalid", text)
// then
assert.Contains(t, renderer.string(), "<invalid>")
}
func TestLenWithoutANSI(t *testing.T) {
text := "\x1b[44mhello\x1b[0m"
renderer := &Renderer{
@ -68,3 +99,68 @@ func TestLenWithoutANSIZsh(t *testing.T) {
strippedLength := renderer.lenWithoutANSI(text)
assert.Equal(t, 5, strippedLength)
}
func TestGetAnsiFromColorStringBg(t *testing.T) {
// given
renderer := &Renderer{
Buffer: new(bytes.Buffer),
}
// when
colorCode := renderer.getAnsiFromColorString("blue", true)
// then
assert.Equal(t, color.BgBlue.Code(), colorCode)
}
func TestGetAnsiFromColorStringFg(t *testing.T) {
// given
renderer := &Renderer{
Buffer: new(bytes.Buffer),
}
// when
colorCode := renderer.getAnsiFromColorString("red", false)
// then
assert.Equal(t, color.FgRed.Code(), colorCode)
}
func TestGetAnsiFromColorStringHex(t *testing.T) {
// given
renderer := &Renderer{
Buffer: new(bytes.Buffer),
}
// when
colorCode := renderer.getAnsiFromColorString("#AABBCC", false)
// then
assert.Equal(t, color.HEX("#AABBCC").Code(), colorCode)
}
func TestGetAnsiFromColorStringInvalidFg(t *testing.T) {
// given
renderer := &Renderer{
Buffer: new(bytes.Buffer),
}
// when
colorCode := renderer.getAnsiFromColorString("invalid", false)
// then
assert.Equal(t, "", colorCode)
}
func TestGetAnsiFromColorStringInvalidBg(t *testing.T) {
// given
renderer := &Renderer{
Buffer: new(bytes.Buffer),
}
// when
colorCode := renderer.getAnsiFromColorString("invalid", true)
// then
assert.Equal(t, "", colorCode)
}