aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/shell-integration
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/shell-integration
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/shell-integration')
-rw-r--r--vendor/libghostty-vt/src/shell-integration/bash/bash-preexec.sh342
1 files changed, 266 insertions, 76 deletions
diff --git a/vendor/libghostty-vt/src/shell-integration/bash/bash-preexec.sh b/vendor/libghostty-vt/src/shell-integration/bash/bash-preexec.sh
index f20ece17..ebf79d56 100644
--- a/vendor/libghostty-vt/src/shell-integration/bash/bash-preexec.sh
+++ b/vendor/libghostty-vt/src/shell-integration/bash/bash-preexec.sh
@@ -9,7 +9,7 @@
# Author: Ryan Caloras (ryan@bashhub.com)
# Forked from Original Author: Glyph Lefkowitz
#
-# V0.6.0
+# V0.7.0
#
# General Usage:
@@ -71,7 +71,8 @@ __bp_inside_precmd=0
__bp_inside_preexec=0
# Initial PROMPT_COMMAND string that is removed from PROMPT_COMMAND post __bp_install
-__bp_install_string=$'__bp_trap_string="$(trap -p DEBUG)"\ntrap - DEBUG\n__bp_install'
+# shellcheck disable=SC2016
+__bp_install_string='__bp_install "$_"'
# Fails if any of the given variables are readonly
# Reference https://stackoverflow.com/a/4441178
@@ -106,9 +107,11 @@ __bp_adjust_histcontrol() {
# and unset as soon as the trace hook is run.
__bp_preexec_interactive_mode=""
-# These arrays are used to add functions to be run before, or after, prompts.
-declare -a precmd_functions
-declare -a preexec_functions
+# These global arrays are used to add functions to be run before, or after,
+# prompts. Note that Bash < 4.2 does not have the "-g" option of the "declare"
+# builtin. We actually do not need to explicitly initialize these arrays.
+#declare -ga precmd_functions
+#declare -ga preexec_functions
# Trims leading and trailing whitespace from $2 and writes it to the variable
# name passed as $1
@@ -121,21 +124,96 @@ __bp_trim_whitespace() {
# Trims whitespace and removes any leading or trailing semicolons from $2 and
-# writes the resulting string to the variable name passed as $1. Used for
-# manipulating substrings in PROMPT_COMMAND
+# writes the resulting string to the variable name passed as $1. This also
+# removes the no-op colons, which are converted from the hooks to remove. Used
+# for manipulating substrings in PROMPT_COMMAND
__bp_sanitize_string() {
- local var=${1:?} text=${2:-} sanitized
- __bp_trim_whitespace sanitized "$text"
+ local var=${1:?} sanitized=${2:-}
+
+ local unset_extglob=
+ if ! shopt -q extglob; then
+ unset_extglob=yes
+ shopt -s extglob
+ fi
+
+ # We specify newline character through the variable `nl' because $'\n'
+ # inside "${var//...}" is treated literally as "\$'\\n'" when `extquote' is
+ # unset (shopt -u extquote). (Note: Bash 5.2's extquote seems to be buggy.)
+ local tmp nl=$'\n'
+ while
+ # Note: Quoting parameter expansions $nl in PAT of ${var//PAT/REP} is
+ # required by shellcheck. On the other hand, we should not quote the
+ # parameter expansions $nl in REP because the quotes will remain in the
+ # replaced result with `shopt -s compat42'.
+ # Note: We use ?(+([[:blank:]])) instead of *([[:blank:]]) to work
+ # around a bug of Bash 3.2 that *(...) is not properly processed as
+ # extglob at the beginning of the pattern in ${var//pat/rep}.
+ tmp="${sanitized//?(+([[:blank:]]))[";$nl"]*([[:blank:]]):*([[:blank:]])[";$nl"]*([[:blank:]])/$nl}"
+ [[ "$tmp" != "$sanitized" ]]
+ do
+ sanitized="$tmp"
+ done
+ sanitized="${sanitized#:*([[:blank:]])[";$nl"]}"
+ sanitized="${sanitized%[";$nl"]*([[:blank:]]):}"
+ __bp_trim_whitespace sanitized "$sanitized"
sanitized=${sanitized%;}
sanitized=${sanitized#;}
__bp_trim_whitespace sanitized "$sanitized"
+ if [[ "$sanitized" == ":" ]]; then
+ sanitized=
+ fi
printf -v "$var" '%s' "$sanitized"
+
+ if [[ -n "$unset_extglob" ]]; then
+ shopt -u extglob
+ fi
+}
+
+
+# Bash >= 5.1 supports the array version of PROMPT_COMMAND.
+__bp_use_array_prompt_command() {
+ (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1) ))
+}
+
+
+# Remove $1 and sanitize each elements of PROMPT_COMMAND. We want to keep
+# PROMPT_COMMAND scalar in bash < 5.1 because some configuration tests the
+# support for the array PROMPT_COMMAND by checking the array attribute of
+# PROMPT_COMMAND.
+__bp_remove_command_from_prompt_command() {
+ local removed_command="${1-}"
+ if __bp_use_array_prompt_command; then
+ local i sanitized_prompt_command
+ for i in "${!PROMPT_COMMAND[@]}"; do
+ sanitized_prompt_command="${PROMPT_COMMAND[i]:-}"
+ sanitized_prompt_command="${sanitized_prompt_command//"$removed_command"/:}"
+ __bp_sanitize_string sanitized_prompt_command "$sanitized_prompt_command"
+ if [[ -n "$sanitized_prompt_command" ]]; then
+ PROMPT_COMMAND[i]="$sanitized_prompt_command"
+ else
+ unset -v 'PROMPT_COMMAND[i]'
+ fi
+ done
+ else
+ local sanitized_prompt_command="${PROMPT_COMMAND:-}"
+ sanitized_prompt_command="${sanitized_prompt_command//"$removed_command"/:}" # no-op
+ __bp_sanitize_string PROMPT_COMMAND "$sanitized_prompt_command"
+ fi
}
+
# This function is installed as part of the PROMPT_COMMAND;
# It sets a variable to indicate that the prompt was just displayed,
# to allow the DEBUG trap to know that the next command is likely interactive.
__bp_interactive_mode() {
+ if [[ "${1-}" != "force" && ! "${BATS_VERSION-}" ]] && (( ${#FUNCNAME[*]} > 1 )); then
+ # When this function is not called from the top level, the current
+ # function call is probably performed via PROMPT_COMMAND saved by
+ # another framework (e.g., starship). In this case, we do not want to
+ # turn on the "interactive mode" here.
+ return 0
+ fi
+
__bp_preexec_interactive_mode="on"
}
@@ -143,35 +221,69 @@ __bp_interactive_mode() {
# This function is installed as part of the PROMPT_COMMAND.
# It will invoke any functions defined in the precmd_functions array.
__bp_precmd_invoke_cmd() {
- # Save the returned value from our last command, and from each process in
- # its pipeline. Note: this MUST be the first thing done in this function.
+ # Save the returned value and the last argument from our last command, and
+ # the returned value from each process in its pipeline. Note: this MUST be
+ # the first thing done in this function.
# BP_PIPESTATUS may be unused, ignore
# shellcheck disable=SC2034
+ __bp_last_ret_value="$?" __bp_last_argument_prev_command="$_" \
+ BP_PIPESTATUS=("${PIPESTATUS[@]}")
- __bp_last_ret_value="$?" BP_PIPESTATUS=("${PIPESTATUS[@]}")
# Don't invoke precmds if we are inside an execution of an "original
# prompt command" by another precmd execution loop. This avoids infinite
# recursion.
if (( __bp_inside_precmd > 0 )); then
- return
+ return "$__bp_last_ret_value"
fi
+
+ # Check and adjust PROMPT_COMMAND to make sure that PROMPT_COMMAND has the
+ # form "__bp_precmd_invoke_cmd; ...; __bp_interactive_mode"
+ if ! __bp_install_prompt_command; then
+ if [[ "${1-}" != "force" && ! "${BATS_VERSION-}" ]] && (( ${#FUNCNAME[*]} > 1 )); then
+ # When PROMPT_COMMAND is already properly set up but this function
+ # is not called from the top level, the current function call is
+ # probably performed via PROMPT_COMMAND saved by another framework
+ # (e.g., starship). In this case, we do not need to invoke precmd
+ # because it is supposed to be already processed by the top-level
+ # __bp_precmd_invoke_cmd.
+ return "$__bp_last_ret_value"
+ fi
+ fi
+
local __bp_inside_precmd=1
+ __bp_invoke_precmd_functions "$__bp_last_ret_value" "$__bp_last_argument_prev_command"
+ __bp_set_ret_value "$__bp_last_ret_value" "$__bp_last_argument_prev_command"
+}
+
+# This function invokes every function defined in the "precmd_functions" array.
+# This function receives the arguments $1 and $2 for $? and $_, respectively,
+# which will be set for each precmd function. This function returns the last
+# non-zero exit status of the hook functions. If there is no error, this
+# function returns 0.
+__bp_invoke_precmd_functions() {
+ local lastexit=$1 lastarg=$2
# Invoke every function defined in our function array.
local precmd_function
+ local precmd_function_ret_value
+ local precmd_ret_value=0
for precmd_function in "${precmd_functions[@]}"; do
# Only execute this function if it actually exists.
# Test existence of functions with: declare -[Ff]
if type -t "$precmd_function" 1>/dev/null; then
- __bp_set_ret_value "$__bp_last_ret_value" "$__bp_last_argument_prev_command"
+ __bp_set_ret_value "$lastexit" "$lastarg"
# Quote our function invocation to prevent issues with IFS
"$precmd_function"
+ precmd_function_ret_value=$?
+ if [[ "$precmd_function_ret_value" != 0 ]]; then
+ precmd_ret_value="$precmd_function_ret_value"
+ fi
fi
done
- __bp_set_ret_value "$__bp_last_ret_value"
+ __bp_set_ret_value "$precmd_ret_value"
}
# Sets a return value in $?. We may want to get access to the $? variable in our
@@ -200,15 +312,21 @@ __bp_in_prompt_command() {
return 1
}
+__bp_load_this_command_from_history() {
+ this_command=$(LC_ALL=C HISTTIMEFORMAT='' builtin history 1)
+ this_command="${this_command#*[[:digit:]][* ] }"
+
+ # Sanity check to make sure we have something to invoke our function with.
+ [[ -n "$this_command" ]]
+}
+
# This function is installed as the DEBUG trap. It is invoked before each
# interactive prompt display. Its purpose is to inspect the current
# environment to attempt to detect if the current command is being invoked
# interactively, and invoke 'preexec' if so.
__bp_preexec_invoke_exec() {
+ local lastarg=$_
- # Save the contents of $_ so that it can be restored later on.
- # https://stackoverflow.com/questions/40944532/bash-preserve-in-a-debug-trap#40944702
- __bp_last_argument_prev_command="${1:-}"
# Don't invoke preexecs if we are inside of another preexec.
if (( __bp_inside_preexec > 0 )); then
return
@@ -249,16 +367,43 @@ __bp_preexec_invoke_exec() {
return
fi
+ # Save the contents of $_ so that it can be restored later on.
+ # https://stackoverflow.com/questions/40944532/bash-preserve-in-a-debug-trap#40944702
+ __bp_last_argument_prev_command=$lastarg
+
local this_command
- this_command=$(LC_ALL=C HISTTIMEFORMAT='' builtin history 1)
- this_command="${this_command#*[[:digit:]][* ] }"
+ __bp_load_this_command_from_history || return
- # Sanity check to make sure we have something to invoke our function with.
- if [[ -z "$this_command" ]]; then
- return
- fi
+ __bp_invoke_preexec_functions "${__bp_last_ret_value:-}" "$__bp_last_argument_prev_command" "$this_command"
+ local preexec_ret_value=$?
- # Invoke every function defined in our function array.
+ # Restore the last argument of the last executed command, and set the return
+ # value of the DEBUG trap to be the return code of the last preexec function
+ # to return an error.
+ # If `extdebug` is enabled a non-zero return value from any preexec function
+ # will cause the user's command not to execute.
+ # Run `shopt -s extdebug` to enable
+ __bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command"
+}
+
+__bp_invoke_preexec_from_ps0() {
+ __bp_last_argument_prev_command="${1:-}"
+
+ local this_command
+ __bp_load_this_command_from_history || return
+
+ __bp_invoke_preexec_functions "${__bp_last_ret_value:-}" "$__bp_last_argument_prev_command" "$this_command"
+}
+
+# This function invokes every function defined in the "preexec_functions"
+# array. This function receives the arguments $1 and $2 for $? and $_,
+# respectively, which will be set for each preexec function. The third
+# argument $3 specifies the user command that is going to be executed
+# (corresponding to BASH_COMMAND in the DEBUG trap). This function returns the
+# last non-zero exit status from the preexec functions. If there is no error,
+# this function returns `0`.
+__bp_invoke_preexec_functions() {
+ local lastexit=$1 lastarg=$2 this_command=$3
local preexec_function
local preexec_function_ret_value
local preexec_ret_value=0
@@ -267,7 +412,7 @@ __bp_preexec_invoke_exec() {
# Only execute each function if it actually exists.
# Test existence of function with: declare -[fF]
if type -t "$preexec_function" 1>/dev/null; then
- __bp_set_ret_value "${__bp_last_ret_value:-}"
+ __bp_set_ret_value "$lastexit" "$lastarg"
# Quote our function invocation to prevent issues with IFS
"$preexec_function" "$this_command"
preexec_function_ret_value="$?"
@@ -276,28 +421,17 @@ __bp_preexec_invoke_exec() {
fi
fi
done
-
- # Restore the last argument of the last executed command, and set the return
- # value of the DEBUG trap to be the return code of the last preexec function
- # to return an error.
- # If `extdebug` is enabled a non-zero return value from any preexec function
- # will cause the user's command not to execute.
- # Run `shopt -s extdebug` to enable
- __bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command"
+ __bp_set_ret_value "$preexec_ret_value"
}
-__bp_install() {
- # Exit if we already have this installed.
- if [[ "${PROMPT_COMMAND[*]:-}" == *"__bp_precmd_invoke_cmd"* ]]; then
- return 1
- fi
-
+__bp_hook_preexec_into_debug() {
+ local trap_string
+ trap_string=$(trap -p DEBUG)
trap '__bp_preexec_invoke_exec "$_"' DEBUG
# Preserve any prior DEBUG trap as a preexec function
- eval "local trap_argv=(${__bp_trap_string:-})"
+ eval "local trap_argv=(${trap_string:-})"
local prior_trap=${trap_argv[2]:-}
- unset __bp_trap_string
if [[ -n "$prior_trap" ]]; then
eval '__bp_original_debug_trap() {
'"$prior_trap"'
@@ -307,11 +441,11 @@ __bp_install() {
# Adjust our HISTCONTROL Variable if needed.
#
- # GHOSTTY: Don't modify HISTCONTROL. This hack is only needed to improve the
- # accuracy of the command argument passed to the preexec functions, and we
- # don't use that argument in our bash shell integration script (and nor does
- # the __bp_original_debug_trap function above, which is the only other active
- # preexec function).
+ # GHOSTTY: Don't modify HISTCONTROL (#2269). This only improves the
+ # accuracy of the command passed to preexec functions when the user has
+ # ignorespace set. Our bash >= 4.4 integration reads `history 1` without
+ # adjusting HISTCONTROL and accepts the same inaccuracy, so keep the
+ # legacy path consistent with it and respect the user's setting.
#__bp_adjust_histcontrol
# Issue #25. Setting debug trap for subshells causes sessions to exit for
@@ -324,38 +458,87 @@ __bp_install() {
set -o functrace > /dev/null 2>&1
shopt -s extdebug > /dev/null 2>&1
fi
+}
- local existing_prompt_command
- # Remove setting our trap install string and sanitize the existing prompt command string
- existing_prompt_command="${PROMPT_COMMAND:-}"
- # Edge case of appending to PROMPT_COMMAND
- existing_prompt_command="${existing_prompt_command//$__bp_install_string/:}" # no-op
- existing_prompt_command="${existing_prompt_command//$'\n':$'\n'/$'\n'}" # remove known-token only
- existing_prompt_command="${existing_prompt_command//$'\n':;/$'\n'}" # remove known-token only
- __bp_sanitize_string existing_prompt_command "$existing_prompt_command"
- if [[ "${existing_prompt_command:-:}" == ":" ]]; then
- existing_prompt_command=
- fi
+__bp_hook_preexec_into_ps0() {
+ # shellcheck disable=SC2016
+ PS0=${PS0-}'${ __bp_invoke_preexec_from_ps0 "$_" >&2; }'
- # Install our hooks in PROMPT_COMMAND to allow our trap to know when we've
- # actually entered something.
- PROMPT_COMMAND='__bp_precmd_invoke_cmd'
- PROMPT_COMMAND+=${existing_prompt_command:+$'\n'$existing_prompt_command}
- if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1) )); then
- PROMPT_COMMAND+=('__bp_interactive_mode')
- else
- # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
- PROMPT_COMMAND+=$'\n__bp_interactive_mode'
+ # Adjust our HISTCONTROL Variable if needed.
+ __bp_adjust_histcontrol
+}
+
+if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3) )); then
+ __bp_hook_preexec_proc=__bp_hook_preexec_into_ps0
+else
+ __bp_hook_preexec_proc=__bp_hook_preexec_into_debug
+fi
+
+__bp_install() {
+ local lastexit=$? lastarg=$_
+ # Exit if we already have this installed.
+ # shellcheck disable=SC2016
+ if [[ "${PROMPT_COMMAND[*]:-}" == *'__bp_precmd_invoke_cmd "$_"'* ]]; then
+ return 1
fi
+ "$__bp_hook_preexec_proc"
+
+ # Remove setting our trap install string and sanitize the existing prompt command string
+ __bp_remove_command_from_prompt_command "$__bp_install_string"
+
+ __bp_install_prompt_command || true
+
# Add two functions to our arrays for convenience
# of definition.
precmd_functions+=(precmd)
preexec_functions+=(preexec)
# Invoke our two functions manually that were added to $PROMPT_COMMAND
- __bp_precmd_invoke_cmd
- __bp_interactive_mode
+ __bp_set_ret_value "$lastexit" "$lastarg"
+ __bp_precmd_invoke_cmd force
+ __bp_interactive_mode force
+}
+
+# Note: We need to add the "trace" attribute to these functions so that "trap
+# ... DEBUG" inside "__bp_install" and "__bp_hook_preexec_into_debug" takes
+# effect even when there is an existing DEBUG trap.
+declare -ft __bp_install __bp_hook_preexec_into_debug
+
+# Encloses PROMPT_COMMAND hooks within __bp_precmd_invoke_cmd and
+# __bp_interactive_mode. If all the PROMPT_COMMAND hooks are already surrounded
+# by __bp_precmd_invoke_cmd and __bp_interactive_mode, the function exits with
+# status 1.
+__bp_install_prompt_command() {
+ local prompt_command="${PROMPT_COMMAND:-}"
+ if __bp_use_array_prompt_command; then
+ local IFS=$'\n'
+ prompt_command="${PROMPT_COMMAND[*]:-}"
+ IFS=$' \t\n'
+ fi
+
+ # Exit if we already have a properly set-up hooks in PROMPT_COMMAND
+ # shellcheck disable=SC2016
+ local prologue='__bp_precmd_invoke_cmd "$_"'
+ local epilogue='__bp_interactive_mode'
+ if [[ "$prompt_command" == "$prologue"$'\n'* && "$prompt_command" == *$'\n'"$epilogue" ]]; then
+ return 1
+ fi
+
+ __bp_remove_command_from_prompt_command "$prologue"
+ __bp_remove_command_from_prompt_command "$epilogue"
+
+ # Install our hooks in PROMPT_COMMAND to allow our trap to know when we've
+ # actually entered something.
+ # shellcheck disable=SC2128,SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
+ PROMPT_COMMAND=$prologue${PROMPT_COMMAND:+$'\n'$PROMPT_COMMAND}
+ if __bp_use_array_prompt_command; then
+ PROMPT_COMMAND+=("$epilogue")
+ else
+ # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
+ PROMPT_COMMAND+=$'\n'$epilogue
+ fi
+ return 0
}
# Sets an installation string as part of our PROMPT_COMMAND to install
@@ -365,15 +548,22 @@ __bp_install_after_session_init() {
# bash-preexec needs to modify these variables in order to work correctly
# if it can't, just stop the installation
__bp_require_not_readonly PROMPT_COMMAND HISTCONTROL HISTTIMEFORMAT || return
+ if [[ $__bp_hook_preexec_proc == '__bp_hook_preexec_into_ps0' ]]; then
+ __bp_require_not_readonly PS0 || return
+ fi
- local sanitized_prompt_command
- __bp_sanitize_string sanitized_prompt_command "${PROMPT_COMMAND:-}"
- if [[ -n "$sanitized_prompt_command" ]]; then
- # shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
- PROMPT_COMMAND=${sanitized_prompt_command}$'\n'
+ if __bp_use_array_prompt_command; then
+ PROMPT_COMMAND+=("${__bp_install_string}")
+ else
+ local sanitized_prompt_command
+ __bp_sanitize_string sanitized_prompt_command "${PROMPT_COMMAND:-}"
+ if [[ -n "$sanitized_prompt_command" ]]; then
+ # shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
+ PROMPT_COMMAND=${sanitized_prompt_command}$'\n'
+ fi
+ # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
+ PROMPT_COMMAND+=${__bp_install_string}
fi
- # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
- PROMPT_COMMAND+=${__bp_install_string}
}
# Run our install so long as we're not delaying it.