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"
"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
@ -61,15 +59,6 @@ func (b *Block) Init(env runtime.Environment) {
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 {
if b.Type == LineBreak {
return true

View file

@ -73,17 +73,6 @@ func (e *Engine) canWriteRightBlock(rprompt bool) (int, bool) {
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() {
// only print when supported
sh := e.Env.Shell()
@ -199,13 +188,7 @@ func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool {
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)
}
if !block.Enabled() {
return false

View file

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

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)
return text
case shell.PWSH, shell.PWSH5:
block.InitPlain(e.Env, e.Config)
if !block.Enabled() {
return ""
}

View file

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

View file

@ -3,7 +3,13 @@ export POSH_SHELL_VERSION=$BASH_VERSION
export POWERLINE_COMMAND="oh-my-posh"
export POSH_PID=$$
export CONDA_PROMPT_MODIFIER=false
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
PS0='${omp_start_time:0:$((omp_start_time="$(_omp_start_timer)",0))}$(_omp_ftcs_command_start)'
@ -45,35 +51,42 @@ function set_poshcontext() {
return
}
# regular prompt
function _omp_hook() {
local ret=$? pipeStatus=(${PIPESTATUS[@]})
if [[ "${#BP_PIPESTATUS[@]}" -ge "${#pipeStatus[@]}" ]]; then
pipeStatus=(${BP_PIPESTATUS[@]})
omp_ret=$?
omp_pipe_status=(${PIPESTATUS[@]})
if [[ "${#BP_PIPESTATUS[@]}" -ge "${#omp_pipe_status[@]}" ]]; then
omp_pipe_status=(${BP_PIPESTATUS[@]})
fi
local omp_stack_count=$((${#DIRSTACK[@]} - 1))
local omp_elapsed=-1
local no_exit_code="true"
omp_stack_count=$((${#DIRSTACK[@]} - 1))
if [[ "$omp_start_time" ]]; then
local omp_now=$(::OMP:: get millis --shell=bash)
omp_elapsed=$((omp_now - omp_start_time))
omp_start_time=""
no_exit_code="false"
omp_no_exit_code="false"
fi
if [[ "${pipeStatus[-1]}" != "$ret" ]]; then
pipeStatus=("$ret")
if [[ "${omp_pipe_status[-1]}" != "$omp_ret" ]]; then
omp_pipe_status=("$omp_ret")
fi
set_poshcontext
_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
}
# 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
PROMPT_COMMAND="_omp_hook; $PROMPT_COMMAND"
PROMPT_COMMAND="_omp_hook; _omp_rprompt; $PROMPT_COMMAND"
fi
if [[ "::UPGRADE::" == "true" ]]; then