From 216804c36804ccdd7bec0ef78e7cb89c49129a9c Mon Sep 17 00:00:00 2001 From: Fernando Schauenburg Date: Sun, 8 Oct 2023 17:33:56 +0200 Subject: [PATCH] zsh: rework/simplify prompt --- config/zsh/prompt.zsh | 253 +++++++++++++++++++++--------------------- 1 file changed, 129 insertions(+), 124 deletions(-) diff --git a/config/zsh/prompt.zsh b/config/zsh/prompt.zsh index e2bc7fd..170f2ac 100644 --- a/config/zsh/prompt.zsh +++ b/config/zsh/prompt.zsh @@ -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=() -render() { # $1: optional color, $2...: contents - if [[ -n $2 ]] { - PROMPT_SECTIONS+="%{%F{$1}%}${@[2,-1]}%{%f%}" - } else { - PROMPT_SECTIONS+="$1" - } -} - render_prompt() { - setopt localoptions shortloops - local separator="%{%F{$gray}%} ❯ %{%f%}" + setopt localoptions shortloops - PROMPT_SECTIONS=() - for s in exit_code user_host pwd git venv jobs exec_time; { render_$s } + PROMPT_SECTIONS=() + render_exit_code + render_user_host + render_pwd + render_git + render_venv + render_jobs + render_exec_time - echo ${(pj.$separator.)PROMPT_SECTIONS} - echo -n "%{%F{$gray}%}" - printf '❯%.0s' {1..$SHLVL} - echo -n "%{%f%} " + local separator="${fg_gray} ❯ " + echo ${(pj.$separator.)PROMPT_SECTIONS} + echo -n "${fg_gray}" + printf '❯%.0s' {1..$SHLVL} + echo -n "${fg_reset} " } render_exit_code() { - if (($PROMPT_EXIT_CODE != 0)) { - if ((PROMPT_EXIT_CODE > 128 && PROMPT_EXIT_CODE < 160 )) { - render $white $(kill -l $PROMPT_EXIT_CODE) - } else { - render $red "%{%B%}✘ $PROMPT_EXIT_CODE%{%b%}" - } - } + if ((PROMPT_EXIT_CODE != 0)); then + if ((PROMPT_EXIT_CODE > 128 && PROMPT_EXIT_CODE < 160)); then + PROMPT_SECTIONS+="${fg_white}$(kill -l $PROMPT_EXIT_CODE)" + else + PROMPT_SECTIONS+="${fg_red}✘ $PROMPT_EXIT_CODE" + fi + fi } render_user_host() { - local sep="%{%F{$gray}%}@%{%f%}" - local parts=() + local parts=() - # username in red if root, yellow if otherwise relevant - if [[ $UID == 0 ]] { - parts+="%{%F{$red}%}%n%{%f%}" - } elif [[ $LOGNAME != $USER ]] || [[ -n $SSH_CONNECTION ]] { - parts+="%{%F{$yellow}%}%n%{%f%}" - } + # username in red if root, yellow if otherwise relevant + if [[ $UID == 0 ]]; then + parts+="${fg_red}%n" + elif [[ $LOGNAME != $USER ]] || [[ -n $SSH_CONNECTION ]]; then + parts+="${fg_yellow}%n" + fi - # hostname in yellow if relevant - [[ -n $SSH_CONNECTION ]] && parts+="%{%F{$yellow}%}%m%{%f%}" + # hostname in yellow if relevant + [[ -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 $cyan '%~' # TODO add RO if now write permission + PROMPT_SECTIONS+="${fg_cyan}%~${fg_reset}" } -# TODO add stash? -# TODO add action? render_git() { - local gitstatus # local swallows git's exit code if not on its own line - gitstatus=$(command git status --porcelain -b 2>/dev/null) || return + local gitstatus # local swallows git's exit code if not on its own line + gitstatus=$(command git status --porcelain -b 2>/dev/null) || return - # Sort through the status of files. - local untracked=0 dirty=0 staged=0 conflicts=0 branch_line='' - { - while IFS='' read -r line || [[ -n $line ]] { - case $line in - \#\#*) branch_line=$line;; - \?\?*) ((untracked++));; - AA*|DD*|U?*|?U*) ((conflicts++));; - *) - [[ ${line:0:1} =~ '[MADRC]' ]] && ((staged++)) - [[ ${line:1:1} =~ '[MADRC]' ]] && ((dirty++)) - ;; - esac - } <<<$gitstatus - } + # Sort through the status of files. + local untracked=0 dirty=0 staged=0 conflicts=0 branch_line='' + { + while IFS='' read -r line; do + case $line in + \#\#*) branch_line=$line;; + \?\?*) ((untracked++));; + AA*|DD*|U?*|?U*) ((conflicts++));; + *) + [[ ${line:0:1} =~ '[MADRC]' ]] && ((staged++)) + [[ ${line:1:1} =~ '[MADRC]' ]] && ((dirty++)) + ;; + esac + done <<<$gitstatus + } - # Find out branch and upstream. - local branch='' upstream='' ahead=0 behind=0 - { - local fields=(${(s:...:)${branch_line#\#\# }}) - branch=$fields[1] - local tracking=$fields[2] + # Find out branch and upstream. + local branch='' upstream='' ahead=0 behind=0 + { + local fields=(${(s:...:)${branch_line#\#\# }}) + branch=$fields[1] + local tracking=$fields[2] - if [[ $branch == *'Initial commit on'* ]] \ - || [[ $branch == *'No commits yet on'* ]] { - # Branch name is last word in these possible branch lines: - # ## Initial commit on - # ## No commits yet on - branch=(${${(s: :)branch}[-1]}) + if [[ $branch == *'Initial commit on'* ]] \ + || [[ $branch == *'No commits yet on'* ]]; then + # Branch name is last word in these possible branch lines: + # ## Initial commit on + # ## No commits yet on + branch=${${(s: :)branch}[-1]} - } elif [[ $branch == *'no branch'* ]] { - # Dettached HEAD (also if a tag is checked out), branch line: - # ## HEAD (no branch) - local tag=$(git describe --tags --exact-match 2>/dev/null) - if [[ -n $tag ]] { - branch="⚑$tag" - } else { - branch="#$(git rev-parse --short HEAD)" - } + elif [[ $branch == *'no branch'* ]]; then + # Dettached HEAD (also if a tag is checked out), branch line: + # ## HEAD (no branch) + local tag=$(command git describe --tags --exact-match HEAD 2>/dev/null) + if [[ -n $tag ]]; then + branch="⚑$tag" + else + 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)) { - # There is a tracking branch. Possibilites: - # ## ... - # ## ... [ahead 1] - # ## ... [behind 1] - # ## ... [ahead 1, behind 1] - tracking=(${(s: [:)${tracking%]}}) - upstream=$tracking[1] - if (($#tracking > 1)) { - for e in ${(s:, :)tracking[2]}; { - if [[ $e == 'ahead '* ]] { ahead=${e:6} } - if [[ $e == 'behind '* ]] { behind=${e:7} } - } - } - } - } + elif (($#fields > 1)); then + # There is a tracking branch. Possibilites: + # ## ... + # ## ... [ahead 1] + # ## ... [behind 1] + # ## ... [ahead 1, behind 1] + tracking=(${(s: [:)${tracking%]}}) + upstream=$tracking[1] + if (($#tracking > 1)); then + for e in ${(s:, :)tracking[2]}; do + [[ $e == 'ahead '* ]] && ahead=${e:6} + [[ $e == 'behind '* ]] && behind=${e:7} + done + fi + fi + } - local color=$blue trackinfo='' state='' - { - local attention=() - (($conflicts > 0 )) && attention+="%{%B%F{$red}%}$conflicts!%{%b%f%}" - (($untracked > 0 )) && attention+="%{%F{$white}%}$untracked?%{%f%}" + local track_parts=() + (($ahead > 0 )) && track_parts+="${fg_blue}↑${ahead}" + (($behind > 0 )) && track_parts+="${fg_cyan}${behind}↓" - local changes=() - (($staged > 0)) && changes+="%{%F{$green}%}+$staged%{%f%}" - (($dirty > 0 )) && changes+="%{%F{$red}%}$dirty✶%{%f%}" + local state_parts=() + (($staged > 0)) && state_parts+="${fg_green}+${staged}" + (($dirty > 0 )) && state_parts+="${fg_red}${dirty}✶" - if (($#attention > 0 || $#changes > 0)) { - local sep="%{%F{$gray}%}|%{%f%}" - local state_array=(${(j: :)attention} ${(pj:$sep:)changes}) - state=${(j: :)state_array} - } + local separator="${fg_gray}" + local gitinfo=("${fg_blue}${branch}") + (($#track_parts > 0)) && gitinfo+="${(pj:$separator:)track_parts}" + (($conflicts > 0 )) && gitinfo+="${fg_red}${conflicts} " + (($untracked > 0 )) && gitinfo+="${fg_white}${untracked}?" + (($#state_parts)) && gitinfo+="${(pj:$separator:)state_parts}" - local extra=() - (($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} + PROMPT_SECTIONS+="${(j: :)gitinfo}" } 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() { - (($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() { - (($PROMPT_EXEC_TIME > 5)) && { # last command execution time if over 5s - local components=( - "$((PROMPT_EXEC_TIME / 60 / 60 / 24))d" # days - "$((PROMPT_EXEC_TIME / 60 / 60 % 24))h" # hours - "$((PROMPT_EXEC_TIME / 60 % 60))m" # minutes - "$((PROMPT_EXEC_TIME % 60))s" # seconds - ) - render $gray ${components:#0*} # only keep non-zero parts - } + (($PROMPT_EXEC_TIME > 3)) && { # last command execution time if over 3s + local parts=( + "$((PROMPT_EXEC_TIME / 60 / 60 / 24))d" # days + "$((PROMPT_EXEC_TIME / 60 / 60 % 24))h" # hours + "$((PROMPT_EXEC_TIME / 60 % 60))m" # minutes + "$((PROMPT_EXEC_TIME % 60))s" # seconds + ) + PROMPT_SECTIONS+=${fg_gray}${parts:#0*} # only keep non-zero parts + } } # Hook triggered when a command is about to be executed. @@ -195,3 +199,4 @@ prompt_setup() { } prompt_setup +