fix(bash): native rprompt

resolves #5221
This commit is contained in:
Jan De Dobbeleer 2024-07-07 10:57:03 +02:00 committed by Jan De Dobbeleer
parent b103581ed6
commit a65abd25a5
7 changed files with 118 additions and 87 deletions

View file

@ -4,8 +4,6 @@ import (
"sync" "sync"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
) )
// BlockType type of block // BlockType type of block
@ -61,15 +59,6 @@ func (b *Block) Init(env runtime.Environment) {
b.executeSegmentLogic() b.executeSegmentLogic()
} }
func (b *Block) InitPlain(env runtime.Environment, config *Config) {
terminal.Init(shell.GENERIC)
terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, config.TerminalBackground)
terminal.Colors = config.MakeColors()
b.env = env
b.executeSegmentLogic()
}
func (b *Block) Enabled() bool { func (b *Block) Enabled() bool {
if b.Type == LineBreak { if b.Type == LineBreak {
return true return true

View file

@ -73,17 +73,6 @@ func (e *Engine) canWriteRightBlock(rprompt bool) (int, bool) {
return availableSpace, canWrite return availableSpace, canWrite
} }
func (e *Engine) writeRPrompt() {
space, OK := e.canWriteRightBlock(true)
if !OK {
return
}
e.write(terminal.SaveCursorPosition())
e.write(strings.Repeat(" ", space))
e.write(e.rprompt)
e.write(terminal.RestoreCursorPosition())
}
func (e *Engine) pwd() { func (e *Engine) pwd() {
// only print when supported // only print when supported
sh := e.Env.Shell() sh := e.Env.Shell()
@ -199,13 +188,7 @@ func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool {
return false return false
} }
// when in bash, for rprompt blocks we need to write plain
// and wrap in escaped mode or the prompt will not render correctly
if e.Env.Shell() == shell.BASH && block.Type == config.RPrompt {
block.InitPlain(e.Env, e.Config)
} else {
block.Init(e.Env) block.Init(e.Env)
}
if !block.Enabled() { if !block.Enabled() {
return false return false

View file

@ -30,6 +30,8 @@ func (e *Engine) Primary() string {
cycle = &e.Config.Cycle cycle = &e.Config.Cycle
var cancelNewline, didRender bool var cancelNewline, didRender bool
needsPrimaryRPrompt := e.needsPrimaryRPrompt()
for i, block := range e.Config.Blocks { for i, block := range e.Config.Blocks {
// do not print a leading newline when we're at the first row and the prompt is cleared // do not print a leading newline when we're at the first row and the prompt is cleared
if i == 0 { if i == 0 {
@ -42,14 +44,7 @@ func (e *Engine) Primary() string {
cancelNewline = !didRender cancelNewline = !didRender
} }
// only render rprompt for shells where we need it from the primary prompt if block.Type == config.RPrompt && !needsPrimaryRPrompt {
renderRPrompt := true
switch e.Env.Shell() {
case shell.ELVISH, shell.FISH, shell.NU, shell.XONSH, shell.CMD:
renderRPrompt = false
}
if block.Type == config.RPrompt && !renderRPrompt {
continue continue
} }
@ -84,61 +79,48 @@ func (e *Engine) Primary() string {
if !e.Env.Flags().Eval { if !e.Env.Flags().Eval {
break break
} }
// Warp doesn't support RPROMPT so we need to write it manually // Warp doesn't support RPROMPT so we need to write it manually
if e.isWarp() { if e.isWarp() {
e.writeRPrompt() e.writePrimaryRPrompt()
// escape double quotes contained in the prompt // escape double quotes contained in the prompt
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), `"`, `\"`)) prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), `"`, `\"`))
return prompt return prompt
} }
// escape double quotes contained in the prompt // escape double quotes contained in the prompt
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.GENERIC: default:
e.writeRPrompt() if !needsPrimaryRPrompt {
case shell.BASH:
space, OK := e.canWriteRightBlock(true)
if !OK {
break 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
terminal.Init(shell.GENERIC) e.writePrimaryRPrompt()
prompt := terminal.SaveCursorPosition()
prompt += strings.Repeat(" ", space)
prompt += e.rprompt
prompt += terminal.RestoreCursorPosition()
prompt = terminal.EscapeText(prompt)
e.write(prompt)
} }
return e.string() return e.string()
} }
func (e *Engine) RPrompt() string { func (e *Engine) needsPrimaryRPrompt() bool {
filterRPromptBlock := func(blocks []*config.Block) *config.Block { switch e.Env.Shell() {
for _, block := range blocks { case shell.PWSH, shell.PWSH5, shell.GENERIC, shell.ZSH:
if block.Type == config.RPrompt { return true
return block default:
return false
} }
} }
return nil
}
block := filterRPromptBlock(e.Config.Blocks) func (e *Engine) writePrimaryRPrompt() {
if block == nil { space, OK := e.canWriteRightBlock(true)
return "" if !OK {
return
} }
block.Init(e.Env) e.write(terminal.SaveCursorPosition())
if !block.Enabled() { e.write(strings.Repeat(" ", space))
return "" e.write(e.rprompt)
} e.write(terminal.RestoreCursorPosition())
text, length := e.renderBlockSegments(block)
e.rpromptLength = length
return text
} }

59
src/prompt/rprompt.go Normal file
View file

@ -0,0 +1,59 @@
package prompt
import (
"fmt"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
)
func (e *Engine) RPrompt() string {
filterRPromptBlock := func(blocks []*config.Block) *config.Block {
for _, block := range blocks {
if block.Type == config.RPrompt {
return block
}
}
return nil
}
block := filterRPromptBlock(e.Config.Blocks)
if block == nil {
return ""
}
if e.Env.Shell() == shell.BASH {
terminal.Init(shell.GENERIC)
}
block.Init(e.Env)
if !block.Enabled() {
return ""
}
text, length := e.renderBlockSegments(block)
e.rpromptLength = length
if e.Env.Shell() != shell.BASH {
return text
}
width, err := e.Env.TerminalWidth()
if err != nil {
log.Error(err)
return ""
}
padding := width - e.rpromptLength
if padding < 0 {
padding = 0
}
text = fmt.Sprintf("%s%s\r", strings.Repeat(" ", padding), text)
return text
}

View file

@ -45,7 +45,6 @@ func (e *Engine) Tooltip(tip string) string {
text, _ := e.renderBlockSegments(block) text, _ := e.renderBlockSegments(block)
return text return text
case shell.PWSH, shell.PWSH5: case shell.PWSH, shell.PWSH5:
block.InitPlain(e.Env, e.Config)
if !block.Enabled() { if !block.Enabled() {
return "" return ""
} }

View file

@ -82,6 +82,7 @@ func quotePosixStr(str string) string {
if len(str) == 0 { if len(str) == 0 {
return "''" return "''"
} }
needQuoting := false needQuoting := false
var b strings.Builder var b strings.Builder
for _, r := range str { for _, r := range str {
@ -166,19 +167,23 @@ func quoteNuStr(str string) string {
func Init(env runtime.Environment) string { func Init(env runtime.Environment) string {
shell := env.Flags().Shell shell := env.Flags().Shell
switch shell { switch shell {
case PWSH, PWSH5, ELVISH: case PWSH, PWSH5, ELVISH:
executable, err := getExecutablePath(env) executable, err := getExecutablePath(env)
if err != nil { if err != nil {
return noExe return noExe
} }
var additionalParams string var additionalParams string
if env.Flags().Strict { if env.Flags().Strict {
additionalParams += " --strict" additionalParams += " --strict"
} }
if env.Flags().Manual { if env.Flags().Manual {
additionalParams += " --manual" additionalParams += " --manual"
} }
var command, config string var command, config string
switch shell { switch shell {
case PWSH, PWSH5: case PWSH, PWSH5:
@ -189,6 +194,7 @@ func Init(env runtime.Environment) string {
command = "eval (%s init %s --config=%s --print%s | slurp)" command = "eval (%s init %s --config=%s --print%s | slurp)"
config = env.Flags().Config config = env.Flags().Config
} }
return fmt.Sprintf(command, executable, shell, config, additionalParams) return fmt.Sprintf(command, executable, shell, config, additionalParams)
case ZSH, BASH, FISH, CMD, TCSH, XONSH: case ZSH, BASH, FISH, CMD, TCSH, XONSH:
return PrintInit(env) return PrintInit(env)

View file

@ -3,7 +3,13 @@ export POSH_SHELL_VERSION=$BASH_VERSION
export POWERLINE_COMMAND="oh-my-posh" export POWERLINE_COMMAND="oh-my-posh"
export POSH_PID=$$ export POSH_PID=$$
export CONDA_PROMPT_MODIFIER=false export CONDA_PROMPT_MODIFIER=false
omp_start_time="" omp_start_time=""
omp_stack_count=0
omp_elapsed=-1
omp_no_exit_code="true"
omp_ret=0
omp_pipe_status=0
# start timer on command start # start timer on command start
PS0='${omp_start_time:0:$((omp_start_time="$(_omp_start_timer)",0))}$(_omp_ftcs_command_start)' PS0='${omp_start_time:0:$((omp_start_time="$(_omp_start_timer)",0))}$(_omp_ftcs_command_start)'
@ -45,35 +51,42 @@ function set_poshcontext() {
return return
} }
# regular prompt
function _omp_hook() { function _omp_hook() {
local ret=$? pipeStatus=(${PIPESTATUS[@]}) omp_ret=$?
if [[ "${#BP_PIPESTATUS[@]}" -ge "${#pipeStatus[@]}" ]]; then omp_pipe_status=(${PIPESTATUS[@]})
pipeStatus=(${BP_PIPESTATUS[@]})
if [[ "${#BP_PIPESTATUS[@]}" -ge "${#omp_pipe_status[@]}" ]]; then
omp_pipe_status=(${BP_PIPESTATUS[@]})
fi fi
local omp_stack_count=$((${#DIRSTACK[@]} - 1)) omp_stack_count=$((${#DIRSTACK[@]} - 1))
local omp_elapsed=-1
local no_exit_code="true"
if [[ "$omp_start_time" ]]; then if [[ "$omp_start_time" ]]; then
local omp_now=$(::OMP:: get millis --shell=bash) local omp_now=$(::OMP:: get millis --shell=bash)
omp_elapsed=$((omp_now - omp_start_time)) omp_elapsed=$((omp_now - omp_start_time))
omp_start_time="" omp_start_time=""
no_exit_code="false" omp_no_exit_code="false"
fi fi
if [[ "${pipeStatus[-1]}" != "$ret" ]]; then
pipeStatus=("$ret") if [[ "${omp_pipe_status[-1]}" != "$omp_ret" ]]; then
omp_pipe_status=("$omp_ret")
fi fi
set_poshcontext set_poshcontext
_set_posh_cursor_position _set_posh_cursor_position
PS1="$(::OMP:: print primary --config="$POSH_THEME" --shell=bash --shell-version="$BASH_VERSION" --status="$ret" --pipestatus="${pipeStatus[*]}" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --no-status="$no_exit_code" --terminal-width="${COLUMNS-0}" | tr -d '\0')" PS1="$(::OMP:: print primary --config="$POSH_THEME" --shell=bash --shell-version="$BASH_VERSION" --status="$omp_ret" --pipestatus="${omp_pipe_status[*]}" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --no-status="$omp_no_exit_code" --terminal-width="${COLUMNS-0}" | tr -d '\0')"
return $ret return $ret
} }
# rprompt
_omp_rprompt() {
::OMP:: print right --config="$POSH_THEME" --shell=bash --shell-version="$BASH_VERSION" --status="$omp_ret" --pipestatus="${omp_pipe_status[*]}" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --no-status="$omp_no_exit_code" --terminal-width="${COLUMNS-0}" | tr -d '\0'
}
if [[ "$TERM" != "linux" ]] && [[ -x "$(command -v ::OMP::)" ]] && ! [[ "$PROMPT_COMMAND" =~ "_omp_hook" ]]; then if [[ "$TERM" != "linux" ]] && [[ -x "$(command -v ::OMP::)" ]] && ! [[ "$PROMPT_COMMAND" =~ "_omp_hook" ]]; then
PROMPT_COMMAND="_omp_hook; $PROMPT_COMMAND" PROMPT_COMMAND="_omp_hook; _omp_rprompt; $PROMPT_COMMAND"
fi fi
if [[ "::UPGRADE::" == "true" ]]; then if [[ "::UPGRADE::" == "true" ]]; then