From 3e8ac63ace07fbc40f490d275e30d1f16cef0334 Mon Sep 17 00:00:00 2001 From: Von Random Date: Sat, 20 Aug 2022 19:07:49 +0300 Subject: [PATCH] zsh: a proper powerline implementation, readable and extensible --- zsh/init.zsh | 7 +- zsh/prompt-powerline-native.zsh | 227 +++++++++++++++++++------------- 2 files changed, 140 insertions(+), 94 deletions(-) diff --git a/zsh/init.zsh b/zsh/init.zsh index 1820460..7befc6a 100644 --- a/zsh/init.zsh +++ b/zsh/init.zsh @@ -9,14 +9,9 @@ confdir=$(dirname $0) conflist=( settings.zsh functions.zsh + prompt-powerline-native.zsh ) -if [[ -x $(whence powerline-go) ]]; then - conflist+=(prompt-powerline-go.zsh) -else - conflist+=(prompt-plain.zsh) -fi - for config in $conflist; do [[ -r $confdir/$config ]] && . $confdir/$config done diff --git a/zsh/prompt-powerline-native.zsh b/zsh/prompt-powerline-native.zsh index 6b1ed40..09d2deb 100644 --- a/zsh/prompt-powerline-native.zsh +++ b/zsh/prompt-powerline-native.zsh @@ -1,67 +1,135 @@ -# Abandon all hope -# -# This is an implementation of powerline with git support. -# The main feature is it being asynchronous: git status is updated in parallel. -# Despite all the hours spent I have on this I am not going to use this atrocity. -# But it's sorta funny to keep this around. -# -# I see now why common powerline prompts are usually not implemented in shell. - prompt_fmtn='[ %%{\e[2;3m%%}%s%%{\e[0m%%} ] ' printf -v PROMPT2 $prompt_fmtn '%_' printf -v PROMPT3 $prompt_fmtn '?#' printf -v PROMPT4 $prompt_fmtn '+%N:%i' prompt_state_file=/tmp/zsh_gitstatus_$$.tmp -precmd.home() { - [[ $PWD =~ ^$HOME ]] && printf '~' +PROMPT= +typeset -A prompt_symbols=( + sep_a $'\ue0b0' + sep_b $'\ue0b1' + ellipsis $'\u2026' + ro $'\u2717' + ssh $'\u2191' + git $'\ue0a0' + git_unstaged '±' + git_staged $'\u2713' + git_untracked '!' + git_unmerged '*' + bang $'\n\U01f525' +) +typeset -A prompt_colors=( + fg 253 + user 53 + ssh 90 + root 52 + host 240 + home 237 + / 237 + dirs 234 + ro 124 + git_branch 237 + git_unstaged 130 + git_staged 25 + git_untracked 88 + git_unmerged 30 +) + +precmd.prompt.clear() { + PROMPT= } -precmd.cwd() { - typeset shifted symbol=$' \ue0b1 ' cwd=${PWD#$HOME} - [[ -z $cwd ]] && return - typeset -a cwd_array=(${(ps:/:)cwd}) - cwd= - while ((${#cwd_array} > 3)); do - shift cwd_array - typeset shifted=1 - done - ((shifted)) && cwd_array=(... $cwd_array) - while ((${#cwd_array})); do - cwd+="$cwd_array[1]$symbol" - shift cwd_array - done - printf ${cwd%$symbol} -} - -precmd.vars() { - typeset user_color=blue - ((UID)) || user_color=red - - typeset -ga prompt_strings=( - %n::$user_color - %m::246 - "$(precmd.home)::242" - "$(precmd.cwd)::245" - ) -} - -precmd.prompt() { - typeset prev_color symbol=$'\ue0b0' last=$'\n\U01f525 ' fg_color=black n=1 limit=${#prompt_strings} - typeset ps1 line val color - ps1= - for ((i=1; i<=limit; i++)); do - line=$prompt_strings[$i] - val=${line%%::*} - color=${line##*::} - if [[ -n $val ]]; then - [[ -z $prev_color ]] && prev_color=$color - ps1+="%F{$prev_color}%K{$color}$symbol%F{$fg_color} $val " - prev_color=$color +precmd.prompt.add() { + typeset string + typeset data=$1 color=$2 + if [[ $color == same ]]; then + PROMPT+="$prompt_symbols[sep_b] $data " + else + if ((${#PROMPT})); then + PROMPT+="%F{$prev_color}%K{$color}$prompt_symbols[sep_a]%F{$prompt_colors[fg]} $data " + else + PROMPT="%K{$color}%F{$prompt_colors[fg]} $data " fi + prev_color=$color + fi +} + +precmd.prompt.bang() { + PROMPT+="%F{$prev_color}%k$prompt_symbols[sep_a]%f$prompt_symbols[bang] " +} + +precmd.prompt.user() { + typeset user_color + ((UID)) && user_color=$prompt_colors[user] || user_color=$prompt_colors[root] + + precmd.prompt.add %n $user_color +} + +precmd.prompt.ssh() { + [[ -n $SSH_CONNECTION ]] && precmd.prompt.add $prompt_symbols[ssh] $prompt_colors[ssh] +} + +precmd.prompt.host() { + precmd.prompt.add %m $prompt_colors[host] +} + +precmd.prompt.cwd() { + typeset cwd limit=${1:-3} + if [[ $PWD =~ ^$HOME ]]; then + precmd.prompt.add \~ $prompt_colors[home] + cwd=${PWD#$HOME} + else + precmd.prompt.add / $prompt_colors[/] + cwd=${PWD:1} + fi + [[ -z $cwd ]] && return + + typeset -a cwd_array=(${(ps:/:)cwd}) + if ((${#cwd_array} > limit)); then + precmd.prompt.add $prompt_symbols[ellipsis] $prompt_colors[dirs] + while ((${#cwd_array} > limit)); do + shift cwd_array + done + else + precmd.prompt.add $cwd_array[1] $prompt_colors[dirs] + shift cwd_array + fi + while ((${#cwd_array})); do + precmd.prompt.add $cwd_array[1] same + shift cwd_array + done +} + +precmd.prompt.ro() { + [[ -w . ]] || precmd.prompt.add $prompt_symbols[ro] $prompt_colors[ro] +} + +precmd.prompt.pre_git() { + precmd.prompt.add "$prompt_symbols[git] $prompt_symbols[ellipsis]" $prompt_colors[git_branch] +} + +precmd.prompt.git() { + typeset raw_status + raw_status=$(flock -n $prompt_state_file git --no-optional-locks status --porcelain -bu 2>/dev/null) || return 0 + + typeset -A count + typeset branch_status git_status_string IFS= + while read line; do + if [[ $line[1,2] == '##' ]]; then + branch_status=${line[4,-1]%%...*} + [[ $line =~ behind ]] && branch_status+=? + [[ $line =~ ahead ]] && branch_status+=! + precmd.prompt.add "$prompt_symbols[git] $branch_status" $prompt_colors[git_branch] + fi + [[ $line[1,2] == '??' ]] && (( count[git_untracked]++ )) + [[ $line[1,2] =~ .[MD] ]] && (( count[git_unstaged]++ )) + [[ $line[1,2] =~ [MDARC]. ]] && (( count[git_staged]++ )) + [[ $line[1,2] =~ [ADU]{2} ]] && (( count[git_unmerged]++ )) + done <<< $raw_status + + for i in git_unstaged git_staged git_untracked git_unmerged; do + (( count[$i] )) && precmd.prompt.add "$count[$i]$prompt_symbols[$i]" $prompt_colors[$i] done - ps1+="%F{$prev_color}%k$symbol%f$last" - echo -n $ps1 } precmd.is_git_repo() { @@ -69,50 +137,33 @@ precmd.is_git_repo() { [[ ! -e $git_dir/nozsh ]] } -precmd.git() { - typeset raw_status - raw_status=$(flock -n $prompt_state_file git --no-optional-locks status --porcelain -bu 2>/dev/null) || return 0 - - typeset symbol=$'\ue0a0' branch_status git_status_string IFS= - typeset staged_count unstaged_count untracked_count unmerged_coun - while read line; do - if [[ $line[1,2] == '##' ]]; then - branch_status=${line[4,-1]%%...*} - [[ $line =~ behind ]] && branch_status+=? - [[ $line =~ ahead ]] && branch_status+=! - prompt_strings+=("$symbol $branch_status::249") - fi - [[ $line[1,2] == '??' ]] && (( untracked_count++ )) - [[ $line[1,2] =~ .[MD] ]] && (( unstaged_count++ )) - [[ $line[1,2] =~ [MDARC]. ]] && (( staged_count++ )) - [[ $line[1,2] =~ [ADU]{2} ]] && (( unmerged_count++ )) - done <<< $raw_status - if ! ((untracked_count || unstaged_count || staged_count || unmerged_count)); then - prompt_strings+=(ok::10) - fi - - (( unstaged_count )) && prompt_strings+=( "~$unstaged_count::11" ) - (( staged_count )) && prompt_strings+=( "+$staged_count::12" ) - (( untracked_count )) && prompt_strings+=( "-$untracked_count::9" ) - (( unmerged_count )) && prompt_strings+=( "*$unmerged_count::14" ) +precmd.prompt() { + precmd.prompt.clear + precmd.prompt.user + precmd.prompt.ssh + precmd.prompt.host + precmd.prompt.cwd 2 + precmd.prompt.ro } precmd.git_update() { - precmd.vars - precmd.git - precmd.prompt > $prompt_state_file + umask 077 + precmd.prompt + precmd.prompt.git + precmd.prompt.bang + > $prompt_state_file <<< $PROMPT kill -s USR1 $$ } precmd() { if precmd.is_git_repo; then - precmd.vars - prompt_strings+=($'\ue0a0 ...'::249) - PROMPT=$(precmd.prompt) + precmd.prompt + precmd.prompt.pre_git + precmd.prompt.bang precmd.git_update &! else - precmd.vars - PROMPT=$(precmd.prompt) + precmd.prompt + precmd.prompt.bang fi }