zsh: rework/simplify prompt

This commit is contained in:
Fernando Schauenburg 2023-10-08 17:33:56 +02:00
parent 2f1f3527c5
commit 216804c368

View file

@ -1,166 +1,170 @@
black=0 red=1 green=2 yellow=3 blue=4 magenta=5 cyan=6 white=7 gray=8 fg_reset='%{%f%}'
fg_black='%{%F{black}%}'
fg_red='%{%F{red}%}'
fg_green='%{%F{green}%}'
fg_yellow='%{%F{yellow}%}'
fg_blue='%{%F{blue}%}'
fg_magenta='%{%F{magenta}%}'
fg_cyan='%{%F{cyan}%}'
fg_white='%{%F{white}%}'
fg_gray='%{%F{8}%}' # %F{...} only supports the 8 basic colors by name
PROMPT_SECTIONS=() PROMPT_SECTIONS=()
render() { # $1: optional color, $2...: contents
if [[ -n $2 ]] {
PROMPT_SECTIONS+="%{%F{$1}%}${@[2,-1]}%{%f%}"
} else {
PROMPT_SECTIONS+="$1"
}
}
render_prompt() { render_prompt() {
setopt localoptions shortloops setopt localoptions shortloops
local separator="%{%F{$gray}%} %{%f%}"
PROMPT_SECTIONS=() PROMPT_SECTIONS=()
for s in exit_code user_host pwd git venv jobs exec_time; { render_$s } render_exit_code
render_user_host
render_pwd
render_git
render_venv
render_jobs
render_exec_time
echo ${(pj.$separator.)PROMPT_SECTIONS} local separator="${fg_gray} "
echo -n "%{%F{$gray}%}" echo ${(pj.$separator.)PROMPT_SECTIONS}
printf '%.0s' {1..$SHLVL} echo -n "${fg_gray}"
echo -n "%{%f%} " printf '%.0s' {1..$SHLVL}
echo -n "${fg_reset} "
} }
render_exit_code() { render_exit_code() {
if (($PROMPT_EXIT_CODE != 0)) { if ((PROMPT_EXIT_CODE != 0)); then
if ((PROMPT_EXIT_CODE > 128 && PROMPT_EXIT_CODE < 160 )) { if ((PROMPT_EXIT_CODE > 128 && PROMPT_EXIT_CODE < 160)); then
render $white $(kill -l $PROMPT_EXIT_CODE) PROMPT_SECTIONS+="${fg_white}$(kill -l $PROMPT_EXIT_CODE)"
} else { else
render $red "%{%B%}✘ $PROMPT_EXIT_CODE%{%b%}" PROMPT_SECTIONS+="${fg_red}$PROMPT_EXIT_CODE"
} fi
} fi
} }
render_user_host() { render_user_host() {
local sep="%{%F{$gray}%}@%{%f%}" local parts=()
local parts=()
# username in red if root, yellow if otherwise relevant # username in red if root, yellow if otherwise relevant
if [[ $UID == 0 ]] { if [[ $UID == 0 ]]; then
parts+="%{%F{$red}%}%n%{%f%}" parts+="${fg_red}%n"
} elif [[ $LOGNAME != $USER ]] || [[ -n $SSH_CONNECTION ]] { elif [[ $LOGNAME != $USER ]] || [[ -n $SSH_CONNECTION ]]; then
parts+="%{%F{$yellow}%}%n%{%f%}" parts+="${fg_yellow}%n"
} fi
# hostname in yellow if relevant # hostname in yellow if relevant
[[ -n $SSH_CONNECTION ]] && parts+="%{%F{$yellow}%}%m%{%f%}" [[ -n $SSH_CONNECTION ]] && parts+="${fg_yellow}%m"
(($#parts)) && render "${(pj:$sep:)parts}" (($#parts)) && {
local separator="${fg_gray}@"
PROMPT_SECTIONS+="${(pj:$separator:)parts}"
}
} }
render_pwd() { render_pwd() {
render $cyan '%~' # TODO add RO if now write permission PROMPT_SECTIONS+="${fg_cyan}%~${fg_reset}"
} }
# TODO add stash?
# TODO add action?
render_git() { render_git() {
local gitstatus # local swallows git's exit code if not on its own line local gitstatus # local swallows git's exit code if not on its own line
gitstatus=$(command git status --porcelain -b 2>/dev/null) || return gitstatus=$(command git status --porcelain -b 2>/dev/null) || return
# Sort through the status of files. # Sort through the status of files.
local untracked=0 dirty=0 staged=0 conflicts=0 branch_line='' local untracked=0 dirty=0 staged=0 conflicts=0 branch_line=''
{ {
while IFS='' read -r line || [[ -n $line ]] { while IFS='' read -r line; do
case $line in case $line in
\#\#*) branch_line=$line;; \#\#*) branch_line=$line;;
\?\?*) ((untracked++));; \?\?*) ((untracked++));;
AA*|DD*|U?*|?U*) ((conflicts++));; AA*|DD*|U?*|?U*) ((conflicts++));;
*) *)
[[ ${line:0:1} =~ '[MADRC]' ]] && ((staged++)) [[ ${line:0:1} =~ '[MADRC]' ]] && ((staged++))
[[ ${line:1:1} =~ '[MADRC]' ]] && ((dirty++)) [[ ${line:1:1} =~ '[MADRC]' ]] && ((dirty++))
;; ;;
esac esac
} <<<$gitstatus done <<<$gitstatus
} }
# Find out branch and upstream. # Find out branch and upstream.
local branch='' upstream='' ahead=0 behind=0 local branch='' upstream='' ahead=0 behind=0
{ {
local fields=(${(s:...:)${branch_line#\#\# }}) local fields=(${(s:...:)${branch_line#\#\# }})
branch=$fields[1] branch=$fields[1]
local tracking=$fields[2] local tracking=$fields[2]
if [[ $branch == *'Initial commit on'* ]] \ if [[ $branch == *'Initial commit on'* ]] \
|| [[ $branch == *'No commits yet on'* ]] { || [[ $branch == *'No commits yet on'* ]]; then
# Branch name is last word in these possible branch lines: # Branch name is last word in these possible branch lines:
# ## Initial commit on <branch> # ## Initial commit on <branch>
# ## No commits yet on <branch> # ## No commits yet on <branch>
branch=(${${(s: :)branch}[-1]}) branch=${${(s: :)branch}[-1]}
} elif [[ $branch == *'no branch'* ]] { elif [[ $branch == *'no branch'* ]]; then
# Dettached HEAD (also if a tag is checked out), branch line: # Dettached HEAD (also if a tag is checked out), branch line:
# ## HEAD (no branch) # ## HEAD (no branch)
local tag=$(git describe --tags --exact-match 2>/dev/null) local tag=$(command git describe --tags --exact-match HEAD 2>/dev/null)
if [[ -n $tag ]] { if [[ -n $tag ]]; then
branch="$tag" branch="$tag"
} else { else
branch="#$(git rev-parse --short HEAD)" tag=$(command git describe --tags --long HEAD 2>/dev/null)
} if [[ -n $tag ]]; then
branch="$tag"
else
branch="#$(command git rev-parse --short HEAD 2>/dev/null)"
fi
fi
} elif (($#fields > 1)) { elif (($#fields > 1)); then
# There is a tracking branch. Possibilites: # There is a tracking branch. Possibilites:
# ## <branch>...<upstream> # ## <branch>...<upstream>
# ## <branch>...<upstream> [ahead 1] # ## <branch>...<upstream> [ahead 1]
# ## <branch>...<upstream> [behind 1] # ## <branch>...<upstream> [behind 1]
# ## <branch>...<upstream> [ahead 1, behind 1] # ## <branch>...<upstream> [ahead 1, behind 1]
tracking=(${(s: [:)${tracking%]}}) tracking=(${(s: [:)${tracking%]}})
upstream=$tracking[1] upstream=$tracking[1]
if (($#tracking > 1)) { if (($#tracking > 1)); then
for e in ${(s:, :)tracking[2]}; { for e in ${(s:, :)tracking[2]}; do
if [[ $e == 'ahead '* ]] { ahead=${e:6} } [[ $e == 'ahead '* ]] && ahead=${e:6}
if [[ $e == 'behind '* ]] { behind=${e:7} } [[ $e == 'behind '* ]] && behind=${e:7}
} done
} fi
} fi
} }
local color=$blue trackinfo='' state='' local track_parts=()
{ (($ahead > 0 )) && track_parts+="${fg_blue}${ahead}"
local attention=() (($behind > 0 )) && track_parts+="${fg_cyan}${behind}"
(($conflicts > 0 )) && attention+="%{%B%F{$red}%}$conflicts!%{%b%f%}"
(($untracked > 0 )) && attention+="%{%F{$white}%}$untracked?%{%f%}"
local changes=() local state_parts=()
(($staged > 0)) && changes+="%{%F{$green}%}+$staged%{%f%}" (($staged > 0)) && state_parts+="${fg_green}+${staged}"
(($dirty > 0 )) && changes+="%{%F{$red}%}$dirty✶%{%f%}" (($dirty > 0 )) && state_parts+="${fg_red}${dirty}"
if (($#attention > 0 || $#changes > 0)) { local separator="${fg_gray}"
local sep="%{%F{$gray}%}|%{%f%}" local gitinfo=("${fg_blue}${branch}")
local state_array=(${(j: :)attention} ${(pj:$sep:)changes}) (($#track_parts > 0)) && gitinfo+="${(pj:$separator:)track_parts}"
state=${(j: :)state_array} (($conflicts > 0 )) && gitinfo+="${fg_red}${conflicts}"
} (($untracked > 0 )) && gitinfo+="${fg_white}${untracked}?"
(($#state_parts)) && gitinfo+="${(pj:$separator:)state_parts}"
local extra=() PROMPT_SECTIONS+="${(j: :)gitinfo}"
(($ahead > 0 )) && extra+="$ahead"
(($behind > 0 )) && extra+="%{%F{$cyan}%}↓$behind%{%f%}"
(($#extra > 0)) && trackinfo="(${(j::)extra}%{%F{$color}%})%{%f%}"
}
local gitinfo=("$branch$trackinfo" $state)
render $color ${(j: :)gitinfo}
} }
render_venv() { render_venv() {
[[ -n $VIRTUAL_ENV ]] && render $green $VIRTUAL_ENV:t # venv if active [[ -n "$VIRTUAL_ENV" ]] && PROMPT_SECTIONS+="${fg_green}${VIRTUAL_ENV:t}"
} }
render_jobs() { render_jobs() {
(($PROMPT_JOB_COUNT > 0)) && render $magenta '%j bg' # background jobs if any (($PROMPT_JOB_COUNT > 0)) && PROMPT_SECTIONS+="${fg_magenta}%j bg"
} }
render_exec_time() { render_exec_time() {
(($PROMPT_EXEC_TIME > 5)) && { # last command execution time if over 5s (($PROMPT_EXEC_TIME > 3)) && { # last command execution time if over 3s
local components=( local parts=(
"$((PROMPT_EXEC_TIME / 60 / 60 / 24))d" # days "$((PROMPT_EXEC_TIME / 60 / 60 / 24))d" # days
"$((PROMPT_EXEC_TIME / 60 / 60 % 24))h" # hours "$((PROMPT_EXEC_TIME / 60 / 60 % 24))h" # hours
"$((PROMPT_EXEC_TIME / 60 % 60))m" # minutes "$((PROMPT_EXEC_TIME / 60 % 60))m" # minutes
"$((PROMPT_EXEC_TIME % 60))s" # seconds "$((PROMPT_EXEC_TIME % 60))s" # seconds
) )
render $gray ${components:#0*} # only keep non-zero parts PROMPT_SECTIONS+=${fg_gray}${parts:#0*} # only keep non-zero parts
} }
} }
# Hook triggered when a command is about to be executed. # Hook triggered when a command is about to be executed.
@ -195,3 +199,4 @@ prompt_setup() {
} }
prompt_setup prompt_setup