]> git.madduck.net Git - etc/zsh.git/blob - .zsh/func/prompt_madduck_setup

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

add aliases
[etc/zsh.git] / .zsh / func / prompt_madduck_setup
1 #
2 # My zsh prompt theme
3 #
4 # Copyright © 1994–2017 martin f. krafft <madduck@madduck.net>
5 # Released under the terms of the Artistic Licence 2.0
6 #
7 # Source repository: http://git.madduck.net/v/etc/zsh.git
8 #
9 # vcs stuff shamelessly based on http://glandium.org/blog/?p=170
10 #
11
12 zstyle -m :madduck:prompt:default path-maxlen '*' \
13   || zstyle :madduck:prompt:default path-maxlen 25
14 zstyle -m :madduck:prompt:default path-minlen '*' \
15   || zstyle :madduck:prompt:default path-minlen 10
16
17 __on_networkfs()
18 {
19   emulate -L zsh
20   case $(df -T . | sed -rne '$s,^[^[:space:]]+[[:space:]]+([^[:space:]]+).*,\1,p') in
21     (cifs|nfs) return 0;;
22   esac
23   return 1
24 }
25
26 __git_get_reporoot()
27 {
28   emulate -L zsh
29   # return the full path to the root of the current git repository
30   [ -d "$GIT_DIR" ] && echo "$GIT_DIR" && return 0
31   local dir; dir="$PWD/$(git rev-parse --show-cdup)"
32   # do not use --show-toplevel because it resolves symlinks
33   echo $dir:a
34 }
35
36 __git_get_branch()
37 {
38   emulate -L zsh
39   # use oh-my-zsh prompt info function if it exists
40   $(command -v git_prompt_info) && return
41
42   # return the name of the git branch we're on
43   local ref gitdir
44   gitdir="$(git rev-parse --git-dir)"
45   ref=$(git --git-dir="$gitdir" symbolic-ref -q HEAD 2>/dev/null \
46      || git --git-dir="$gitdir" name-rev --name-only HEAD 2>/dev/null) || return 1
47   echo "${ref#refs/heads/}"
48 }
49
50 __git_print_preprompt()
51 {
52   emulate -L zsh
53   [ "$(git config --get core.bare)" = false ] || return
54   __on_networkfs && return
55
56   local COLUMNS=${COLUMNS:-80}
57   local LINES=${LINES:-25}
58
59   function output() {
60     emulate -L zsh
61     local title="$@"
62     local output=(${(f)"$(cat)"})
63
64     [[ ${#output} -ge 1 ]] || return
65
66     local statl="$(echo ${output[-1]} | sed -re 's@^\s*([0-9]+)[^,]+(, ([0-9]+) [^(]+\(([-+])\))(, ([0-9]+) [^(]+\(([-+])\))?@\1/\4\3/\7\6@')"
67
68     if [[ ${output[-2]## } = '...' ]]; then
69       print "${title} (${statl%/}, abbrev.):"
70       print "${(F)output[1,-3]}"
71       print " …"
72     else
73       print "${title} (${statl%/}):"
74       print "${(F)output[1,-2]}"
75     fi
76   }
77
78   function gitdiffstat() {
79     emulate -L zsh
80     local common_options="--stat=$((COLUMNS/2-1)),$((COLUMNS/4-2)),$(($LINES/3)) --relative"
81     eval git diff $common_options "$@" 2>/dev/null
82   }
83
84   local cached=(${(f)"$(gitdiffstat --cached | output cached)"})
85   local changed=(${(f)"$(gitdiffstat | output changed)"})
86
87   local max=${#changed}
88   [[ $max -lt ${#cached} ]] && max=${#cached}
89
90   ((max == 0)) && return
91
92   local width=$(((COLUMNS-3)/2))
93
94   if (( ${#cached} > 0 && ${#changed} > 0 )); then
95     local i
96     for (( i=1 ; i <= max ; i++ )) do
97       printf "%-${width}s │ %-${width}s\n" "${cached[$i]}" "${changed[$i]}"
98     done
99   else
100     print ${(F)cached}${(F)changed}
101   fi
102 }
103
104 __hg_get_reporoot()
105 {
106   emulate -L zsh
107   hg root
108 }
109
110 __hg_get_branch()
111 {
112   emulate -L zsh
113   echo "hg:$(hg branch)"
114 }
115
116 __bzr_get_reporoot()
117 {
118   emulate -L zsh
119   local reporoot
120   reporoot="$(bzr info | sed -rne 's, *branch root: ,,p')"
121   case "$reporoot" in
122     .) echo "$PWD";;
123     *) echo "$reporoot";;
124   esac
125 }
126
127 __bzr_get_branch()
128 {
129   emulate -L zsh
130   local branch revno
131   bzr version-info | while read i j; do
132       case "$i" in
133         revno:) revno="$j";;
134         branch-nick:) branch="$j";;
135       esac
136     done
137   echo "bzr:${branch}@$revno"
138 }
139
140 __vcs_get_repo_type()
141 {
142   emulate -L zsh
143   # return the type of the closest repository in the path hierarchy
144   local dir
145   while true; do
146     [ -d ${dir}.git ] && echo git && break
147     [ -d "$GIT_DIR" ] && echo git && break
148     [ -d ${dir}.bzr ] && echo bzr && break
149     [ -d ${dir}.hg ] && echo hg && break
150     [ "$(readlink -f ${dir:-.})" = / ] && echo NONE && break
151     dir="../$dir"
152   done
153 }
154
155 __get_prompt_path_len() {
156   emulate -L zsh
157   local result
158   zstyle -s ":madduck:prompt:$PWD" path-${1}len result
159   [ -z "$result" ] && zstyle -s ':madduck:prompt:default' path-${1}len result
160   echo $result
161 }
162
163 __vcs_get_prompt_path_components()
164 {
165   emulate -L zsh
166   # return formatted path components (prefix branch postfix) given
167   # the repository root and the branch.
168
169   local MAXLEN MINLEN
170   MAXLEN=$(__get_prompt_path_len max)
171   MINLEN=$(__get_prompt_path_len min)
172
173   # shortcut: if there are no arguments, return a default prompt
174   if [ -z "${1:-}" ]; then
175     pwdnamed="${(%):-%${MAXLEN}<…<%~%<<}"
176     echo "$pwdnamed"
177     return
178   fi
179
180   local reporoot branch
181   reporoot="${1%%/}"
182   branch="$2"
183
184   # replace named directories in the PWD, we need thi for the proper component
185   # count later
186   local pwdnamed
187   pwdnamed="${(%):-%~}"
188
189   # store paths in arrays for component count calculation
190   typeset -la apwd apwdnamed areporoot
191   apwd=(${(s:/:)PWD})
192   apwdnamed=(${(s:/:)pwdnamed})
193   areporoot=(${(s:/:)reporoot})
194
195   # get the number of leading and trailing path components. Since we're using
196   # %~ later and then /home/madduck suddenly becomes ~, which is 1, not
197   # 2 components, we calculate the leading component count by using the named
198   # path and the number of post components
199   local precomps postcomps
200   postcomps=$(($#apwd - $#areporoot))
201   precomps=$(($#apwdnamed - $postcomps))
202
203   local postfix
204   (( $postcomps > 0 )) && postfix="${(%):-%${postcomps}~}"
205
206   # we don't want the prompt to get too long, so keep the total prompt length
207   # under $MAXLEN, but ensure that the prefix is not shorter
208   # than $MINLEN, no matter what
209   local prelen minlen prefix
210   prelen=$((${MAXLEN} - $#branch - $#postfix))
211   minlen=${MINLEN}
212   (( $prelen < $minlen )) && prelen=$minlen
213   prefix="${(%):-%${prelen}<…<%-${precomps}~%<<}"
214
215   echo "'$prefix'" "'$branch'" "'$postfix'"
216 }
217
218 __vcs_set_prompt_variables()
219 {
220   emulate -L zsh
221   # set psvar[1..3] depending on repo type, or just psvar[1] if no repo found
222   local reporoot branch repotype
223   repotype="${1:-$(__vcs_get_repo_type)}"
224
225   case "$repotype" in
226     git)
227       reporoot="$(__git_get_reporoot)" ||
228         { zerror "could not determine git repository root"; return 1 }
229       branch="$(__git_get_branch)" ||
230         { zerror "could not determine git branch"; return 1 }
231       if [ -n "$VCSH_REPO_NAME" ]; then
232         # if vcsh is used to get a subshell, then the repo root is the home
233         # directory, but we want to indicate the vcsh context too:
234         eval set -- $(__vcs_get_prompt_path_components "$HOME" "$branch")
235         set -- "vcsh:$VCSH_REPO_NAME" "$2" "$1${3:+/$3}"
236       else
237         eval set -- $(__vcs_get_prompt_path_components "$reporoot" "$branch")
238         if [ -d "$GIT_DIR" ]; then
239           # poor man's replace until I find out how to do named dirs properly
240           # here:
241           local _D="${GIT_DIR/$HOME/~}"
242           set -- "$_D" "$2" "${${1#$_D}%/}"
243         fi
244       fi
245       ;;
246     hg)
247       reporoot="$(__hg_get_reporoot)" ||
248         { zerror "could not determine hg repository root"; return 1 }
249       branch="$(__hg_get_branch)" ||
250         { zerror "could not determine hg branch"; return 1 }
251       eval set -- $(__vcs_get_prompt_path_components "$reporoot" "$branch")
252       ;;
253     bzr)
254       reporoot="$(__bzr_get_reporoot)" ||
255         { zerror "could not determine bzr repository root"; return 1 }
256       branch="$(__bzr_get_branch)" ||
257         { zerror "could not determine bzr branch"; return 1 }
258       eval set -- $(__vcs_get_prompt_path_components "$reporoot" "$branch")
259       ;;
260     *)
261       case "$repotype" in
262         NONE) :;;
263         *) warn "$repotype repositories not (yet) supported in the prompt";;
264       esac
265       local MAXLEN MINLEN
266       MAXLEN=$(__get_prompt_path_len max)
267       local p="%${MAXLEN}<…<%~%<<"
268       #TODO find a better way so we don't have to nuke $psvar, but since the
269       #     %(nv.true.false) check for prompts checks element count, not
270       #     content, that's all we get for now
271       psvar=("${(%)p}")
272       return
273   esac
274
275   psvar[1,3]=($1 $2 $3)
276 }
277
278 __vcs_print_preprompt()
279 {
280   emulate -L zsh
281   local repotype="${1:-$(__vcs_get_repo_type)}"
282
283   case "$repotype" in
284     git)
285       __git_print_preprompt
286       ;;
287   esac
288 }
289
290 if ! is_root; then
291   # too dangerous to be run as root
292   autoload -U add-zsh-hook
293
294   _update_vcs_prompt_vars_if_vcs_ran() {
295     local vcs="$(__vcs_get_repo_type)"
296     case "$(history $(($HISTCMD - 1)))" in
297       # $vcs appeared in last command, so be sure to update
298       *${vcs}*) __vcs_set_prompt_variables "$vcs"
299     esac
300   }
301   add-zsh-hook precmd _update_vcs_prompt_vars_if_vcs_ran
302
303   _update_vcs_prompt_vars() {
304     __vcs_set_prompt_variables
305   }
306   add-zsh-hook chpwd _update_vcs_prompt_vars
307
308   _print_preprompt() {
309     [[ $? -eq 0 ]] && __vcs_print_preprompt
310   }
311   add-zsh-hook precmd _print_preprompt
312
313   # call it once
314   _update_vcs_prompt_vars
315 fi
316
317 function make_ps1() {
318   # start with '+' if in a subshell
319   echo -n '%(2L.+.)'
320
321   # the machine name, bold or underlined based on non-root/root
322   local ps1_hl=B
323   is_root && ps1_hl=U
324   echo -n "%${ps1_hl:=B}%m%${(L)ps1_hl}"
325
326   # if we're in a Debian chroot, make that stand out
327   echo -n "${DEBIAN_CHROOT:+/%S$DEBIAN_CHROOT%s}"
328
329   # we end this with a :
330   echo -n :
331
332   # now comes the working directory, composed from parts in $psvar,
333   # which is managed by $ZDOTDIR/zshrc/06-vcsprompt
334   echo -n '%1v%(2v.|%B%2v%b|.)%(3v.%3v.)'
335
336   # and we finish with #/% for root/non-root, and a space
337   echo -n '%# '
338   echo
339 }
340 PS1=$(make_ps1)
341 unfunction make_ps1
342 PS2="%{$fg[red]%}%_>%{$reset_color%}"
343
344 function make_rps1() {
345   # First, a comment character and parens
346   echo -n '#('
347
348   # Next, if the returncode was non-zero, make it stand-out
349   # and include a trailing space
350   echo -n "%(0?..%{$fg[red]%}%S%?%s%{$reset_color%} )"
351
352   # If there are background jobs, print their number, followed by
353   # '@':
354   echo -n '%(1j.%j@.)'
355
356   # and then the terminal line we're using
357   echo -n '%l'
358
359   # this concludes the first part, but there's more
360   echo -n ') '
361
362   # the timestamp will finish it off:
363   echo -n '%D{%d %H:%M:%S.%.}'
364   echo
365 }
366 RPS1=$(make_rps1)
367 unfunction make_rps1
368
369 # vim:ft=zsh