Skip to content

Commit

Permalink
fix(ansi): escape prompt sequences correctly
Browse files Browse the repository at this point in the history
resolves #5090
  • Loading branch information
JanDeDobbeleer committed Jun 16, 2024
1 parent 4ed8104 commit 48a2dff
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 22 deletions.
43 changes: 39 additions & 4 deletions src/ansi/ansi_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -111,6 +113,9 @@ type Writer struct {
osc7 string
osc51 string

lastRune rune
escapeSequences map[rune]rune

hyperlinkStart string
hyperlinkCenter string
hyperlinkEnd string
Expand Down Expand Up @@ -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%%}"
Expand Down Expand Up @@ -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) {
Expand All @@ -207,21 +223,26 @@ 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)
}

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)
Expand All @@ -238,6 +259,7 @@ func (w *Writer) ClearAfter() string {
if w.Plain {
return ""
}

return w.clearLine + w.clearBelow
}

Expand All @@ -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:
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down
3 changes: 3 additions & 0 deletions src/engine/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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())
}

Expand Down
6 changes: 1 addition & 5 deletions src/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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\\",
},
}

Expand Down
30 changes: 18 additions & 12 deletions src/engine/segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -371,16 +370,20 @@ 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
}

func (segment *Segment) shouldIncludeFolder() bool {
if segment.env == nil {
return true
}

cwdIncluded := segment.cwdIncluded()
cwdExcluded := segment.cwdExcluded()

return cwdIncluded && !cwdExcluded
}

Expand Down Expand Up @@ -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)
}
Expand All @@ -429,26 +433,31 @@ func (segment *Segment) shouldInvokeWithTip(tip string) bool {
return true
}
}

return false
}

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
}

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
}

Expand Down Expand Up @@ -481,30 +490,36 @@ 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
}

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
}
Expand Down Expand Up @@ -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)
}
}

0 comments on commit 48a2dff

Please sign in to comment.