diff options
Diffstat (limited to 'dot_config/zsh/executable_git-prompt.zsh')
| -rw-r--r-- | dot_config/zsh/executable_git-prompt.zsh | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/dot_config/zsh/executable_git-prompt.zsh b/dot_config/zsh/executable_git-prompt.zsh new file mode 100644 index 0000000..95c87a0 --- /dev/null +++ b/dot_config/zsh/executable_git-prompt.zsh @@ -0,0 +1,408 @@ +# git-prompt.zsh -- a lightweight git prompt for zsh. +# Copyright © 2024 Wolfgang Popp +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +autoload -U colors && colors + +# Settings +: "${ZSH_GIT_PROMPT_SHOW_UPSTREAM=""}" +: "${ZSH_GIT_PROMPT_SHOW_STASH=""}" +: "${ZSH_GIT_PROMPT_SHOW_TRACKING_COUNTS="1"}" +: "${ZSH_GIT_PROMPT_SHOW_LOCAL_COUNTS="1"}" +: "${ZSH_GIT_PROMPT_ENABLE_SECONDARY=""}" +: "${ZSH_GIT_PROMPT_NO_ASYNC=""}" +: "${ZSH_GIT_PROMPT_FORCE_BLANK=""}" +: "${ZSH_GIT_PROMPT_AWK_CMD=""}" + +# Theming +: "${ZSH_THEME_GIT_PROMPT_PREFIX="["}" +: "${ZSH_THEME_GIT_PROMPT_SUFFIX="] "}" +: "${ZSH_THEME_GIT_PROMPT_SEPARATOR="|"}" +: "${ZSH_THEME_GIT_PROMPT_DETACHED="%{$fg_bold[cyan]%}:"}" +: "${ZSH_THEME_GIT_PROMPT_BRANCH="%{$fg_bold[magenta]%}"}" +: "${ZSH_THEME_GIT_PROMPT_UPSTREAM_SYMBOL="%{$fg_bold[yellow]%}⟳ "}" +: "${ZSH_THEME_GIT_PROMPT_UPSTREAM_NO_TRACKING=""}" +: "${ZSH_THEME_GIT_PROMPT_UPSTREAM_PREFIX="%{$fg[red]%}(%{$fg[yellow]%}"}" +: "${ZSH_THEME_GIT_PROMPT_UPSTREAM_SUFFIX="%{$fg[red]%})"}" +: "${ZSH_THEME_GIT_PROMPT_BEHIND="↓"}" +: "${ZSH_THEME_GIT_PROMPT_AHEAD="↑"}" +: "${ZSH_THEME_GIT_PROMPT_UNMERGED="%{$fg[red]%}✖"}" +: "${ZSH_THEME_GIT_PROMPT_STAGED="%{$fg[green]%}●"}" +: "${ZSH_THEME_GIT_PROMPT_UNSTAGED="%{$fg[red]%}✚"}" +: "${ZSH_THEME_GIT_PROMPT_UNTRACKED="…"}" +: "${ZSH_THEME_GIT_PROMPT_STASHED="%{$fg[blue]%}⚑"}" +: "${ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[green]%}✔"}" +: "${ZSH_THEME_GIT_PROMPT_SECONDARY_PREFIX=""}" +: "${ZSH_THEME_GIT_PROMPT_SECONDARY_SUFFIX=""}" +: "${ZSH_THEME_GIT_PROMPT_TAGS_SEPARATOR=", "}" +: "${ZSH_THEME_GIT_PROMPT_TAGS_PREFIX="🏷 "}" +: "${ZSH_THEME_GIT_PROMPT_TAGS_SUFFIX=""}" +: "${ZSH_THEME_GIT_PROMPT_TAG="%{$fg_bold[magenta]%}"}" + +# Disable promptinit if it is loaded +(( $+functions[promptinit] )) && {promptinit; prompt off} + +# Allow parameter and command substitution in the prompt +setopt PROMPT_SUBST + +# Override PROMPT if it does not use the gitprompt function +[[ "$PROMPT" != *gitprompt* && "$RPROMPT" != *gitprompt* ]] \ + && PROMPT='%B%40<..<%~ %b$(gitprompt)' \ + && PROMPT+='%(?.%(!.%F{white}❯%F{yellow}❯%F{red}.%F{blue}❯%F{cyan}❯%F{green})❯.%F{red}❯❯❯)%f ' + +# Find an awk implementation +# Prefer nawk over mawk and mawk over awk +(( $+commands[mawk] )) && : "${ZSH_GIT_PROMPT_AWK_CMD:=mawk}" +(( $+commands[nawk] )) && : "${ZSH_GIT_PROMPT_AWK_CMD:=nawk}" + : "${ZSH_GIT_PROMPT_AWK_CMD:=awk}" + +# Use --show-stash for git versions newer than 2.35.0 +_zsh_git_prompt_git_version=$(command git version) +if [[ "${_zsh_git_prompt_git_version:12}" == 2.<35->.<-> ]]; then + _zsh_git_prompt_git_cmd() { + GIT_OPTIONAL_LOCKS=0 command git status --show-stash --branch --porcelain=v2 2>&1 \ + || echo "fatal: git command failed" + } +else + _zsh_git_prompt_git_cmd() { + [[ -n "$ZSH_GIT_PROMPT_SHOW_STASH" ]] && ( + c=$(command git rev-list --walk-reflogs --count refs/stash 2> /dev/null) + [[ -n "$c" ]] && echo "# stash $c" + ) + GIT_OPTIONAL_LOCKS=0 command git status --branch --porcelain=v2 2>&1 \ + || echo "fatal: git command failed" + } +fi +unset _zsh_git_prompt_git_version + + +function _zsh_git_prompt_git_status() { + emulate -L zsh + _zsh_git_prompt_git_cmd | $ZSH_GIT_PROMPT_AWK_CMD \ + -v PREFIX="$ZSH_THEME_GIT_PROMPT_PREFIX" \ + -v SUFFIX="$ZSH_THEME_GIT_PROMPT_SUFFIX" \ + -v SEPARATOR="$ZSH_THEME_GIT_PROMPT_SEPARATOR" \ + -v DETACHED="$ZSH_THEME_GIT_PROMPT_DETACHED" \ + -v BRANCH="$ZSH_THEME_GIT_PROMPT_BRANCH" \ + -v UPSTREAM_TYPE="$ZSH_GIT_PROMPT_SHOW_UPSTREAM" \ + -v SHOW_TRACKING_COUNTS="$ZSH_GIT_PROMPT_SHOW_TRACKING_COUNTS" \ + -v SHOW_LOCAL_COUNTS="$ZSH_GIT_PROMPT_SHOW_LOCAL_COUNTS" \ + -v UPSTREAM_SYMBOL="$ZSH_THEME_GIT_PROMPT_UPSTREAM_SYMBOL" \ + -v UPSTREAM_NO_TRACKING="$ZSH_THEME_GIT_PROMPT_UPSTREAM_NO_TRACKING" \ + -v UPSTREAM_PREFIX="$ZSH_THEME_GIT_PROMPT_UPSTREAM_PREFIX" \ + -v UPSTREAM_SUFFIX="$ZSH_THEME_GIT_PROMPT_UPSTREAM_SUFFIX" \ + -v BEHIND="$ZSH_THEME_GIT_PROMPT_BEHIND" \ + -v AHEAD="$ZSH_THEME_GIT_PROMPT_AHEAD" \ + -v UNMERGED="$ZSH_THEME_GIT_PROMPT_UNMERGED" \ + -v STAGED="$ZSH_THEME_GIT_PROMPT_STAGED" \ + -v UNSTAGED="$ZSH_THEME_GIT_PROMPT_UNSTAGED" \ + -v UNTRACKED="$ZSH_THEME_GIT_PROMPT_UNTRACKED" \ + -v STASHED="$ZSH_THEME_GIT_PROMPT_STASHED" \ + -v SHOW_STASH="$ZSH_GIT_PROMPT_SHOW_STASH" \ + -v CLEAN="$ZSH_THEME_GIT_PROMPT_CLEAN" \ + -v RC="%{$reset_color%}" \ + ' + BEGIN { + ORS = ""; + + fatal = 0; + oid = ""; + head = ""; + upstream = ""; + ahead = 0; + behind = 0; + untracked = 0; + unmerged = 0; + staged = 0; + unstaged = 0; + stashed = 0; + } + + function prompt_element(prefix, content, suffix) { + print(prefix); + gsub("%", "%%", content); + print(content); + print(suffix); + print(RC); + } + + function count_element(prefix, count, show_count) { + content = ""; + if (show_count) { + content = count; + } + if (count > 0) { + prompt_element(prefix, content); + } + } + + function local_element(prefix, count) { + count_element(prefix, count, SHOW_LOCAL_COUNTS) + } + + function tracking_element(prefix, count) { + count_element(prefix, count, SHOW_TRACKING_COUNTS) + } + + $1 == "fatal:" { + fatal = 1; + } + + $2 == "branch.oid" { + oid = $3; + } + + $2 == "branch.head" { + head = $3; + } + + $2 == "branch.upstream" { + upstream = $3; + } + + $2 == "branch.ab" { + ahead = $3; + behind = $4; + } + + $1 == "?" { + ++untracked; + } + + $1 == "u" { + ++unmerged; + } + + $1 == "1" || $1 == "2" { + split($2, arr, ""); + if (arr[1] != ".") { + ++staged; + } + if (arr[2] != ".") { + ++unstaged; + } + } + + $2 == "stash" { + stashed = $3; + } + + END { + if (fatal == 1) { + exit(1); + } + + prompt_element(PREFIX); + + if (head == "(detached)") { + prompt_element(DETACHED, substr(oid, 0, 7)); + } else { + prompt_element(BRANCH, head); + } + + if (upstream == "") { + prompt_element(UPSTREAM_NO_TRACKING); + } else if (UPSTREAM_TYPE == "symbol") { + prompt_element(UPSTREAM_SYMBOL); + } else if (UPSTREAM_TYPE == "full") { + prompt_element(UPSTREAM_PREFIX, upstream, UPSTREAM_SUFFIX); + } + + tracking_element(BEHIND, behind * -1); + + tracking_element(AHEAD, ahead * 1); + + prompt_element(SEPARATOR); + + local_element(UNMERGED, unmerged); + + local_element(STAGED, staged); + + local_element(UNSTAGED, unstaged); + + local_element(UNTRACKED, untracked); + + if (SHOW_STASH) { + local_element(STASHED, stashed); + } + + if (unmerged == 0 && staged == 0 && unstaged == 0 && untracked == 0) { + prompt_element(CLEAN); + } + + prompt_element(SUFFIX); + } + ' +} + +function _zsh_git_prompt_git_status_secondary() { + tags=$(command git tag --points-at=HEAD 2> /dev/null) + + [[ -z "$tags" ]] && return + + echo -n ${ZSH_THEME_GIT_PROMPT_SECONDARY_PREFIX} + echo -n ${ZSH_THEME_GIT_PROMPT_TAGS_PREFIX} + + echo "$tags" | $ZSH_GIT_PROMPT_AWK_CMD \ + -v SEPARATOR="$ZSH_THEME_GIT_PROMPT_TAGS_SEPARATOR" \ + -v TAG="$ZSH_THEME_GIT_PROMPT_TAG" \ + -v RC="%{$reset_color%}" \ + ' + BEGIN { + ORS = ""; + } + { + if (NR != 1) { + print SEPARATOR; + print RC; + } + print TAG; + print $0; + print RC; + } + ' + + echo -n ${ZSH_THEME_GIT_PROMPT_TAGS_SUFFIX} + echo -n ${ZSH_THEME_GIT_PROMPT_SECONDARY_SUFFIX} +} + + +# The async code is taken from +# https://github.com/zsh-users/zsh-autosuggestions/blob/master/src/async.zsh + +zmodload zsh/system + +function _zsh_git_prompt_async_request() { + typeset -g _ZSH_GIT_PROMPT_ASYNC_FD _ZSH_GIT_PROMPT_ASYNC_PID + + # If we've got a pending request, cancel it + if [[ -n "$_ZSH_GIT_PROMPT_ASYNC_FD" ]] && { true <&$_ZSH_GIT_PROMPT_ASYNC_FD } 2>/dev/null; + then + + # Close the file descriptor and remove the handler + exec {_ZSH_GIT_PROMPT_ASYNC_FD}<&- + zle -F $_ZSH_GIT_PROMPT_ASYNC_FD + + # Zsh will make a new process group for the child process only if job + # control is enabled (MONITOR option) + if [[ -o MONITOR ]]; then + # Send the signal to the process group to kill any processes that may + # have been forked by the suggestion strategy + kill -TERM -$_ZSH_GIT_PROMPT_ASYNC_PID 2>/dev/null + else + # Kill just the child process since it wasn't placed in a new process + # group. If the suggestion strategy forked any child processes they may + # be orphaned and left behind. + kill -TERM $_ZSH_GIT_PROMPT_ASYNC_PID 2>/dev/null + fi + fi + + # Fork a process to fetch the git status and open a pipe to read from it + exec {_ZSH_GIT_PROMPT_ASYNC_FD}< <( + # Tell parent process our pid + builtin echo $sysparams[pid] + + _zsh_git_prompt_git_status + [[ -n "$ZSH_GIT_PROMPT_ENABLE_SECONDARY" ]] \ + && builtin echo -n "##secondary##" \ + && _zsh_git_prompt_git_status_secondary + ) + + # There's a weird bug here where ^C stops working unless we force a fork + # See https://github.com/zsh-users/zsh-autosuggestions/issues/364 + command true + + # Read the pid from the child process + read _ZSH_GIT_PROMPT_ASYNC_PID <&$_ZSH_GIT_PROMPT_ASYNC_FD + + # When the fd is readable, call the response handler + zle -F "$_ZSH_GIT_PROMPT_ASYNC_FD" _zsh_git_prompt_callback +} + +# Called when new data is ready to be read from the pipe +# First arg will be fd ready for reading +# Second arg will be passed in case of error +_ZSH_GIT_PROMPT_STATUS_OUTPUT="" +_ZSH_GIT_PROMPT_STATUS_SECONDARY_OUTPUT="" +function _zsh_git_prompt_callback() { + emulate -L zsh + local old_primary="$_ZSH_GIT_PROMPT_STATUS_OUTPUT" + local old_secondary="$_ZSH_GIT_PROMPT_STATUS_SECONDARY_OUTPUT" + local fd_data + local -a output + + if [[ -z "$2" || "$2" == "hup" ]]; then + # Read output from fd + fd_data="$(cat <&$1)" + output=( ${(s:##secondary##:)fd_data} ) + _ZSH_GIT_PROMPT_STATUS_OUTPUT="${output[1]}" + _ZSH_GIT_PROMPT_STATUS_SECONDARY_OUTPUT="${output[2]}" + + if [[ "$old_primary" != "$_ZSH_GIT_PROMPT_STATUS_OUTPUT" ]] \ + || [[ "$old_secondary" != "$_ZSH_GIT_PROMPT_STATUS_SECONDARY_OUTPUT" ]] ; then + zle reset-prompt + zle -R + fi + + # Close the fd + exec {1}<&- + fi + + # Always remove the handler + zle -F "$1" + + # Unset global FD variable to prevent closing user created FDs in the precmd hook + unset _ZSH_GIT_PROMPT_ASYNC_FD +} + +function _zsh_git_prompt_precmd_hook() { + if [[ -n "$ZSH_GIT_PROMPT_FORCE_BLANK" ]]; then + _ZSH_GIT_PROMPT_STATUS_OUTPUT="" + _ZSH_GIT_PROMPT_STATUS_SECONDARY_OUTPUT="" + fi + _zsh_git_prompt_async_request +} + +if (( $+commands[git] )); then + if [[ -z "$ZSH_GIT_PROMPT_NO_ASYNC" ]]; then + autoload -U add-zsh-hook \ + && add-zsh-hook precmd _zsh_git_prompt_precmd_hook + + function gitprompt() { + echo -n "$_ZSH_GIT_PROMPT_STATUS_OUTPUT" + } + + function gitprompt_secondary() { + echo -n "$_ZSH_GIT_PROMPT_STATUS_SECONDARY_OUTPUT" + } + else + function gitprompt() { + _zsh_git_prompt_git_status + } + + function gitprompt_secondary() { + [[ -n "$ZSH_GIT_PROMPT_ENABLE_SECONDARY" ]] \ + && _zsh_git_prompt_git_status_secondary + } + fi +else + function gitprompt() { } + function gitprompt_secondary() { } +fi |