From eff5a3d0b14c58f7ed5d0a4762665e5fde812d55 Mon Sep 17 00:00:00 2001 From: Fernando Schauenburg Date: Mon, 29 Jul 2024 19:46:35 +0200 Subject: [PATCH] zsh: avoid global array of colors in prompt --- config/zsh/prompt.zsh | 145 +++++++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/config/zsh/prompt.zsh b/config/zsh/prompt.zsh index df48410..c6d8df3 100644 --- a/config/zsh/prompt.zsh +++ b/config/zsh/prompt.zsh @@ -1,19 +1,6 @@ -typeset -gA zfg=( - [reset]='%{%f%}' - - [black]='%{%F{black}%}' - [red]='%{%F{red}%}' - [green]='%{%F{green}%}' - [yellow]='%{%F{yellow}%}' - [blue]='%{%F{blue}%}' - [magenta]='%{%F{magenta}%}' - [cyan]='%{%F{cyan}%}' - [white]='%{%F{white}%}' - - # %F{...} only supports the 8 basic colors by name - [gray]='%{%F{8}%}' - [bright]='%{%F{15}%}' - [faded]='%{%F{240}%}' +typeset -gA _fs_prompt=( + [is_transient]=true + [transient_color]='%{%F{magenta}%}' ) typeset -gA icons=( @@ -28,69 +15,101 @@ typeset -gA icons=( ) fs_prompt_render_full() { - local separator="${zfg[faded]} ❯ " + local black='%{%F{0}%}' red='%{%F{1}%}' green='%{%F{2}%}' yellow='%{%F{3}%}' \ + blue='%{%F{4}%}' magenta='%{%F{5}%}' cyan='%{%F{6}%}' white='%{%F{7}%}' \ + gray='%{%F{8}%}' orange='%{%F{166}%}' faded='%{%F{240}%}' \ + reset='%{%f%}' + + local sep_color="$faded" signal_color="$white" error_color="$red" reset="$reset" + local sections=( - "$(fs_prompt_exit_code)" - "$(fs_prompt_user_and_hostname)" - "$(fs_prompt_pwd)" - "$(fs_prompt_git)" - "$(fs_prompt_virtualenv)" - "$(fs_prompt_jobs)" - "$(fs_prompt_exec_time)" + "$(fs_prompt_exit_code $error_color $signal_color)" + # root user separator + "$(fs_prompt_user_and_hostname $red $yellow $faded)" + "$(fs_prompt_pwd $cyan)" + # branch ahead behind untracked staged dirty conflict separator + "$(fs_prompt_git $blue $blue $cyan $white $green $orange $red $gray)" + "$(fs_prompt_virtualenv $yellow)" + "$(fs_prompt_jobs $magenta)" + "$(fs_prompt_exec_time $gray)" ) - echo "\n${(@pj.$separator.)sections:#}${zfg[reset]}" + local sep="$sep_color ❯ " + echo "\n${(@pj:$sep:)sections:#}$reset" if ((PROMPT_EXIT_CODE == 0)); then - echo -n "${zfg[faded]}" + echo -n "$sep_color" elif ((PROMPT_EXIT_CODE > 128 && PROMPT_EXIT_CODE < 160)); then - echo -n "${zfg[white]}" + echo -n "$signal_color" else - echo -n "${zfg[red]}" + echo -n "$error_color" fi (($SHLVL > 1)) && printf '󰅂%.0s' {2..$SHLVL} - echo -n "❯ ${zfg[reset]}" + echo -n "❯ $reset" } fs_prompt_render_compact() { - echo -n "${zfg[magenta]}❯ ${zfg[reset]}" + echo -n "${_fs_prompt[transient_color]}❯ %{%f%}" } fs_prompt_exit_code() { ((PROMPT_EXIT_CODE == 0)) && return + local error_color="$1" signal_color="$2" bold="%{%B%}" no_bold="%{%b%}" + if ((PROMPT_EXIT_CODE > 128 && PROMPT_EXIT_CODE < 160)); then - print "%{%B%}${zfg[white]}$(kill -l $PROMPT_EXIT_CODE)%{%b%}" + print "${bold}${signal_color}$(kill -l $PROMPT_EXIT_CODE)${no_bold}" else - print "%{%B%}${zfg[red]} $PROMPT_EXIT_CODE%{%b%}" + print "${bold}${error_color} ${PROMPT_EXIT_CODE}${no_bold}" fi } fs_prompt_user_and_hostname() { + local root_color="$1" user_color="$2" at_color="${3:-$2}" local parts=() # username in red if root, yellow if otherwise relevant if [[ $UID == 0 ]]; then - parts+="${zfg[red]}${icons[user]}%n" + parts+="${root_color}${icons[user]}%n" elif [[ $LOGNAME != $USER ]] || [[ -n $SSH_CONNECTION ]]; then - parts+="${zfg[yellow]}${icons[user]}%n" + parts+="${user_color}${icons[user]}%n" fi # hostname in yellow if relevant - [[ -n $SSH_CONNECTION ]] && parts+="${zfg[yellow]}%m" + [[ -n $SSH_CONNECTION ]] && parts+="${user_color}%m" (($#parts)) && { - local separator="${zfg[gray]}@" - print "${(pj:$separator:)parts}" + local at="${at_color}@" + print "${(pj:$at:)parts}" } } fs_prompt_pwd() { - print "${zfg[cyan]}${icons[folder]}%~" + local color="$1" + print "${color}${icons[folder]}%~" } fs_prompt_git() { + local branch_color="$1" + local ahead_color="$2" + local behind_color="$3" + local untracked_color="$4" + local staged_color="$5" + local dirty_color="$6" + local conflict_color="$7" + local sep_color="$8" + 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 + +gitstatus="## some-longer-name...origin/tracking [ahead 5, behind 12] +M staged_1 +M staged_2 + M dirty_1 + M dirty_2 + M dirty_3 +DD conflict +?? untracked +" # Sort through the status of files. local untracked=0 dirty=0 staged=0 conflicts=0 branch_line='' @@ -150,42 +169,50 @@ fs_prompt_git() { fi } - local track_parts=() - (($ahead > 0 )) && track_parts+="${zfg[blue]}↑${ahead}" - (($behind > 0 )) && track_parts+="${zfg[cyan]}${behind}↓" + local result=() + { + local sep="${sep_color}" + local tracking=() + (($ahead)) && tracking+="${ahead_color}${ahead}" # ↑ + (($behind)) && tracking+="${behind_color}${behind}" # ↓ + (($#tracking)) && result+="${(pj:$sep:)tracking} " - local state_parts=() - (($staged > 0)) && state_parts+="${zfg[green]}+${staged}" - (($dirty > 0 )) && state_parts+="${zfg[red]}${dirty}✶" + (($conflicts)) && result+="${conflict_color} %{%B%}${conflicts}%{%b%}" - local separator="${zfg[gray]}" - local gitinfo=("${zfg[blue]}${branch}") - (($#track_parts > 0)) && gitinfo+="${(pj:$separator:)track_parts}" - (($conflicts > 0 )) && gitinfo+="${zfg[red]}${conflicts} " - (($untracked > 0 )) && gitinfo+="${zfg[white]}${untracked}?" - (($#state_parts)) && gitinfo+="${(pj:$separator:)state_parts}" + local state=() + (($staged)) && state+="${staged_color}+${staged}" + (($dirty)) && state+="${dirty_color}${dirty}✶" + (($#state)) && result+="${(pj:$sep:)state}" - print "${(j: :)gitinfo}" + (($untracked)) && result+="${untracked_color}${untracked}?" + } + print "${branch_color}${branch} ${(j: :)result}" } fs_prompt_virtualenv() { - [[ -n "$VIRTUAL_ENV" ]] && print "${zfg[green]}${icons[python]}${VIRTUAL_ENV:t}" + [[ -n "$VIRTUAL_ENV" ]] || return + local color="$1" + print "${color}${icons[python]}${VIRTUAL_ENV:t}" } fs_prompt_jobs() { - (($PROMPT_JOB_COUNT > 0)) && print "${zfg[magenta]}${icons[background]}%j" + (($PROMPT_JOB_COUNT)) || return + local color="$1" + print "${color}${icons[background]}%j" } fs_prompt_exec_time() { (($PROMPT_EXEC_TIME <= 3)) && return # don't print time if under 3s + local color="$1" 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 ) - print ${zfg[gray]}${icons[clock]}${parts:#0*} # only keep non-zero parts + local exec_time="${(@)parts:#0*}" # only keep non-zero parts + [[ -n "$exec_time" ]] && print "${color}${icons[clock]}${exec_time}" } # Hook triggered when a command is about to be executed. @@ -224,12 +251,12 @@ fs_setup_prompt() { add-zsh-hook preexec fs_prompt_preexec add-zsh-hook precmd fs_prompt_precmd - # Change to false to disable transient prompt. - local use_transient_prompt=true - if $use_transient_prompt; then + if ${_fs_prompt[is_transient]}; then autoload -Uz add-zle-hook-widget add-zle-hook-widget line-finish fs_prompt_zle_line_finish - PS2="${zfg[magenta]}%_❯${zfg[reset]} " + PS2="${_fs_prompt[transient_color]}%_❯%{%f%} " + else + add-zle-hook-widget -d line-finish fs_prompt_zle_line_finish fi }