bash: fix multiline PS1 with command substitutions (#11369)

Only replace the \n prompt escape when inserting secondary prompt marks,
not literal newlines `($'\n')`. Literal newlines may appear inside
`$(...)` or `...` command substitutions, and inserting escape sequences
there breaks the shell syntax. For example:

      PS1='$(if [ $? -eq 0 ]; then echo -e "P";
                    else echo -e "F";
                    fi) $ '

The literal newlines between the if/else/fi are part of the shell syntax
inside the command substitution. The previous code replaced all literal
newlines in PS1 with newline + OSC 133 escape sequences, which injected
terminal escapes into the middle of the command substitution and caused
bash to report a syntax error when evaluating it.

The \n prompt escape is PS1-specific and safe to replace globally. This
means prompts using literal newlines for line breaks (rather than \n)
won't get per-line secondary marks, but this is the conventional form
and avoids the need for complex shell parsing.

Fixes: #11267
This commit is contained in:
Mitchell Hashimoto
2026-03-11 08:36:36 -07:00
committed by GitHub

View File

@@ -201,14 +201,16 @@ function __ghostty_precmd() {
PS1='\[\e]133;A;redraw=last;cl=line;aid='"$BASHPID"'\a\]'$PS1'\[\e]133;B\a\]'
PS2='\[\e]133;A;k=s\a\]'$PS2'\[\e]133;B\a\]'
# Bash doesn't redraw the leading lines in a multiline prompt so
# we mark the start of each line (after each newline) as a secondary
# prompt. This correctly handles multiline prompts by setting the first
# to primary and the subsequent lines to secondary.
if [[ "${PS1}" == *"\n"* || "${PS1}" == *$'\n'* ]]; then
builtin local __ghostty_mark=$'\\[\\e]133;A;k=s\\a\\]'
PS1="${PS1//$'\n'/$'\n'$__ghostty_mark}"
PS1="${PS1//\\n/\\n$__ghostty_mark}"
# Bash doesn't redraw the leading lines in a multiline prompt so we mark
# the start of each line (after each newline) as a secondary prompt. This
# correctly handles multiline prompts by setting the first to primary and
# the subsequent lines to secondary.
#
# We only replace the \n prompt escape, not literal newlines ($'\n'),
# because literal newlines may appear inside $(...) command substitutions
# where inserting escape sequences would break shell syntax.
if [[ "$PS1" == *"\n"* ]]; then
PS1="${PS1//\\n/\\n$'\\[\\e]133;A;k=s\\a\\]'}"
fi
# Cursor