fix(bash): escape rprompt entirely

closes #2398
This commit is contained in:
Jan De Dobbeleer 2022-06-12 19:55:57 +02:00 committed by Jan De Dobbeleer
parent 992add9a78
commit d8b979ec3b
3 changed files with 60 additions and 25 deletions

View file

@ -10,7 +10,8 @@ func (ansi *Ansi) MeasureText(text string) int {
// skip strings with ANSI // skip strings with ANSI
if !strings.Contains(text, "\x1b") { if !strings.Contains(text, "\x1b") {
text = ansi.TrimEscapeSequences(text) text = ansi.TrimEscapeSequences(text)
return utf8.RuneCountInString(text) length := utf8.RuneCountInString(text)
return length
} }
if strings.Contains(text, "\x1b]8;;") { if strings.Contains(text, "\x1b]8;;") {
matches := regex.FindAllNamedRegexMatch(ansi.hyperlinkRegex, text) matches := regex.FindAllNamedRegexMatch(ansi.hyperlinkRegex, text)
@ -20,7 +21,8 @@ func (ansi *Ansi) MeasureText(text string) int {
} }
text = ansi.TrimAnsi(text) text = ansi.TrimAnsi(text)
text = ansi.TrimEscapeSequences(text) text = ansi.TrimEscapeSequences(text)
return utf8.RuneCountInString(text) length := utf8.RuneCountInString(text)
return length
} }
func (ansi *Ansi) TrimAnsi(text string) string { func (ansi *Ansi) TrimAnsi(text string) string {

View file

@ -41,6 +41,9 @@ func (e *Engine) string() string {
} }
func (e *Engine) canWriteRPrompt() bool { func (e *Engine) canWriteRPrompt() bool {
if e.rprompt == "" || e.Plain {
return false
}
consoleWidth, err := e.Env.TerminalWidth() consoleWidth, err := e.Env.TerminalWidth()
if err != nil || consoleWidth == 0 { if err != nil || consoleWidth == 0 {
return true return true
@ -86,7 +89,7 @@ func (e *Engine) shouldFill(block *Block, length int) (string, bool) {
return "", false return "", false
} }
terminalWidth, err := e.Env.TerminalWidth() terminalWidth, err := e.Env.TerminalWidth()
if err != nil && terminalWidth == 0 { if err != nil || terminalWidth == 0 {
return "", false return "", false
} }
padLength := terminalWidth - e.currentLineLength - length padLength := terminalWidth - e.currentLineLength - length
@ -106,7 +109,7 @@ func (e *Engine) shouldFill(block *Block, length int) (string, bool) {
func (e *Engine) renderBlock(block *Block) { func (e *Engine) renderBlock(block *Block) {
// when in bash, for rprompt blocks we need to write plain // when in bash, for rprompt blocks we need to write plain
// and wrap in escaped mode or the prompt will not render correctly // and wrap in escaped mode or the prompt will not render correctly
if block.Type == RPrompt && e.Env.Shell() == shell.BASH { if e.Env.Shell() == shell.BASH && (block.Type == RPrompt || block.Alignment == Right) {
block.InitPlain(e.Env, e.Config) block.InitPlain(e.Env, e.Config)
} else { } else {
block.Init(e.Env, e.Writer, e.Ansi) block.Init(e.Env, e.Writer, e.Ansi)
@ -127,28 +130,42 @@ func (e *Engine) renderBlock(block *Block) {
if block.VerticalOffset != 0 { if block.VerticalOffset != 0 {
e.writeANSI(e.Ansi.ChangeLine(block.VerticalOffset)) e.writeANSI(e.Ansi.ChangeLine(block.VerticalOffset))
} }
switch block.Alignment {
case Right: if block.Alignment == Left {
text, length := block.RenderSegments()
if padText, OK := e.shouldFill(block, length); OK {
e.write(padText)
}
e.writeANSI(e.Ansi.CarriageForward())
e.writeANSI(e.Ansi.GetCursorForRightWrite(length, block.HorizontalOffset))
e.currentLineLength = 0
e.write(text)
case Left:
text, length := block.RenderSegments() text, length := block.RenderSegments()
e.currentLineLength += length e.currentLineLength += length
e.write(text) e.write(text)
break
} }
case RPrompt:
if block.Alignment != Right {
break
}
text, length := block.RenderSegments() text, length := block.RenderSegments()
e.rpromptLength = length padText, hasPadText := e.shouldFill(block, length)
if e.Env.Shell() == shell.BASH { if hasPadText {
text = e.Ansi.FormatText(text) // in this case we can print plain
e.write(padText)
e.write(text)
break
} }
e.rprompt = text // this can contain ANSI escape sequences
ansi := e.Ansi
if e.Env.Shell() == shell.BASH {
ansi = &color.Ansi{}
ansi.InitPlain()
}
prompt := ansi.CarriageForward()
prompt += ansi.GetCursorForRightWrite(length, block.HorizontalOffset)
prompt += text
e.currentLineLength = 0
if e.Env.Shell() == shell.BASH {
prompt = e.Ansi.FormatText(prompt)
}
e.write(prompt)
case RPrompt:
e.rprompt, e.rpromptLength = block.RenderSegments()
} }
// Due to a bug in Powershell, the end of the line needs to be cleared. // Due to a bug in Powershell, the end of the line needs to be cleared.
// If this doesn't happen, the portion after the prompt gets colored in the background // If this doesn't happen, the portion after the prompt gets colored in the background
@ -213,8 +230,8 @@ func (e *Engine) print() string {
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), `"`, `\"`)) prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), `"`, `\"`))
prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt) prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt)
return prompt return prompt
case shell.PWSH, shell.PWSH5, shell.BASH, shell.PLAIN, shell.NU: case shell.PWSH, shell.PWSH5, shell.PLAIN, shell.NU:
if e.rprompt == "" || !e.canWriteRPrompt() || e.Plain { if !e.canWriteRPrompt() {
break break
} }
e.write(e.Ansi.SaveCursorPosition()) e.write(e.Ansi.SaveCursorPosition())
@ -222,6 +239,21 @@ func (e *Engine) print() string {
e.write(e.Ansi.GetCursorForRightWrite(e.rpromptLength, 0)) e.write(e.Ansi.GetCursorForRightWrite(e.rpromptLength, 0))
e.write(e.rprompt) e.write(e.rprompt)
e.write(e.Ansi.RestoreCursorPosition()) e.write(e.Ansi.RestoreCursorPosition())
case shell.BASH:
if !e.canWriteRPrompt() {
break
}
// in bash, the entire rprompt needs to be escaped for the prompt to be interpreted correctly
// see https://github.com/JanDeDobbeleer/oh-my-posh/pull/2398
ansi := &color.Ansi{}
ansi.InitPlain()
prompt := ansi.SaveCursorPosition()
prompt += ansi.CarriageForward()
prompt += ansi.GetCursorForRightWrite(e.rpromptLength, 0)
prompt += e.rprompt
prompt += ansi.RestoreCursorPosition()
prompt = e.Ansi.FormatText(prompt)
e.write(prompt)
} }
return e.string() return e.string()
} }

View file

@ -34,10 +34,11 @@ func TestCanWriteRPrompt(t *testing.T) {
env := new(mock.MockedEnvironment) env := new(mock.MockedEnvironment)
env.On("TerminalWidth").Return(tc.TerminalWidth, tc.TerminalWidthError) env.On("TerminalWidth").Return(tc.TerminalWidth, tc.TerminalWidthError)
engine := &Engine{ engine := &Engine{
Env: env, Env: env,
rpromptLength: tc.RPromptLength,
currentLineLength: tc.PromptLength,
rprompt: "hello",
} }
engine.rpromptLength = tc.RPromptLength
engine.currentLineLength = tc.PromptLength
got := engine.canWriteRPrompt() got := engine.canWriteRPrompt()
assert.Equal(t, tc.Expected, got, tc.Case) assert.Equal(t, tc.Expected, got, tc.Case)
} }