#!/bin/bash if [[ "$-" != *i* ]] ; then builtin return; fi # check in interactive mode if [[ -z "$KITTY_SHELL_INTEGRATION" ]]; then builtin return; fi _ksi_inject() { # Load the normal bash startup files if [[ -n "$KITTY_BASH_INJECT" ]]; then builtin local kitty_bash_inject="$KITTY_BASH_INJECT" builtin unset KITTY_BASH_INJECT builtin unset ENV if [[ -z "$HOME" ]]; then HOME=~; fi if [[ -z "$KITTY_BASH_ETC_LOCATION" ]]; then KITTY_BASH_ETC_LOCATION="/etc"; fi _ksi_safe_source() { if [[ -f "$1" && -r "$1" ]]; then builtin source "$1" builtin return 0 fi builtin return 1 } if [[ "$kitty_bash_inject" == *"posix"* ]]; then _ksi_safe_source "$KITTY_BASH_POSIX_ENV" && builtin export ENV="$KITTY_BASH_POSIX_ENV" else builtin set +o posix if [[ -n "$KITTY_BASH_UNEXPORT_HISTFILE" ]]; then builtin export -n HISTFILE builtin unset KITTY_BASH_UNEXPORT_HISTFILE fi # See run_startup_files() in shell.c in the Bash source code if builtin shopt -q login_shell; then if [[ "$kitty_bash_inject" != *"no-profile"* ]]; then _ksi_safe_source "$KITTY_BASH_ETC_LOCATION/profile" _ksi_safe_source "$HOME/.bash_profile" || _ksi_safe_source "$HOME/.bash_login" || _ksi_safe_source "$HOME/.profile" fi else if [[ "$kitty_bash_inject" != *"no-rc"* ]]; then # Linux distros build bash with -DSYS_BASHRC. Unfortunately, there is # no way to to probe bash for it and different distros use different files _ksi_safe_source "$KITTY_BASH_ETC_LOCATION/bash.bashrc" # Arch, Debian, Ubuntu # Fedora uses /etc/bashrc sourced from ~/.bashrc instead of SYS_BASHRC if [[ -z "$KITTY_BASH_RCFILE" ]]; then KITTY_BASH_RCFILE="$HOME/.bashrc"; fi _ksi_safe_source "$KITTY_BASH_RCFILE" fi fi fi builtin unset KITTY_BASH_RCFILE builtin unset KITTY_BASH_POSIX_ENV builtin unset KITTY_BASH_ETC_LOCATION builtin unset -f _ksi_safe_source fi } _ksi_inject builtin unset -f _ksi_inject if [ "${BASH_VERSINFO:-0}" -lt 4 ]; then builtin unset KITTY_SHELL_INTEGRATION builtin printf "%s\n" "Bash version ${BASH_VERSION} too old, kitty shell integration disabled" > /dev/stderr builtin return fi if [[ "${_ksi_prompt[sourced]}" == "y" ]]; then # we have already run builtin unset KITTY_SHELL_INTEGRATION builtin return fi # this is defined outside _ksi_main to make it global without using declare -g # which is not available on older bash builtin declare -A _ksi_prompt _ksi_prompt=( [cursor]='y' [title]='y' [mark]='y' [complete]='y' [ps0]='' [ps0_suffix]='' [ps1]='' [ps1_suffix]='' [ps2]='' [hostname_prefix]='' [sourced]='y' ) _ksi_main() { for i in ${KITTY_SHELL_INTEGRATION[@]}; do if [[ "$i" == "no-cursor" ]]; then _ksi_prompt[cursor]='n'; fi if [[ "$i" == "no-title" ]]; then _ksi_prompt[title]='n'; fi if [[ "$i" == "no-prompt-mark" ]]; then _ksi_prompt[mark]='n'; fi if [[ "$i" == "no-complete" ]]; then _ksi_prompt[complete]='n'; fi done builtin unset KITTY_SHELL_INTEGRATION _ksi_debug_print() { # print a line to STDERR of parent kitty process builtin local b b=$(builtin command base64 <<< "${@}") builtin printf "\eP@kitty-print|%s\e\\" "${b//[[:space:]]}}" } _ksi_set_mark() { _ksi_prompt["${1}_mark"]="\[\e]133;k;${1}_kitty\a\]" } _ksi_set_mark start _ksi_set_mark end _ksi_set_mark start_secondary _ksi_set_mark end_secondary _ksi_set_mark start_suffix _ksi_set_mark end_suffix builtin unset -f _ksi_set_mark _ksi_prompt[secondary_prompt]="\n${_ksi_prompt[start_secondary_mark]}\[\e]133;A;k=s\a\]${_ksi_prompt[end_secondary_mark]}" _ksi_prompt_command() { # we first remove any previously added kitty code from the prompt variables and then add # it back, to ensure we have only a single instance if [[ -n "${_ksi_prompt[ps0]}" ]]; then PS0=${PS0//\\\[\\e\]133;k;start_kitty\\a\\\]*end_kitty\\a\\\]} PS0="${_ksi_prompt[ps0]}$PS0" fi if [[ -n "${_ksi_prompt[ps0_suffix]}" ]]; then PS0=${PS0//\\\[\\e\]133;k;start_suffix_kitty\\a\\\]*end_suffix_kitty\\a\\\]} PS0="${PS0}${_ksi_prompt[ps0_suffix]}" fi # restore PS1 to its pristine state without our additions if [[ -n "${_ksi_prompt[ps1]}" ]]; then PS1=${PS1//\\\[\\e\]133;k;start_kitty\\a\\\]*end_kitty\\a\\\]} PS1=${PS1//\\\[\\e\]133;k;start_secondary_kitty\\a\\\]*end_secondary_kitty\\a\\\]} fi if [[ -n "${_ksi_prompt[ps1_suffix]}" ]]; then PS1=${PS1//\\\[\\e\]133;k;start_suffix_kitty\\a\\\]*end_suffix_kitty\\a\\\]} fi if [[ -n "${_ksi_prompt[ps1]}" ]]; then if [[ "${_ksi_prompt[mark]}" == "y" && ( "${PS1}" == *"\n"* || "${PS1}" == *$'\n'* ) ]]; then builtin local oldval oldval=$(builtin shopt -p extglob) builtin shopt -s extglob # bash does not redraw the leading lines in a multiline prompt so # mark the last line as a secondary prompt. Otherwise on resize the # lines before the last line will be erased by kitty. # the first part removes everything from the last \n onwards # the second part appends a newline with the secondary marking # the third part appends everything after the last newline PS1=${PS1%@('\n'|$'\n')*}${_ksi_prompt[secondary_prompt]}${PS1##*@('\n'|$'\n')} builtin eval "$oldval" fi PS1="${_ksi_prompt[ps1]}$PS1" fi if [[ -n "${_ksi_prompt[ps1_suffix]}" ]]; then PS1="${PS1}${_ksi_prompt[ps1_suffix]}" fi if [[ -n "${_ksi_prompt[ps2]}" ]]; then PS2=${PS2//\\\[\\e\]133;k;start_kitty\\a\\\]*end_kitty\\a\\\]} PS2="${_ksi_prompt[ps2]}$PS2" fi } if [[ "${_ksi_prompt[cursor]}" == "y" ]]; then _ksi_prompt[ps1_suffix]+="\[\e[5 q\]" # blinking bar cursor _ksi_prompt[ps0_suffix]+="\[\e[0 q\]" # blinking default cursor fi if [[ "${_ksi_prompt[title]}" == "y" ]]; then if [[ -z "$KITTY_PID" ]]; then if [[ -n "$SSH_TTY" || -n "$SSH2_TTY$KITTY_WINDOW_ID" ]]; then # connected to most SSH servers # or use ssh kitten to connected to some SSH servers that do not set SSH_TTY _ksi_prompt[hostname_prefix]="\h: " else if [[ -n "$(builtin command -v who)" && "$(builtin command who -m 2> /dev/null)" =~ "\([a-fA-F.:0-9]+\)$" ]]; then # the shell integration script is installed manually on the remote system # the environment variables are cleared after sudo # OpenSSH's sshd creates entries in utmp for every login so use those _ksi_prompt[hostname_prefix]="\h: " fi fi fi # see https://www.gnu.org/software/bash/manual/html_node/Controlling-the-Prompt.html#Controlling-the-Prompt # we use suffix here because some distros add title setting to their bashrc files by default _ksi_prompt[ps1_suffix]+="\[\e]2;${_ksi_prompt[hostname_prefix]}\w\a\]" if [[ "$HISTCONTROL" == *"ignoreboth"* ]] || [[ "$HISTCONTROL" == *"ignorespace"* ]]; then _ksi_debug_print "ignoreboth or ignorespace present in bash HISTCONTROL setting, showing running command in window title will not be robust" fi _ksi_get_current_command() { builtin local last_cmd 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%s\a" "${_ksi_prompt[hostname_prefix]@P}" "${last_cmd//[[:cntrl:]]}" # remove any control characters } _ksi_prompt[ps0_suffix]+='$(_ksi_get_current_command)' fi if [[ "${_ksi_prompt[mark]}" == "y" ]]; then _ksi_prompt[ps1]+="\[\e]133;A\a\]" _ksi_prompt[ps2]+="\[\e]133;A;k=s\a\]" _ksi_prompt[ps0]+="\[\e]133;C\a\]" fi if [[ "${_ksi_prompt[complete]}" == "y" ]]; then _ksi_completions() { builtin local src builtin local limit # Send all words up to the word the cursor is currently on builtin let limit=1+$COMP_CWORD src=$(builtin printf "%s\n" "${COMP_WORDS[@]:0:$limit}" | builtin command kitty +complete bash) if [[ $? == 0 ]]; then builtin eval ${src} fi } builtin complete -o nospace -F _ksi_completions kitty fi # wrap our prompt additions in markers we can use to remove them using # bash's anemic pattern substitution if [[ -n "${_ksi_prompt[ps0]}" ]]; then _ksi_prompt[ps0]="${_ksi_prompt[start_mark]}${_ksi_prompt[ps0]}${_ksi_prompt[end_mark]}" fi if [[ -n "${_ksi_prompt[ps0_suffix]}" ]]; then _ksi_prompt[ps0_suffix]="${_ksi_prompt[start_suffix_mark]}${_ksi_prompt[ps0_suffix]}${_ksi_prompt[end_suffix_mark]}" fi if [[ -n "${_ksi_prompt[ps1]}" ]]; then _ksi_prompt[ps1]="${_ksi_prompt[start_mark]}${_ksi_prompt[ps1]}${_ksi_prompt[end_mark]}" fi if [[ -n "${_ksi_prompt[ps1_suffix]}" ]]; then _ksi_prompt[ps1_suffix]="${_ksi_prompt[start_suffix_mark]}${_ksi_prompt[ps1_suffix]}${_ksi_prompt[end_suffix_mark]}" fi if [[ -n "${_ksi_prompt[ps2]}" ]]; then _ksi_prompt[ps2]="${_ksi_prompt[start_mark]}${_ksi_prompt[ps2]}${_ksi_prompt[end_mark]}" fi builtin unset _ksi_prompt[start_mark] builtin unset _ksi_prompt[end_mark] builtin unset _ksi_prompt[start_suffix_mark] builtin unset _ksi_prompt[end_suffix_mark] builtin unset _ksi_prompt[start_secondary_mark] builtin unset _ksi_prompt[end_secondary_mark] # install our prompt command, using an array if it is unset or already an array, # otherwise append a string. We check if _ksi_prompt_command exists as some shell # scripts stupidly export PROMPT_COMMAND making it inherited by all programs launched # from the shell builtin local pc pc='builtin declare -F _ksi_prompt_command > /dev/null 2> /dev/null && _ksi_prompt_command' if [[ -z "${PROMPT_COMMAND}" ]]; then PROMPT_COMMAND=([0]="$pc") elif [[ $(builtin declare -p PROMPT_COMMAND 2> /dev/null) =~ 'declare -a PROMPT_COMMAND' ]]; then PROMPT_COMMAND+=("$pc") else PROMPT_COMMAND="${PROMPT_COMMAND%% }" PROMPT_COMMAND="${PROMPT_COMMAND%%;}" PROMPT_COMMAND+="; $pc" fi } _ksi_main builtin unset -f _ksi_main # freeze _ksi_prompt to prevent it from being changed builtin declare -r _ksi_prompt