# Frontend to controlling prompt
function prompt {
# If no arguments, print the prompt strings as they are
if ! (($#)) ; then
printf '%s\n' PS1="$PS1" PS2="$PS2" PS3="$PS3" PS4="$PS4"
return
fi
# What's done next depends on the first argument to the function
case $1 in
# Turn complex, colored PS1 and debugging PS4 prompts on
on)
# Basic prompt shape depends on whether we're in SSH or not
PS1=
if [[ -n $SSH_CLIENT ]] || [[ -n $SSH_CONNECTION ]] ; then
PS1=$PS1'${HOSTNAME%%.*}:'
fi
# Add sub-commands; working directory with ~ abbreviation, VCS, job
# count, and previous command return value
PS1=$PS1'$(ret=$?;jobc=$(jobs -p|sed -n '\''$='\'');prompt pwd;prompt vcs;prompt job;prompt ret;:)'
# Add a helpful prefix if this shell appears to be exotic
typeset ksh
case $KSH_VERSION in
(*'93'*) ksh=ksh93 ;;
(*'PD KSH'*) ksh=pdksh ;;
(*'MIRBSD KSH'*) ksh=mksh ;;
esac
case ${SHELL##*/} in
(''|ksh|"$ksh") ;;
(*) PS1=$ksh:$PS1 ;;
esac
# Add prefix and suffix
PS1='${PROMPT_PREFIX}'$PS1'${PROMPT_SUFFIX}'
# Add terminating "$" or "#" sign
PS1=$PS1'\$'
# Declare variables to contain terminal control strings
typeset format reset
# Disregard output and error from these tput(1) calls
{
# Count available colors
typeset -i colors
colors=$(tput colors || tput Co)
# Prepare reset code
reset=$(tput sgr0 || tput me)
# Check if we have non-bold bright yellow available
if ((colors >= 16)) ; then
format=$(
pc=${PROMPT_COLOR:-11}
tput setaf "$pc" ||
tput setaf "$pc" 0 0 ||
tput AF "$pc" ||
tput AF "$pc" 0 0
)
# If we have only eight colors, use bold yellow
elif ((colors >= 8)) ; then
format=$(
pc=${PROMPT_COLOR:-3}
tput setaf "$pc" || tput AF "$pc"
tput bold || tput md
)
# Otherwise, we just try bold
else
format=$(tput bold || tput md)
fi
} >/dev/null 2>&1
# Play ball with ksh's way of escaping non-printing characters
typeset es cr
es=$(printf '\01')
cr=$(printf '\r')
# String it all together
PS1="${es}${cr}${es}${format}${es}${PS1}${es}${reset}${es}"' '
PS2='> '
PS3='? '
PS4='+<$?> $LINENO:'
;;
# Git prompt function
git)
# Wrap as compound command; we don't want to see output from any of
# these git(1) calls
{
# Bail if we're not in a work tree--or, implicitly, if we don't
# have git(1).
[[ -n $(git rev-parse --is-inside-work-tree) ]] ||
return
# Refresh index so e.g. git-diff-files(1) is accurate
git update-index --refresh
# Find a local branch, remote branch, or tag (annotated or
# not), or failing all of that just show the short commit ID,
# in that order of preference; if none of that works, bail out
typeset name
name=$(
git symbolic-ref --quiet HEAD ||
git describe --tags --exact-match HEAD ||
git rev-parse --short HEAD
) || return
name=${name#refs/*/}
[[ -n $name ]] || return
# Check various files in .git to flag processes
typeset proc
[[ -d .git/rebase-merge || -d .git/rebase-apply ]] &&
proc=${proc:+"$proc",}'REBASE'
[[ -f .git/MERGE_HEAD ]] &&
proc=${proc:+"$proc",}'MERGE'
[[ -f .git/CHERRY_PICK_HEAD ]] &&
proc=${proc:+"$proc",}'PICK'
[[ -f .git/REVERT_HEAD ]] &&
proc=${proc:+"$proc",}'REVERT'
[[ -f .git/BISECT_LOG ]] &&
proc=${proc:+"$proc",}'BISECT'
# Collect symbols representing repository state
typeset state
# Upstream HEAD has commits after local HEAD; we're "behind"
(($(git rev-list --count 'HEAD..@{u}'))) &&
state=${state}'<'
# Local HEAD has commits after upstream HEAD; we're "ahead"
(($(git rev-list --count '@{u}..HEAD'))) &&
state=${state}'>'
# Tracked files are modified
if ! git diff-files --no-ext-diff --quiet ; then
# Different ksh flavours process a bang in PS1 after prompt
# parameter expansion in different ways
case $KSH_VERSION in
# ksh93 requires a double-bang to escape it
(*'93'*) state=${state}'!!' ;;
# OpenBSD's pdksh requires a double-bang too, but its
# upstream does not
(*'PD KSH'*)
case $OS in
('OpenBSD') state=${state}'!!' ;;
(*) state=${state}'!' ;;
esac
;;
# Everything else should need only one bang
(*) state=${state}'!' ;;
esac
fi
# Changes are staged
git diff-index --cached --no-ext-diff --quiet HEAD ||
state=${state}'+'
# There are some untracked and unignored files
git ls-files --directory --error-unmatch --exclude-standard \
--no-empty-directory --others -- ':/*' &&
state=${state}'?'
# There are stashed changes
git rev-parse --quiet --verify refs/stash &&
state=${state}'^'
} >/dev/null 2>&1
# Print the status in brackets; add a git: prefix only if there
# might be another VCS prompt (because PROMPT_VCS is set)
printf '(%s%s%s%s)' \
"${PROMPT_VCS:+git:}" "$name" "${proc:+:"$proc"}" "$state"
;;
# Revert to simple inexpensive prompts
off)
PS1='$ '
PS2='> '
PS3='? '
PS4='+ '
if [[ -n $SSH_CLIENT || -n $SSH_CONNECTION ]] ; then
PS1=$(hostname -s)'$ '
fi
;;
# Abbreviated working directory
pwd)
case $PWD in
"$HOME"|"$HOME"/*) printf ~%s "${PWD#"$HOME"}" ;;
*) printf %s "$PWD" ;;
esac
;;
# VCS wrapper prompt function; print the first relevant prompt, if any
vcs)
typeset vcs
for vcs in "${PROMPT_VCS[@]:-git}" ; do
prompt "$vcs" && return
done
;;
# Show return status of previous command in angle brackets, if not zero
ret)
# shellcheck disable=SC2154
((ret)) && printf '<%u>' "$ret"
;;
# Show the count of background jobs in curly brackets, if not zero
job)
# shellcheck disable=SC2154
((jobc)) && printf '{%u}' "$jobc"
;;
# Print error
*)
printf 'prompt: Unknown command %s\n' "$1" >&2
return 2
;;
esac
}
# Default to a full-featured prompt, but use PROMPT_MODE if that's set
prompt "${PROMPT_MODE:-on}"