mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-18 09:46:07 +03:00

Our bash shell integration code uses ENV (in POSIX mode) to bootstrap our shell integration script. This had the side effect of overwriting an existing ENV value. This change preserves ENV by storing it temporarily in GHOSTTY_BASH_ENV. Note that this doesn't enable --posix mode support for automatic shell integration. (--posix does work; we just skip shell integration when that flag is specified.) We can reconsider implementing full --posix support separately.
247 lines
9.1 KiB
Bash
247 lines
9.1 KiB
Bash
# Parts of this script are based on Kitty's bash integration. Kitty is
|
|
# distributed under GPLv3, so this file is also distributed under GPLv3.
|
|
# The license header is reproduced below:
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# We need to be in interactive mode to proceed.
|
|
if [[ "$-" != *i* ]] ; then builtin return; fi
|
|
|
|
# When automatic shell integration is active, we were started in POSIX
|
|
# mode and need to manually recreate the bash startup sequence.
|
|
if [ -n "$GHOSTTY_BASH_INJECT" ]; then
|
|
# Store a temporary copy of our startup flags and unset these global
|
|
# environment variables so we can safely handle reentrancy.
|
|
builtin declare __ghostty_bash_flags="$GHOSTTY_BASH_INJECT"
|
|
builtin unset ENV GHOSTTY_BASH_INJECT
|
|
|
|
# Restore an existing ENV that was replaced by the shell integration code.
|
|
if [[ -n "$GHOSTTY_BASH_ENV" ]]; then
|
|
builtin export ENV=$GHOSTTY_BASH_ENV
|
|
builtin unset GHOSTTY_BASH_ENV
|
|
fi
|
|
|
|
# Restore bash's default 'posix' behavior. Also reset 'inherit_errexit',
|
|
# which doesn't happen as part of the 'posix' reset.
|
|
builtin set +o posix
|
|
builtin shopt -u inherit_errexit 2>/dev/null
|
|
|
|
# Unexport HISTFILE if it was set by the shell integration code.
|
|
if [[ -n "$GHOSTTY_BASH_UNEXPORT_HISTFILE" ]]; then
|
|
builtin export -n HISTFILE
|
|
builtin unset GHOSTTY_BASH_UNEXPORT_HISTFILE
|
|
fi
|
|
|
|
# Manually source the startup files. See INVOCATION in bash(1) and
|
|
# run_startup_files() in shell.c in the Bash source code.
|
|
if builtin shopt -q login_shell; then
|
|
if [[ $__ghostty_bash_flags != *"--noprofile"* ]]; then
|
|
[ -r /etc/profile ] && builtin source "/etc/profile"
|
|
for __ghostty_rcfile in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do
|
|
[ -r "$__ghostty_rcfile" ] && { builtin source "$__ghostty_rcfile"; break; }
|
|
done
|
|
fi
|
|
else
|
|
if [[ $__ghostty_bash_flags != *"--norc"* ]]; then
|
|
# The location of the system bashrc is determined at bash build
|
|
# time via -DSYS_BASHRC and can therefore vary across distros:
|
|
# Arch, Debian, Ubuntu use /etc/bash.bashrc
|
|
# Fedora uses /etc/bashrc sourced from ~/.bashrc instead of SYS_BASHRC
|
|
# Void Linux uses /etc/bash/bashrc
|
|
# Nixos uses /etc/bashrc
|
|
for __ghostty_rcfile in /etc/bash.bashrc /etc/bash/bashrc /etc/bashrc; do
|
|
[ -r "$__ghostty_rcfile" ] && { builtin source "$__ghostty_rcfile"; break; }
|
|
done
|
|
if [[ -z "$GHOSTTY_BASH_RCFILE" ]]; then GHOSTTY_BASH_RCFILE="$HOME/.bashrc"; fi
|
|
[ -r "$GHOSTTY_BASH_RCFILE" ] && builtin source "$GHOSTTY_BASH_RCFILE"
|
|
fi
|
|
fi
|
|
|
|
builtin unset __ghostty_rcfile
|
|
builtin unset __ghostty_bash_flags
|
|
builtin unset GHOSTTY_BASH_RCFILE
|
|
fi
|
|
|
|
# Sudo
|
|
if [[ "$GHOSTTY_SHELL_FEATURES" == *"sudo"* && -n "$TERMINFO" ]]; then
|
|
# Wrap `sudo` command to ensure Ghostty terminfo is preserved.
|
|
#
|
|
# This approach supports wrapping a `sudo` alias, but the alias definition
|
|
# must come _after_ this function is defined. Otherwise, the alias expansion
|
|
# will take precedence over this function, and it won't be wrapped.
|
|
function sudo {
|
|
builtin local sudo_has_sudoedit_flags="no"
|
|
for arg in "$@"; do
|
|
# Check if argument is '-e' or '--edit' (sudoedit flags)
|
|
if [[ "$arg" == "-e" || $arg == "--edit" ]]; then
|
|
sudo_has_sudoedit_flags="yes"
|
|
builtin break
|
|
fi
|
|
# Check if argument is neither an option nor a key-value pair
|
|
if [[ "$arg" != -* && "$arg" != *=* ]]; then
|
|
builtin break
|
|
fi
|
|
done
|
|
if [[ "$sudo_has_sudoedit_flags" == "yes" ]]; then
|
|
builtin command sudo "$@";
|
|
else
|
|
builtin command sudo TERMINFO="$TERMINFO" "$@";
|
|
fi
|
|
}
|
|
fi
|
|
|
|
# SSH Integration
|
|
if [[ "$GHOSTTY_SHELL_FEATURES" == *ssh-* ]]; then
|
|
ssh() {
|
|
builtin local ssh_term ssh_opts
|
|
ssh_term="xterm-256color"
|
|
ssh_opts=()
|
|
|
|
# Configure environment variables for remote session
|
|
if [[ "$GHOSTTY_SHELL_FEATURES" == *ssh-env* ]]; then
|
|
ssh_opts+=(-o "SetEnv COLORTERM=truecolor")
|
|
ssh_opts+=(-o "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION")
|
|
fi
|
|
|
|
# Install terminfo on remote host if needed
|
|
if [[ "$GHOSTTY_SHELL_FEATURES" == *ssh-terminfo* ]]; then
|
|
builtin local ssh_user ssh_hostname
|
|
|
|
while IFS=' ' read -r ssh_key ssh_value; do
|
|
case "$ssh_key" in
|
|
user) ssh_user="$ssh_value" ;;
|
|
hostname) ssh_hostname="$ssh_value" ;;
|
|
esac
|
|
[[ -n "$ssh_user" && -n "$ssh_hostname" ]] && break
|
|
done < <(builtin command ssh -G "$@" 2>/dev/null)
|
|
|
|
if [[ -n "$ssh_hostname" ]]; then
|
|
builtin local ssh_target="${ssh_user}@${ssh_hostname}"
|
|
|
|
# Check if terminfo is already cached
|
|
if "$GHOSTTY_BIN_DIR/ghostty" +ssh-cache --host="$ssh_target" >/dev/null 2>&1; then
|
|
ssh_term="xterm-ghostty"
|
|
elif builtin command -v infocmp >/dev/null 2>&1; then
|
|
builtin local ssh_terminfo ssh_cpath_dir ssh_cpath
|
|
|
|
ssh_terminfo=$(infocmp -0 -x xterm-ghostty 2>/dev/null)
|
|
|
|
if [[ -n "$ssh_terminfo" ]]; then
|
|
builtin echo "Setting up xterm-ghostty terminfo on $ssh_hostname..." >&2
|
|
|
|
ssh_cpath_dir=$(mktemp -d "/tmp/ghostty-ssh-$ssh_user.XXXXXX" 2>/dev/null) || ssh_cpath_dir="/tmp/ghostty-ssh-$ssh_user.$$"
|
|
ssh_cpath="$ssh_cpath_dir/socket"
|
|
|
|
if builtin echo "$ssh_terminfo" | builtin command ssh -o ControlMaster=yes -o ControlPath="$ssh_cpath" -o ControlPersist=60s "$@" '
|
|
infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
|
|
command -v tic >/dev/null 2>&1 || exit 1
|
|
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
|
|
exit 1
|
|
' 2>/dev/null; then
|
|
ssh_term="xterm-ghostty"
|
|
ssh_opts+=(-o "ControlPath=$ssh_cpath")
|
|
|
|
# Cache successful installation
|
|
"$GHOSTTY_BIN_DIR/ghostty" +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true
|
|
else
|
|
builtin echo "Warning: Failed to install terminfo." >&2
|
|
fi
|
|
else
|
|
builtin echo "Warning: Could not generate terminfo data." >&2
|
|
fi
|
|
else
|
|
builtin echo "Warning: ghostty command not available for cache management." >&2
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Execute SSH with TERM environment variable
|
|
TERM="$ssh_term" builtin command ssh "${ssh_opts[@]}" "$@"
|
|
}
|
|
fi
|
|
|
|
# Import bash-preexec, safe to do multiple times
|
|
builtin source "$(dirname -- "${BASH_SOURCE[0]}")/bash-preexec.sh"
|
|
|
|
# This is set to 1 when we're executing a command so that we don't
|
|
# send prompt marks multiple times.
|
|
_ghostty_executing=""
|
|
_ghostty_last_reported_cwd=""
|
|
|
|
function __ghostty_precmd() {
|
|
local ret="$?"
|
|
if test "$_ghostty_executing" != "0"; then
|
|
_GHOSTTY_SAVE_PS1="$PS1"
|
|
_GHOSTTY_SAVE_PS2="$PS2"
|
|
|
|
# Marks
|
|
PS1=$PS1'\[\e]133;B\a\]'
|
|
PS2=$PS2'\[\e]133;B\a\]'
|
|
|
|
# bash doesn't redraw the leading lines in a multiline prompt so
|
|
# mark the last line as a secondary prompt (k=s) to prevent the
|
|
# preceding lines from being erased by ghostty after a resize.
|
|
if [[ "${PS1}" == *"\n"* || "${PS1}" == *$'\n'* ]]; then
|
|
PS1=$PS1'\[\e]133;A;k=s\a\]'
|
|
fi
|
|
|
|
# Cursor
|
|
if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then
|
|
[[ "$PS1" != *'\[\e[5 q\]'* ]] && PS1=$PS1'\[\e[5 q\]' # input
|
|
[[ "$PS0" != *'\[\e[0 q\]'* ]] && PS0=$PS0'\[\e[0 q\]' # reset
|
|
fi
|
|
|
|
# Title (working directory)
|
|
if [[ "$GHOSTTY_SHELL_FEATURES" == *"title"* ]]; then
|
|
PS1=$PS1'\[\e]2;\w\a\]'
|
|
fi
|
|
fi
|
|
|
|
if test "$_ghostty_executing" != ""; then
|
|
# End of current command. Report its status.
|
|
builtin printf "\e]133;D;%s;aid=%s\a" "$ret" "$BASHPID"
|
|
fi
|
|
|
|
# unfortunately bash provides no hooks to detect cwd changes
|
|
# in particular this means cwd reporting will not happen for a
|
|
# command like cd /test && cat. PS0 is evaluated before cd is run.
|
|
if [[ "$_ghostty_last_reported_cwd" != "$PWD" ]]; then
|
|
_ghostty_last_reported_cwd="$PWD"
|
|
builtin printf "\e]7;kitty-shell-cwd://%s%s\a" "$HOSTNAME" "$PWD"
|
|
fi
|
|
|
|
# Fresh line and start of prompt.
|
|
builtin printf "\e]133;A;aid=%s\a" "$BASHPID"
|
|
_ghostty_executing=0
|
|
}
|
|
|
|
function __ghostty_preexec() {
|
|
builtin local cmd="$1"
|
|
|
|
PS1="$_GHOSTTY_SAVE_PS1"
|
|
PS2="$_GHOSTTY_SAVE_PS2"
|
|
|
|
# Title (current command)
|
|
if [[ -n $cmd && "$GHOSTTY_SHELL_FEATURES" == *"title"* ]]; then
|
|
builtin printf "\e]2;%s\a" "${cmd//[[:cntrl:]]}"
|
|
fi
|
|
|
|
# End of input, start of output.
|
|
builtin printf "\e]133;C;\a"
|
|
_ghostty_executing=1
|
|
}
|
|
|
|
preexec_functions+=(__ghostty_preexec)
|
|
precmd_functions+=(__ghostty_precmd)
|