Last active
November 29, 2023 00:27
-
-
Save jart/9e3b4c60337f03851b58a1b2db8504e1 to your computer and use it in GitHub Desktop.
PS1 with low latency git branch detection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# jartps1.sh - Enhanced bash prompt | |
# Author: Justine Tunney <jart@google.com> | |
# Modified: 2023-11-28 | |
# | |
# LIST OF BASH PROMPT ENHANCEMENTS | |
# | |
# - No latency issues when holding down enter key | |
# - Doesn't look unfamiliar compared to bash defaults | |
# - Show current git branch | |
# - Show background job count if \j > 0 | |
# - Show process exit code if $? != 0 | |
# | |
# TRY IT OUT | |
# | |
# JARTPS1_THEME=dark source .jartps1.sh | |
# | |
# CUSTOMIZING | |
# | |
# Copy/paste the `PS1="..."` line from this file and add it to your bashrc | |
# after the line where you source this file and refer to the `man bash` | |
# section titled "PROMPTING". | |
# | |
# If you're using a black background terminal then you might want to | |
# customize the color and make things look more fabulous. You just have to | |
# figure out the ANSI escape codes for the colors you want. Don't remove the | |
# 0's because they're used to reset the current formatting. Also don't remove | |
# the \[...\] escapes or else bash will print artifacts when you do things | |
# like reverse search. A library called Fabulous will help you generate the | |
# escape codes: | |
# | |
# $ sudo apt-get install python-pip | |
# $ sudo pip install fabulous | |
# $ python | |
# >>> from fabulous import color | |
# >>> color.red('hello') | |
# u'\x1b[31mhello\x1b[39m' | |
# >>> color.fg256('#ff6699', 'hello') | |
# u'\x1b[38;5;204mhello\x1b[39m' | |
# >>> color.bold(color.flip(color.fg256('indianred', 'hello'))) | |
# u'\x1b[1;7;38;5;167mhello\x1b[39;27;22m' | |
# | |
# See also: https://github.com/jart/fabulous/blob/master/fabulous/color.py | |
# | |
# NOTES | |
# | |
# This script will leave a tiny temp file behind for each terminal you | |
# open. This isn't a problem because `/etc/cron.daily/tmpreaper` will delete | |
# them after seven days or so. | |
# | |
# CHANGELOG | |
# | |
# 2023-11-28 Justine Tunney <jart@google.com> | |
# | |
# * Support shells other than bash | |
# * Support /Users convention on MacOS | |
# | |
# 2014-06-20 Justine Tunney <jart@google.com> | |
# | |
# * Conform to go/bashstyle | |
# | |
# 2013-12-10 Justine Tunney <jart@google.com> | |
# | |
# * Fixed a bug which caused bash to display a warning each time the prompt | |
# is displayed if a certain /tmp file got deleted by a weekly cleanup | |
# cron job. | |
# | |
# * Fixed ps1_error() and ps1_jobs() now that I understand bash arithmetic | |
# expressions better. | |
# | |
ps1_error() { | |
local e=$? | |
if [ $e -ne 0 ]; then | |
case $e in | |
129) printf "\$?=SIGHUP " ;; | |
130) printf "\$?=SIGINT " ;; | |
131) printf "\$?=SIGQUIT " ;; | |
132) printf "\$?=SIGILL " ;; | |
133) printf "\$?=SIGTRAP " ;; | |
134) printf "\$?=SIGABRT " ;; | |
135) printf "\$?=SIGBUS " ;; | |
136) printf "\$?=SIGFPE " ;; | |
137) printf "\$?=SIGKILL " ;; | |
138) printf "\$?=SIGUSR1 " ;; | |
139) printf "\$?=SIGSEGV " ;; | |
140) printf "\$?=SIGUSR2 " ;; | |
141) printf "\$?=SIGPIPE " ;; | |
142) printf "\$?=SIGALRM " ;; | |
143) printf "\$?=SIGTERM " ;; | |
145) printf "\$?=SIGCHLD " ;; | |
146) printf "\$?=SIGCONT " ;; | |
147) printf "\$?=SIGSTOP " ;; | |
148) printf "\$?=SIGTSTP " ;; | |
149) printf "\$?=SIGTTIN " ;; | |
150) printf "\$?=SIGTTOU " ;; | |
151) printf "\$?=SIGURG " ;; | |
152) printf "\$?=SIGXCPU " ;; | |
153) printf "\$?=SIGXFSZ " ;; | |
154) printf "\$?=SIGVTALRM " ;; | |
155) printf "\$?=SIGPROF " ;; | |
156) printf "\$?=SIGWINCH " ;; | |
157) printf "\$?=SIGIO " ;; | |
159) printf "\$?=SIGSYS " ;; | |
*) printf "\$?=%d " $e ;; | |
esac | |
fi | |
} | |
ps1_jobs() { | |
local count=$1 | |
if [ $count -gt 0 ]; then | |
printf "jobs=%d " ${count} | |
fi | |
} | |
ps1_git() { | |
if jartps1_gitbranch_find; then | |
printf "%s " "${jartps1_gitbranch}" | |
fi | |
} | |
ps1_dir() { | |
local path="$PWD" | |
case "${path}" in | |
${HOME}) | |
path="~" | |
;; | |
${HOME}/*) | |
path="~/${path#$HOME/}" | |
;; | |
esac | |
printf "%s" "${path}" | |
} | |
if [ -n "${BASH_VERSION}" ]; then | |
case ${JARTPS1_THEME:-default} in | |
default) | |
PS1="\[\e[1;31m\]\$(ps1_error)\[\e[34m\]\$(ps1_jobs \j)\[\e[32m\]\$(ps1_git)\[\e[0;95m\]\u@\h\[\e[m\]:\$(ps1_dir)\$ " | |
PS2='> ' | |
PS4='+ ' | |
;; | |
dark) | |
PS1="\[\e[1;31m\]\$(ps1_error)\[\e[34m\]\$(ps1_jobs \j)\[\e[32m\]\$(ps1_git)\[\e[0;95m\]\u@\h\[\e[m\]:\[\e[0;96m\]\$(ps1_dir)\[\e[0m\]\$ " | |
PS2='> ' | |
PS4='+ ' | |
;; | |
*) | |
printf "Invalid \$JARTPS1_THEME: %s\n" "${JARTPS1_THEME}" >&2 | |
;; | |
esac | |
else | |
PS1="\$(ps1_error)\$(ps1_git)\$USER@$(hostname -s):\$(ps1_dir)\$ " | |
PS2='> ' | |
PS4='+ ' | |
fi | |
# Caches last directory for which `jartps1_gitdir_find` failed. | |
export JARTPS1_GITDIR_TMP="$(mktemp /tmp/jartps1.XXXXXX)" | |
################################################################################ | |
# Swiftly determine name of current Git branch. | |
# | |
# Arguments: | |
# None | |
# Returns: | |
# 0 on success, storing result to ${jartps1_gitbranch}. | |
# 1 if ${PWD} isn't inside a Git repo. | |
# Globals: | |
# jartps1_gitbranch - Used to store result. | |
################################################################################ | |
jartps1_gitbranch_find() { | |
if jartps1_gitdir_find; then | |
local head | |
if read head <"${jartps1_gitdir}/.git/HEAD" >/dev/null 2>&1; then | |
jartps1_gitbranch="${head##*/}" | |
if [ x"${jartps1_gitbranch}" != x"" ]; then | |
return 0 | |
fi | |
fi | |
fi | |
return 1 | |
} | |
################################################################################ | |
# Swiftly locate root of current Git repository. | |
# | |
# This is the fastest possible solution. Rather than the naïve approach of | |
# executing the git command (which could take hundreds of milliseconds), this | |
# routine launches no processes and only requires a few stat() system calls. In | |
# some cases it can avoid lookup or cache the result. | |
# | |
# Arguments: | |
# dir - Optional, defaults to ${PWD} | |
# Returns: | |
# 0 on success, storing result to ${jartps1_gitdir}. | |
# 1 if dir isn't inside a Git repo. | |
# Globals: | |
# jartps1_gitdir - Used to store result. | |
# JARTPS1_GITDIR_TMP - Temp file used to cache last failed search. | |
################################################################################ | |
jartps1_gitdir_find() { | |
local dir="$1" | |
if [ -z "${dir}" ]; then | |
dir="${PWD}" | |
case "${dir}" in | |
/) return 1 ;; | |
${HOME}) return 1 ;; | |
/google/*) return 1 ;; | |
/home/build/*) return 1 ;; | |
/usr/local/google${HOME}) return 1 ;; | |
esac | |
if [ -f "${JARTPS1_GITDIR_TMP}" ]; then | |
local notgit | |
if read notgit <"${JARTPS1_GITDIR_TMP}" >/dev/null 2>&1; then | |
if [ "${dir}" == "${notgit}" ]; then | |
return 1 | |
fi | |
fi | |
fi | |
fi | |
if [ -d "${dir}/.git" ]; then | |
jartps1_gitdir="${dir}" | |
else | |
local parent="${dir%/*}" | |
if [ -z "${parent}" ]; then | |
printf "%s" "$PWD" >"${JARTPS1_GITDIR_TMP}" | |
return 1 | |
fi | |
jartps1_gitdir_find "${parent}" | |
fi | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment