diff options
Diffstat (limited to 'powerlevel9k.zsh-theme')
-rwxr-xr-x | powerlevel9k.zsh-theme | 1068 |
1 files changed, 610 insertions, 458 deletions
diff --git a/powerlevel9k.zsh-theme b/powerlevel9k.zsh-theme index 4939cb35..f3c8a6b5 100755 --- a/powerlevel9k.zsh-theme +++ b/powerlevel9k.zsh-theme @@ -1,19 +1,18 @@ # vim:ft=zsh ts=2 sw=2 sts=2 et fenc=utf-8 ################################################################ -# powerlevel9k Theme +# powerlevel10k Theme +# https://github.com/romkatv/powerlevel10k +# +# Forked from powerlevel9k Theme # https://github.com/bhilburn/powerlevel9k # -# This theme was inspired by agnoster's Theme: +# Which was inspired by agnoster's Theme: # https://gist.github.com/3712874 ################################################################ ################################################################ -# For basic documentation, please refer to the README.md in the top-level -# directory. For more detailed documentation, refer to the project wiki, hosted -# on Github: https://github.com/bhilburn/powerlevel9k/wiki -# -# There are a lot of easy ways you can customize your prompt segments and -# theming with simple variables defined in your `~/.zshrc`. +# For basic documentation, please refer to the README.md in the +# top-level directory. ################################################################ ## Turn on for Debugging @@ -69,6 +68,9 @@ source "${p9k_directory}/functions/vcs.zsh" # cleanup temporary variables. unset p9k_directory +[[ -v POWERLEVEL9K_LEFT_PROMPT_ELEMENTS ]] || POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(context dir vcs) +[[ -v POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS ]] || POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status root_indicator background_jobs history time) + ################################################################ # Color Scheme ################################################################ @@ -94,9 +96,102 @@ fi ################################################################ # The `CURRENT_BG` variable is used to remember what the last BG color used was -# when building the left-hand prompt. Because the RPROMPT is created from -# right-left but reads the opposite, this isn't necessary for the other side. -CURRENT_BG='NONE' +# when building a prompt segment. +CURRENT_BG=NONE +LAST_SEGMENT_INDEX=0 + +# Specifies the maximum number of elements in the cache. When the cache grows over this limit, +# it gets cleared. This is meant to avoid memory leaks when a rogue prompt is filling the cache +# with data. +set_default POWERLEVEL9K_MAX_CACHE_SIZE 10000 + +# Functions producing left and right prompts are called from subshells, so any +# changes to the environment variables they do get wiped out after the prompt is +# printed. In order to cache the results of expensive computations in these functions, +# we use a temporary file to communicate with the parent shell and to ask it to +# change environment variables. +typeset -AH _p9k_cache_data=() +typeset -H _P9K_CACHE_CHANNEL=${$(mktemp -u)%/*}/p9k_cache_channel.$$ + +# Note: Several performance-critical functions return results to the caller via global +# variable _P9K_RETVAL rather than stdout. This is faster. + +# Store a key-value pair in the cache. +# +# * $1: Key. +# * $2: Value. Can be empty. +# +# Note that an attempt to retrieve the value right away won't succeed. All requested +# cache update get batched and flushed together after a prompt is built. +_p9k_cache_set() { + # Uncomment to see cache misses. + # echo "cache: ${(qq)1} => ${(qq)2}" >&2 + echo -E "_p9k_cache_data+=(${(qq)1} ${(qq)2})" >>$_P9K_CACHE_CHANNEL + _P9K_RETVAL=$2 +} + +# Retrieve a value from the cache. +# +# * $1: Key. +# +# If there is value associated with the specified key, sets _P9K_RETVAL and returns 0. +# Otherwise returns 1. +_p9k_cache_get() { + _P9K_RETVAL=${_p9k_cache_data[$1]-__p9k_empty__} + [[ $_P9K_RETVAL != __p9k_empty__ ]] +} + +# Sets _P9K_RETVAL to icon whose name is supplied via $1. +_p9k_get_icon() { + local var_name=POWERLEVEL9K_$1 + _P9K_RETVAL=${${(P)var_name}-$icons[$1]} +} + +typeset -aH _p9k_left_join=(1) +for ((i = 2; i <= $#POWERLEVEL9K_LEFT_PROMPT_ELEMENTS; ++i)); do + elem=$POWERLEVEL9K_LEFT_PROMPT_ELEMENTS[$i] + if [[ ${elem[-7,-1]} == '_joined' ]]; then + _p9k_left_join+=$_p9k_left_join[((i-1))] + else + _p9k_left_join+=$i + fi +done + +typeset -aH _p9k_right_join=(1) +for ((i = 2; i <= $#POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS; ++i)); do + elem=$POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS[$i] + if [[ ${elem[-7,-1]} == '_joined' ]]; then + _p9k_right_join+=$_p9k_right_join[((i-1))] + else + _p9k_right_join+=$i + fi +done +unset elem + +_p9k_should_join_left() [[ $LAST_SEGMENT_INDEX -ge ${_p9k_left_join[$1]:-$1} ]] +_p9k_should_join_right() [[ $LAST_SEGMENT_INDEX -ge ${_p9k_right_join[$1]:-$1} ]] + +# Resolves a color to its numerical value, or an empty string. Communicates the result back +# by setting _P9K_RETVAL. +_p9k_color() { + local user_var=POWERLEVEL9K_${(U)${2}#prompt_}_${3} + local color=${${(P)user_var}:-${1}} + # Check if given value is already numerical. + if [[ $color == <-> ]]; then + _P9K_RETVAL=${(l:3::0:)color} + else + # Strip prifices if there are any. + _P9K_RETVAL=$__P9K_COLORS[${${${color#bg-}#fg-}#br}] + fi +} + +_p9k_background() { + [[ -n $1 ]] && _P9K_RETVAL="%K{$1}" || _P9K_RETVAL="%k" +} + +_p9k_foreground() { + [[ -n $1 ]] && _P9K_RETVAL="%F{$1}" || _P9K_RETVAL="%f" +} # Begin a left prompt segment # Takes four arguments: @@ -108,97 +203,87 @@ CURRENT_BG='NONE' # * $5: The segment content # * $6: An identifying icon (must be a key of the icons array) # The latter three can be omitted, -set_default last_left_element_index 1 set_default POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS " " left_prompt_segment() { - local segment_name="${1}" - local current_index=$2 - # Check if the segment should be joined with the previous one - local joined - segmentShouldBeJoined $current_index $last_left_element_index "$POWERLEVEL9K_LEFT_PROMPT_ELEMENTS" && joined=true || joined=false - - # Colors - local backgroundColor="${3}" - local foregroundColor="${4}" - - # Overwrite given background-color by user defined variable for this segment. - local BACKGROUND_USER_VARIABLE=POWERLEVEL9K_${(U)${segment_name}#prompt_}_BACKGROUND - local BG_COLOR_MODIFIER=${(P)BACKGROUND_USER_VARIABLE} - [[ -n $BG_COLOR_MODIFIER ]] && backgroundColor="$BG_COLOR_MODIFIER" - - # Overwrite given foreground-color by user defined variable for this segment. - local FOREGROUND_USER_VARIABLE=POWERLEVEL9K_${(U)${segment_name}#prompt_}_FOREGROUND - local FG_COLOR_MODIFIER=${(P)FOREGROUND_USER_VARIABLE} - [[ -n $FG_COLOR_MODIFIER ]] && foregroundColor="$FG_COLOR_MODIFIER" - - # Get color codes here to save some calls later on - backgroundColor="$(getColorCode ${backgroundColor})" - foregroundColor="$(getColorCode ${foregroundColor})" - - local background foreground - [[ -n "${backgroundColor}" ]] && background="$(backgroundColor ${backgroundColor})" || background="%k" - [[ -n "${foregroundColor}" ]] && foreground="$(foregroundColor ${foregroundColor})" || foreground="%f" - - if [[ $CURRENT_BG != 'NONE' ]] && ! isSameColor "${backgroundColor}" "$CURRENT_BG"; then - echo -n "${background}%F{$CURRENT_BG}" - if [[ $joined == false ]]; then - # Middle segment - echo -n "$(print_icon 'LEFT_SEGMENT_SEPARATOR')$POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS" - fi - elif isSameColor "$CURRENT_BG" "${backgroundColor}"; then - # Middle segment with same color as previous segment - # We take the current foreground color as color for our - # subsegment (or the default color). This should have - # enough contrast. - local complement - [[ -n "${foregroundColor}" ]] && complement="${foreground}" || complement="$(foregroundColor $DEFAULT_COLOR)" - echo -n "${background}${complement}" - if [[ $joined == false ]]; then - echo -n "$(print_icon 'LEFT_SUBSEGMENT_SEPARATOR')$POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS" + _p9k_should_join_left $2 && local join=true || local join=false + local cache_key="${(q)0} ${(q)1} ${(q)3} ${(q)4} ${(q)5:+1} ${(q)6} ${(q)CURRENT_BG} $join" + if ! _p9k_cache_get $cache_key; then + _p9k_color $3 $1 BACKGROUND + local background_color=$_P9K_RETVAL + + _p9k_color $4 $1 FOREGROUND + local foreground_color=$_P9K_RETVAL + _p9k_foreground $foreground_color + local foreground=$_P9K_RETVAL + + _p9k_background $background_color + local output=$_P9K_RETVAL + + if [[ $CURRENT_BG == NONE ]]; then + # The first segment on the line. + output+=$POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS + elif [[ $join == false ]]; then + if [[ $background_color == $CURRENT_BG ]]; then + # Middle segment with same color as previous segment + # We take the current foreground color as color for our + # subsegment (or the default color). This should have + # enough contrast. + if [[ $foreground == "%f" ]]; then + _p9k_foreground $DEFAULT_COLOR + output+=$_P9K_RETVAL + else + output+=$foreground + fi + _p9k_get_icon LEFT_SUBSEGMENT_SEPARATOR + output+="${_P9K_RETVAL}${POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS}" + else + _p9k_foreground $CURRENT_BG + output+=$_P9K_RETVAL + _p9k_get_icon LEFT_SEGMENT_SEPARATOR + output+="${_P9K_RETVAL}${POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS}" + fi fi - else - # First segment - echo -n "${background}$POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS" - fi - - local visual_identifier - if [[ -n $6 ]]; then - visual_identifier="$(print_icon $6)" - if [[ -n "$visual_identifier" ]]; then - # Add an whitespace if we print more than just the visual identifier. - # To avoid cutting off the visual identifier in some terminal emulators (e.g., Konsole, st), - # we need to color both the visual identifier and the whitespace. - [[ -n "$5" ]] && visual_identifier="$visual_identifier " - # Allow users to overwrite the color for the visual identifier only. - local visual_identifier_color_variable=POWERLEVEL9K_${(U)${segment_name}#prompt_}_VISUAL_IDENTIFIER_COLOR - set_default $visual_identifier_color_variable "${foregroundColor}" - visual_identifier="$(foregroundColor ${(P)visual_identifier_color_variable})${visual_identifier}" + + if [[ -n $6 ]]; then + _p9k_get_icon $6 + if [[ -n $_P9K_RETVAL ]]; then + local icon=$_P9K_RETVAL + _p9k_foreground $foreground_color $1 VISUAL_IDENTIFIER_COLOR + # Add an whitespace if we print more than just the visual identifier. + # To avoid cutting off the visual identifier in some terminal emulators (e.g., Konsole, st), + # we need to color both the visual identifier and the whitespace. + output+="${_P9K_RETVAL}${icon}${5:+ }" + fi fi - fi - # Print the visual identifier - echo -n "${visual_identifier}" - # Print the content of the segment, if there is any - [[ -n "$5" ]] && echo -n "${foreground}${5}" - echo -n "${POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS}" + output+=$foreground + _p9k_cache_set $cache_key "${(qq)output} ${(qq)background_color}" + fi - CURRENT_BG="${backgroundColor}" - last_left_element_index=$current_index + local tuple=("${(@Q)${(z)_P9K_RETVAL}}") + echo -n "${tuple[1]}${5}${POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS}" + LAST_SEGMENT_INDEX=$2 + CURRENT_BG=$tuple[2] } # End the left prompt, closes the final segment. left_prompt_end() { - if [[ -n $CURRENT_BG ]]; then - echo -n "%k$(foregroundColor ${CURRENT_BG})$(print_icon 'LEFT_SEGMENT_SEPARATOR')" - else - echo -n "%k" + local cache_key="$0 ${(q)CURRENT_BG}" + if ! _p9k_cache_get $cache_key; then + local output="%k" + if [[ $CURRENT_BG != NONE ]]; then + _p9k_foreground $CURRENT_BG + output+=$_P9K_RETVAL + _p9k_get_icon LEFT_SEGMENT_SEPARATOR + output+="${_P9K_RETVAL}" + fi + _p9k_get_icon LEFT_SEGMENT_END_SEPARATOR + output+="%f${_P9K_RETVAL}" + _p9k_cache_set $cache_key $output fi - echo -n "%f$(print_icon 'LEFT_SEGMENT_END_SEPARATOR')" - CURRENT_BG='' + echo -n $_P9K_RETVAL } -CURRENT_RIGHT_BG='NONE' - # Begin a right prompt segment # Takes four arguments: # * $1: Name of the function that was originally invoked (mandatory). @@ -209,87 +294,72 @@ CURRENT_RIGHT_BG='NONE' # * $5: The segment content # * $6: An identifying icon (must be a key of the icons array) # No ending for the right prompt segment is needed (unlike the left prompt, above). -set_default last_right_element_index 1 set_default POWERLEVEL9K_WHITESPACE_BETWEEN_RIGHT_SEGMENTS " " right_prompt_segment() { - local segment_name="${1}" - local current_index=$2 - - # Check if the segment should be joined with the previous one - local joined - segmentShouldBeJoined $current_index $last_right_element_index "$POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS" && joined=true || joined=false - - # Colors - local backgroundColor="${3}" - local foregroundColor="${4}" - - # Overwrite given background-color by user defined variable for this segment. - local BACKGROUND_USER_VARIABLE=POWERLEVEL9K_${(U)${segment_name}#prompt_}_BACKGROUND - local BG_COLOR_MODIFIER=${(P)BACKGROUND_USER_VARIABLE} - [[ -n $BG_COLOR_MODIFIER ]] && backgroundColor="$BG_COLOR_MODIFIER" - - # Overwrite given foreground-color by user defined variable for this segment. - local FOREGROUND_USER_VARIABLE=POWERLEVEL9K_${(U)${segment_name}#prompt_}_FOREGROUND - local FG_COLOR_MODIFIER=${(P)FOREGROUND_USER_VARIABLE} - [[ -n $FG_COLOR_MODIFIER ]] && foregroundColor="$FG_COLOR_MODIFIER" - - # Get color codes here to save some calls later on - backgroundColor="$(getColorCode ${backgroundColor})" - foregroundColor="$(getColorCode ${foregroundColor})" - - local background foreground - [[ -n "${backgroundColor}" ]] && background="$(backgroundColor ${backgroundColor})" || background="%k" - [[ -n "${foregroundColor}" ]] && foreground="$(foregroundColor ${foregroundColor})" || foreground="%f" - - # If CURRENT_RIGHT_BG is "NONE", we are the first right segment. - - if [[ "$CURRENT_RIGHT_BG" != "NONE" ]]; then - # This is the closing whitespace for the previous segment - echo -n "${POWERLEVEL9K_WHITESPACE_BETWEEN_RIGHT_SEGMENTS}%f" - fi - - if [[ $joined == false ]] || [[ "$CURRENT_RIGHT_BG" == "NONE" ]]; then - if isSameColor "$CURRENT_RIGHT_BG" "${backgroundColor}"; then - # Middle segment with same color as previous segment - # We take the current foreground color as color for our - # subsegment (or the default color). This should have - # enough contrast. - local complement - [[ -n "${foregroundColor}" ]] && complement="${foreground}" || complement="$(foregroundColor $DEFAULT_COLOR)" - echo -n "$complement$(print_icon 'RIGHT_SUBSEGMENT_SEPARATOR')%f" + _p9k_should_join_right $2 && local join=true || local join=false + local cache_key="${(q)0} ${(q)1} ${(q)3} ${(q)4} ${(q)6} ${(q)CURRENT_BG} $join" + + if ! _p9k_cache_get $cache_key; then + _p9k_color $3 $1 BACKGROUND + local background_color=$_P9K_RETVAL + _p9k_background $background_color + local background=$_P9K_RETVAL + + _p9k_color $4 $1 FOREGROUND + local foreground_color=$_P9K_RETVAL + _p9k_foreground $foreground_color + local foreground=$_P9K_RETVAL + + local output='' + + if [[ $CURRENT_BG == NONE || $join == false ]]; then + if [[ $background_color == $CURRENT_BG ]]; then + # Middle segment with same color as previous segment + # We take the current foreground color as color for our + # subsegment (or the default color). This should have + # enough contrast. + if [[ $foreground == "%f" ]]; then + _p9k_foreground $DEFAULT_COLOR + output+=$_P9K_RETVAL + else + output+=$foreground + fi + _p9k_get_icon RIGHT_SUBSEGMENT_SEPARATOR + output+=$_P9K_RETVAL + else + _p9k_foreground $background_color + output+=$_P9K_RETVAL + _p9k_get_icon RIGHT_SEGMENT_SEPARATOR + output+=$_P9K_RETVAL + fi + output+="${background}${POWERLEVEL9K_WHITESPACE_BETWEEN_RIGHT_SEGMENTS}" else - # Use the new Background Color as the foreground of the segment separator - echo -n "$(foregroundColor ${backgroundColor})$(print_icon 'RIGHT_SEGMENT_SEPARATOR')%f" + output+=$background fi - fi - local visual_identifier - if [[ -n "$6" ]]; then - visual_identifier="$(print_icon $6)" - if [[ -n "$visual_identifier" ]]; then - # Add an whitespace if we print more than just the visual identifier. - # To avoid cutting off the visual identifier in some terminal emulators (e.g., Konsole, st), - # we need to color both the visual identifier and the whitespace. - [[ -n "$5" ]] && visual_identifier=" $visual_identifier" - # Allow users to overwrite the color for the visual identifier only. - local visual_identifier_color_variable=POWERLEVEL9K_${(U)${segment_name}#prompt_}_VISUAL_IDENTIFIER_COLOR - set_default $visual_identifier_color_variable "${foregroundColor}" - visual_identifier="$(foregroundColor ${(P)visual_identifier_color_variable})${visual_identifier}" + output+=$foreground + + local icon='' + if [[ -n $6 ]]; then + _p9k_get_icon $6 + if [[ -n $_P9K_RETVAL ]]; then + local icon=$_P9K_RETVAL + _p9k_foreground $foreground_color $1 VISUAL_IDENTIFIER_COLOR + # Add an whitespace if we print more than just the visual identifier. + # To avoid cutting off the visual identifier in some terminal emulators (e.g., Konsole, st), + # we need to color both the visual identifier and the whitespace. + icon="${_P9K_RETVAL}${icon} " + fi fi - fi - - echo -n "${background}${foreground}" - # Print whitespace only if segment is not joined or first right segment - [[ $joined == false ]] || [[ "$CURRENT_RIGHT_BG" == "NONE" ]] && echo -n "${POWERLEVEL9K_WHITESPACE_BETWEEN_RIGHT_SEGMENTS}" + _p9k_cache_set $cache_key "${(qq)output} ${(qq)background_color} ${(qq)icon}" + fi - # Print segment content if there is any - [[ -n "$5" ]] && echo -n "${5}" - # Print the visual identifier - echo -n "${visual_identifier}" + local tuple=("${(@Q)${(z)_P9K_RETVAL}}") + echo -n "${tuple[1]}${5}${5:+ }${tuple[3]}" - CURRENT_RIGHT_BG="${backgroundColor}" - last_right_element_index=$current_index + CURRENT_BG=$tuple[2] + LAST_SEGMENT_INDEX=$2 } ################################################################ @@ -335,18 +405,19 @@ prompt_aws_eb_env() { set_default POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE true set_default POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE_ALWAYS false prompt_background_jobs() { - local background_jobs_number=${$(jobs -l | wc -l)// /} - local wrong_lines=`jobs -l | awk '/pwd now/{ count++ } END {print count}'` - if [[ wrong_lines -gt 0 ]]; then - background_jobs_number=$(( $background_jobs_number - $wrong_lines )) - fi - if [[ background_jobs_number -gt 0 ]]; then - local background_jobs_number_print="" - if [[ "$POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE" == "true" ]] && ([[ "$background_jobs_number" -gt 1 ]] || [[ "$POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE_ALWAYS" == "true" ]]); then - background_jobs_number_print="$background_jobs_number" + local n && n="${(fw)#$(jobs -rd)}" && ((n > 1)) || return + (( n /= 2 )) + + local cache_key="$0 $n" + if ! _p9k_cache_get $cache_key; then + if [[ "$POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE" == "true" && + ("$n" -gt 1 || "$POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE_ALWAYS" == "true") ]]; then + _p9k_cache_set $cache_key $n + else + _p9k_cache_set $cache_key '' fi - "$1_prompt_segment" "$0" "$2" "$DEFAULT_COLOR" "cyan" "$background_jobs_number_print" 'BACKGROUND_JOBS_ICON' fi + "$1_prompt_segment" "$0" "$2" "$DEFAULT_COLOR" "cyan" "$_P9K_RETVAL" 'BACKGROUND_JOBS_ICON' } ################################################################ @@ -364,7 +435,7 @@ prompt_newline() { "$0" \ "$2" \ "" "" "${newline}" - CURRENT_BG='NONE' + CURRENT_BG=NONE POWERLEVEL9K_WHITESPACE_BETWEEN_LEFT_SEGMENTS=$lws } @@ -617,15 +688,6 @@ set_default POWERLEVEL9K_ALWAYS_SHOW_USER false set_default POWERLEVEL9K_CONTEXT_TEMPLATE "%n@%m" prompt_context() { local current_state="DEFAULT" - typeset -AH context_states - context_states=( - "ROOT" "yellow" - "SUDO" "yellow" - "DEFAULT" "yellow" - "REMOTE" "yellow" - "REMOTE_SUDO" "yellow" - ) - local content="" if [[ "$POWERLEVEL9K_ALWAYS_SHOW_CONTEXT" == true ]] || [[ "$(whoami)" != "$DEFAULT_USER" ]] || [[ -n "$SSH_CLIENT" || -n "$SSH_TTY" ]]; then @@ -648,7 +710,7 @@ prompt_context() { current_state="SUDO" fi - "$1_prompt_segment" "${0}_${current_state}" "$2" "$DEFAULT_COLOR" "${context_states[$current_state]}" "${content}" + "$1_prompt_segment" "${0}_${current_state}" "$2" "$DEFAULT_COLOR" yellow "${content}" } ################################################################ @@ -798,249 +860,255 @@ set_default POWERLEVEL9K_DIR_PATH_HIGHLIGHT_BOLD false prompt_dir() { # using $PWD instead of "$(print -P '%~')" to allow use of POWERLEVEL9K_DIR_PATH_ABSOLUTE local current_path=$PWD # WAS: local current_path="$(print -P '%~')" - # check if the user wants to use absolute paths or "~" paths - [[ ${(L)POWERLEVEL9K_DIR_PATH_ABSOLUTE} != "true" ]] && current_path=${current_path/#$HOME/"~"} - # declare all local variables - local paths directory test_dir test_dir_length trunc_path threshhold - # if we are not in "~" or "/", split the paths into an array and exclude "~" - (( ${#current_path} > 1 )) && paths=(${(s:/:)${current_path//"~\/"/}}) || paths=() - # only run the code if SHORTEN_DIR_LENGTH is set, or we are using the two strategies that don't rely on it. - if [[ -n "$POWERLEVEL9K_SHORTEN_DIR_LENGTH" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_with_folder_marker" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_to_last" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_with_package_name" ]]; then - set_default POWERLEVEL9K_SHORTEN_DELIMITER "\u2026" - # convert delimiter from unicode to literal character, so that we can get the correct length later - local delim=$(echo -n $POWERLEVEL9K_SHORTEN_DELIMITER) - - case "$POWERLEVEL9K_SHORTEN_STRATEGY" in - truncate_absolute_chars) - if [ ${#current_path} -gt $(( $POWERLEVEL9K_SHORTEN_DIR_LENGTH + ${#POWERLEVEL9K_SHORTEN_DELIMITER} )) ]; then - current_path=$POWERLEVEL9K_SHORTEN_DELIMITER${current_path:(-POWERLEVEL9K_SHORTEN_DIR_LENGTH)} - fi - ;; - truncate_middle) - # truncate characters from the middle of the path - current_path=$(truncatePath $current_path $POWERLEVEL9K_SHORTEN_DIR_LENGTH $POWERLEVEL9K_SHORTEN_DELIMITER "middle") - ;; - truncate_from_right) - # truncate characters from the right of the path - current_path=$(truncatePath "$current_path" $POWERLEVEL9K_SHORTEN_DIR_LENGTH $POWERLEVEL9K_SHORTEN_DELIMITER) - ;; - truncate_absolute) - # truncate all characters except the last POWERLEVEL9K_SHORTEN_DIR_LENGTH characters - if [ ${#current_path} -gt $(( $POWERLEVEL9K_SHORTEN_DIR_LENGTH + ${#POWERLEVEL9K_SHORTEN_DELIMITER} )) ]; then - current_path=$POWERLEVEL9K_SHORTEN_DELIMITER${current_path:(-POWERLEVEL9K_SHORTEN_DIR_LENGTH)} - fi - ;; - truncate_to_last) - # truncate all characters before the current directory - current_path=${current_path##*/} - ;; - truncate_to_first_and_last) - if (( ${#current_path} > 1 )) && (( ${POWERLEVEL9K_SHORTEN_DIR_LENGTH} > 0 )); then - threshhold=$(( ${POWERLEVEL9K_SHORTEN_DIR_LENGTH} * 2)) - # if we are in "~", add it back into the paths array - [[ $current_path == '~'* ]] && paths=("~" "${paths[@]}") - if (( ${#paths} > $threshhold )); then - local num=$(( ${#paths} - ${POWERLEVEL9K_SHORTEN_DIR_LENGTH} )) - # repace the middle elements - for (( i=$POWERLEVEL9K_SHORTEN_DIR_LENGTH; i<$num; i++ )); do - paths[$i+1]=$POWERLEVEL9K_SHORTEN_DELIMITER - done - [[ $current_path != '~'* ]] && current_path="/" || current_path="" - current_path+="${(j:/:)paths}" + local cache_key="$0 $2 ${(q)current_path}" + [[ "${POWERLEVEL9K_DIR_SHOW_WRITABLE}" == true && ! -w "$PWD" ]] && cache_key+=" W"; + if ! _p9k_cache_get $cache_key; then + # check if the user wants to use absolute paths or "~" paths + [[ ${(L)POWERLEVEL9K_DIR_PATH_ABSOLUTE} != "true" ]] && current_path=${current_path/#$HOME/"~"} + # declare all local variables + local paths directory test_dir test_dir_length trunc_path threshhold + # if we are not in "~" or "/", split the paths into an array and exclude "~" + (( ${#current_path} > 1 )) && paths=(${(s:/:)${current_path//"~\/"/}}) || paths=() + # only run the code if SHORTEN_DIR_LENGTH is set, or we are using the two strategies that don't rely on it. + if [[ -n "$POWERLEVEL9K_SHORTEN_DIR_LENGTH" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_with_folder_marker" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_to_last" || "$POWERLEVEL9K_SHORTEN_STRATEGY" == "truncate_with_package_name" ]]; then + set_default POWERLEVEL9K_SHORTEN_DELIMITER "\u2026" + # convert delimiter from unicode to literal character, so that we can get the correct length later + local delim=$(echo -n $POWERLEVEL9K_SHORTEN_DELIMITER) + + case "$POWERLEVEL9K_SHORTEN_STRATEGY" in + truncate_absolute_chars) + if [ ${#current_path} -gt $(( $POWERLEVEL9K_SHORTEN_DIR_LENGTH + ${#POWERLEVEL9K_SHORTEN_DELIMITER} )) ]; then + current_path=$POWERLEVEL9K_SHORTEN_DELIMITER${current_path:(-POWERLEVEL9K_SHORTEN_DIR_LENGTH)} fi - fi - ;; - truncate_to_unique) - # for each parent path component find the shortest unique beginning - # characters sequence. Source: https://stackoverflow.com/a/45336078 - if (( ${#current_path} > 1 )); then # root and home are exceptions and won't have paths - # cheating here to retain ~ as home folder - local home_path="$(getUniqueFolder $HOME)" - trunc_path="$(getUniqueFolder $PWD)" - [[ $current_path == "~"* ]] && current_path="~${trunc_path//${home_path}/}" || current_path="/${trunc_path}" - fi - ;; - truncate_with_folder_marker) - if (( ${#paths} > 0 )); then # root and home are exceptions and won't have paths, so skip this - local last_marked_folder marked_folder - set_default POWERLEVEL9K_SHORTEN_FOLDER_MARKER ".shorten_folder_marker" - - # Search for the folder marker in the parent directories and - # buildup a pattern that is removed from the current path - # later on. - for marked_folder in $(upsearch $POWERLEVEL9K_SHORTEN_FOLDER_MARKER); do - if [[ "$marked_folder" == "/" ]]; then - # If we reached root folder, stop upsearch. - trunc_path="/" - elif [[ "$marked_folder" == "$HOME" ]]; then - # If we reached home folder, stop upsearch. - trunc_path="~" - elif [[ "${marked_folder%/*}" == $last_marked_folder ]]; then - trunc_path="${trunc_path%/}/${marked_folder##*/}" - else - trunc_path="${trunc_path%/}/$POWERLEVEL9K_SHORTEN_DELIMITER/${marked_folder##*/}" + ;; + truncate_middle) + # truncate characters from the middle of the path + current_path=$(truncatePath $current_path $POWERLEVEL9K_SHORTEN_DIR_LENGTH $POWERLEVEL9K_SHORTEN_DELIMITER "middle") + ;; + truncate_from_right) + # truncate characters from the right of the path + current_path=$(truncatePath "$current_path" $POWERLEVEL9K_SHORTEN_DIR_LENGTH $POWERLEVEL9K_SHORTEN_DELIMITER) + ;; + truncate_absolute) + # truncate all characters except the last POWERLEVEL9K_SHORTEN_DIR_LENGTH characters + if [ ${#current_path} -gt $(( $POWERLEVEL9K_SHORTEN_DIR_LENGTH + ${#POWERLEVEL9K_SHORTEN_DELIMITER} )) ]; then + current_path=$POWERLEVEL9K_SHORTEN_DELIMITER${current_path:(-POWERLEVEL9K_SHORTEN_DIR_LENGTH)} + fi + ;; + truncate_to_last) + # truncate all characters before the current directory + current_path=${current_path##*/} + ;; + truncate_to_first_and_last) + if (( ${#current_path} > 1 )) && (( ${POWERLEVEL9K_SHORTEN_DIR_LENGTH} > 0 )); then + threshhold=$(( ${POWERLEVEL9K_SHORTEN_DIR_LENGTH} * 2)) + # if we are in "~", add it back into the paths array + [[ $current_path == '~'* ]] && paths=("~" "${paths[@]}") + if (( ${#paths} > $threshhold )); then + local num=$(( ${#paths} - ${POWERLEVEL9K_SHORTEN_DIR_LENGTH} )) + # repace the middle elements + for (( i=$POWERLEVEL9K_SHORTEN_DIR_LENGTH; i<$num; i++ )); do + paths[$i+1]=$POWERLEVEL9K_SHORTEN_DELIMITER + done + [[ $current_path != '~'* ]] && current_path="/" || current_path="" + current_path+="${(j:/:)paths}" fi - last_marked_folder=$marked_folder - done + fi + ;; + truncate_to_unique) + # for each parent path component find the shortest unique beginning + # characters sequence. Source: https://stackoverflow.com/a/45336078 + if (( ${#current_path} > 1 )); then # root and home are exceptions and won't have paths + # cheating here to retain ~ as home folder + local home_path="$(getUniqueFolder $HOME)" + trunc_path="$(getUniqueFolder $PWD)" + [[ $current_path == "~"* ]] && current_path="~${trunc_path//${home_path}/}" || current_path="/${trunc_path}" + fi + ;; + truncate_with_folder_marker) + if (( ${#paths} > 0 )); then # root and home are exceptions and won't have paths, so skip this + local last_marked_folder marked_folder + set_default POWERLEVEL9K_SHORTEN_FOLDER_MARKER ".shorten_folder_marker" + + # Search for the folder marker in the parent directories and + # buildup a pattern that is removed from the current path + # later on. + for marked_folder in $(upsearch $POWERLEVEL9K_SHORTEN_FOLDER_MARKER); do + if [[ "$marked_folder" == "/" ]]; then + # If we reached root folder, stop upsearch. + trunc_path="/" + elif [[ "$marked_folder" == "$HOME" ]]; then + # If we reached home folder, stop upsearch. + trunc_path="~" + elif [[ "${marked_folder%/*}" == $last_marked_folder ]]; then + trunc_path="${trunc_path%/}/${marked_folder##*/}" + else + trunc_path="${trunc_path%/}/$POWERLEVEL9K_SHORTEN_DELIMITER/${marked_folder##*/}" + fi + last_marked_folder=$marked_folder + done + + # Replace the shortest possible match of the marked folder from + # the current path. + current_path=$trunc_path${current_path#${last_marked_folder}*} + fi + ;; + truncate_with_package_name) + local name repo_path package_path current_dir zero + + # Get the path of the Git repo, which should have the package.json file + if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == "true" ]]; then + # Get path from the root of the git repository to the current dir + local gitPath=$(git rev-parse --show-prefix) + # Remove trailing slash from git path, so that we can + # remove that git path from the pwd. + gitPath=${gitPath%/} + package_path=${$(pwd)%%$gitPath} + # Remove trailing slash + package_path=${package_path%/} + elif [[ $(git rev-parse --is-inside-git-dir 2> /dev/null) == "true" ]]; then + package_path=${$(pwd)%%/.git*} + fi + + [[ ${(L)POWERLEVEL9K_DIR_PATH_ABSOLUTE} != "true" ]] && package_path=${package_path/$HOME/"~"} # Replace the shortest possible match of the marked folder from - # the current path. - current_path=$trunc_path${current_path#${last_marked_folder}*} - fi - ;; - truncate_with_package_name) - local name repo_path package_path current_dir zero - - # Get the path of the Git repo, which should have the package.json file - if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == "true" ]]; then - # Get path from the root of the git repository to the current dir - local gitPath=$(git rev-parse --show-prefix) - # Remove trailing slash from git path, so that we can - # remove that git path from the pwd. - gitPath=${gitPath%/} - package_path=${$(pwd)%%$gitPath} - # Remove trailing slash - package_path=${package_path%/} - elif [[ $(git rev-parse --is-inside-git-dir 2> /dev/null) == "true" ]]; then - package_path=${$(pwd)%%/.git*} - fi + # the current path. Remove the amount of characters up to the + # folder marker from the left. Count only the visible characters + # in the path (this is done by the "zero" pattern; see + # http://stackoverflow.com/a/40855342/5586433). + local zero='%([BSUbfksu]|([FB]|){*})' + trunc_path=$(pwd) + # Then, find the length of the package_path string, and save the + # subdirectory path as a substring of the current directory's path from 0 + # to the length of the package path's string + subdirectory_path=$(truncatePath "${trunc_path:${#${(S%%)package_path//$~zero/}}}" $POWERLEVEL9K_SHORTEN_DIR_LENGTH $POWERLEVEL9K_SHORTEN_DELIMITER) + # Parse the 'name' from the package.json; if there are any problems, just + # print the file path + defined POWERLEVEL9K_DIR_PACKAGE_FILES || POWERLEVEL9K_DIR_PACKAGE_FILES=(package.json composer.json) + + local pkgFile="unknown" + for file in "${POWERLEVEL9K_DIR_PACKAGE_FILES[@]}"; do + if [[ -f "${package_path}/${file}" ]]; then + pkgFile="${package_path}/${file}" + break; + fi + done - [[ ${(L)POWERLEVEL9K_DIR_PATH_ABSOLUTE} != "true" ]] && package_path=${package_path/$HOME/"~"} - - # Replace the shortest possible match of the marked folder from - # the current path. Remove the amount of characters up to the - # folder marker from the left. Count only the visible characters - # in the path (this is done by the "zero" pattern; see - # http://stackoverflow.com/a/40855342/5586433). - local zero='%([BSUbfksu]|([FB]|){*})' - trunc_path=$(pwd) - # Then, find the length of the package_path string, and save the - # subdirectory path as a substring of the current directory's path from 0 - # to the length of the package path's string - subdirectory_path=$(truncatePath "${trunc_path:${#${(S%%)package_path//$~zero/}}}" $POWERLEVEL9K_SHORTEN_DIR_LENGTH $POWERLEVEL9K_SHORTEN_DELIMITER) - # Parse the 'name' from the package.json; if there are any problems, just - # print the file path - defined POWERLEVEL9K_DIR_PACKAGE_FILES || POWERLEVEL9K_DIR_PACKAGE_FILES=(package.json composer.json) - - local pkgFile="unknown" - for file in "${POWERLEVEL9K_DIR_PACKAGE_FILES[@]}"; do - if [[ -f "${package_path}/${file}" ]]; then - pkgFile="${package_path}/${file}" - break; + local packageName=$(jq '.name' ${pkgFile} 2> /dev/null \ + || node -e 'console.log(require(process.argv[1]).name);' ${pkgFile} 2>/dev/null \ + || cat "${pkgFile}" 2> /dev/null | grep -m 1 "\"name\"" | awk -F ':' '{print $2}' | awk -F '"' '{print $2}' 2>/dev/null \ + ) + if [[ -n "${packageName}" ]]; then + # Instead of printing out the full path, print out the name of the package + # from the package.json and append the current subdirectory + current_path="`echo $packageName | tr -d '"'`$subdirectory_path" fi - done - - local packageName=$(jq '.name' ${pkgFile} 2> /dev/null \ - || node -e 'console.log(require(process.argv[1]).name);' ${pkgFile} 2>/dev/null \ - || cat "${pkgFile}" 2> /dev/null | grep -m 1 "\"name\"" | awk -F ':' '{print $2}' | awk -F '"' '{print $2}' 2>/dev/null \ - ) - if [[ -n "${packageName}" ]]; then - # Instead of printing out the full path, print out the name of the package - # from the package.json and append the current subdirectory - current_path="`echo $packageName | tr -d '"'`$subdirectory_path" - fi - ;; - *) - if [[ $current_path != "~" ]]; then - current_path="$(print -P "%$((POWERLEVEL9K_SHORTEN_DIR_LENGTH+1))(c:$POWERLEVEL9K_SHORTEN_DELIMITER/:)%${POWERLEVEL9K_SHORTEN_DIR_LENGTH}c")" - fi - ;; - esac - fi + ;; + *) + if [[ $current_path != "~" ]]; then + current_path="$(print -P "%$((POWERLEVEL9K_SHORTEN_DIR_LENGTH+1))(c:$POWERLEVEL9K_SHORTEN_DELIMITER/:)%${POWERLEVEL9K_SHORTEN_DIR_LENGTH}c")" + fi + ;; + esac + fi - # save state of path for highlighting and bold options - local path_opt=$current_path + # save state of path for highlighting and bold options + local path_opt=$current_path - typeset -AH dir_states - dir_states=( - "DEFAULT" "FOLDER_ICON" - "HOME" "HOME_ICON" - "HOME_SUBFOLDER" "HOME_SUB_ICON" - "NOT_WRITABLE" "LOCK_ICON" - "ETC" "ETC_ICON" - ) - local state_path="$(print -P '%~')" - local current_state="DEFAULT" - if [[ $state_path == '/etc'* ]]; then - current_state='ETC' - elif [[ "${POWERLEVEL9K_DIR_SHOW_WRITABLE}" == true && ! -w "$PWD" ]]; then - current_state="NOT_WRITABLE" - elif [[ $state_path == '~' ]]; then - current_state="HOME" - elif [[ $state_path == '~'* ]]; then - current_state="HOME_SUBFOLDER" - fi - - # declare variables used for bold and state colors - local bld_on bld_off dir_state_foreground dir_state_user_foreground - # test if user wants the last directory printed in bold - if [[ "${(L)POWERLEVEL9K_DIR_PATH_HIGHLIGHT_BOLD}" == "true" ]]; then - bld_on="%B" - bld_off="%b" - else - bld_on="" - bld_off="" - fi - # determine is the user has set a last directory color - local dir_state_user_foreground=POWERLEVEL9K_DIR_${current_state}_FOREGROUND - local dir_state_foreground=${(P)dir_state_user_foreground} - [[ -z ${dir_state_foreground} ]] && dir_state_foreground="${DEFAULT_COLOR}" - - local dir_name base_name - # use ZSH substitution to get the dirname and basename instead of calling external functions - dir_name=${path_opt%/*} - base_name=${path_opt##*/} - - # if the user wants the last directory colored... - if [[ -n ${POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND} ]]; then - # it the path is "/" or "~" - if [[ $path_opt == "/" || $path_opt == "~" ]]; then - current_path="${bld_on}%F{$POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND}${current_path}${bld_off}" - else # has a subfolder - # test if dirname != basename - they are equal if we use truncate_to_last or truncate_absolute - if [[ $dir_name != $base_name ]]; then - current_path="${dir_name}/${bld_on}%F{$POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND}${base_name}${bld_off}" - else - current_path="${bld_on}%F{$POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND}${base_name}${bld_off}" - fi + typeset -AH dir_states + dir_states=( + "DEFAULT" "FOLDER_ICON" + "HOME" "HOME_ICON" + "HOME_SUBFOLDER" "HOME_SUB_ICON" + "NOT_WRITABLE" "LOCK_ICON" + "ETC" "ETC_ICON" + ) + local state_path="$(print -P '%~')" + local current_state="DEFAULT" + if [[ $state_path == '/etc'* ]]; then + current_state='ETC' + elif [[ "${POWERLEVEL9K_DIR_SHOW_WRITABLE}" == true && ! -w "$PWD" ]]; then + current_state="NOT_WRITABLE" + elif [[ $state_path == '~' ]]; then + current_state="HOME" + elif [[ $state_path == '~'* ]]; then + current_state="HOME_SUBFOLDER" fi - else # no coloring - # it the path is "/" or "~" - if [[ $path_opt == "/" || $path_opt == "~" ]]; then - current_path="${bld_on}${current_path}${bld_off}" - else # has a subfolder - # test if dirname != basename - they are equal if we use truncate_to_last or truncate_absolute - if [[ $dir_name != $base_name ]]; then - current_path="${dir_name}/${bld_on}${base_name}${bld_off}" - else - current_path="${bld_on}${base_name}${bld_off}" + + # declare variables used for bold and state colors + local bld_on bld_off dir_state_foreground dir_state_user_foreground + # test if user wants the last directory printed in bold + if [[ "${(L)POWERLEVEL9K_DIR_PATH_HIGHLIGHT_BOLD}" == "true" ]]; then + bld_on="%B" + bld_off="%b" + else + bld_on="" + bld_off="" + fi + # determine is the user has set a last directory color + local dir_state_user_foreground=POWERLEVEL9K_DIR_${current_state}_FOREGROUND + local dir_state_foreground=${(P)dir_state_user_foreground} + [[ -z ${dir_state_foreground} ]] && dir_state_foreground="${DEFAULT_COLOR}" + + local dir_name base_name + # use ZSH substitution to get the dirname and basename instead of calling external functions + dir_name=${path_opt%/*} + base_name=${path_opt##*/} + + # if the user wants the last directory colored... + if [[ -n ${POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND} ]]; then + # it the path is "/" or "~" + if [[ $path_opt == "/" || $path_opt == "~" ]]; then + current_path="${bld_on}%F{$POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND}${current_path}${bld_off}" + else # has a subfolder + # test if dirname != basename - they are equal if we use truncate_to_last or truncate_absolute + if [[ $dir_name != $base_name ]]; then + current_path="${dir_name}/${bld_on}%F{$POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND}${base_name}${bld_off}" + else + current_path="${bld_on}%F{$POWERLEVEL9K_DIR_PATH_HIGHLIGHT_FOREGROUND}${base_name}${bld_off}" + fi + fi + else # no coloring + # it the path is "/" or "~" + if [[ $path_opt == "/" || $path_opt == "~" ]]; then + current_path="${bld_on}${current_path}${bld_off}" + else # has a subfolder + # test if dirname != basename - they are equal if we use truncate_to_last or truncate_absolute + if [[ $dir_name != $base_name ]]; then + current_path="${dir_name}/${bld_on}${base_name}${bld_off}" + else + current_path="${bld_on}${base_name}${bld_off}" + fi fi fi - fi - # check if we need to omit the first character and only do it if we are not in "~" or "/" - if [[ "${POWERLEVEL9K_DIR_OMIT_FIRST_CHARACTER}" == "true" && $path_opt != "/" && $path_opt != "~" ]]; then - current_path="${current_path[2,-1]}" - fi + # check if we need to omit the first character and only do it if we are not in "~" or "/" + if [[ "${POWERLEVEL9K_DIR_OMIT_FIRST_CHARACTER}" == "true" && $path_opt != "/" && $path_opt != "~" ]]; then + current_path="${current_path[2,-1]}" + fi - # check if the user wants the separator colored. - if [[ -n ${POWERLEVEL9K_DIR_PATH_SEPARATOR_FOREGROUND} && $path_opt != "/" ]]; then - # because this contains color changing codes, it is easier to set a variable for what should be replaced - local repl="%F{$POWERLEVEL9K_DIR_PATH_SEPARATOR_FOREGROUND}/%F{$dir_state_foreground}" - # escape the / with a \ - current_path=${current_path//\//$repl} - fi + # check if the user wants the separator colored. + if [[ -n ${POWERLEVEL9K_DIR_PATH_SEPARATOR_FOREGROUND} && $path_opt != "/" ]]; then + # because this contains color changing codes, it is easier to set a variable for what should be replaced + local repl="%F{$POWERLEVEL9K_DIR_PATH_SEPARATOR_FOREGROUND}/%F{$dir_state_foreground}" + # escape the / with a \ + current_path=${current_path//\//$repl} + fi - if [[ "${POWERLEVEL9K_DIR_PATH_SEPARATOR}" != "/" && $path_opt != "/" ]]; then - current_path=${current_path//\//$POWERLEVEL9K_DIR_PATH_SEPARATOR} - fi + if [[ "${POWERLEVEL9K_DIR_PATH_SEPARATOR}" != "/" && $path_opt != "/" ]]; then + current_path=${current_path//\//$POWERLEVEL9K_DIR_PATH_SEPARATOR} + fi + + if [[ "${POWERLEVEL9K_HOME_FOLDER_ABBREVIATION}" != "~" && ! "${(L)POWERLEVEL9K_DIR_OMIT_FIRST_CHARACTER}" == "true" ]]; then + # use :s to only replace the first occurance + current_path=${current_path:s/~/$POWERLEVEL9K_HOME_FOLDER_ABBREVIATION} + fi - if [[ "${POWERLEVEL9K_HOME_FOLDER_ABBREVIATION}" != "~" && ! "${(L)POWERLEVEL9K_DIR_OMIT_FIRST_CHARACTER}" == "true" ]]; then - # use :s to only replace the first occurance - current_path=${current_path:s/~/$POWERLEVEL9K_HOME_FOLDER_ABBREVIATION} + _p9k_cache_set $cache_key "$0_$current_state ${(qq)2} blue ${(qq)DEFAULT_COLOR} ${(qq)current_path} ${(qq)dir_states[$current_state]}" fi - "$1_prompt_segment" "$0_${current_state}" "$2" "blue" "$DEFAULT_COLOR" "${current_path}" "${dir_states[$current_state]}" + "$1_prompt_segment" "${(@Q)${(z)_P9K_RETVAL}}" } ################################################################ @@ -1389,39 +1457,46 @@ exit_code_or_status() { } prompt_status() { - local ec_text - local ec_sum - local ec - - if [[ $POWERLEVEL9K_STATUS_SHOW_PIPESTATUS == true ]]; then - if (( $#RETVALS > 1 )); then - ec_text=$(exit_code_or_status "${RETVALS[1]}") - ec_sum=${RETVALS[1]} + local cache_key="$0 $2 $RETVAL $RETVALS" + if ! _p9k_cache_get $cache_key; then + local ec_text + local ec_sum + local ec + + if [[ $POWERLEVEL9K_STATUS_SHOW_PIPESTATUS == true ]]; then + if (( $#RETVALS > 1 )); then + ec_text=$(exit_code_or_status "${RETVALS[1]}") + ec_sum=${RETVALS[1]} + else + ec_text=$(exit_code_or_status "${RETVAL}") + ec_sum=${RETVAL} + fi + + for ec in "${(@)RETVALS[2,-1]}"; do + ec_text="${ec_text}|$(exit_code_or_status "$ec")" + ec_sum=$(( $ec_sum + $ec )) + done else + # We use RETVAL instead of the right-most RETVALS item because + # PIPE_FAIL may be set. ec_text=$(exit_code_or_status "${RETVAL}") ec_sum=${RETVAL} fi - for ec in "${(@)RETVALS[2,-1]}"; do - ec_text="${ec_text}|$(exit_code_or_status "$ec")" - ec_sum=$(( $ec_sum + $ec )) - done - else - # We use RETVAL instead of the right-most RETVALS item because - # PIPE_FAIL may be set. - ec_text=$(exit_code_or_status "${RETVAL}") - ec_sum=${RETVAL} - fi - - if (( ec_sum > 0 )); then - if [[ "$POWERLEVEL9K_STATUS_CROSS" == false && "$POWERLEVEL9K_STATUS_VERBOSE" == true ]]; then - "$1_prompt_segment" "$0_ERROR" "$2" "red" "yellow1" "$ec_text" 'CARRIAGE_RETURN_ICON' - else - "$1_prompt_segment" "$0_ERROR" "$2" "$DEFAULT_COLOR" "red" "" 'FAIL_ICON' + if (( ec_sum > 0 )); then + if [[ "$POWERLEVEL9K_STATUS_CROSS" == false && "$POWERLEVEL9K_STATUS_VERBOSE" == true ]]; then + _P9K_RETVAL="$0_ERROR ${(qq)2} red yellow1 ${(qq)ec_text} CARRIAGE_RETURN_ICON" + else + _P9K_RETVAL="$0_ERROR ${(qq)2} ${(qq)DEFAULT_COLOR} red '' FAIL_ICON" + fi + elif [[ "$POWERLEVEL9K_STATUS_OK" == true ]] && [[ "$POWERLEVEL9K_STATUS_VERBOSE" == true || "$POWERLEVEL9K_STATUS_OK_IN_NON_VERBOSE" == true ]]; then + _P9K_RETVAL="$0_OK ${(qq)2} ${(qq)DEFAULT_COLOR} green '' OK_ICON" + fi + if (( $#RETVALS < 3 )); then + _p9k_cache_set $cache_key $_P9K_RETVAL fi - elif [[ "$POWERLEVEL9K_STATUS_OK" == true ]] && [[ "$POWERLEVEL9K_STATUS_VERBOSE" == true || "$POWERLEVEL9K_STATUS_OK_IN_NON_VERBOSE" == true ]]; then - "$1_prompt_segment" "$0_OK" "$2" "$DEFAULT_COLOR" "green" "" 'OK_ICON' fi + "$1_prompt_segment" "${(@Q)${(z)_P9K_RETVAL}}" } ################################################################ @@ -1582,30 +1657,101 @@ powerlevel9k_vcs_init() { fi } +# If not empty, should be a command that provides vcs info in the format of gitstatus. +# See https://github.com/romkatv/gitstatus. +set_default POWERLEVEL9K_VCS_STATUS_COMMAND "" + ################################################################ # Segment to show VCS information prompt_vcs() { - VCS_WORKDIR_DIRTY=false - VCS_WORKDIR_HALF_DIRTY=false - local current_state="" - - # Actually invoke vcs_info manually to gather all information. - vcs_info - local vcs_prompt="${vcs_info_msg_0_}" - - if [[ -n "$vcs_prompt" ]]; then - if [[ "$VCS_WORKDIR_DIRTY" == true ]]; then - # $vcs_visual_identifier gets set in +vi-vcs-detect-changes in functions/vcs.zsh, - # as we have there access to vcs_info internal hooks. - current_state='modified' - else - if [[ "$VCS_WORKDIR_HALF_DIRTY" == true ]]; then + if [[ -z $POWERLEVEL9K_VCS_STATUS_COMMAND ]]; then + VCS_WORKDIR_DIRTY=false + VCS_WORKDIR_HALF_DIRTY=false + local current_state="" + + # Actually invoke vcs_info manually to gather all information. + vcs_info + local vcs_prompt="${vcs_info_msg_0_}" + + if [[ -n "$vcs_prompt" ]]; then + if [[ "$VCS_WORKDIR_DIRTY" == true ]]; then + # $vcs_visual_identifier gets set in +vi-vcs-detect-changes in functions/vcs.zsh, + # as we have there access to vcs_info internal hooks. + current_state='modified' + else + if [[ "$VCS_WORKDIR_HALF_DIRTY" == true ]]; then + current_state='untracked' + else + current_state='clean' + fi + fi + "$1_prompt_segment" "${0}_${(U)current_state}" "$2" "${vcs_states[$current_state]}" "$DEFAULT_COLOR" "$vcs_prompt" "$vcs_visual_identifier" + fi + else + "${(@Q)${(z)POWERLEVEL9K_VCS_STATUS_COMMAND}}" || return + local cache_key="$0 ${(@q)VCS_STATUS_ALL}" + if ! _p9k_cache_get $cache_key; then + local current_state + if [[ $VCS_STATUS_HAS_STAGED != 0 || $VCS_STATUS_HAS_UNSTAGED != 0 ]]; then + current_state='modified' + elif [[ $VCS_STATUS_HAS_UNTRACKED != 0 ]]; then current_state='untracked' else current_state='clean' fi + + local vcs_prompt + if [[ "$VCS_STATUS_REMOTE_URL" =~ "github" ]] then + _p9k_get_icon VCS_GIT_GITHUB_ICON + vcs_prompt+=$_P9K_RETVAL + elif [[ "$VCS_STATUS_REMOTE_URL" =~ "bitbucket" ]] then + _p9k_get_icon VCS_GIT_BITBUCKET_ICON + vcs_prompt+=$_P9K_RETVAL + elif [[ "$VCS_STATUS_REMOTE_URL" =~ "stash" ]] then + _p9k_get_icon VCS_GIT_GITHUB_ICON + vcs_prompt+=$_P9K_RETVAL + elif [[ "$VCS_STATUS_REMOTE_URL" =~ "gitlab" ]] then + _p9k_get_icon VCS_GIT_GITLAB_ICON + vcs_prompt+=$_P9K_RETVAL + else + _p9k_get_icon VCS_GIT_ICON + vcs_prompt+=$_P9K_RETVAL + fi + + _p9k_get_icon VCS_BRANCH_ICON + vcs_prompt+="$_P9K_RETVAL$VCS_STATUS_LOCAL_BRANCH" + if [[ -n $VCS_STATUS_ACTION ]]; then + vcs_prompt+=" %F{${POWERLEVEL9K_VCS_ACTIONFORMAT_FOREGROUND}}| $VCS_STATUS_ACTION%f" + else + if [[ -n $VCS_STATUS_REMOTE_BRANCH && + $VCS_STATUS_LOCAL_BRANCH != $VCS_STATUS_REMOTE_BRANCH ]]; then + _p9k_get_icon VCS_REMOTE_BRANCH_ICON + vcs_prompt+=" $_P9K_RETVAL$VCS_STATUS_REMOTE_BRANCH" + fi + if [[ $VCS_STATUS_HAS_STAGED == 1 ]]; then + _p9k_get_icon VCS_STAGED_ICON && vcs_prompt+=" $_P9K_RETVAL" + fi + if [[ $VCS_STATUS_HAS_UNSTAGED == 1 ]]; then + _p9k_get_icon VCS_UNSTAGED_ICON && vcs_prompt+=" $_P9K_RETVAL" + fi + if [[ $VCS_STATUS_HAS_UNTRACKED == 1 ]]; then + _p9k_get_icon VCS_UNTRACKED_ICON && vcs_prompt+=" $_P9K_RETVAL" + fi + if [[ $VCS_STATUS_COMMITS_AHEAD -gt 0 ]]; then + _p9k_get_icon VCS_OUTGOING_CHANGES_ICON && vcs_prompt+=" $_P9K_RETVAL$VCS_STATUS_COMMITS_AHEAD" + fi + if [[ $VCS_STATUS_COMMITS_BEHIND -gt 0 ]]; then + _p9k_get_icon VCS_INCOMING_CHANGES_ICON && vcs_prompt+=" $_P9K_RETVAL$VCS_STATUS_COMMITS_BEHIND" + fi + if [[ $VCS_STATUS_STASHES -gt 0 ]]; then + _p9k_get_icon VCS_STASH_ICON && vcs_prompt+=" $_P9K_RETVAL$VCS_STATUS_STASHES" + fi + fi + + _p9k_cache_set $cache_key "${0}_${(U)current_state} $2 ${(qq)vcs_states[$current_state]} ${(qq)DEFAULT_COLOR} ${(qq)vcs_prompt}" fi - "$1_prompt_segment" "${0}_${(U)current_state}" "$2" "${vcs_states[$current_state]}" "$DEFAULT_COLOR" "$vcs_prompt" "$vcs_visual_identifier" + + "$1_prompt_segment" "${(@Q)${(z)_P9K_RETVAL}}" fi } @@ -1799,6 +1945,9 @@ powerlevel9k_preexec() { _P9K_TIMER_START=$EPOCHREALTIME } +_P9K_MULTILINE_FIRST_PROMPT_PREFIX=$(print_icon MULTILINE_FIRST_PROMPT_PREFIX) +_P9K_MULTILINE_LAST_PROMPT_PREFIX=$(print_icon MULTILINE_LAST_PROMPT_PREFIX) + set_default POWERLEVEL9K_PROMPT_ADD_NEWLINE false powerlevel9k_prepare_prompts() { # Return values. These need to be global, because @@ -1816,9 +1965,18 @@ powerlevel9k_prepare_prompts() { # Reset start time _P9K_TIMER_START=0x7FFFFFFF + if [[ -s $_P9K_CACHE_CHANNEL ]]; then + eval $(<$_P9K_CACHE_CHANNEL) + rm -f $_P9K_CACHE_CHANNEL + if [[ -n $POWERLEVEL9K_MAX_CACHE_SIZE && $#_p9k_cache_data -gt $POWERLEVEL9K_MAX_CACHE_SIZE ]]; then + _p9k_cache_data=() + fi + fi + + local NEWLINE=$'\n' + if [[ "$POWERLEVEL9K_PROMPT_ON_NEWLINE" == true ]]; then - PROMPT='$(print_icon 'MULTILINE_FIRST_PROMPT_PREFIX')%f%b%k$(build_left_prompt) -$(print_icon 'MULTILINE_LAST_PROMPT_PREFIX')' + PROMPT="$_P9K_MULTILINE_FIRST_PROMPT_PREFIX%f%b%k\$(build_left_prompt)$NEWLINE$_P9K_MULTILINE_LAST_PROMPT_PREFIX" if [[ "$POWERLEVEL9K_RPROMPT_ON_NEWLINE" != true ]]; then # The right prompt should be on the same line as the first line of the left # prompt. To do so, there is just a quite ugly workaround: Before zsh draws @@ -1842,9 +2000,6 @@ $(print_icon 'MULTILINE_LAST_PROMPT_PREFIX')' RPROMPT="${RPROMPT_PREFIX}"'%f%b%k$(build_right_prompt)%{$reset_color%}'"${RPROMPT_SUFFIX}" fi -local NEWLINE=' -' - if [[ $POWERLEVEL9K_PROMPT_ADD_NEWLINE == true ]]; then NEWLINES="" repeat ${POWERLEVEL9K_PROMPT_ADD_NEWLINE_COUNT:-1} { NEWLINES+=$NEWLINE } @@ -1896,9 +2051,6 @@ prompt_powerlevel9k_setup() { fi fi - defined POWERLEVEL9K_LEFT_PROMPT_ELEMENTS || POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(context dir vcs) - defined POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS || POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status root_indicator background_jobs history time) - # Display a warning if deprecated segments are in use. typeset -AH deprecated_segments # old => new |