358 lines
9.0 KiB
Bash
Executable File
358 lines
9.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Based on https://github.com/undu/bash-powerline
|
|
|
|
__powerline() {
|
|
|
|
# User config variables,
|
|
# it's recommended to override those variables through .bashrc or similar
|
|
#
|
|
# Use powerline mode
|
|
# readonly POWERLINE_FONT=''
|
|
#
|
|
# Always show user in the prompt
|
|
# readonly SHOW_USER=''
|
|
#
|
|
# Never show a default user
|
|
# readonly DEFAULT_USER='user'
|
|
|
|
# Default background and foreground ANSI colours
|
|
readonly DEFAULT_BG=0
|
|
readonly DEFAULT_FG=7
|
|
|
|
# Max length of full path
|
|
readonly MAX_PATH_LENGTH=30
|
|
|
|
# Unicode symbols
|
|
if [ -z "${POWERLINE_FONT+x}" ]; then
|
|
readonly GIT_BRANCH_SYMBOL='⑂'
|
|
else
|
|
readonly GIT_BRANCH_SYMBOL=''
|
|
fi
|
|
readonly GIT_BRANCH_CHANGED_SYMBOL='Δ'
|
|
readonly GIT_NEED_PUSH_SYMBOL='↑'
|
|
readonly GIT_NEED_PULL_SYMBOL='↓'
|
|
|
|
# Powerline symbols
|
|
readonly BLOCK_START=''
|
|
|
|
# ANSI Colours
|
|
readonly BLACK=0
|
|
readonly RED=1
|
|
readonly GREEN=2
|
|
readonly YELLOW=3
|
|
readonly BLUE=4
|
|
readonly MAGENTA=5
|
|
readonly CYAN=6
|
|
readonly WHITE=7
|
|
|
|
readonly BLACK_BRIGHT=8
|
|
readonly RED_BRIGHT=9
|
|
readonly GREEN_BRIGHT=10
|
|
readonly YELLOW_BRIGHT=11
|
|
readonly BLUE_BRIGHT=12
|
|
readonly MAGENTA_BRIGHT=13
|
|
readonly CYAN_BRIGHT=14
|
|
readonly WHITE_BRIGHT=15
|
|
|
|
# Font effects
|
|
readonly DIM="\\[$(tput dim)\\]"
|
|
readonly REVERSE="\\[$(tput rev)\\]"
|
|
readonly RESET="\\[$(tput sgr0)\\]"
|
|
readonly BOLD="\\[$(tput bold)\\]"
|
|
|
|
# Generate terminal colour codes
|
|
# $1 is an int (a colour) and $2 must be 'fg' or 'bg'
|
|
__colour() {
|
|
case "$2" in
|
|
'fg'*)
|
|
echo "\\[$(tput setaf "$1")\\]"
|
|
;;
|
|
'bg'*)
|
|
echo "\\[$(tput setab "$1")\\]"
|
|
;;
|
|
*)
|
|
echo "\\[$(tput setab "$1")\\]"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Generate a single-coloured block for the prompt
|
|
__prompt_block() {
|
|
local bg; local fg
|
|
if [ ! -z "${1+x}" ]; then
|
|
bg=$1
|
|
else
|
|
if [ ! -z "$last_bg" ]; then
|
|
bg=$last_bg
|
|
else
|
|
bg=$DEFAULT_BG
|
|
fi
|
|
fi
|
|
if [ ! -z "${2+x}" ]; then
|
|
fg=$2
|
|
else
|
|
fg=$DEFAULT_FG
|
|
fi
|
|
|
|
local block
|
|
|
|
# Need to generate a separator if the background changes
|
|
if [[ ! -z "$last_bg" && "$bg" != "$last_bg" && ! -z "${POWERLINE_FONT+x}" ]]; then
|
|
block+="$(__colour "$bg" 'bg')"
|
|
block+="$(__colour "$last_bg" 'fg')"
|
|
block+="$BLOCK_START $RESET"
|
|
block+="$(__colour "$bg" 'bg')"
|
|
block+="$(__colour "$fg" 'fg')"
|
|
else
|
|
block+="$(__colour "$bg" 'bg')"
|
|
block+="$(__colour "$fg" 'fg')"
|
|
block+=" "
|
|
fi
|
|
|
|
if [ ! -z "${3+x}" ]; then
|
|
block+="$3 $RESET"
|
|
fi
|
|
|
|
last_bg=$bg
|
|
|
|
__block_text="$block"
|
|
}
|
|
|
|
function __end_block() {
|
|
__block_text=''
|
|
if [ ! -z "$last_bg" ]; then
|
|
if [ ! -z "${POWERLINE_FONT+x}" ]; then
|
|
__block_text+="$(__colour $DEFAULT_BG 'bg')"
|
|
__block_text+="$(__colour "$last_bg" 'fg')"
|
|
__block_text+="$BLOCK_START$RESET"
|
|
__block_text+="$(__colour $DEFAULT_BG 'bg')"
|
|
__block_text+="$(__colour "$DEFAULT_FG" 'fg')"
|
|
else
|
|
__block_text+="$(__colour $DEFAULT_BG 'bg')"
|
|
__block_text+="$(__colour "$DEFAULT_FG" 'fg')"
|
|
fi
|
|
fi
|
|
__block_text+=' '
|
|
}
|
|
|
|
### Prompt components
|
|
|
|
__git_block() {
|
|
if ! command -V git > /dev/null; then
|
|
# git not found
|
|
__block_text=''
|
|
return
|
|
fi
|
|
# force git output in English to make our work easier
|
|
local git_eng="env LANG=C git"
|
|
|
|
# check if pwd is under git
|
|
if ! git rev-parse --is-inside-git-dir > /dev/null 2> /dev/null; then
|
|
# not in a git repo, bail out
|
|
__block_text=''
|
|
return
|
|
fi
|
|
|
|
# get current branch name or short SHA1 hash for detached head
|
|
local branch; local ref_symbol
|
|
branch="$($git_eng symbolic-ref --short HEAD 2>/dev/null)"
|
|
# shellcheck disable=SC2181
|
|
if [ $? != 0 ]; then
|
|
branch="$($git_eng describe --tags --always 2>/dev/null)"
|
|
ref_symbol='➦'
|
|
else
|
|
ref_symbol=$GIT_BRANCH_SYMBOL
|
|
fi
|
|
|
|
# In pcmode (and only pcmode) the contents of
|
|
# $gitstring are subject to expansion by the shell.
|
|
# Avoid putting the raw ref name in the prompt to
|
|
# protect the user from arbitrary code execution via
|
|
# specially crafted ref names (e.g., a ref named
|
|
# '$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' would execute
|
|
# 'sudo rm -rf /' when the prompt is drawn). Instead,
|
|
# put the ref name in a new global variable (in the
|
|
# __git_ps1_* namespace to avoid colliding with the
|
|
# user's environment) and reference that variable from
|
|
# PS1.
|
|
# note that the $ is escaped -- the variable will be
|
|
# expanded later (when it's time to draw the prompt)
|
|
if shopt -q promptvars; then
|
|
export __git_ps1_block="$branch"
|
|
ref="$ref_symbol \${__git_ps1_block}"
|
|
else
|
|
ref="$ref_symbol $branch"
|
|
fi
|
|
|
|
local marks
|
|
|
|
# check if HEAD is dirty
|
|
if [ -n "$($git_eng status --porcelain 2>/dev/null)" ]; then
|
|
dirty='y'
|
|
marks+=" $GIT_BRANCH_CHANGED_SYMBOL"
|
|
fi
|
|
|
|
# how many commits local branch is ahead/behind of remote?
|
|
local stat; local aheadN; local behindN
|
|
stat="$($git_eng status --porcelain --branch 2>/dev/null | grep '^##' | grep -o '\[.\+\]$')"
|
|
aheadN="$(echo "$stat" | grep -o 'ahead [[:digit:]]\+' | grep -o '[[:digit:]]\+')"
|
|
behindN="$(echo "$stat" | grep -o 'behind [[:digit:]]\+' | grep -o '[[:digit:]]\+')"
|
|
[ -n "$aheadN" ] && marks+=" $GIT_NEED_PUSH_SYMBOL$aheadN"
|
|
[ -n "$behindN" ] && marks+=" $GIT_NEED_PULL_SYMBOL$behindN"
|
|
|
|
local bg; local fg
|
|
fg=$BLACK
|
|
if [ -z "$dirty" ]; then
|
|
bg=$GREEN
|
|
else
|
|
bg=$YELLOW
|
|
fi
|
|
|
|
__prompt_block $bg $fg "$ref$marks"
|
|
}
|
|
|
|
__virtualenv_block() {
|
|
# Copied from Python virtualenv's activate.sh script.
|
|
# https://github.com/pypa/virtualenv/blob/a9b4e673559a5beb24bac1a8fb81446dd84ec6ed/virtualenv_embedded/activate.sh#L62
|
|
# License: MIT
|
|
if [ -n "$VIRTUAL_ENV" ]; then
|
|
local venv
|
|
# In pcmode (and only pcmode) the contents of
|
|
# $gitstring are subject to expansion by the shell.
|
|
# Avoid putting the raw ref name in the prompt to
|
|
# protect the user from arbitrary code execution via
|
|
# specially crafted ref names (e.g., a ref named
|
|
# '$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' would execute
|
|
# 'sudo rm -rf /' when the prompt is drawn). Instead,
|
|
# put the ref name in a new global variable (in the
|
|
# __git_ps1_* namespace to avoid colliding with the
|
|
# user's environment) and reference that variable from
|
|
# PS1.
|
|
# note that the $ is escaped -- the variable will be
|
|
# expanded later (when it's time to draw the prompt)
|
|
if shopt -q promptvars; then
|
|
export __venv_ps1_block
|
|
__venv_ps1_block=$(basename "$VIRTUAL_ENV")
|
|
venv="$ref_symbol \${__venv_ps1_block}"
|
|
else
|
|
venv="$(basename "$VIRTUAL_ENV")"
|
|
fi
|
|
__prompt_block $WHITE $BLACK "$venv"
|
|
else
|
|
__block_text=''
|
|
fi
|
|
}
|
|
|
|
__pwd_block() {
|
|
# Use ~ to represent $HOME prefix
|
|
local pwd; pwd=$(pwd | sed -e "s|^$HOME|~|")
|
|
# shellcheck disable=SC1001,SC2088
|
|
if [[ ( $pwd = ~\/*\/* || $pwd = \/*\/*/* ) && ${#pwd} -gt $MAX_PATH_LENGTH ]]; then
|
|
local IFS='/'
|
|
read -ra split <<< "$pwd"
|
|
if [[ $pwd = ~* ]]; then
|
|
pwd="~/${split[1]}/.../${split[*]:(-2):1}/${split[*]:(-1)}"
|
|
else
|
|
pwd="/${split[1]}/.../${split[*]:(-2):1}/${split[*]:(-1)}"
|
|
fi
|
|
fi
|
|
__prompt_block $BLACK_BRIGHT $WHITE_BRIGHT "$pwd"
|
|
}
|
|
|
|
# superuser or not, here I go!
|
|
__user_block() {
|
|
# Colours to use
|
|
local fg=$WHITE_BRIGHT
|
|
local bg=$BLUE
|
|
|
|
if [[ ! -z "$SSH_CLIENT" ]]; then
|
|
local show_host="y"
|
|
bg=$CYAN
|
|
fi
|
|
|
|
if [ -z "$(id -u "$USER")" ]; then
|
|
bg=$RED
|
|
fi
|
|
|
|
# shellcheck disable=SC2153
|
|
if [[ ! -z "${SHOW_USER+x}" || ( ! -z "${DEFAULT_USER+x}" && "$DEFAULT_USER" != "$(whoami)" ) ]]; then
|
|
local show_user="y"
|
|
fi
|
|
|
|
local text
|
|
if [ ! -z ${show_user+x} ]; then
|
|
text+="$BOLD$(whoami)"
|
|
fi
|
|
if [ ! -z ${show_host+x} ]; then
|
|
if [ ! -z "${text+x}" ]; then
|
|
text+="@"
|
|
fi
|
|
text+="\\h"
|
|
fi
|
|
|
|
if [ ! -z ${text+x} ]; then
|
|
__prompt_block $bg $fg $text
|
|
fi
|
|
}
|
|
|
|
__status_block() {
|
|
local text
|
|
if [ "$exit_code" != 0 ]; then
|
|
__prompt_block $BLACK $RED '✘'
|
|
text+=$__block_text
|
|
fi
|
|
|
|
if [ "$(id -u "$USER")" == 0 ]; then
|
|
__prompt_block $BLACK $YELLOW '⚡'
|
|
text+=$__block_text
|
|
fi
|
|
|
|
if [ "$(jobs -l | wc -l)" != 0 ]; then
|
|
__prompt_block $BLACK $CYAN '⚙'
|
|
text+=$__block_text
|
|
fi
|
|
|
|
if [ ! -z "$text" ]; then
|
|
__block_text=$text
|
|
else
|
|
__block_text=''
|
|
fi
|
|
}
|
|
|
|
# Build the prompt
|
|
prompt() {
|
|
# I don't like bash; execute first to capture correct status code
|
|
local exit_code=$?
|
|
# shellcheck disable=SC2091
|
|
$(history -a ; history -n)
|
|
|
|
last_bg=''
|
|
|
|
PS1=''
|
|
|
|
__status_block
|
|
PS1+=$__block_text
|
|
|
|
__virtualenv_block
|
|
PS1+=$__block_text
|
|
|
|
__user_block
|
|
PS1+=$__block_text
|
|
|
|
__pwd_block
|
|
PS1+=$__block_text
|
|
|
|
__git_block
|
|
PS1+=$__block_text
|
|
|
|
__end_block
|
|
PS1+=$__block_text
|
|
}
|
|
|
|
PROMPT_COMMAND=prompt
|
|
}
|
|
|
|
__powerline
|
|
unset __powerline
|