mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-26 11:34:04 -08:00
00e62e4810
Closes #127
228 lines
6.9 KiB
Go
Executable file
228 lines
6.9 KiB
Go
Executable file
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/gookit/color"
|
|
"golang.org/x/text/unicode/norm"
|
|
)
|
|
|
|
type formats struct {
|
|
single string
|
|
full string
|
|
transparent string
|
|
linechange string
|
|
left string
|
|
right string
|
|
rANSI string
|
|
title string
|
|
creset string
|
|
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
|
|
formats *formats
|
|
shell string
|
|
}
|
|
|
|
const (
|
|
//Transparent implies a transparent color
|
|
Transparent string = "transparent"
|
|
)
|
|
|
|
func (r *Renderer) init(shell string) {
|
|
r.shell = shell
|
|
r.formats = &formats{
|
|
rANSI: "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))",
|
|
}
|
|
switch shell {
|
|
case "zsh":
|
|
r.formats.single = "%%{\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
|
r.formats.full = "%%{\x1b[%sm\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
|
r.formats.transparent = "%%{\x1b[%s;49m\x1b[7m%%}%s%%{\x1b[m\x1b[0m%%}"
|
|
r.formats.linechange = "%%{\x1b[%d%s%%}"
|
|
r.formats.left = "%%{\x1b[%dC%%}"
|
|
r.formats.right = "%%{\x1b[%dD%%}"
|
|
r.formats.title = "%%{\033]0;%s\007%%}"
|
|
r.formats.creset = "%{\x1b[0m%}"
|
|
r.formats.clearOEL = "%{\x1b[K%}"
|
|
case "bash":
|
|
r.formats.single = "\\[\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
|
r.formats.full = "\\[\x1b[%sm\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
|
r.formats.transparent = "\\[\x1b[%s;49m\x1b[7m\\]%s\\[\x1b[m\x1b[0m\\]"
|
|
r.formats.linechange = "\\[\x1b[%d%s\\]"
|
|
r.formats.left = "\\[\x1b[%dC\\]"
|
|
r.formats.right = "\\[\x1b[%dD\\]"
|
|
r.formats.title = "\\[\033]0;%s\007\\]"
|
|
r.formats.creset = "\\[\x1b[0m\\]"
|
|
r.formats.clearOEL = "\\[\x1b[K\\]"
|
|
default:
|
|
r.formats.single = "\x1b[%sm%s\x1b[0m"
|
|
r.formats.full = "\x1b[%sm\x1b[%sm%s\x1b[0m"
|
|
r.formats.transparent = "\x1b[%s;49m\x1b[7m%s\x1b[m\x1b[0m"
|
|
r.formats.linechange = "\x1b[%d%s"
|
|
r.formats.left = "\x1b[%dC"
|
|
r.formats.right = "\x1b[%dD"
|
|
r.formats.title = "\033]0;%s\007"
|
|
r.formats.creset = "\x1b[0m"
|
|
r.formats.clearOEL = "\x1b[K"
|
|
}
|
|
}
|
|
|
|
// 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.getAnsiFromColorString(background, false)
|
|
coloredText = fmt.Sprintf(r.formats.transparent, ansiColor, text)
|
|
} else if background == "" || background == Transparent {
|
|
ansiColor := r.getAnsiFromColorString(foreground, false)
|
|
coloredText = fmt.Sprintf(r.formats.single, ansiColor, text)
|
|
} else if foreground != "" && background != "" {
|
|
bgAnsiColor := r.getAnsiFromColorString(background, true)
|
|
fgAnsiColor := r.getAnsiFromColorString(foreground, false)
|
|
coloredText = fmt.Sprintf(r.formats.full, bgAnsiColor, fgAnsiColor, text)
|
|
}
|
|
r.Buffer.WriteString(coloredText)
|
|
}
|
|
|
|
func (r *Renderer) writeAndRemoveText(background string, foreground string, text string, textToRemove string, parentText string) string {
|
|
r.writeColoredText(background, foreground, text)
|
|
return strings.Replace(parentText, textToRemove, "", 1)
|
|
}
|
|
|
|
func (r *Renderer) write(background string, foreground string, text string) {
|
|
// 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 {
|
|
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, extractedColor, innerText, escapedTextSegment, text)
|
|
}
|
|
// color the remaining part of text with background and foreground
|
|
r.writeColoredText(background, foreground, text)
|
|
}
|
|
|
|
func (r *Renderer) lenWithoutANSI(str string) int {
|
|
re := regexp.MustCompile(r.formats.rANSI)
|
|
stripped := re.ReplaceAllString(str, "")
|
|
switch r.shell {
|
|
case "zsh":
|
|
stripped = strings.Replace(stripped, "%{", "", -1)
|
|
stripped = strings.Replace(stripped, "%}", "", -1)
|
|
case "bash":
|
|
stripped = strings.Replace(stripped, "\\[", "", -1)
|
|
stripped = strings.Replace(stripped, "\\]", "", -1)
|
|
}
|
|
var i norm.Iter
|
|
i.InitString(norm.NFD, stripped)
|
|
var count int
|
|
for !i.Done() {
|
|
i.Next()
|
|
count++
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (r *Renderer) carriageForward() string {
|
|
return fmt.Sprintf(r.formats.left, 1000)
|
|
}
|
|
|
|
func (r *Renderer) setCursorForRightWrite(text string, offset int) string {
|
|
strippedLen := r.lenWithoutANSI(text) + -offset
|
|
return fmt.Sprintf(r.formats.right, strippedLen)
|
|
}
|
|
|
|
func (r *Renderer) changeLine(numberOfLines int) string {
|
|
position := "B"
|
|
if numberOfLines < 0 {
|
|
position = "F"
|
|
numberOfLines = -numberOfLines
|
|
}
|
|
return fmt.Sprintf(r.formats.linechange, numberOfLines, position)
|
|
}
|
|
|
|
func (r *Renderer) setConsoleTitle(title string) {
|
|
fmt.Printf(r.formats.title, title)
|
|
}
|
|
|
|
func (r *Renderer) string() string {
|
|
return r.Buffer.String()
|
|
}
|
|
|
|
func (r *Renderer) reset() {
|
|
r.Buffer.Reset()
|
|
}
|
|
|
|
func (r *Renderer) creset() {
|
|
fmt.Print(r.formats.creset)
|
|
}
|
|
|
|
func (r *Renderer) print(text string) {
|
|
fmt.Print(text)
|
|
r.clearEOL()
|
|
}
|
|
|
|
func (r *Renderer) clearEOL() {
|
|
fmt.Print(r.formats.clearOEL)
|
|
}
|