mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-01-13 04:07:25 -08:00
feat: zsh rprompt compatibility
This commit is contained in:
parent
adb205fe66
commit
3ca2cb5ef3
150
ansi_color.go
Normal file
150
ansi_color.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
type colorFormats struct {
|
||||
single string
|
||||
full string
|
||||
transparent 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": {"30", "40"},
|
||||
"red": {"31", "41"},
|
||||
"green": {"32", "42"},
|
||||
"yellow": {"33", "43"},
|
||||
"blue": {"34", "44"},
|
||||
"magenta": {"35", "45"},
|
||||
"cyan": {"36", "46"},
|
||||
"white": {"37", "47"},
|
||||
"default": {"39", "49"},
|
||||
"darkGray": {"90", "100"},
|
||||
"lightRed": {"91", "101"},
|
||||
"lightGreen": {"92", "102"},
|
||||
"lightYellow": {"93", "103"},
|
||||
"lightBlue": {"94", "104"},
|
||||
"lightMagenta": {"95", "105"},
|
||||
"lightCyan": {"96", "106"},
|
||||
"lightWhite": {"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("color name does not exist")
|
||||
}
|
||||
|
||||
// AnsiColor writes colorized strings
|
||||
type AnsiColor struct {
|
||||
buffer *bytes.Buffer
|
||||
formats *colorFormats
|
||||
}
|
||||
|
||||
const (
|
||||
// Transparent implies a transparent color
|
||||
Transparent = "transparent"
|
||||
)
|
||||
|
||||
func (a *AnsiColor) init(shell string) {
|
||||
a.formats = &colorFormats{}
|
||||
switch shell {
|
||||
case zsh:
|
||||
a.formats.single = "%%{\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
||||
a.formats.full = "%%{\x1b[%sm\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
||||
a.formats.transparent = "%%{\x1b[%s;49m\x1b[7m%%}%s%%{\x1b[m\x1b[0m%%}"
|
||||
case bash:
|
||||
a.formats.single = "\\[\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
||||
a.formats.full = "\\[\x1b[%sm\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
||||
a.formats.transparent = "\\[\x1b[%s;49m\x1b[7m\\]%s\\[\x1b[m\x1b[0m\\]"
|
||||
default:
|
||||
a.formats.single = "\x1b[%sm%s\x1b[0m"
|
||||
a.formats.full = "\x1b[%sm\x1b[%sm%s\x1b[0m"
|
||||
a.formats.transparent = "\x1b[%s;49m\x1b[7m%s\x1b[m\x1b[0m"
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (a *AnsiColor) 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 (a *AnsiColor) writeColoredText(background, foreground, text string) {
|
||||
var coloredText string
|
||||
if foreground == Transparent && background != "" {
|
||||
ansiColor := a.getAnsiFromColorString(background, false)
|
||||
coloredText = fmt.Sprintf(a.formats.transparent, ansiColor, text)
|
||||
} else if background == "" || background == Transparent {
|
||||
ansiColor := a.getAnsiFromColorString(foreground, false)
|
||||
coloredText = fmt.Sprintf(a.formats.single, ansiColor, text)
|
||||
} else if foreground != "" && background != "" {
|
||||
bgAnsiColor := a.getAnsiFromColorString(background, true)
|
||||
fgAnsiColor := a.getAnsiFromColorString(foreground, false)
|
||||
coloredText = fmt.Sprintf(a.formats.full, bgAnsiColor, fgAnsiColor, text)
|
||||
}
|
||||
a.buffer.WriteString(coloredText)
|
||||
}
|
||||
|
||||
func (a *AnsiColor) writeAndRemoveText(background, foreground, text, textToRemove, parentText string) string {
|
||||
a.writeColoredText(background, foreground, text)
|
||||
return strings.Replace(parentText, textToRemove, "", 1)
|
||||
}
|
||||
|
||||
func (a *AnsiColor) write(background, foreground, text string) {
|
||||
// first we match for any potentially valid colors enclosed in <>
|
||||
match := findAllNamedRegexMatch(`<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`, text)
|
||||
for i := range match {
|
||||
extractedForegroundColor := match[i]["foreground"]
|
||||
extractedBackgroundColor := match[i]["background"]
|
||||
if col := a.getAnsiFromColorString(extractedForegroundColor, false); col == "" && extractedForegroundColor != Transparent && len(extractedBackgroundColor) == 0 {
|
||||
continue // we skip invalid colors
|
||||
}
|
||||
if col := a.getAnsiFromColorString(extractedBackgroundColor, false); col == "" && extractedBackgroundColor != Transparent && len(extractedForegroundColor) == 0 {
|
||||
continue // we skip invalid colors
|
||||
}
|
||||
// reuse function colors if only one was specified
|
||||
if len(extractedBackgroundColor) == 0 {
|
||||
extractedBackgroundColor = background
|
||||
}
|
||||
if len(extractedForegroundColor) == 0 {
|
||||
extractedForegroundColor = foreground
|
||||
}
|
||||
escapedTextSegment := match[i]["text"]
|
||||
innerText := match[i]["content"]
|
||||
textBeforeColorOverride := strings.Split(text, escapedTextSegment)[0]
|
||||
text = a.writeAndRemoveText(background, foreground, textBeforeColorOverride, textBeforeColorOverride, text)
|
||||
text = a.writeAndRemoveText(extractedBackgroundColor, extractedForegroundColor, innerText, escapedTextSegment, text)
|
||||
}
|
||||
// color the remaining part of text with background and foreground
|
||||
a.writeColoredText(background, foreground, text)
|
||||
}
|
||||
|
||||
func (a *AnsiColor) string() string {
|
||||
return a.buffer.String()
|
||||
}
|
||||
|
||||
func (a *AnsiColor) reset() {
|
||||
a.buffer.Reset()
|
||||
}
|
|
@ -13,8 +13,8 @@ const (
|
|||
)
|
||||
|
||||
func TestWriteAndRemoveText(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
text := renderer.writeAndRemoveText("#193549", "#fff", "This is white, ", "This is white, ", inputText)
|
||||
|
@ -23,8 +23,8 @@ func TestWriteAndRemoveText(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteAndRemoveTextColored(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
text := renderer.writeAndRemoveText("#193549", "#ff5733", "this is orange", "<#ff5733>this is orange</>", inputText)
|
||||
|
@ -33,8 +33,8 @@ func TestWriteAndRemoveTextColored(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteColorOverride(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
renderer.write("#193549", "#ff5733", inputText)
|
||||
|
@ -42,8 +42,8 @@ func TestWriteColorOverride(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteColorOverrideBackground(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
text := "This is white, <,#000000>this is black</>, white again"
|
||||
renderer.init("pwsh")
|
||||
|
@ -52,8 +52,8 @@ func TestWriteColorOverrideBackground(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteColorOverrideBackground16(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
text := "This is default <,white> this background is changed</> default again"
|
||||
renderer.init("pwsh")
|
||||
|
@ -64,8 +64,8 @@ func TestWriteColorOverrideBackground16(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteColorOverrideBoth(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
text := "This is white, <#000000,#ffffff>this is black</>, white again"
|
||||
renderer.init("pwsh")
|
||||
|
@ -75,8 +75,8 @@ func TestWriteColorOverrideBoth(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteColorOverrideBoth16(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
text := "This is white, <black,white>this is black</>, white again"
|
||||
renderer.init("pwsh")
|
||||
|
@ -86,8 +86,8 @@ func TestWriteColorOverrideBoth16(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteColorOverrideDouble(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
text := "<#ffffff>jan</>@<#ffffff>Jans-MBP</>"
|
||||
renderer.init("pwsh")
|
||||
|
@ -97,8 +97,8 @@ func TestWriteColorOverrideDouble(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteColorTransparent(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
text := "This is white"
|
||||
|
@ -108,8 +108,8 @@ func TestWriteColorTransparent(t *testing.T) {
|
|||
|
||||
func TestWriteColorName(t *testing.T) {
|
||||
// given
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
text := "This is white, <red>this is red</>, white again"
|
||||
|
@ -123,8 +123,8 @@ func TestWriteColorName(t *testing.T) {
|
|||
|
||||
func TestWriteColorInvalid(t *testing.T) {
|
||||
// given
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
text := "This is white, <invalid>this is orange</>, white again"
|
||||
|
@ -138,28 +138,28 @@ func TestWriteColorInvalid(t *testing.T) {
|
|||
|
||||
func TestLenWithoutANSI(t *testing.T) {
|
||||
text := "\x1b[44mhello\x1b[0m"
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
strippedLength := renderer.lenWithoutANSI(text)
|
||||
strippedLength := lenWithoutANSI(text, "zsh")
|
||||
assert.Equal(t, 5, strippedLength)
|
||||
}
|
||||
|
||||
func TestLenWithoutANSIZsh(t *testing.T) {
|
||||
text := "%{\x1b[44m%}hello%{\x1b[0m%}"
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("zsh")
|
||||
strippedLength := renderer.lenWithoutANSI(text)
|
||||
strippedLength := lenWithoutANSI(text, "zsh")
|
||||
assert.Equal(t, 5, strippedLength)
|
||||
}
|
||||
|
||||
func TestGetAnsiFromColorStringBg(t *testing.T) {
|
||||
// given
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// when
|
||||
|
@ -171,8 +171,8 @@ func TestGetAnsiFromColorStringBg(t *testing.T) {
|
|||
|
||||
func TestGetAnsiFromColorStringFg(t *testing.T) {
|
||||
// given
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// when
|
||||
|
@ -184,8 +184,8 @@ func TestGetAnsiFromColorStringFg(t *testing.T) {
|
|||
|
||||
func TestGetAnsiFromColorStringHex(t *testing.T) {
|
||||
// given
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// when
|
||||
|
@ -197,8 +197,8 @@ func TestGetAnsiFromColorStringHex(t *testing.T) {
|
|||
|
||||
func TestGetAnsiFromColorStringInvalidFg(t *testing.T) {
|
||||
// given
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// when
|
||||
|
@ -210,8 +210,8 @@ func TestGetAnsiFromColorStringInvalidFg(t *testing.T) {
|
|||
|
||||
func TestGetAnsiFromColorStringInvalidBg(t *testing.T) {
|
||||
// given
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
renderer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// when
|
118
ansi_renderer.go
Normal file
118
ansi_renderer.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
func lenWithoutANSI(text, shell string) int {
|
||||
rANSI := "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
|
||||
stripped := replaceAllString(rANSI, text, "")
|
||||
switch shell {
|
||||
case zsh:
|
||||
stripped = strings.ReplaceAll(stripped, "%{", "")
|
||||
stripped = strings.ReplaceAll(stripped, "%}", "")
|
||||
case bash:
|
||||
stripped = strings.ReplaceAll(stripped, "\\[", "")
|
||||
stripped = strings.ReplaceAll(stripped, "\\]", "")
|
||||
}
|
||||
var i norm.Iter
|
||||
i.InitString(norm.NFD, stripped)
|
||||
var count int
|
||||
for !i.Done() {
|
||||
i.Next()
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
type formats struct {
|
||||
linechange string
|
||||
left string
|
||||
right string
|
||||
title string
|
||||
creset string
|
||||
clearOEL string
|
||||
}
|
||||
|
||||
// AnsiRenderer exposes functionality using ANSI
|
||||
type AnsiRenderer struct {
|
||||
buffer *bytes.Buffer
|
||||
formats *formats
|
||||
shell string
|
||||
}
|
||||
|
||||
const (
|
||||
zsh = "zsh"
|
||||
bash = "bash"
|
||||
)
|
||||
|
||||
func (r *AnsiRenderer) init(shell string) {
|
||||
r.shell = shell
|
||||
r.formats = &formats{}
|
||||
switch shell {
|
||||
case zsh:
|
||||
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.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.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"
|
||||
}
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) carriageForward() {
|
||||
r.buffer.WriteString(fmt.Sprintf(r.formats.left, 1000))
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) setCursorForRightWrite(text string, offset int) {
|
||||
strippedLen := lenWithoutANSI(text, r.shell) + -offset
|
||||
r.buffer.WriteString(fmt.Sprintf(r.formats.right, strippedLen))
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) changeLine(numberOfLines int) {
|
||||
position := "B"
|
||||
if numberOfLines < 0 {
|
||||
position = "F"
|
||||
numberOfLines = -numberOfLines
|
||||
}
|
||||
r.buffer.WriteString(fmt.Sprintf(r.formats.linechange, numberOfLines, position))
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) setConsoleTitle(title string) {
|
||||
r.buffer.WriteString(fmt.Sprintf(r.formats.title, title))
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) creset() {
|
||||
r.buffer.WriteString(r.formats.creset)
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) print(text string) {
|
||||
r.buffer.WriteString(text)
|
||||
r.clearEOL()
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) clearEOL() {
|
||||
r.buffer.WriteString(r.formats.clearOEL)
|
||||
}
|
||||
|
||||
func (r *AnsiRenderer) string() string {
|
||||
return r.buffer.String()
|
||||
}
|
|
@ -67,7 +67,7 @@ boxes with question marks, [set up your terminal][setupterm] to use a supported
|
|||
|
||||
Let's take a closer look at what defines a block.
|
||||
|
||||
- type: `prompt` | `newline`
|
||||
- type: `prompt` | `newline` | `rprompt`
|
||||
- alignment: `left` | `right`
|
||||
- vertical_offset: `int`
|
||||
- horizontal_offset: `int`
|
||||
|
@ -75,9 +75,9 @@ Let's take a closer look at what defines a block.
|
|||
|
||||
### Type
|
||||
|
||||
Tells the engine what to do with the block. There are two options, either it renders one or more segments,
|
||||
or it inserts a newline to start the next block on a new line. New line blocks require no additional
|
||||
configuration other than the `type`.
|
||||
Tells the engine what to do with the block. There are three options, either it renders one or more segments,
|
||||
inserts a newline to start the next block on a new line or sets a block as the `RPROMPT` when on [ZSH][rprompt].
|
||||
New line blocks require no additional configuration other than the `type`.
|
||||
|
||||
### Alignment
|
||||
|
||||
|
@ -350,3 +350,4 @@ Oh my Posh mainly supports three different color types being
|
|||
[fg]: /docs/configure#foreground
|
||||
[regex]: https://www.regular-expressions.info/tutorial.html
|
||||
[regex-nl]: https://www.regular-expressions.info/lookaround.html
|
||||
[rprompt]: https://scriptingosx.com/2019/07/moving-to-zsh-06-customizing-the-zsh-prompt/
|
||||
|
|
|
@ -286,7 +286,7 @@ function omp_precmd() {
|
|||
omp_now=$(oh-my-posh --millis)
|
||||
omp_elapsed=$(($omp_now-$omp_start_time))
|
||||
fi
|
||||
PS1="$(oh-my-posh -config ~/.poshthemes/jandedobbeleer.omp.json --error $? --execution-time $omp_elapsed)"
|
||||
eval "$(oh-my-posh --config ~/.poshthemes/jandedobbeleer.omp.json --error $? --execution-time $omp_elapsed --eval)"
|
||||
unset omp_start_time
|
||||
unset omp_now
|
||||
unset omp_elapsed
|
||||
|
@ -326,7 +326,7 @@ Add the following to `~/.bashrc` (or `~/.profile` on MacOS):
|
|||
|
||||
```bash
|
||||
function _update_ps1() {
|
||||
PS1="$(oh-my-posh -config ~/.poshthemes/jandedobbeleer.omp.json -error $?)"
|
||||
eval "$(oh-my-posh --config ~/.poshthemes/jandedobbeleer.omp.json --error $? --eval)"
|
||||
}
|
||||
|
||||
if [ "$TERM" != "linux" ] && [ -x "$(command -v oh-my-posh)" ]; then
|
||||
|
@ -346,30 +346,6 @@ Or, when using `~/.profile`.
|
|||
. ~/.profile
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="nix">
|
||||
|
||||
When using `nix-shell --pure`, `oh-my-posh` will not be accessible, and
|
||||
your prompt will not appear.
|
||||
|
||||
As a workaround you can add this snippet to `~/.bashrc`,
|
||||
which should re-enable the prompt in most cases:
|
||||
|
||||
```bash
|
||||
# Workaround for nix-shell --pure
|
||||
if [ "$IN_NIX_SHELL" == "pure" ]; then
|
||||
if [ -x oh-my-posh ]; then
|
||||
alias powerline-go="oh-my-posh -config ~/.poshthemes/jandedobbeleer.omp.json"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
Once added, reload your profile for the changes to take effect.
|
||||
|
||||
```bash
|
||||
. ~/.bashrc
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fish">
|
||||
|
||||
|
@ -377,7 +353,7 @@ Redefine `fish_prompt` in `~/.config/fish/config.fish`:
|
|||
|
||||
```bash
|
||||
function fish_prompt
|
||||
eval oh-my-posh -config ~/.poshthemes/jandedobbeleer.omp.json -error $status
|
||||
eval oh-my-posh --config ~/.poshthemes/jandedobbeleer.omp.json --error $status --eval
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -430,7 +406,7 @@ This will write the current configuration in your shell, allowing you to copy pa
|
|||
and store it somehwere. Once adjusted to your liking, [change the prompt setting][prompt] to use the newly created file.
|
||||
|
||||
```bash
|
||||
oh-my-posh -config ~/.mytheme.omp.json
|
||||
oh-my-posh --config ~/.mytheme.omp.json
|
||||
```
|
||||
|
||||
#### JSON Schema
|
||||
|
|
67
engine.go
67
engine.go
|
@ -8,10 +8,12 @@ import (
|
|||
type engine struct {
|
||||
settings *Settings
|
||||
env environmentInfo
|
||||
renderer *Renderer
|
||||
color *AnsiColor
|
||||
renderer *AnsiRenderer
|
||||
activeBlock *Block
|
||||
activeSegment *Segment
|
||||
previousActiveSegment *Segment
|
||||
rprompt string
|
||||
}
|
||||
|
||||
func (e *engine) getPowerlineColor(foreground bool) string {
|
||||
|
@ -33,10 +35,10 @@ func (e *engine) writePowerLineSeparator(background, foreground string, end bool
|
|||
symbol = e.previousActiveSegment.PowerlineSymbol
|
||||
}
|
||||
if e.activeSegment.InvertPowerline {
|
||||
e.renderer.write(foreground, background, symbol)
|
||||
e.color.write(foreground, background, symbol)
|
||||
return
|
||||
}
|
||||
e.renderer.write(background, foreground, symbol)
|
||||
e.color.write(background, foreground, symbol)
|
||||
}
|
||||
|
||||
func (e *engine) endPowerline() {
|
||||
|
@ -58,9 +60,9 @@ func (e *engine) renderPlainSegment(text string) {
|
|||
}
|
||||
|
||||
func (e *engine) renderDiamondSegment(text string) {
|
||||
e.renderer.write(Transparent, e.activeSegment.Background, e.activeSegment.LeadingDiamond)
|
||||
e.color.write(Transparent, e.activeSegment.Background, e.activeSegment.LeadingDiamond)
|
||||
e.renderText(text)
|
||||
e.renderer.write(Transparent, e.activeSegment.Background, e.activeSegment.TrailingDiamond)
|
||||
e.color.write(Transparent, e.activeSegment.Background, e.activeSegment.TrailingDiamond)
|
||||
}
|
||||
|
||||
func (e *engine) renderText(text string) {
|
||||
|
@ -70,9 +72,9 @@ func (e *engine) renderText(text string) {
|
|||
}
|
||||
prefix := e.activeSegment.getValue(Prefix, defaultValue)
|
||||
postfix := e.activeSegment.getValue(Postfix, defaultValue)
|
||||
e.renderer.write(e.activeSegment.Background, e.activeSegment.Foreground, fmt.Sprintf("%s%s%s", prefix, text, postfix))
|
||||
e.color.write(e.activeSegment.Background, e.activeSegment.Foreground, fmt.Sprintf("%s%s%s", prefix, text, postfix))
|
||||
if *e.env.getArgs().Debug {
|
||||
e.renderer.write(e.activeSegment.Background, e.activeSegment.Foreground, fmt.Sprintf("(%s:%s)", e.activeSegment.Type, e.activeSegment.timing))
|
||||
e.color.write(e.activeSegment.Background, e.activeSegment.Foreground, fmt.Sprintf("(%s:%s)", e.activeSegment.Type, e.activeSegment.timing))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +91,7 @@ func (e *engine) renderSegmentText(text string) {
|
|||
}
|
||||
|
||||
func (e *engine) renderBlockSegments(block *Block) string {
|
||||
defer e.reset()
|
||||
defer e.resetBlock()
|
||||
e.activeBlock = block
|
||||
e.setStringValues(block.Segments)
|
||||
for _, segment := range block.Segments {
|
||||
|
@ -106,7 +108,7 @@ func (e *engine) renderBlockSegments(block *Block) string {
|
|||
if e.previousActiveSegment != nil && e.previousActiveSegment.Style == Powerline {
|
||||
e.writePowerLineSeparator(Transparent, e.previousActiveSegment.Background, true)
|
||||
}
|
||||
return e.renderer.string()
|
||||
return e.color.string()
|
||||
}
|
||||
|
||||
func (e *engine) setStringValues(segments []*Segment) {
|
||||
|
@ -126,21 +128,24 @@ func (e *engine) setStringValues(segments []*Segment) {
|
|||
func (e *engine) render() {
|
||||
for _, block := range e.settings.Blocks {
|
||||
// if line break, append a line break
|
||||
if block.Type == LineBreak {
|
||||
switch block.Type {
|
||||
case LineBreak:
|
||||
e.renderer.print("\n")
|
||||
continue
|
||||
}
|
||||
if block.VerticalOffset != 0 {
|
||||
e.renderer.changeLine(block.VerticalOffset)
|
||||
}
|
||||
switch block.Alignment {
|
||||
case Right:
|
||||
e.renderer.carriageForward()
|
||||
blockText := e.renderBlockSegments(block)
|
||||
e.renderer.setCursorForRightWrite(blockText, block.HorizontalOffset)
|
||||
e.renderer.print(blockText)
|
||||
case Left:
|
||||
e.renderer.print(e.renderBlockSegments(block))
|
||||
case Prompt:
|
||||
if block.VerticalOffset != 0 {
|
||||
e.renderer.changeLine(block.VerticalOffset)
|
||||
}
|
||||
switch block.Alignment {
|
||||
case Right:
|
||||
e.renderer.carriageForward()
|
||||
blockText := e.renderBlockSegments(block)
|
||||
e.renderer.setCursorForRightWrite(blockText, block.HorizontalOffset)
|
||||
e.renderer.print(blockText)
|
||||
case Left:
|
||||
e.renderer.print(e.renderBlockSegments(block))
|
||||
}
|
||||
case RPrompt:
|
||||
e.rprompt = e.renderBlockSegments(block)
|
||||
}
|
||||
}
|
||||
if e.settings.ConsoleTitle {
|
||||
|
@ -157,10 +162,22 @@ func (e *engine) render() {
|
|||
if e.settings.FinalSpace {
|
||||
e.renderer.print(" ")
|
||||
}
|
||||
e.write()
|
||||
}
|
||||
|
||||
func (e *engine) reset() {
|
||||
e.renderer.reset()
|
||||
func (e *engine) write() {
|
||||
if *e.env.getArgs().Eval {
|
||||
fmt.Printf("PS1=\"%s\"", e.renderer.string())
|
||||
if e.rprompt != "" && e.env.getShellName() == zsh {
|
||||
fmt.Printf("\nRPROMPT=\"%s\"", e.rprompt)
|
||||
}
|
||||
return
|
||||
}
|
||||
fmt.Print(e.renderer.string())
|
||||
}
|
||||
|
||||
func (e *engine) resetBlock() {
|
||||
e.color.reset()
|
||||
e.previousActiveSegment = nil
|
||||
e.activeBlock = nil
|
||||
}
|
||||
|
|
20
main.go
20
main.go
|
@ -22,6 +22,7 @@ type args struct {
|
|||
Debug *bool
|
||||
ExecutionTime *float64
|
||||
Millis *bool
|
||||
Eval *bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -66,6 +67,10 @@ func main() {
|
|||
"millis",
|
||||
false,
|
||||
"Get the current time in milliseconds"),
|
||||
Eval: flag.Bool(
|
||||
"eval",
|
||||
false,
|
||||
"Run in eval mode"),
|
||||
}
|
||||
flag.Parse()
|
||||
env := &environment{
|
||||
|
@ -89,18 +94,23 @@ func main() {
|
|||
fmt.Println(Version)
|
||||
return
|
||||
}
|
||||
colorWriter := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
shell := env.getShellName()
|
||||
if *args.Shell != "" {
|
||||
shell = *args.Shell
|
||||
}
|
||||
colorWriter.init(shell)
|
||||
renderer := &AnsiRenderer{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
colorer := &AnsiColor{
|
||||
buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init(shell)
|
||||
colorer.init(shell)
|
||||
engine := &engine{
|
||||
settings: settings,
|
||||
env: env,
|
||||
renderer: colorWriter,
|
||||
color: colorer,
|
||||
renderer: renderer,
|
||||
}
|
||||
engine.render()
|
||||
}
|
||||
|
|
237
renderer.go
237
renderer.go
|
@ -1,237 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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": {"30", "40"},
|
||||
"red": {"31", "41"},
|
||||
"green": {"32", "42"},
|
||||
"yellow": {"33", "43"},
|
||||
"blue": {"34", "44"},
|
||||
"magenta": {"35", "45"},
|
||||
"cyan": {"36", "46"},
|
||||
"white": {"37", "47"},
|
||||
"default": {"39", "49"},
|
||||
"darkGray": {"90", "100"},
|
||||
"lightRed": {"91", "101"},
|
||||
"lightGreen": {"92", "102"},
|
||||
"lightYellow": {"93", "103"},
|
||||
"lightBlue": {"94", "104"},
|
||||
"lightMagenta": {"95", "105"},
|
||||
"lightCyan": {"96", "106"},
|
||||
"lightWhite": {"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("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 = "transparent"
|
||||
zsh = "zsh"
|
||||
bash = "bash"
|
||||
)
|
||||
|
||||
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, foreground, 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, foreground, text, textToRemove, parentText string) string {
|
||||
r.writeColoredText(background, foreground, text)
|
||||
return strings.Replace(parentText, textToRemove, "", 1)
|
||||
}
|
||||
|
||||
func (r *Renderer) write(background, foreground, text string) {
|
||||
// first we match for any potentially valid colors enclosed in <>
|
||||
match := findAllNamedRegexMatch(`<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`, text)
|
||||
for i := range match {
|
||||
extractedForegroundColor := match[i]["foreground"]
|
||||
extractedBackgroundColor := match[i]["background"]
|
||||
if col := r.getAnsiFromColorString(extractedForegroundColor, false); col == "" && extractedForegroundColor != Transparent && len(extractedBackgroundColor) == 0 {
|
||||
continue // we skip invalid colors
|
||||
}
|
||||
if col := r.getAnsiFromColorString(extractedBackgroundColor, false); col == "" && extractedBackgroundColor != Transparent && len(extractedForegroundColor) == 0 {
|
||||
continue // we skip invalid colors
|
||||
}
|
||||
// reuse function colors if only one was specified
|
||||
if len(extractedBackgroundColor) == 0 {
|
||||
extractedBackgroundColor = background
|
||||
}
|
||||
if len(extractedForegroundColor) == 0 {
|
||||
extractedForegroundColor = foreground
|
||||
}
|
||||
escapedTextSegment := match[i]["text"]
|
||||
innerText := match[i]["content"]
|
||||
textBeforeColorOverride := strings.Split(text, escapedTextSegment)[0]
|
||||
text = r.writeAndRemoveText(background, foreground, textBeforeColorOverride, textBeforeColorOverride, text)
|
||||
text = r.writeAndRemoveText(extractedBackgroundColor, extractedForegroundColor, 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 {
|
||||
stripped := replaceAllString(r.formats.rANSI, str, "")
|
||||
switch r.shell {
|
||||
case zsh:
|
||||
stripped = strings.ReplaceAll(stripped, "%{", "")
|
||||
stripped = strings.ReplaceAll(stripped, "%}", "")
|
||||
case bash:
|
||||
stripped = strings.ReplaceAll(stripped, "\\[", "")
|
||||
stripped = strings.ReplaceAll(stripped, "\\]", "")
|
||||
}
|
||||
var i norm.Iter
|
||||
i.InitString(norm.NFD, stripped)
|
||||
var count int
|
||||
for !i.Done() {
|
||||
i.Next()
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (r *Renderer) carriageForward() {
|
||||
fmt.Printf(r.formats.left, 1000)
|
||||
}
|
||||
|
||||
func (r *Renderer) setCursorForRightWrite(text string, offset int) {
|
||||
strippedLen := r.lenWithoutANSI(text) + -offset
|
||||
fmt.Printf(r.formats.right, strippedLen)
|
||||
}
|
||||
|
||||
func (r *Renderer) changeLine(numberOfLines int) {
|
||||
position := "B"
|
||||
if numberOfLines < 0 {
|
||||
position = "F"
|
||||
numberOfLines = -numberOfLines
|
||||
}
|
||||
fmt.Printf(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)
|
||||
}
|
|
@ -28,6 +28,8 @@ const (
|
|||
Prompt BlockType = "prompt"
|
||||
// LineBreak creates a line break in the prompt
|
||||
LineBreak BlockType = "newline"
|
||||
// RPrompt a right aligned prompt in ZSH
|
||||
RPrompt BlockType = "rprompt"
|
||||
// Left aligns left
|
||||
Left BlockAlignment = "left"
|
||||
// Right aligns right
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"definitions": {
|
||||
"color": {
|
||||
"type": "string",
|
||||
"pattern": "^(#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})|black|red|green|yellow|blue|magenta|cyan|white|default|darkGray|lightRed|lightGreen|lightYellow|lightBlue|lightMagenta|lightCyan|lightWhite)$",
|
||||
"pattern": "^(#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})|black|red|green|yellow|blue|magenta|cyan|white|default|darkGray|lightRed|lightGreen|lightYellow|lightBlue|lightMagenta|lightCyan|lightWhite|transparent)$",
|
||||
"title": "Color string",
|
||||
"description": "https://ohmyposh.dev/docs/configure#colors"
|
||||
},
|
||||
|
@ -30,11 +30,29 @@
|
|||
"then": {
|
||||
"required": ["type"],
|
||||
"title": "Newline, renders a line break"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": { "const": "prompt" }
|
||||
}
|
||||
},
|
||||
"else": {
|
||||
"then": {
|
||||
"required": ["type", "alignment", "segments"],
|
||||
"title": "Prompt definition, contains 1 or more segments to render"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": { "const": "rprompt" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["type", "segments"],
|
||||
"title": "RPrompt definition, contains 1 or more segments to render in ZSH RPROMPT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
|
@ -42,7 +60,7 @@
|
|||
"type": "string",
|
||||
"title": "Block type",
|
||||
"description": "https://ohmyposh.dev/docs/configure#type",
|
||||
"enum": ["prompt", "newline"],
|
||||
"enum": ["prompt", "newline", "rprompt"],
|
||||
"default": "prompt"
|
||||
},
|
||||
"alignment": {
|
||||
|
|
Loading…
Reference in a new issue