diff --git a/src/engine/engine.go b/src/engine/engine.go index 340e887f..176efd99 100644 --- a/src/engine/engine.go +++ b/src/engine/engine.go @@ -35,27 +35,48 @@ func (e *Engine) string() string { return text } -func (e *Engine) canWriteRightBlock(rprompt bool) bool { - if rprompt && (e.rprompt == "" || e.Plain) { - return false +func (e *Engine) canWriteRightBlock(rprompt bool) (int, bool) { + if rprompt && (len(e.rprompt) == 0 || e.Plain) { + return 0, false } + consoleWidth, err := e.Env.TerminalWidth() if err != nil || consoleWidth == 0 { - return true + return 0, false } + promptWidth := e.currentLineLength availableSpace := consoleWidth - promptWidth + // spanning multiple lines if availableSpace < 0 { overflow := promptWidth % consoleWidth availableSpace = consoleWidth - overflow } + + if rprompt { + availableSpace -= e.rpromptLength + } + promptBreathingRoom := 5 if rprompt { promptBreathingRoom = 30 } - canWrite := (availableSpace - e.rpromptLength) >= promptBreathingRoom - return canWrite + + canWrite := availableSpace >= promptBreathingRoom + + return availableSpace, canWrite +} + +func (e *Engine) writeRPrompt() { + space, OK := e.canWriteRightBlock(true) + if !OK { + return + } + e.write(e.Writer.SaveCursorPosition()) + e.write(strings.Repeat(" ", space)) + e.write(e.rprompt) + e.write(e.Writer.RestoreCursorPosition()) } func (e *Engine) pwd() { @@ -105,23 +126,28 @@ func (e *Engine) isWarp() bool { return e.Env.Getenv("TERM_PROGRAM") == "WarpTerminal" } -func (e *Engine) shouldFill(filler string, length int) (string, bool) { +func (e *Engine) shouldFill(filler string, blockLength int) (string, bool) { if len(filler) == 0 { return "", false } + terminalWidth, err := e.Env.TerminalWidth() if err != nil || terminalWidth == 0 { return "", false } - padLength := terminalWidth - e.currentLineLength - length + + padLength := terminalWidth - e.currentLineLength - blockLength if padLength <= 0 { return "", false } + + // allow for easy color overrides and templates e.Writer.Write("", "", filler) filler, lenFiller := e.Writer.String() if lenFiller == 0 { return "", false } + repeat := padLength / lenFiller return strings.Repeat(filler, repeat), true } @@ -197,7 +223,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) { text, length := block.RenderSegments() e.rpromptLength = length - if !e.canWriteRightBlock(false) { + if _, OK := e.canWriteRightBlock(false); OK { switch block.Overflow { case Break: e.newline() @@ -216,6 +242,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) { e.write(text) return } + prompt := e.Writer.CarriageForward() prompt += e.Writer.GetCursorForRightWrite(length, block.HorizontalOffset) prompt += text diff --git a/src/engine/engine_test.go b/src/engine/engine_test.go index 0e1f9d2c..ca98506f 100644 --- a/src/engine/engine_test.go +++ b/src/engine/engine_test.go @@ -21,7 +21,7 @@ func TestCanWriteRPrompt(t *testing.T) { PromptLength int RPromptLength int }{ - {Case: "Width Error", Expected: true, TerminalWidthError: errors.New("burp")}, + {Case: "Width Error", Expected: false, TerminalWidthError: errors.New("burp")}, {Case: "Terminal > Prompt enabled", Expected: true, TerminalWidth: 200, PromptLength: 100, RPromptLength: 10}, {Case: "Terminal > Prompt enabled edge", Expected: true, TerminalWidth: 200, PromptLength: 100, RPromptLength: 70}, {Case: "Prompt > Terminal enabled", Expected: true, TerminalWidth: 200, PromptLength: 300, RPromptLength: 70}, @@ -39,7 +39,7 @@ func TestCanWriteRPrompt(t *testing.T) { currentLineLength: tc.PromptLength, rprompt: "hello", } - got := engine.canWriteRightBlock(true) + _, got := engine.canWriteRightBlock(true) assert.Equal(t, tc.Expected, got, tc.Case) } } diff --git a/src/engine/prompt.go b/src/engine/prompt.go index f419f3e1..8e095fb9 100644 --- a/src/engine/prompt.go +++ b/src/engine/prompt.go @@ -44,6 +44,7 @@ func (e *Engine) Primary() string { if e.Config.FinalSpace { e.write(" ") + e.currentLineLength++ } if e.Config.ShellIntegration && e.Config.TransientPrompt == nil { @@ -59,11 +60,7 @@ func (e *Engine) Primary() string { } // Warp doesn't support RPROMPT so we need to write it manually if e.isWarp() { - e.write(e.Writer.SaveCursorPosition()) - e.write(e.Writer.CarriageForward()) - e.write(e.Writer.GetCursorForRightWrite(e.rpromptLength, 0)) - e.write(e.rprompt) - e.write(e.Writer.RestoreCursorPosition()) + e.writeRPrompt() // escape double quotes contained in the prompt prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), `"`, `\"`)) return prompt @@ -73,16 +70,10 @@ func (e *Engine) Primary() string { prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt) return prompt case shell.PWSH, shell.PWSH5, shell.GENERIC, shell.NU: - if !e.canWriteRightBlock(true) { - break - } - e.write(e.Writer.SaveCursorPosition()) - e.write(e.Writer.CarriageForward()) - e.write(e.Writer.GetCursorForRightWrite(e.rpromptLength, 0)) - e.write(e.rprompt) - e.write(e.Writer.RestoreCursorPosition()) + e.writeRPrompt() case shell.BASH: - if !e.canWriteRightBlock(true) { + space, OK := e.canWriteRightBlock(true) + if !OK { break } // in bash, the entire rprompt needs to be escaped for the prompt to be interpreted correctly @@ -92,8 +83,7 @@ func (e *Engine) Primary() string { } writer.Init(shell.GENERIC) prompt := writer.SaveCursorPosition() - prompt += writer.CarriageForward() - prompt += writer.GetCursorForRightWrite(e.rpromptLength, 0) + prompt += strings.Repeat(" ", space) prompt += e.rprompt prompt += writer.RestoreCursorPosition() prompt = e.Writer.FormatText(prompt)