mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-26 19:39:39 -08:00
refactor: escape color sequences on zsh
This commit is contained in:
parent
7d8892020e
commit
f478255bbf
|
@ -1,63 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
//ColorWriter writes colorized strings
|
||||
type ColorWriter struct {
|
||||
Buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
const (
|
||||
//Transparent implies a transparent color
|
||||
Transparent string = "transparent"
|
||||
)
|
||||
|
||||
func (w *ColorWriter) writeColoredText(background string, foreground string, text string) {
|
||||
var coloredText string
|
||||
if foreground == Transparent {
|
||||
style := color.HEX(background, false)
|
||||
colorCodes := style.Code()
|
||||
// this takes the colors and inverts them so the foreground becomes transparent
|
||||
coloredText = fmt.Sprintf("\x1b[%s;49m\x1b[7m%s\x1b[m\x1b[0m", colorCodes, text)
|
||||
} else if background == "" || background == Transparent {
|
||||
style := color.HEX(foreground)
|
||||
coloredText = style.Sprint(text)
|
||||
} else {
|
||||
style := color.HEXStyle(foreground, background)
|
||||
coloredText = style.Sprint(text)
|
||||
}
|
||||
w.Buffer.WriteString(coloredText)
|
||||
}
|
||||
|
||||
func (w *ColorWriter) writeAndRemoveText(background string, foreground string, text string, textToRemove string, parentText string) string {
|
||||
w.writeColoredText(background, foreground, text)
|
||||
return strings.Replace(parentText, textToRemove, "", 1)
|
||||
}
|
||||
|
||||
func (w *ColorWriter) write(background string, foreground string, text string) {
|
||||
r := regexp.MustCompile(`<\s*(?P<color>#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})>(?P<text>.*?)<\s*/\s*>`)
|
||||
match := r.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]
|
||||
text = w.writeAndRemoveText(background, foreground, textBeforeColorOverride, textBeforeColorOverride, text)
|
||||
text = w.writeAndRemoveText(background, match[i][1], match[i][2], match[i][0], text)
|
||||
}
|
||||
// color the remaining part of text with background and foreground
|
||||
w.writeColoredText(background, foreground, text)
|
||||
}
|
||||
|
||||
func (w *ColorWriter) string() string {
|
||||
return w.Buffer.String()
|
||||
}
|
||||
|
||||
func (w *ColorWriter) reset() {
|
||||
w.Buffer.Reset()
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWriteAndRemoveText(t *testing.T) {
|
||||
writer := &ColorWriter{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
inputText := "This is white, <#ff5733>this is orange</>, white again"
|
||||
text := writer.writeAndRemoveText("#193549", "#fff", "This is white, ", "This is white, ", inputText)
|
||||
assert.Equal(t, "<#ff5733>this is orange</>, white again", text)
|
||||
assert.NotContains(t, writer.string(), "<#ff5733>")
|
||||
}
|
||||
|
||||
func TestWriteAndRemoveTextColored(t *testing.T) {
|
||||
writer := &ColorWriter{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
inputText := "This is white, <#ff5733>this is orange</>, white again"
|
||||
text := writer.writeAndRemoveText("#193549", "#ff5733", "this is orange", "<#ff5733>this is orange</>", inputText)
|
||||
assert.Equal(t, "This is white, , white again", text)
|
||||
assert.NotContains(t, writer.string(), "<#ff5733>")
|
||||
}
|
||||
|
||||
func TestWriteColorOverride(t *testing.T) {
|
||||
writer := &ColorWriter{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
text := "This is white, <#ff5733>this is orange</>, white again"
|
||||
writer.write("#193549", "#ff5733", text)
|
||||
assert.NotContains(t, writer.string(), "<#ff5733>")
|
||||
}
|
||||
|
||||
func TestWriteColorTransparent(t *testing.T) {
|
||||
writer := &ColorWriter{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
text := "This is white"
|
||||
writer.writeColoredText("#193549", Transparent, text)
|
||||
t.Log(writer.string())
|
||||
}
|
32
engine.go
32
engine.go
|
@ -2,15 +2,12 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
type engine struct {
|
||||
settings *Settings
|
||||
env environmentInfo
|
||||
renderer *ColorWriter
|
||||
renderer *Renderer
|
||||
activeBlock *Block
|
||||
activeSegment *Segment
|
||||
previousActiveSegment *Segment
|
||||
|
@ -107,37 +104,22 @@ func (e *engine) renderBlockSegments(block *Block) string {
|
|||
return e.renderer.string()
|
||||
}
|
||||
|
||||
func (e *engine) lenWithoutANSI(str string) int {
|
||||
ansi := "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
|
||||
re := regexp.MustCompile(ansi)
|
||||
stripped := re.ReplaceAllString(str, "")
|
||||
var i norm.Iter
|
||||
i.InitString(norm.NFD, stripped)
|
||||
var count int
|
||||
for !i.Done() {
|
||||
i.Next()
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (e *engine) render() {
|
||||
for _, block := range e.settings.Blocks {
|
||||
// if line break, append a line break
|
||||
if block.Type == LineBreak {
|
||||
fmt.Printf("\x1b[%dC ", 1000)
|
||||
fmt.Print(e.renderer.lineBreak())
|
||||
continue
|
||||
}
|
||||
if block.LineOffset < 0 {
|
||||
fmt.Printf("\x1b[%dF", -block.LineOffset)
|
||||
} else if block.LineOffset > 0 {
|
||||
fmt.Printf("\x1b[%dB", block.LineOffset)
|
||||
if block.LineOffset != 0 {
|
||||
fmt.Print(e.renderer.changeLine(block.LineOffset))
|
||||
}
|
||||
switch block.Alignment {
|
||||
case Right:
|
||||
fmt.Printf("\x1b[%dC", 1000)
|
||||
fmt.Print(e.renderer.carriageReturn())
|
||||
blockText := e.renderBlockSegments(block)
|
||||
fmt.Printf("\x1b[%dD", e.lenWithoutANSI(blockText)+e.settings.RightSegmentOffset)
|
||||
cursorMove := e.renderer.setCursorForRightWrite(blockText, e.settings.RightSegmentOffset)
|
||||
fmt.Print(cursorMove)
|
||||
fmt.Print(blockText)
|
||||
default:
|
||||
fmt.Print(e.renderBlockSegments(block))
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLenWithoutANSI(t *testing.T) {
|
||||
block := &Block{
|
||||
Type: Prompt,
|
||||
Alignment: Right,
|
||||
Segments: []*Segment{
|
||||
{
|
||||
Type: Time,
|
||||
Style: Plain,
|
||||
Background: "#B8B80A",
|
||||
Foreground: "#ffffff",
|
||||
},
|
||||
},
|
||||
}
|
||||
engine := &engine{
|
||||
renderer: &ColorWriter{
|
||||
Buffer: new(bytes.Buffer),
|
||||
},
|
||||
}
|
||||
blockText := engine.renderBlockSegments(block)
|
||||
strippedLength := engine.lenWithoutANSI(blockText)
|
||||
assert.Equal(t, 10, strippedLength)
|
||||
}
|
12
main.go
12
main.go
|
@ -38,12 +38,18 @@ func main() {
|
|||
fmt.Println(string(theme))
|
||||
return
|
||||
}
|
||||
colorWriter := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
var shell string
|
||||
if parentProcess, err := env.getParentProcess(); err != nil {
|
||||
shell = parentProcess.Executable()
|
||||
}
|
||||
colorWriter.init(shell)
|
||||
engine := &engine{
|
||||
settings: settings,
|
||||
env: env,
|
||||
renderer: &ColorWriter{
|
||||
Buffer: new(bytes.Buffer),
|
||||
},
|
||||
renderer: colorWriter,
|
||||
}
|
||||
engine.render()
|
||||
}
|
||||
|
|
151
renderer.go
Executable file
151
renderer.go
Executable file
|
@ -0,0 +1,151 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
type formats struct {
|
||||
single string
|
||||
full string
|
||||
transparent string
|
||||
linebreak string
|
||||
linechange string
|
||||
left string
|
||||
right string
|
||||
rANSI string
|
||||
}
|
||||
|
||||
//Shell indicates the shell we're currently in
|
||||
type Shell string
|
||||
|
||||
//Renderer writes colorized strings
|
||||
type Renderer struct {
|
||||
Buffer *bytes.Buffer
|
||||
formats *formats
|
||||
shell Shell
|
||||
}
|
||||
|
||||
const (
|
||||
//Transparent implies a transparent color
|
||||
Transparent string = "transparent"
|
||||
zsh Shell = "zsh"
|
||||
universal Shell = "any"
|
||||
)
|
||||
|
||||
func (r *Renderer) init(shell string) {
|
||||
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.linebreak = "\n"
|
||||
r.formats.linechange = "%%{\x1b[%d%s%%}"
|
||||
r.formats.left = "%%{\x1b[%d%%}"
|
||||
r.formats.right = "%%{\x1b[%dD%%}"
|
||||
r.shell = zsh
|
||||
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.linebreak = "\x1b[1000C "
|
||||
r.formats.linechange = "\x1b[%d%s"
|
||||
r.formats.left = "\x1b[%dC"
|
||||
r.formats.right = "\x1b[%dD"
|
||||
r.shell = universal
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) getAnsiFromHex(hexColor string, isBackground bool) string {
|
||||
style := color.HEX(hexColor, 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)
|
||||
coloredText = fmt.Sprintf(r.formats.transparent, ansiColor, text)
|
||||
} else if background == "" || background == Transparent {
|
||||
ansiColor := r.getAnsiFromHex(foreground, false)
|
||||
coloredText = fmt.Sprintf(r.formats.single, ansiColor, text)
|
||||
} else if foreground != "" && background != "" {
|
||||
bgAnsiColor := r.getAnsiFromHex(background, true)
|
||||
fgAnsiColor := r.getAnsiFromHex(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) {
|
||||
rex := regexp.MustCompile(`<\s*(?P<color>#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})>(?P<text>.*?)<\s*/\s*>`)
|
||||
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]
|
||||
text = r.writeAndRemoveText(background, foreground, textBeforeColorOverride, textBeforeColorOverride, text)
|
||||
text = r.writeAndRemoveText(background, match[i][1], match[i][2], match[i][0], 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, "")
|
||||
if r.shell == zsh {
|
||||
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) lineBreak() string {
|
||||
return r.formats.linebreak
|
||||
}
|
||||
|
||||
func (r *Renderer) carriageReturn() 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) string() string {
|
||||
return r.Buffer.String()
|
||||
}
|
||||
|
||||
func (r *Renderer) reset() {
|
||||
r.Buffer.Reset()
|
||||
}
|
70
renderer_test.go
Executable file
70
renderer_test.go
Executable file
|
@ -0,0 +1,70 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWriteAndRemoveText(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
inputText := "This is white, <#ff5733>this is orange</>, white again"
|
||||
text := renderer.writeAndRemoveText("#193549", "#fff", "This is white, ", "This is white, ", inputText)
|
||||
assert.Equal(t, "<#ff5733>this is orange</>, white again", text)
|
||||
assert.NotContains(t, renderer.string(), "<#ff5733>")
|
||||
}
|
||||
|
||||
func TestWriteAndRemoveTextColored(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
inputText := "This is white, <#ff5733>this is orange</>, white again"
|
||||
text := renderer.writeAndRemoveText("#193549", "#ff5733", "this is orange", "<#ff5733>this is orange</>", inputText)
|
||||
assert.Equal(t, "This is white, , white again", text)
|
||||
assert.NotContains(t, renderer.string(), "<#ff5733>")
|
||||
}
|
||||
|
||||
func TestWriteColorOverride(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
text := "This is white, <#ff5733>this is orange</>, white again"
|
||||
renderer.write("#193549", "#ff5733", text)
|
||||
assert.NotContains(t, renderer.string(), "<#ff5733>")
|
||||
}
|
||||
|
||||
func TestWriteColorTransparent(t *testing.T) {
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
text := "This is white"
|
||||
renderer.writeColoredText("#193549", Transparent, text)
|
||||
t.Log(renderer.string())
|
||||
}
|
||||
|
||||
func TestLenWithoutANSI(t *testing.T) {
|
||||
text := "\x1b[44mhello\x1b[0m"
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("pwsh")
|
||||
strippedLength := renderer.lenWithoutANSI(text)
|
||||
assert.Equal(t, 5, strippedLength)
|
||||
}
|
||||
|
||||
func TestLenWithoutANSIZsh(t *testing.T) {
|
||||
text := "%{\x1b[44m%}hello%{\x1b[0m%}"
|
||||
renderer := &Renderer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
renderer.init("zsh")
|
||||
strippedLength := renderer.lenWithoutANSI(text)
|
||||
assert.Equal(t, 5, strippedLength)
|
||||
}
|
|
@ -9,14 +9,14 @@ type command struct {
|
|||
}
|
||||
|
||||
const (
|
||||
//Shell to execute command in
|
||||
Shell Property = "shell"
|
||||
//ExecutableShell to execute command in
|
||||
ExecutableShell Property = "shell"
|
||||
//Command to execute
|
||||
Command Property = "command"
|
||||
)
|
||||
|
||||
func (c *command) enabled() bool {
|
||||
shell := c.props.getString(Shell, "bash")
|
||||
shell := c.props.getString(ExecutableShell, "bash")
|
||||
command := c.props.getString(Command, "echo no command specified")
|
||||
if strings.Contains(command, "||") {
|
||||
commands := strings.Split(command, "||")
|
||||
|
|
Loading…
Reference in a new issue