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

The previous approach to wrapping `sudo` had a few shortcomings: 1. We were (re)defining our 'sudo' function wrapper in the "precmd" path. It only needs to be defined once in the shell session. 2. If there was an existing 'sudo' alias, the function definition would conflict and result in a syntax error. Fix (1) by hoisting the 'sudo' function into global scope. I also considered only defining our wrapper if an executable `sudo` binary could be found (e.g. `-x $(builtin command -v sudo)`, but let's keep the existing behavior for now. This allows for a `sudo` command to be installed later in the shell session and still be wrapped. Address (2) by defining the wrapper function using `function sudo` (instead of `sudo()`) syntax. An explicit function definition won't clash with an existing 'sudo' alias, although the alias will continue to take precedence (i.e. our wrapper won't be called). If the alias is defined _after_ our 'sudo' function is defined, our function will call the aliased command. This ordering is relevant because it can result in different behaviors depending on when a user defines their aliases relative to sourcing the shell integration script. Our recommendation remains that users either use automatic shell injection or manually source the shell integration script _before_ other things in their `.bashrc`, so that aligns with the expected behavior of the 'sudo' wrapper with regard to aliases. Given that, I don't think we need any more explicit user-facing documentation on this beyond the script-level comments.
180 lines
6.6 KiB
Bash
180 lines
6.6 KiB
Bash
# This is originally based on the recommended bash integration from
|
|
# the semantic prompts proposal as well as some logic from Kitty's
|
|
# bash integration.
|
|
#
|
|
# I'm not a bash expert so this probably has some major issues but for
|
|
# my simple bash usage this is working. If a bash expert wants to
|
|
# improve this please do!
|
|
|
|
# We need to be in interactive mode and we need to have the Ghostty
|
|
# resources dir set which also tells us we're running in Ghostty.
|
|
if [[ "$-" != *i* ]] ; then builtin return; fi
|
|
if [ -z "$GHOSTTY_RESOURCES_DIR" ]; then builtin return; fi
|
|
|
|
# When automatic shell integration is active, we need to manually
|
|
# load the normal bash startup files based on the injected state.
|
|
if [ -n "$GHOSTTY_BASH_INJECT" ]; then
|
|
builtin declare ghostty_bash_inject="$GHOSTTY_BASH_INJECT"
|
|
builtin unset GHOSTTY_BASH_INJECT ENV
|
|
|
|
# At this point, we're in POSIX mode and rely on the injected
|
|
# flags to guide is through the rest of the startup sequence.
|
|
|
|
# POSIX mode was requested by the user so there's nothing
|
|
# more to do that optionally source their original $ENV.
|
|
# No other startup files are read, per the standard.
|
|
if [[ "$ghostty_bash_inject" == *"--posix"* ]]; then
|
|
if [ -n "$GHOSTTY_BASH_ENV" ]; then
|
|
builtin source "$GHOSTTY_BASH_ENV"
|
|
builtin export ENV="$GHOSTTY_BASH_ENV"
|
|
fi
|
|
else
|
|
# 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, respecting the injected flags like
|
|
# --norc and --noprofile that we parsed with the shell integration code.
|
|
#
|
|
# See also: run_startup_files() in shell.c in the Bash source code
|
|
if builtin shopt -q login_shell; then
|
|
if [[ $ghostty_bash_inject != *"--noprofile"* ]]; then
|
|
[ -r /etc/profile ] && builtin source "/etc/profile"
|
|
for rcfile in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do
|
|
[ -r "$rcfile" ] && { builtin source "$rcfile"; break; }
|
|
done
|
|
fi
|
|
else
|
|
if [[ $ghostty_bash_inject != *"--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 rcfile in /etc/bash.bashrc /etc/bash/bashrc /etc/bashrc; do
|
|
[ -r "$rcfile" ] && { builtin source "$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
|
|
fi
|
|
|
|
builtin unset GHOSTTY_BASH_ENV GHOSTTY_BASH_RCFILE
|
|
builtin unset ghostty_bash_inject rcfile
|
|
fi
|
|
|
|
# Sudo
|
|
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_SUDO" != "1" && -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
|
|
|
|
# Import bash-preexec, safe to do multiple times
|
|
builtin source "$GHOSTTY_RESOURCES_DIR/shell-integration/bash/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_get_current_command() {
|
|
builtin local last_cmd
|
|
# shellcheck disable=SC1007
|
|
last_cmd=$(HISTTIMEFORMAT= builtin history 1)
|
|
last_cmd="${last_cmd#*[[:digit:]]*[[:space:]]}" # remove leading history number
|
|
last_cmd="${last_cmd#"${last_cmd%%[![:space:]]*}"}" # remove remaining leading whitespace
|
|
builtin printf "\e]2;%s\a" "${last_cmd//[[:cntrl:]]}" # remove any control characters
|
|
}
|
|
|
|
function __ghostty_precmd() {
|
|
local ret="$?"
|
|
if test "$_ghostty_executing" != "0"; then
|
|
_GHOSTTY_SAVE_PS0="$PS0"
|
|
_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 test "$GHOSTTY_SHELL_INTEGRATION_NO_CURSOR" != "1"; then
|
|
PS1=$PS1'\[\e[5 q\]'
|
|
PS0=$PS0'\[\e[0 q\]'
|
|
fi
|
|
|
|
if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_TITLE" != 1 ]]; then
|
|
# Command and working directory
|
|
# shellcheck disable=SC2016
|
|
PS0=$PS0'$(__ghostty_get_current_command)'
|
|
PS1=$PS1'\[\e]2;$PWD\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() {
|
|
PS0="$_GHOSTTY_SAVE_PS0"
|
|
PS1="$_GHOSTTY_SAVE_PS1"
|
|
PS2="$_GHOSTTY_SAVE_PS2"
|
|
builtin printf "\e]133;C;\a"
|
|
_ghostty_executing=1
|
|
}
|
|
|
|
preexec_functions+=(__ghostty_preexec)
|
|
precmd_functions+=(__ghostty_precmd)
|