fix(ansi): escape prompt sequences correctly

resolves #5090
This commit is contained in:
Jan De Dobbeleer 2024-06-16 09:27:28 +02:00 committed by Jan De Dobbeleer
parent 4ed8104d2d
commit 48a2dff7c2
5 changed files with 62 additions and 22 deletions

View file

@ -82,8 +82,10 @@ type Writer struct {
Colors *Colors
ParentColors []*Colors
AnsiColors ColorString
Plain bool
TrueColor bool
Plain bool
TrueColor bool
Interactive bool
builder strings.Builder
length int
@ -111,6 +113,9 @@ type Writer struct {
osc7 string
osc51 string
lastRune rune
escapeSequences map[rune]rune
hyperlinkStart string
hyperlinkCenter string
hyperlinkEnd string
@ -144,6 +149,10 @@ func (w *Writer) Init(shellName string) {
w.iTermPromptMark = "\\[$(iterm2_prompt_mark)\\]"
w.iTermCurrentDir = "\\[\x1b]1337;CurrentDir=%s\x07\\]"
w.iTermRemoteHost = "\\[\x1b]1337;RemoteHost=%s@%s\x07\\]"
w.escapeSequences = map[rune]rune{
96: 92, // backtick
92: 92, // backslash
}
case shell.ZSH, shell.TCSH:
w.format = "%%{%s%%}"
w.linechange = "%%{\x1b[%d%s%%}"
@ -184,6 +193,13 @@ func (w *Writer) Init(shellName string) {
w.iTermCurrentDir = "\x1b]1337;CurrentDir=%s\x07"
w.iTermRemoteHost = "\x1b]1337;RemoteHost=%s@%s\x07"
}
if shellName == shell.ZSH {
w.escapeSequences = map[rune]rune{
96: 92, // backtick
37: 37, // %
}
}
}
func (w *Writer) SetColors(background, foreground string) {
@ -207,11 +223,14 @@ func (w *Writer) ChangeLine(numberOfLines int) string {
if w.Plain {
return ""
}
position := "B"
if numberOfLines < 0 {
position = "F"
numberOfLines = -numberOfLines
}
return fmt.Sprintf(w.linechange, numberOfLines, position)
}
@ -219,9 +238,11 @@ func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string {
if w.Plain {
return ""
}
if strings.HasSuffix(pwd, ":") {
pwd += "\\"
}
switch pwdType {
case OSC7:
return fmt.Sprintf(w.osc7, hostName, pwd)
@ -238,6 +259,7 @@ func (w *Writer) ClearAfter() string {
if w.Plain {
return ""
}
return w.clearLine + w.clearBelow
}
@ -246,6 +268,7 @@ func (w *Writer) FormatTitle(title string) string {
if w.Plain {
return title
}
// we have to do this to prevent bash/zsh from misidentifying escape sequences
switch w.shell {
case shell.BASH:
@ -256,6 +279,7 @@ func (w *Writer) FormatTitle(title string) string {
// these shells don't support setting the title
return ""
}
return fmt.Sprintf(w.title, title)
}
@ -416,10 +440,21 @@ func (w *Writer) write(s rune) {
return
}
if !w.hyperlink {
w.length += runewidth.RuneWidth(s)
if w.hyperlink {
w.builder.WriteRune(s)
return
}
if !w.Interactive {
for special, escape := range w.escapeSequences {
if s == special && w.lastRune != escape {
w.builder.WriteRune(escape)
}
}
}
w.length += runewidth.RuneWidth(s)
w.lastRune = s
w.builder.WriteRune(s)
}

View file

@ -70,6 +70,7 @@ func (b *Block) InitPlain(env platform.Environment, config *Config) {
AnsiColors: config.MakeColors(),
TrueColor: env.Flags().TrueColor,
}
b.writer.Init(shell.GENERIC)
b.env = env
b.executeSegmentLogic()
@ -79,12 +80,14 @@ func (b *Block) executeSegmentLogic() {
if shouldHideForWidth(b.env, b.MinWidth, b.MaxWidth) {
return
}
b.setEnabledSegments()
b.setSegmentsText()
}
func (b *Block) setActiveSegment(segment *Segment) {
b.activeSegment = segment
b.writer.Interactive = segment.Interactive
b.writer.SetColors(segment.background(), segment.foreground())
}

View file

@ -92,16 +92,12 @@ func (e *Engine) pwd() {
cwd := e.Env.Pwd()
// in BASH, we need to escape the path
if e.Env.Shell() == shell.BASH {
cwd = strings.ReplaceAll(cwd, `\`, `\\`)
}
// Backwards compatibility for deprecated OSC99
if e.Config.OSC99 {
e.write(e.Writer.ConsolePwd(ansi.OSC99, "", "", cwd))
return
}
// Allow template logic to define when to enable the PWD (when supported)
tmpl := &template.Text{
Template: e.Config.PWD,

View file

@ -66,7 +66,7 @@ func TestPrintPWD(t *testing.T) {
Pwd: `C:\Users\user\Documents\GitHub\oh-my-posh`,
Config: ansi.OSC99,
Shell: shell.BASH,
Expected: "\x1b]9;9;C:\\\\Users\\\\user\\\\Documents\\\\GitHub\\\\oh-my-posh\x1b\\",
Expected: "\x1b]9;9;C:\\Users\\user\\Documents\\GitHub\\oh-my-posh\x1b\\",
},
}

View file

@ -11,7 +11,6 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
c "golang.org/x/text/cases"
@ -371,7 +370,9 @@ func (segment *Segment) style() SegmentStyle {
if len(segment.styleCache) != 0 {
return segment.styleCache
}
segment.styleCache = segment.Style.Resolve(segment.env, segment.writer)
return segment.styleCache
}
@ -379,8 +380,10 @@ func (segment *Segment) shouldIncludeFolder() bool {
if segment.env == nil {
return true
}
cwdIncluded := segment.cwdIncluded()
cwdExcluded := segment.cwdExcluded()
return cwdIncluded && !cwdExcluded
}
@ -419,6 +422,7 @@ func (segment *Segment) cwdExcluded() bool {
if !ok {
value = segment.Properties[properties.IgnoreFolders]
}
list := properties.ParseStringArray(value)
return segment.env.DirMatchesOneOf(segment.env.Pwd(), list)
}
@ -429,6 +433,7 @@ func (segment *Segment) shouldInvokeWithTip(tip string) bool {
return true
}
}
return false
}
@ -436,9 +441,11 @@ func (segment *Segment) foreground() string {
if segment.colors == nil {
segment.colors = &ansi.Colors{}
}
if len(segment.colors.Foreground) == 0 {
segment.colors.Foreground = segment.ForegroundTemplates.FirstMatch(segment.writer, segment.env, segment.Foreground)
}
return segment.colors.Foreground
}
@ -446,9 +453,11 @@ func (segment *Segment) background() string {
if segment.colors == nil {
segment.colors = &ansi.Colors{}
}
if len(segment.colors.Background) == 0 {
segment.colors.Background = segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background)
}
return segment.colors.Background
}
@ -481,19 +490,23 @@ func (segment *Segment) string() string {
return templatesResult
}
}
if len(segment.Template) == 0 {
segment.Template = segment.writer.Template()
}
tmpl := &template.Text{
Template: segment.Template,
Context: segment.writer,
Env: segment.env,
TemplatesResult: templatesResult,
}
text, err := tmpl.Render()
if err != nil {
return err.Error()
}
return text
}
@ -501,10 +514,12 @@ func (segment *Segment) Name() string {
if len(segment.name) != 0 {
return segment.name
}
name := segment.Alias
if len(name) == 0 {
name = c.Title(language.English).String(string(segment.Type))
}
segment.name = name
return name
}
@ -562,20 +577,11 @@ func (segment *Segment) SetText() {
if !segment.Enabled {
return
}
segment.text = segment.string()
segment.Enabled = len(strings.ReplaceAll(segment.text, " ", "")) > 0
if !segment.Enabled {
segment.env.TemplateCache().RemoveSegmentData(segment.Name())
}
if segment.Interactive {
return
}
// we have to do this to prevent bash/zsh from misidentifying escape sequences
switch segment.env.Shell() {
case shell.BASH:
segment.text = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(segment.text)
case shell.ZSH:
segment.text = strings.NewReplacer("`", "\\`", `%`, `%%`).Replace(segment.text)
}
}