Files
bashPrompt/prompt.sh
T
2023-05-04 21:51:10 -04:00

449 lines
15 KiB
Bash

#!/bin/bash
# Charles Danesi, 2022.
# parts taken from https://gist.github.com/loudambiance/a41b42a4295bce6e7304
# TODO git merge status
# TODO git isLocalOnly to check if there are remotes or if this is only a local repo)
# TODO number of pushes pending
# TODO display number of modified files (maybe number of untracked as well)
# TODO display current tag
# TODO display that files are staged
# TODO display a 'ready to commit' status
# TODO display file deleted
# TODO status to display detached HEAD
# TODO status icon to indicate stash
#=========================================================
#Terminal Color Codes
#=========================================================
WHITE='\[\033[1;37m\]'
LIGHTGRAY='\[\033[0;37m\]'
GRAY='\[\033[1;49;90m\]'
BLACK='\[\033[0;30m\]'
RED='\[\033[0;31m\]'
LIGHTRED='\[\033[1;31m\]'
GREEN='\[\033[0;32m\]'
LIGHTGREEN='\[\033[1;32m\]'
ORANGE='\[\033[0;33m\]'
YELLOW='\[\033[1;33m\]'
BLUE="\[\033[0;34m\]"
LIGHTBLUE='\[\033[1;34m\]'
PURPLE='\[\033[0;35m\]'
PINK='\[\033[1;35m\]'
CYAN='\[\033[0;36m\]'
LIGHTCYAN='\[\033[1;36m\]'
DEFAULT='\[\033[0m\]'
CDNET1="\[\e[38;5;27m\]"
CDNET2="\[\e[38;5;32m\]"
DARKGRAY="\[\e[38;5;237m\]"
DARKGRAY2="\[\e[90m\]"
#=========================================================
# User Configuration
#=========================================================
# TODO refactor colors and config options.
# Configuration
#todoDir = "$HOME/code/todo.txt/" # This MUST be set for todo.txt to work!
# Colors
clrLines=$GRAY # Lines and Arrow
clrBrackets=$GRAY # Brackets around each data item
clrError=$RED # Error displays when previous command did not return 0
clrClock=$DARKGRAY # The current time
clrScreenLabel=$CYAN # Color for terminal multiplexer label
cMPX1=$YELLOW # Color for terminal multiplexer threshold 1
cMPX2=$RED # Color for terminal multiplexer threshold 2
cBGJ=$PURPLE # Color for background job label
cBGJ1=$YELLOW # Color for background job threshold 1
cBGJ2=$RED # Color for background job threshold 2
cSTJ=$RED # Color for stopped job label
cSTJ1=$YELLOW # Color for background job threshold 1
cSTJ2=$RED # Color for background job threshold 2
clrTaskLabel=$PURPLE # Color for todo.txt tasks label
clrTasks=$YELLOW # Color for the number of todo.txt tasks
clrSSH=$PINK # Color for brackets if session is an SSH session
clrUsername=$CDNET2 # Color of user
clrHostname=$CDNET2 # Color of hostname
clrWarnAsRoot=$RED # Color of root warning
clrWorkingDirectory=$CDNET1 # Color of current directory
clrCommand=$DEFAULT # Color of the command you type
clrSeparator=$WHITE # Color for separator for screen/jobs
clrUserSeperator=$GRAY # Color of the user and hostname separator, probably '@'
clrDefaultLines=$clrLines # default color
clrDefaultBrackets=$clrBrackets # default color
clrGitBranch=$LIGHTGREEN
clrGitHash=$CDNET1
clrCmdRunTime=$CYAN
clrUptime=$CYAN
GITBG1="\[\e[47m\]"
GITBG2="\[\e[100m\]"
# Features
fBlankLine=1 # Have a newline between previous command output and new prompt
fGitRepo=1 # Show git status
fGitStatusBar=1 # Show the git status bar above the main prompt (3 lines) NOTE: currently unused
fGitAfterCWD=1 # Show git status on 2-line prompt NOTE: currently unused
fReturnErrors=1 # Previous command return status tracker
fWindowTitle=1 # show uptime/load in titlebar
fClock=1
fShowHostname=1
fScreenTracker=1 # Terminal multiplexer tracker enabled
fSSHSession=0 # Track if session is SSH
fBackgroundJobTracker=1 # Track background jobs
fStoppedJobsTracker=1 # Track stopped jobs
fShowUserHost=1 # Show user and host #### NOTE: CURRENTLY UNUSED ####
fWorkingDirectory=1 # Show current directory
fPathShortening=1 # Enable directory shortening (configurable)
fLastCmdRunTime=1 # Display last cmd wall time
fShowUptime=1 # Display system uptime on the prompt
fShowTaskCount=1 # Show task count from todo.txt, if installed
# Settings
# TODO: add thresholds for cmd wall time
# TODO: add settings for customizing todo.txt (project/context/priority display only)
shortDirectoryThreshold="2" # How many folders deep before we start trimming the path
mpxThresholdMid="0" # Terminal multiplexer threshold 1 value
mpxThresholdHigh="2" # Terminal multiplexer threshold 2 value
bgJobThresholdMid="0" # Background job threshold 1 value
bgJobThresholdHigh="2" # Background job threshold 2 value
stoppedJobThresholdMid="0" # Stopped job threshold 1 value
stoppedJobThresholdHigh="2" # Stopped job threshold 2 value
# Symbols/Indicators
userHostSeparator="@"
symScreen="s"
gitBranch="" # 
gitUntrackedFiles="?"
gitFilesAdded="A"
gitFilesRemoved="D"
gitFilesModified="M"
gitStagedChanges=""
gitUnstagedChanges="Δ"
gitStashedChanges="*" # 
gitMerging="‼" #  
gitUncommittedChanges="✘" # staged, but not committed
gitClean="✓"
gitDirty="✘"
gitPushReady="" # 
gitBehindRemote=""
gitLocalOnly=""
gitLocalRemote=""
taskLabel="☑"
# if user-specific config exists, override default values
if [ -f $HOME/.config/promptrc ]; then
. $HOME/.config/promptrc
fi
function promptcmd() {
prevReturnCode=$?
timer_stop
# check if we're in an SSH session
if [[ $SSH_CLIENT ]] || [[ $SSH2_CLIENT ]]; then
lSSH_FLAG=1
else
lSSH_FLAG=0
fi
# insert newline to clear space from previous cmd
if [ $fBlankLine -eq 1 ]; then
PS1="\n"
else
PS1=
fi
# previous command error
if [ $fReturnErrors -eq 1 ]; then
if [ $prevReturnCode -ne 0 ]; then
clrLines=$clrError
clrBrackets=$clrError
else
clrLines=$clrDefaultLines
clrBrackets=$clrDefaultBrackets
fi
fi
# set window title
if [ $fWindowTitle -eq 1 ]; then
if [ "$STY" != "" ]; then
PS1="\[\e]0;(`echo $STY|cut -d . -f 2`)`uptime`\a\]${PS1}"
else
PS1="\[\e]0;`uptime -p`, `uptime | cut -d " " -f 12-`\a\]${PS1}"
fi
fi
# beginning of first line (if in a git repo)
# if [ $fGitRepo -eq 1 ] && [ "$(is_git_repo)" == "true" ]; then
# PS1="${PS1} ${WHITE} ${clrGitBranch}$(git_branch)$(git_hash) $(git_untracked)\n"
# fi
# first line if not in a git repo
PS1="${PS1}${debian_chroot:+($debian_chroot)}${clrLines}\342\224\214\342\224\200"
# last cmd runtime
if [ $fLastCmdRunTime -eq 1 ]; then
PS1="${PS1}${clrBrackets}[${clrCmdRunTime}$timer_show${clrBrackets}]${clrLines}\342\224\200"
fi
# current time
if [ $fClock -eq 1 ] ; then
PS1="${PS1}${clrBrackets}[${clrClock}\t${clrBrackets}]${clrLines}\342\224\200"
fi
# system uptime
if [ $fShowUptime -eq 1 ] ; then
sysUptime=$(uptime| cut -d "," -f 1-2 | cut -d " " -f 4-5,7)
PS1="${PS1}${clrBrackets}[${clrUptime}${sysUptime}${clrBrackets}]${clrLines}\342\224\200"
fi
# detached screen sessions
if [ $fScreenTracker -eq 1 ] ; then
hTMUX=0
hSCREEN=0
mpxCounter=0
hash tmux --help 2>/dev/null || hTMUX=1
hash screen --version 2>/dev/null || hSCREEN=1
if [ $hTMUX -eq 1 ] && [ $hSCREEN -eq 1 ]; then
mpxCounter=$(echo "$(screen -ls|grep -c -i detach) + $(tmux ls 2> /dev/null | grep -c -i -v attach)" | bc)
elif [ $hTMUX -eq 0 ] && [ $hSCREEN -eq 1 ] ; then
mpxCounter=$(tmux ls 2>/dev/null | grep -c -i -v attach)
elif [ $hTMUX -eq 1 ] && [ $hSCREEN -eq 0 ] ; then
mpxCounter=$(screen -ls | grep -c -i detach)
fi
if [[ $mpxCounter -gt $mpxThresholdHigh ]] ; then
PS1="${PS1}${clrBrackets}[${clrScreenLabel}${symScreen}${clrSeparator}:${cMPX2}${mpxCounter}${clrBrackets}]${clrLines}\342\224\200"
elif [[ $mpxCounter -gt $mpxThresholdMid ]] ; then
PS1="${PS1}${clrBrackets}[${clrScreenLabel}${symScreen}${clrSeparator}:${cMPX1}${mpxCounter}${clrBrackets}]${clrLines}\342\224\200"
fi
fi
# background tasks
if [ $fBackgroundJobTracker -eq 1 ] ; then
BGJC=$(jobs -r|wc -l)
if [ $BGJC -gt $bgJobThresholdHigh ] ; then
PS1="${PS1}${clrBrackets}[${cBGJ2}&${clrSeparator}:${cBGJ2}${BGJC}${clrBrackets}]${clrLines}\342\224\200"
elif [ $BGJC -gt $bgJobThresholdMid ] ; then
PS1="${PS1}${clrBrackets}[${cBGJ1}&${clrSeparator}:${cBGJ2}${BGJC}${clrBrackets}]${clrLines}\342\224\200"
fi
fi
# stopped jobs
if [ $fStoppedJobsTracker -eq 1 ] ; then
STJC=$(jobs -s | wc -l )
if [ $STJC -gt $stoppedJobThresholdHigh ] ; then
PS1="${PS1}${clrBrackets}[${cSTJ2}\342\234\227${clrSeparator}:${cSTJ2}${STJC}${clrBrackets}]${clrLines}\342\224\200"
elif [ $STJC -gt $stoppedJobThresholdMid ] ; then
PS1="${PS1}${clrBrackets}[${cSTJ1}\342\234\227${clrSeparator}:${cSTJ1}${STJC}${clrBrackets}]${clrLines}\342\224\200"
fi
fi
# todo.txt task count
if [ $fShowTaskCount -eq 1 ] ; then
PS1="${PS1}${clrTaskLabel}"
fi
# user@host
# set bracket color if in ssh session
if [ $fSSHSession -eq 1 ] && [ $lSSH_FLAG -eq 1 ]; then
clrSes="$clrSSH"
else
clrSes="$clrBrackets"
fi
# don't display user if root
if [ $EUID -eq 0 ]; then
PS1="${PS1}${clrSes}[${clrWarnAsRoot}!"
else
PS1="${PS1}${clrSes}[${clrUsername}\u"
fi
# Host Section
if [ $fShowHostname -eq 1 ] || [ $lSSH_FLAG -eq 1 ] ; then # Host is optional only without SSH
PS1="${PS1}${clrUserSeperator}${userHostSeparator}${clrHostname}\h${clrSes}]${clrLines}\342\224\200"
else
PS1="${PS1}${clrSes}]${clrLines}\342\224\200"
fi
#PS1="${PS1}${userHostSeparator}${LIGHTGREEN}\h${clrSes}]${GRAY}\342\224\200"
# current directory
if [ $fWorkingDirectory -eq 1 ]; then
PS1="${PS1}[${clrWorkingDirectory}\w"
if [ $fGitRepo -eq 1 ] && [ "$(is_git_repo)" == "true" ]; then
PS1="${PS1}${clrBrackets}]${clrLines}\342\224\200${clrBrackets}[${WHITE}${gitBranch} ${clrGitBranch}$(git_branch)$(git_hash) $(git_untracked)${clrBrackets}]"
else
PS1="${PS1}${clrBrackets}]"
fi
fi
# prompt
PS1="${PS1}\n${clrLines}\342\224\224\342\224\200\342\224\200> ${clrCommand}"
}
function timer_now {
date +%s%3N
}
function timer_start {
timer=${timer:-$(timer_now)}
}
function timer_stop {
local d_ms=$(($(timer_now) - $timer))
local d_s=$((d_ms / 1000))
local ms=$((d_ms % 1000))
local s=$((d_s % 60))
local m=$(((d_s / 60) % 60))
local h=$((d_s / 3600))
if ((h > 0)); then timer_show=${h}h${m}m
elif ((m > 0)); then timer_show=${m}m${s}s
elif ((s >= 10)); then timer_show=${s}.$((ms / 100))s
elif ((s > 0)); then timer_show=${s}.$((ms / 10))s
else timer_show=${ms}ms
fi
unset timer
}
function is_git_repo {
# TODO: can do this without saving as a variable
local isREPO="$(git rev-parse --is-inside-work-tree 2>/dev/null)"
#if [ ! $(git remote 2>/dev/null) ]; then echo "no remote"; else echo "remote available";fi
echo -e $isREPO
}
function git_hash {
local current_hash="$(git log --oneline --max-count=1 2>/dev/null|cut -b -7)"
if [ "$current_hash" != "" ]; then
# TODO: change these to configurable variable
echo -e "${YELLOW}@${clrGitHash}$current_hash"
fi
}
# might be able to include untracked/unmerged/unstaged in one function
function git_untracked {
local git_icons=""
local git_status="$(git status 2>/dev/null)"
local git_shortStatus="$(git status --porcelain|cut -b 1-2 2>/dev/null)"
# local git_stash="$(git stash list 2>/dev/null)"
# M modified
# A added
# D deleted
# R renamed
# C copied
# byte 1 is staged, byte 2 unstaged
# ↑1 ↓10
# has stash present
# NOTE: IDK why this only works with the first '!'
if ! git stash list &>/dev/null != ""; then
git_icons="${git_icons}${LIGHTBLUE}${gitStashedChanges}"
fi
# untracked files
# if git status --porcelain|cut -b 1-2|grep "??" &>/dev/null ; then
# NOTE: this is probably the better way to chain these...
if [[ $git_shortStatus =~ "??" ]]; then
git_icons="${git_icons}${RED}${gitUntrackedFiles}"
fi
# added files
if [[ $git_shortStatus =~ "A" ]]; then
git_icons="${git_icons}${DEFAULT}${gitFilesAdded}"
fi
# deleted files
if [[ $git_shortStatus =~ "D" ]]; then
git_icons="${git_icons}${DEFAULT}${gitFilesRemoved}"
fi
# modified/renamed/copied files
for i in $git_shortStatus; do
case $i in
R)
;&
C)
;&
M)
#echo -n "${RED}${gitFilesModified}"
git_icons="${git_icons}${DEFAULT}${gitFilesModified}"
break;
;;
esac
done
# unmerged ( ‼ )
# FIXME: should not include this all in the same function
# unstaged changes
if git status --porcelain|cut -b 2|grep 'M\|A\|D\|R\|C' &>/dev/null; then
git_icons="${git_icons}${LIGHTBLUE}${gitUnstagedChanges}"
fi
if [ "$git_icons" != "" ]; then
git_icons="${git_icons} "
fi
# if git status --porcelain|cut -b 1|grep 'M\|A\|D\|R\|C' &>/dev/null; then
# git_icons="${git_icons} ${YELLOW}✘"
# if [[ !$git_status =~ "working tree clean" ]] ; then
# git_icons="${git_icons}${RED}✘"
if [[ $git_status =~ "Changes to be committed" ]]; then
git_icons="${git_icons}${GREEN}" # NOTE: staged but not committed
elif [[ $git_status =~ "Changes not staged" ]]; then
git_icons="${git_icons}${LIGHTBLUE}"
elif [[ $git_status =~ "branch is ahead" ]]; then
git_icons="${git_icons}${YELLOW}" #NOTE: ahead of origin
elif [[ $git_status =~ "nothing to commit" ]]; then
git_icons="${git_icons}${LIGHTGREEN}${gitClean}" #NOTE: working
else
git_icons="${git_icons}${RED}${gitDirty}" # NOTE: working
fi
# if [ "$git_icons" != "" ]; then
# echo -e " ${git_icons}"
# fi
echo -e $git_icons
}
function git_branch {
local git_status="$(git status 2>/dev/null)"
local on_branch="On branch ([^${IFS}]*)"
local on_commit="HEAD detached at ([^${IFS}]*)"
if [[ $git_status =~ $on_branch ]]; then
local branch=${BASH_REMATCH[1]}
echo "$branch"
elif [[ $git_status =~ $on_commit ]]; then
local commit=${BASH_REMATCH[1]}
echo "($commit)"
fi
}
function load_prompt() {
# Get PIDs
local parent_process=$(tr -d '\0' < /proc/$PPID/cmdline | cut -d \. -f 1)
local my_process=$(tr -d '\0' < /proc/$$/cmdline | cut -d \. -f 1)
if [[ $parent_process == script* ]]; then
PROMPT_COMMAND=""
PS1="\t - \# - \u@\H { \w }\$ "
elif [[ $parent_process == emacs* || $parent_process == xemacs* ]]; then
PROMPT_COMMAND=""
PS1="\u@\h { \w }\$ "
else
export DAY=$(date +%A)
PROMPT_COMMAND="promptcmd"
fi
if [ $fPathShortening -eq 1 ]; then
export PROMPT_DIRTRIM=$shortDirectoryThreshold
else
export PROMPT_DIRTRIM=
fi
trap 'timer_start' DEBUG
export PS1 PROMPT_COMMAND
}
load_prompt