-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
z4rr3t.zsh
742 lines (615 loc) · 23.8 KB
/
z4rr3t.zsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
# z4rr3t
# https://github.com/inimicus/z4rr3t
# Based on Pure
# by Sindre Sorhus
# https://github.com/sindresorhus/Pure
# MIT License
# For my own and others sanity
# git:
# %b => current branch
# %a => current action (rebase/merge)
# prompt:
# %F => color dict
# %f => reset color
# %~ => current path
# %* => time
# %n => username
# %m => shortname host
# %(?..) => prompt conditional - %(condition.true.false)
# terminal codes:
# \e7 => save cursor position
# \e[2A => move cursor 2 lines up
# \e[1G => go to position 1 in terminal
# \e8 => restore cursor position
# \e[K => clears everything after the cursor on the current line
# \e[2K => clear everything on the current line
# Turns seconds into human readable time.
# 165392 => 1d 21h 56m 32s
# https://github.com/sindresorhus/pretty-time-zsh
prompt_z4rr3t_human_time_to_var() {
local human total_seconds=$1 var=$2
local days=$(( total_seconds / 60 / 60 / 24 ))
local hours=$(( total_seconds / 60 / 60 % 24 ))
local minutes=$(( total_seconds / 60 % 60 ))
local seconds=$(( total_seconds % 60 ))
(( days > 0 )) && human+="${days}d "
(( hours > 0 )) && human+="${hours}h "
(( minutes > 0 )) && human+="${minutes}m "
human+="${seconds}s"
# Store human readable time in a variable as specified by the caller
typeset -g "${var}"="${human}"
}
# Stores (into prompt_z4rr3t_cmd_exec_time) the execution
# time of the last command if set threshold was exceeded.
prompt_z4rr3t_check_cmd_exec_time() {
integer elapsed
(( elapsed = EPOCHSECONDS - ${prompt_z4rr3t_cmd_timestamp:-$EPOCHSECONDS} ))
typeset -g prompt_z4rr3t_cmd_exec_time=
(( elapsed > ${Z4RR3T_CMD_MAX_EXEC_TIME:-5} )) && {
prompt_z4rr3t_human_time_to_var $elapsed "prompt_z4rr3t_cmd_exec_time"
}
}
prompt_z4rr3t_set_title() {
setopt localoptions noshwordsplit
# Emacs terminal does not support settings the title.
(( ${+EMACS} )) && return
case $TTY in
# Don't set title over serial console.
/dev/ttyS[0-9]*) return;;
esac
# Show hostname if connected via SSH.
local hostname=
if [[ -n $prompt_z4rr3t_state[username] ]]; then
# Expand in-place in case ignore-escape is used.
hostname="${(%):-(%m) }"
fi
local -a opts
case $1 in
expand-prompt) opts=(-P);;
ignore-escape) opts=(-r);;
esac
# Set title atomically in one print statement so that it works when XTRACE is enabled.
print -n $opts $'\e]0;'${hostname}${2}$'\a'
}
prompt_z4rr3t_preexec() {
if [[ -n $prompt_z4rr3t_git_fetch_pattern ]]; then
# Detect when Git is performing pull/fetch, including Git aliases.
local -H MATCH MBEGIN MEND match mbegin mend
if [[ $2 =~ (git|hub)\ (.*\ )?($prompt_z4rr3t_git_fetch_pattern)(\ .*)?$ ]]; then
# We must flush the async jobs to cancel our git fetch in order
# to avoid conflicts with the user issued pull / fetch.
async_flush_jobs 'prompt_z4rr3t'
fi
fi
typeset -g prompt_z4rr3t_cmd_timestamp=$EPOCHSECONDS
# Shows the current directory and executed command in the title while a process is active.
prompt_z4rr3t_set_title 'ignore-escape' "$PWD:t: $2"
# Disallow Python virtualenv from updating the prompt. Set it to 12 if
# untouched by the user to indicate that z4rr3t modified it. Here we use
# the magic number 12, same as in `psvar`.
export VIRTUAL_ENV_DISABLE_PROMPT=${VIRTUAL_ENV_DISABLE_PROMPT:-12}
}
# Change the colors if their value are different from the current ones.
prompt_z4rr3t_set_colors() {
local color_temp key value
for key value in ${(kv)prompt_z4rr3t_colors}; do
zstyle -t ":prompt:z4rr3t:$key" color "$value"
case $? in
1) # The current style is different from the one from zstyle.
zstyle -s ":prompt:z4rr3t:$key" color color_temp
prompt_z4rr3t_colors[$key]=$color_temp ;;
2) # No style is defined.
prompt_z4rr3t_colors[$key]=$prompt_z4rr3t_colors_default[$key] ;;
esac
done
}
prompt_z4rr3t_preprompt_render() {
setopt localoptions noshwordsplit
# Set color for Git branch/dirty status and change color if dirty checking has been delayed.
local git_color=$prompt_z4rr3t_colors[git:branch]
[[ -n ${prompt_z4rr3t_git_last_dirty_check_timestamp+x} ]] && git_color=$prompt_z4rr3t_colors[git:branch:cached]
# Initialize the preprompt array.
local -a preprompt_parts
# Username and machine, if applicable.
[[ -n $prompt_z4rr3t_state[username] ]] && preprompt_parts+=($prompt_z4rr3t_state[username])
# Set the path.
preprompt_parts+=('%F{${prompt_z4rr3t_colors[path]}}%~%f')
# Add Git branch and dirty status info.
typeset -gA prompt_z4rr3t_vcs_info
if [[ -n $prompt_z4rr3t_vcs_info[branch] ]]; then
preprompt_parts+=("%F{$git_color}"'${prompt_z4rr3t_vcs_info[branch]}${prompt_z4rr3t_git_dirty}%f')
fi
# Git pull/push arrows.
if [[ -n $prompt_z4rr3t_git_arrows ]]; then
preprompt_parts+=('%F{$prompt_z4rr3t_colors[git:arrow]}${prompt_z4rr3t_git_arrows}%f')
fi
# Execution time.
[[ -n $prompt_z4rr3t_cmd_exec_time ]] && preprompt_parts+=('%F{$prompt_z4rr3t_colors[execution_time]}${prompt_z4rr3t_cmd_exec_time}%f')
local cleaned_ps1=$PROMPT
local -H MATCH MBEGIN MEND
if [[ $PROMPT = *$prompt_newline* ]]; then
# Remove everything from the prompt until the newline. This
# removes the preprompt and only the original PROMPT remains.
cleaned_ps1=${PROMPT##*${prompt_newline}}
fi
unset MATCH MBEGIN MEND
# Construct the new prompt with a clean preprompt.
local -ah ps1
ps1=(
${(j. .)preprompt_parts} # Join parts, space separated.
$prompt_newline # Separate preprompt and prompt.
$cleaned_ps1
)
PROMPT="${(j..)ps1}"
# Expand the prompt for future comparision.
local expanded_prompt
expanded_prompt="${(S%%)PROMPT}"
if [[ $1 == precmd ]]; then
# Initial newline, for spaciousness.
# print
elif [[ $prompt_z4rr3t_last_prompt != $expanded_prompt ]]; then
# Redraw the prompt.
prompt_z4rr3t_reset_prompt
fi
typeset -g prompt_z4rr3t_last_prompt=$expanded_prompt
}
prompt_z4rr3t_precmd() {
# Check execution time and store it in a variable.
prompt_z4rr3t_check_cmd_exec_time
unset prompt_z4rr3t_cmd_timestamp
# Shows the full path in the title.
prompt_z4rr3t_set_title 'expand-prompt' '%~'
# Modify the colors if some have changed..
prompt_z4rr3t_set_colors
# Perform async Git dirty check and fetch.
prompt_z4rr3t_async_tasks
# Check if we should display the virtual env. We use a sufficiently high
# index of psvar (12) here to avoid collisions with user defined entries.
psvar[12]=
# Check if a Conda environment is active and display its name.
if [[ -n $CONDA_DEFAULT_ENV ]]; then
psvar[12]="${CONDA_DEFAULT_ENV//[$'\t\r\n']}"
fi
# When VIRTUAL_ENV_DISABLE_PROMPT is empty, it was unset by the user and
# z4rr3t should take back control.
if [[ -n $VIRTUAL_ENV ]] && [[ -z $VIRTUAL_ENV_DISABLE_PROMPT || $VIRTUAL_ENV_DISABLE_PROMPT = 12 ]]; then
psvar[12]="${VIRTUAL_ENV:t}"
export VIRTUAL_ENV_DISABLE_PROMPT=12
fi
# Make sure VIM prompt is reset.
prompt_z4rr3t_reset_prompt_symbol
# Print the preprompt.
prompt_z4rr3t_preprompt_render "precmd"
if [[ -n $ZSH_THEME ]]; then
print "WARNING: Oh My Zsh themes are enabled (ZSH_THEME='${ZSH_THEME}'). z4rr3t might not be working correctly."
print "For more information, see: https://github.com/sindresorhus/z4rr3t#oh-my-zsh"
unset ZSH_THEME # Only show this warning once.
fi
}
prompt_z4rr3t_async_git_aliases() {
setopt localoptions noshwordsplit
local -a gitalias pullalias
# List all aliases and split on newline.
gitalias=(${(@f)"$(command git config --get-regexp "^alias\.")"})
for line in $gitalias; do
parts=(${(@)=line}) # Split line on spaces.
aliasname=${parts[1]#alias.} # Grab the name (alias.[name]).
shift parts # Remove `aliasname`
# Check alias for pull or fetch. Must be exact match.
if [[ $parts =~ ^(.*\ )?(pull|fetch)(\ .*)?$ ]]; then
pullalias+=($aliasname)
fi
done
print -- ${(j:|:)pullalias} # Join on pipe, for use in regex.
}
prompt_z4rr3t_async_vcs_info() {
setopt localoptions noshwordsplit
# Configure `vcs_info` inside an async task. This frees up `vcs_info`
# to be used or configured as the user pleases.
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*' use-simple true
# Only export two message variables from `vcs_info`.
zstyle ':vcs_info:*' max-exports 2
# Export branch (%b) and Git toplevel (%R).
zstyle ':vcs_info:git*' formats '%b' '%R'
zstyle ':vcs_info:git*' actionformats '%b|%a' '%R'
vcs_info
local -A info
info[pwd]=$PWD
info[top]=$vcs_info_msg_1_
info[branch]=$vcs_info_msg_0_
print -r - ${(@kvq)info}
}
# Fastest possible way to check if a Git repo is dirty.
prompt_z4rr3t_async_git_dirty() {
setopt localoptions noshwordsplit
local untracked_dirty=$1
if [[ $untracked_dirty = 0 ]]; then
command git diff --no-ext-diff --quiet --exit-code
else
test -z "$(command git status --porcelain --ignore-submodules -unormal)"
fi
return $?
}
prompt_z4rr3t_async_git_fetch() {
setopt localoptions noshwordsplit
# Sets `GIT_TERMINAL_PROMPT=0` to disable authentication prompt for Git fetch (Git 2.3+).
export GIT_TERMINAL_PROMPT=0
# Set SSH `BachMode` to disable all interactive SSH password prompting.
export GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-"ssh"} -o BatchMode=yes"
# Default return code, which indicates Git fetch failure.
local fail_code=99
# Guard against all forms of password prompts. By setting the shell into
# MONITOR mode we can notice when a child process prompts for user input
# because it will be suspended. Since we are inside an async worker, we
# have no way of transmitting the password and the only option is to
# kill it. If we don't do it this way, the process will corrupt with the
# async worker.
setopt localtraps monitor
# Make sure local HUP trap is unset to allow for signal propagation when
# the async worker is flushed.
trap - HUP
trap '
# Unset trap to prevent infinite loop
trap - CHLD
if [[ $jobstates = suspended* ]]; then
# Set fail code to password prompt and kill the fetch.
fail_code=98
kill %%
fi
' CHLD
command git -c gc.auto=0 fetch >/dev/null &
wait $! || return $fail_code
unsetopt monitor
# Check arrow status after a successful `git fetch`.
prompt_z4rr3t_async_git_arrows
}
prompt_z4rr3t_async_git_arrows() {
setopt localoptions noshwordsplit
command git rev-list --left-right --count HEAD...@'{u}'
}
prompt_z4rr3t_async_tasks() {
setopt localoptions noshwordsplit
# Initialize the async worker.
((!${prompt_z4rr3t_async_init:-0})) && {
async_start_worker "prompt_z4rr3t" -u -n
async_register_callback "prompt_z4rr3t" prompt_z4rr3t_async_callback
typeset -g prompt_z4rr3t_async_init=1
}
# Update the current working directory of the async worker.
async_worker_eval "prompt_z4rr3t" builtin cd -q $PWD
typeset -gA prompt_z4rr3t_vcs_info
local -H MATCH MBEGIN MEND
if [[ $PWD != ${prompt_z4rr3t_vcs_info[pwd]}* ]]; then
# Stop any running async jobs.
async_flush_jobs "prompt_z4rr3t"
# Reset Git preprompt variables, switching working tree.
unset prompt_z4rr3t_git_dirty
unset prompt_z4rr3t_git_last_dirty_check_timestamp
unset prompt_z4rr3t_git_arrows
unset prompt_z4rr3t_git_fetch_pattern
prompt_z4rr3t_vcs_info[branch]=
prompt_z4rr3t_vcs_info[top]=
fi
unset MATCH MBEGIN MEND
async_job "prompt_z4rr3t" prompt_z4rr3t_async_vcs_info
# Only perform tasks inside a Git working tree.
[[ -n $prompt_z4rr3t_vcs_info[top] ]] || return
prompt_z4rr3t_async_refresh
}
prompt_z4rr3t_async_refresh() {
setopt localoptions noshwordsplit
if [[ -z $prompt_z4rr3t_git_fetch_pattern ]]; then
# We set the pattern here to avoid redoing the pattern check until the
# working three has changed. Pull and fetch are always valid patterns.
typeset -g prompt_z4rr3t_git_fetch_pattern="pull|fetch"
async_job "prompt_z4rr3t" prompt_z4rr3t_async_git_aliases
fi
async_job "prompt_z4rr3t" prompt_z4rr3t_async_git_arrows
# Do not preform `git fetch` if it is disabled or in home folder.
if (( ${Z4RR3T_GIT_PULL:-1} )) && [[ $prompt_z4rr3t_vcs_info[top] != $HOME ]]; then
# Tell the async worker to do a `git fetch`.
async_job "prompt_z4rr3t" prompt_z4rr3t_async_git_fetch
fi
# If dirty checking is sufficiently fast,
# tell the worker to check it again, or wait for timeout.
integer time_since_last_dirty_check=$(( EPOCHSECONDS - ${prompt_z4rr3t_git_last_dirty_check_timestamp:-0} ))
if (( time_since_last_dirty_check > ${Z4RR3T_GIT_DELAY_DIRTY_CHECK:-1800} )); then
unset prompt_z4rr3t_git_last_dirty_check_timestamp
# Check check if there is anything to pull.
async_job "prompt_z4rr3t" prompt_z4rr3t_async_git_dirty ${Z4RR3T_GIT_UNTRACKED_DIRTY:-1}
fi
}
prompt_z4rr3t_check_git_arrows() {
setopt localoptions noshwordsplit
local arrows left=${1:-0} right=${2:-0}
(( right > 0 )) && arrows+=${Z4RR3T_GIT_DOWN_ARROW:-⇣}
(( left > 0 )) && arrows+=${Z4RR3T_GIT_UP_ARROW:-⇡}
[[ -n $arrows ]] || return
typeset -g REPLY=$arrows
}
prompt_z4rr3t_async_callback() {
setopt localoptions noshwordsplit
local job=$1 code=$2 output=$3 exec_time=$4 next_pending=$6
local do_render=0
case $job in
\[async])
# Code is 1 for corrupted worker output and 2 for dead worker.
if [[ $code -eq 2 ]]; then
# Our worker died unexpectedly.
typeset -g prompt_z4rr3t_async_init=0
fi
;;
prompt_z4rr3t_async_vcs_info)
local -A info
typeset -gA prompt_z4rr3t_vcs_info
# Parse output (z) and unquote as array (Q@).
info=("${(Q@)${(z)output}}")
local -H MATCH MBEGIN MEND
if [[ $info[pwd] != $PWD ]]; then
# The path has changed since the check started, abort.
return
fi
# Check if Git top-level has changed.
if [[ $info[top] = $prompt_z4rr3t_vcs_info[top] ]]; then
# If the stored pwd is part of $PWD, $PWD is shorter and likelier
# to be top-level, so we update pwd.
if [[ $prompt_z4rr3t_vcs_info[pwd] = ${PWD}* ]]; then
prompt_z4rr3t_vcs_info[pwd]=$PWD
fi
else
# Store $PWD to detect if we (maybe) left the Git path.
prompt_z4rr3t_vcs_info[pwd]=$PWD
fi
unset MATCH MBEGIN MEND
# The update has a Git top-level set, which means we just entered a new
# Git directory. Run the async refresh tasks.
[[ -n $info[top] ]] && [[ -z $prompt_z4rr3t_vcs_info[top] ]] && prompt_z4rr3t_async_refresh
# Always update branch and top-level.
prompt_z4rr3t_vcs_info[branch]=$info[branch]
prompt_z4rr3t_vcs_info[top]=$info[top]
do_render=1
;;
prompt_z4rr3t_async_git_aliases)
if [[ -n $output ]]; then
# Append custom Git aliases to the predefined ones.
prompt_z4rr3t_git_fetch_pattern+="|$output"
fi
;;
prompt_z4rr3t_async_git_dirty)
local prev_dirty=$prompt_z4rr3t_git_dirty
if (( code == 0 )); then
unset prompt_z4rr3t_git_dirty
else
typeset -g prompt_z4rr3t_git_dirty="⚡"
fi
[[ $prev_dirty != $prompt_z4rr3t_git_dirty ]] && do_render=1
# When `prompt_z4rr3t_git_last_dirty_check_timestamp` is set, the Git info is displayed
# in a different color. To distinguish between a "fresh" and a "cached" result, the
# preprompt is rendered before setting this variable. Thus, only upon the next
# rendering of the preprompt will the result appear in a different color.
(( $exec_time > 5 )) && prompt_z4rr3t_git_last_dirty_check_timestamp=$EPOCHSECONDS
;;
prompt_z4rr3t_async_git_fetch|prompt_z4rr3t_async_git_arrows)
# `prompt_z4rr3t_async_git_fetch` executes `prompt_z4rr3t_async_git_arrows`
# after a successful fetch.
case $code in
0)
local REPLY
prompt_z4rr3t_check_git_arrows ${(ps:\t:)output}
if [[ $prompt_z4rr3t_git_arrows != $REPLY ]]; then
typeset -g prompt_z4rr3t_git_arrows=$REPLY
do_render=1
fi
;;
99|98)
# Git fetch failed.
;;
*)
# Non-zero exit status from `prompt_z4rr3t_async_git_arrows`,
# indicating that there is no upstream configured.
if [[ -n $prompt_z4rr3t_git_arrows ]]; then
unset prompt_z4rr3t_git_arrows
do_render=1
fi
;;
esac
;;
esac
if (( next_pending )); then
(( do_render )) && typeset -g prompt_z4rr3t_async_render_requested=1
return
fi
[[ ${prompt_z4rr3t_async_render_requested:-$do_render} = 1 ]] && prompt_z4rr3t_preprompt_render
unset prompt_z4rr3t_async_render_requested
}
prompt_z4rr3t_reset_prompt() {
if [[ $CONTEXT == cont ]]; then
# When the context is "cont", PS2 is active and calling
# reset-prompt will have no effect on PS1, but it will
# reset the execution context (%_) of PS2 which we don't
# want. Unfortunately, we can't save the output of "%_"
# either because it is only ever rendered as part of the
# prompt, expanding in-place won't work.
return
fi
zle && zle .reset-prompt
}
prompt_z4rr3t_reset_prompt_symbol() {
prompt_z4rr3t_state[prompt]=${Z4RR3T_PROMPT_SYMBOL:-❯}
}
prompt_z4rr3t_update_vim_prompt_widget() {
setopt localoptions noshwordsplit
prompt_z4rr3t_state[prompt]=${${KEYMAP/vicmd/${Z4RR3T_PROMPT_VICMD_SYMBOL:-❮}}/(main|viins)/${Z4RR3T_PROMPT_SYMBOL:-❯}}
prompt_z4rr3t_reset_prompt
}
prompt_z4rr3t_reset_vim_prompt_widget() {
setopt localoptions noshwordsplit
prompt_z4rr3t_reset_prompt_symbol
# We can't perform a prompt reset at this point because it
# removes the prompt marks inserted by macOS Terminal.
}
prompt_z4rr3t_state_setup() {
setopt localoptions noshwordsplit
# Check SSH_CONNECTION and the current state.
local ssh_connection=${SSH_CONNECTION:-$PROMPT_Z4RR3T_SSH_CONNECTION}
local username hostname
if [[ -z $ssh_connection ]] && (( $+commands[who] )); then
# When changing user on a remote system, the $SSH_CONNECTION
# environment variable can be lost. Attempt detection via `who`.
local who_out
who_out=$(who -m 2>/dev/null)
if (( $? )); then
# Who am I not supported, fallback to plain who.
local -a who_in
who_in=( ${(f)"$(who 2>/dev/null)"} )
who_out="${(M)who_in:#*[[:space:]]${TTY#/dev/}[[:space:]]*}"
fi
local reIPv6='(([0-9a-fA-F]+:)|:){2,}[0-9a-fA-F]+' # Simplified, only checks partial pattern.
local reIPv4='([0-9]{1,3}\.){3}[0-9]+' # Simplified, allows invalid ranges.
# Here we assume two non-consecutive periods represents a
# hostname. This matches `foo.bar.baz`, but not `foo.bar`.
local reHostname='([.][^. ]+){2}'
# Usually the remote address is surrounded by parenthesis, but
# not on all systems (e.g. busybox).
local -H MATCH MBEGIN MEND
if [[ $who_out =~ "\(?($reIPv4|$reIPv6|$reHostname)\)?\$" ]]; then
ssh_connection=$MATCH
# Export variable to allow detection propagation inside
# shells spawned by this one (e.g. tmux does not always
# inherit the same tty, which breaks detection).
export PROMPT_Z4RR3T_SSH_CONNECTION=$ssh_connection
fi
unset MATCH MBEGIN MEND
fi
hostname='%F{$prompt_z4rr3t_colors[at]}@%f%F{$prompt_z4rr3t_colors[host]}%m%f'
# Show `username@host` if logged in through SSH.
[[ -n $ssh_connection ]] && username='%F{$prompt_z4rr3t_colors[user]}%n%f'"$hostname"
# Show `username@host` if root, with username in default color.
[[ $UID -eq 0 ]] && username='%F{$prompt_z4rr3t_colors[user:root]}%n%f'"$hostname"
typeset -gA prompt_z4rr3t_state
prompt_z4rr3t_state[version]="1.10.3"
prompt_z4rr3t_state+=(
username "$username"
prompt "${Z4RR3T_PROMPT_SYMBOL:-❯}"
)
}
prompt_z4rr3t_system_report() {
setopt localoptions noshwordsplit
print - "- Zsh: $(zsh --version)"
print -n - "- Operating system: "
case "$(uname -s)" in
Darwin) print "$(sw_vers -productName) $(sw_vers -productVersion) ($(sw_vers -buildVersion))";;
*) print "$(uname -s) ($(uname -v))";;
esac
print - "- Terminal program: $TERM_PROGRAM ($TERM_PROGRAM_VERSION)"
local git_version
git_version=($(git --version)) # Remove newlines, if hub is present.
print - "- Git: $git_version"
print - "- z4rr3t state:"
for k v in "${(@kv)prompt_z4rr3t_state}"; do
print - "\t- $k: \`${(q)v}\`"
done
print - "- Virtualenv: \`$(typeset -p VIRTUAL_ENV_DISABLE_PROMPT)\`"
print - "- Prompt: \`$(typeset -p PROMPT)\`"
local ohmyzsh=0
typeset -la frameworks
(( $+ANTIBODY_HOME )) && frameworks+=("Antibody")
(( $+ADOTDIR )) && frameworks+=("Antigen")
(( $+ANTIGEN_HS_HOME )) && frameworks+=("Antigen-hs")
(( $+functions[upgrade_oh_my_zsh] )) && {
ohmyzsh=1
frameworks+=("Oh My Zsh")
}
(( $+ZPREZTODIR )) && frameworks+=("Prezto")
(( $+ZPLUG_ROOT )) && frameworks+=("Zplug")
(( $+ZPLGM )) && frameworks+=("Zplugin")
(( $#frameworks == 0 )) && frameworks+=("None")
print - "- Detected frameworks: ${(j:, :)frameworks}"
if (( ohmyzsh )); then
print - "\t- Oh My Zsh:"
print - "\t\t- Plugins: ${(j:, :)plugins}"
fi
}
prompt_z4rr3t_setup() {
# Prevent percentage showing up if output doesn't end with a newline.
export PROMPT_EOL_MARK=''
prompt_opts=(subst percent)
# Borrowed from `promptinit`. Sets the prompt options in case z4rr3t was not
# initialized via `promptinit`.
setopt noprompt{bang,cr,percent,subst} "prompt${^prompt_opts[@]}"
if [[ -z $prompt_newline ]]; then
# This variable needs to be set, usually set by promptinit.
typeset -g prompt_newline=$'\n%{\r%}'
fi
zmodload zsh/datetime
zmodload zsh/zle
zmodload zsh/parameter
zmodload zsh/zutil
autoload -Uz add-zsh-hook
autoload -Uz vcs_info
autoload -Uz async && async
# The `add-zle-hook-widget` function is not guaranteed to be available.
# It was added in Zsh 5.3.
autoload -Uz +X add-zle-hook-widget 2>/dev/null
# Set the colors.
typeset -gA prompt_z4rr3t_colors_default prompt_z4rr3t_colors
prompt_z4rr3t_colors_default=(
execution_time yellow
git:arrow cyan
git:branch 242
git:branch:cached red
host 242
path blue
prompt:error red
prompt:success magenta
user yellow
user:root default
at white
virtualenv 242
)
prompt_z4rr3t_colors=("${(@kv)prompt_z4rr3t_colors_default}")
add-zsh-hook precmd prompt_z4rr3t_precmd
add-zsh-hook preexec prompt_z4rr3t_preexec
prompt_z4rr3t_state_setup
zle -N prompt_z4rr3t_reset_prompt
zle -N prompt_z4rr3t_update_vim_prompt_widget
zle -N prompt_z4rr3t_reset_vim_prompt_widget
if (( $+functions[add-zle-hook-widget] )); then
add-zle-hook-widget zle-line-finish prompt_z4rr3t_reset_vim_prompt_widget
add-zle-hook-widget zle-keymap-select prompt_z4rr3t_update_vim_prompt_widget
fi
# If a virtualenv is activated, display it in grey.
PROMPT='%(12V.%F{$prompt_z4rr3t_colors[virtualenv]}%12v%f .)'
# Prompt turns red if the previous command didn't exit with 0.
PROMPT+='%(?.%F{$prompt_z4rr3t_colors[prompt:success]}.%F{$prompt_z4rr3t_colors[prompt:error]})${prompt_z4rr3t_state[prompt]}%f '
# Indicate continuation prompt by ... and use a darker color for it.
PROMPT2='%F{242}... %(1_.%_ .%_)%f%(?.%F{magenta}.%F{red})${prompt_z4rr3t_state[prompt]}%f '
# Store prompt expansion symbols for in-place expansion via (%). For
# some reason it does not work without storing them in a variable first.
typeset -ga prompt_z4rr3t_debug_depth
prompt_z4rr3t_debug_depth=('%e' '%N' '%x')
# Compare is used to check if %N equals %x. When they differ, the main
# prompt is used to allow displaying both filename and function. When
# they match, we use the secondary prompt to avoid displaying duplicate
# information.
local -A ps4_parts
ps4_parts=(
depth '%F{yellow}${(l:${(%)prompt_z4rr3t_debug_depth[1]}::+:)}%f'
compare '${${(%)prompt_z4rr3t_debug_depth[2]}:#${(%)prompt_z4rr3t_debug_depth[3]}}'
main '%F{blue}${${(%)prompt_z4rr3t_debug_depth[3]}:t}%f%F{242}:%I%f %F{242}@%f%F{blue}%N%f%F{242}:%i%f'
secondary '%F{blue}%N%f%F{242}:%i'
prompt '%F{242}>%f '
)
# Combine the parts with conditional logic. First the `:+` operator is
# used to replace `compare` either with `main` or an ampty string. Then
# the `:-` operator is used so that if `compare` becomes an empty
# string, it is replaced with `secondary`.
local ps4_symbols='${${'${ps4_parts[compare]}':+"'${ps4_parts[main]}'"}:-"'${ps4_parts[secondary]}'"}'
# Improve the debug prompt (PS4), show depth by repeating the +-sign and
# add colors to highlight essential parts like file and function name.
PROMPT4="${ps4_parts[depth]} ${ps4_symbols}${ps4_parts[prompt]}"
# Guard against Oh My Zsh themes overriding z4rr3t.
unset ZSH_THEME
}
prompt_z4rr3t_setup "$@"