-
Notifications
You must be signed in to change notification settings - Fork 2
/
zshrc
555 lines (475 loc) · 19.7 KB
/
zshrc
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
# -*- mode: sh -*-
#
# History. Adapted from the prezto history module.
#
setopt BANG_HIST # Treat the '!' character specially during expansion.
setopt EXTENDED_HISTORY # Write the history file in the ':start:elapsed;command' format.
setopt SHARE_HISTORY # Share history between all sessions.
setopt HIST_EXPIRE_DUPS_FIRST # Expire a duplicate event first when trimming history.
setopt HIST_IGNORE_DUPS # Do not record an event that was just recorded again.
setopt HIST_IGNORE_ALL_DUPS # Delete an old recorded event if a new event is a duplicate.
setopt HIST_FIND_NO_DUPS # Do not display a previously found event.
setopt HIST_IGNORE_SPACE # Do not record an event starting with a space.
setopt HIST_SAVE_NO_DUPS # Do not write a duplicate event to the history file.
setopt HIST_VERIFY # Do not execute immediately upon history expansion.
setopt HIST_BEEP # Beep when accessing non-existent history.
HISTFILE="$HOME/.zhistory" # The path to the history file.
HISTSIZE=100000 # The maximum number of events to save in the internal history.
SAVEHIST=100000 # The maximum number of events to save in the history file.
#
# Completion. Adapted from the prezto completion module.
#
setopt COMPLETE_IN_WORD # Complete from both ends of a word.
setopt ALWAYS_TO_END # Move cursor to the end of a completed word.
setopt PATH_DIRS # Perform path search even on command names with slashes.
setopt AUTO_MENU # Show completion menu on a successive tab press.
setopt AUTO_LIST # Automatically list choices on ambiguous completion.
setopt AUTO_PARAM_SLASH # If completed parameter is a directory, add a trailing slash.
setopt EXTENDED_GLOB # Needed for file modification glob modifiers with compinit
unsetopt MENU_COMPLETE # Do not autoselect the first completion entry.
unsetopt FLOW_CONTROL # Disable start/stop characters in shell editor.
unsetopt AUTO_REMOVE_SLASH # Never remove trailing slash when completing.
# Load and initialize the completion system ignoring insecure directories with a
# cache time of 20 hours, so it should almost always regenerate the first time a
# shell is opened each day.
autoload -Uz compinit
_comp_path="$HOME/.zcompdump"
# #q expands globs in conditional expressions
if [[ $_comp_path(#qNmh-20) ]]; then
# -C (skip function check) implies -i (skip security check).
compinit -C -d "$_comp_path"
else
mkdir -p "$_comp_path:h"
compinit -i -d "$_comp_path"
# Keep $_comp_path younger than cache time even if it isn't regenerated.
touch "$_comp_path"
fi
unset _comp_path
# Defaults.
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*:default' list-prompt '%S%M matches%s'
# Use caching to make completion for commands such as dpkg and apt usable.
zstyle ':completion::complete:*' use-cache on
zstyle ':completion::complete:*' cache-path "$HOME/.zcompcache"
# Case-sensitive (all), partial-word, and then substring completion.
zstyle ':completion:*' matcher-list 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'
setopt CASE_GLOB
# Group matches and describe.
zstyle ':completion:*:*:*:*:*' menu select
zstyle ':completion:*:matches' group 'yes'
zstyle ':completion:*:options' description 'yes'
zstyle ':completion:*:options' auto-description '%d'
zstyle ':completion:*:corrections' format ' %F{green}-- %d (errors: %e) --%f'
zstyle ':completion:*:descriptions' format ' %F{yellow}-- %d --%f'
zstyle ':completion:*:messages' format ' %F{purple} -- %d --%f'
zstyle ':completion:*:warnings' format ' %F{red}-- no matches found --%f'
zstyle ':completion:*:default' list-prompt '%S%M matches%s'
zstyle ':completion:*' format ' %F{yellow}-- %d --%f'
zstyle ':completion:*' group-name ''
zstyle ':completion:*' verbose yes
# Fuzzy match mistyped completions.
zstyle ':completion:*' completer _complete _match _approximate
zstyle ':completion:*:match:*' original only
zstyle ':completion:*:approximate:*' max-errors 1 numeric
# Increase the number of errors based on the length of the typed word. But make
# sure to cap (at 7) the max-errors to avoid hanging.
zstyle -e ':completion:*:approximate:*' max-errors 'reply=($((($#PREFIX+$#SUFFIX)/3>7?7:($#PREFIX+$#SUFFIX)/3))numeric)'
# Don't complete unavailable commands.
zstyle ':completion:*:functions' ignored-patterns '(_*|pre(cmd|exec))'
# Array completion element sorting.
zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters
# Directories
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*:*:cd:*' tag-order local-directories directory-stack path-directories
zstyle ':completion:*:*:cd:*:directory-stack' menu yes select
zstyle ':completion:*:-tilde-:*' group-order 'named-directories' 'path-directories' 'users' 'expand'
zstyle ':completion:*' squeeze-slashes true
# History
zstyle ':completion:*:history-words' stop yes
zstyle ':completion:*:history-words' remove-all-dups yes
zstyle ':completion:*:history-words' list false
zstyle ':completion:*:history-words' menu yes
# Environment Variables
zstyle ':completion::*:(-command-|export):*' fake-parameters ${${${_comps[(I)-value-*]#*,}%%,*}:#-*-}
# Populate hostname completion.
zstyle -e ':completion:*:hosts' hosts 'reply=(
${=${=${=${${(f)"$(cat {/etc/ssh/ssh_,~/.ssh/}known_hosts(|2)(N) 2> /dev/null)"}%%[#| ]*}//\]:[0-9]*/ }//,/ }//\[/ }
${=${(f)"$(cat /etc/hosts(|)(N) <<(ypcat hosts 2> /dev/null))"}%%(\#${_etc_host_ignores:+|${(j:|:)~_etc_host_ignores}})*}
${=${${${${(@M)${(f)"$(cat ~/.ssh/config 2> /dev/null)"}:#Host *}#Host }:#*\**}:#*\?*}}
)'
# Ignore multiple entries.
zstyle ':completion:*:(rm|kill|diff):*' ignore-line other
zstyle ':completion:*:rm:*' file-patterns '*:all-files'
# Kill
zstyle ':completion:*:*:*:*:processes' command 'ps -u $LOGNAME -o pid,user,command -w'
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;36=0=01'
zstyle ':completion:*:*:kill:*' menu yes select
zstyle ':completion:*:*:kill:*' force-list always
zstyle ':completion:*:*:kill:*' insert-ids single
# Man
zstyle ':completion:*:manuals' separate-sections true
zstyle ':completion:*:manuals.(^1*)' insert-sections true
# SSH/SCP/RSYNC
zstyle ':completion:*:(ssh|scp|rsync):*' tag-order 'hosts:-host:host hosts:-domain:domain hosts:-ipaddr:ip\ address *'
zstyle ':completion:*:(scp|rsync):*' group-order users files all-files hosts-domain hosts-host hosts-ipaddr
zstyle ':completion:*:ssh:*' group-order users hosts-domain hosts-host users hosts-ipaddr
zstyle ':completion:*:(ssh|scp|rsync):*:hosts-host' ignored-patterns '*(.|:)*' loopback ip6-loopback localhost ip6-localhost broadcasthost
zstyle ':completion:*:(ssh|scp|rsync):*:hosts-domain' ignored-patterns '<->.<->.<->.<->' '^[-[:alnum:]]##(.[-[:alnum:]]##)##' '*@*'
zstyle ':completion:*:(ssh|scp|rsync):*:hosts-ipaddr' ignored-patterns '^(<->.<->.<->.<->|(|::)([[:xdigit:].]##:(#c,2))##(|%*))' '127.0.0.<->' '255.255.255.255' '::1' 'fe80::*'
#
# Terminal. Adapted from the prezto terminal module.
#
# Sets the terminal window title.
function set-window-title {
local title_format{,ted}
title_format="%s"
zformat -f title_formatted "$title_format" "s:$argv"
printf '\e]2;%s\a' "${(V%)title_formatted}"
}
# Sets the terminal tab title.
function set-tab-title {
local title_format{,ted}
title_format="%s"
zformat -f title_formatted "$title_format" "s:$argv"
printf '\e]1;%s\a' "${(V%)title_formatted}"
}
# Sets the terminal multiplexer tab title.
function set-multiplexer-title {
local title_format{,ted}
title_format="%s"
zformat -f title_formatted "$title_format" "s:$argv"
printf '\ek%s\e\\' "${(V%)title_formatted}"
}
# Sets the tab and window titles with a given command.
function _terminal-set-titles-with-command {
emulate -L zsh
setopt EXTENDED_GLOB
# Get the command name that is under job control.
if [[ "${2[(w)1]}" == (fg|%*)(\;|) ]]; then
# Get the job name, and, if missing, set it to the default %+.
local job_name="${${2[(wr)%*(\;|)]}:-%+}"
# Make a local copy for use in the subshell.
local -A jobtexts_from_parent_shell
jobtexts_from_parent_shell=(${(kv)jobtexts})
jobs "$job_name" 2> /dev/null > >(
read index discarded
# The index is already surrounded by brackets: [1].
_terminal-set-titles-with-command "${(e):-\$jobtexts_from_parent_shell$index}"
)
else
# Set the command name, or in the case of sudo, ssh or vpn, the next
# command.
local cmd="${${2[(wr)^(*=*|sudo|ssh|vpn|-*)]}:t}"
local truncated_cmd="${cmd/(#m)?(#c15,)/${MATCH[1,12]}...}"
unset MATCH
if [[ "$TERM" == screen* ]]; then
set-multiplexer-title "$truncated_cmd"
fi
set-tab-title "$truncated_cmd"
set-window-title "$cmd"
fi
}
# Sets the tab and window titles with a given path.
function _terminal-set-titles-with-path {
emulate -L zsh
setopt EXTENDED_GLOB
local absolute_path="${${1:a}:-$PWD}"
local abbreviated_path="${absolute_path/#$HOME/~}"
local truncated_path="${abbreviated_path/(#m)?(#c15,)/...${MATCH[-12,-1]}}"
unset MATCH
if [[ "$TERM" == screen* ]]; then
set-multiplexer-title "$truncated_path"
fi
set-tab-title "$truncated_path"
set-window-title "$abbreviated_path"
}
function set-terminal-title {
autoload -Uz add-zsh-hook
if [[ "$TERM_PROGRAM" == 'Apple_Terminal' ]]; then
# Sets the Terminal.app current working directory before the prompt is
# displayed.
function _terminal-set-terminal-app-proxy-icon {
printf '\e]7;%s\a' "file://${HOST}${PWD// /%20}"
}
add-zsh-hook precmd _terminal-set-terminal-app-proxy-icon
# Unsets the Terminal.app current working directory when a terminal
# multiplexer or remote connection is started since it can no longer be
# updated, and it becomes confusing when the directory displayed in the title
# bar is no longer synchronized with real current working directory.
function _terminal-unset-terminal-app-proxy-icon {
if [[ "${2[(w)1]:t}" == (screen|tmux|dvtm|ssh|mosh) ]]; then
print '\e]7;\a'
fi
}
add-zsh-hook preexec _terminal-unset-terminal-app-proxy-icon
# Do not set the tab and window titles in Terminal.app since it sets the tab
# title to the currently running process by default and the current working
# directory is set separately.
else
# Sets titles before the prompt is displayed.
add-zsh-hook precmd _terminal-set-titles-with-path
# Sets titles before command execution.
add-zsh-hook preexec _terminal-set-titles-with-command
fi
}
# Only set titles for regular terminals
case "$TERM" in
dumb|eterm*)
# Ignore unsupported terminals, e.g. TRAMP or an Emacs terminal emulator.
;;
*)
set-terminal-title
;;
esac
#
# Editor
#
# Always use Emacs keybindings (never consider $EDITOR)
bindkey -e
# Bind Shift + Tab to go to the previous menu item.
# kcbt might not be defined in all terminals
[[ -n "$terminfo[kcbt]" ]] && bindkey -M emacs "$terminfo[kcbt]" reverse-menu-complete
# Allow command line editing in an external editor.
autoload -Uz edit-command-line
zle -N edit-command-line
bindkey -M emacs "\C-X\C-E" edit-command-line
#
# Prompt
#
function load-prompt {
# This configures a simple prompt. Its visual appearance is based on the
# Ubuntu bash prompt, from ~2010.
#
# The prompt looks roughly like this:
# <green>user@host</green>:<blue>current-directory</blue>( <red>git-branch</red>)$<space>
#
# It has the following features:
# - renders quickly
# - the user@host part is only shown when connected through SSH
# - the git branch is shown when the current directory is in a git
# repository
# - the '$' symbol becomes yellow if the previous command exited with
# non-zero status
setopt PROMPT_SUBST
# Call vcs_info before every command
autoload -Uz add-zsh-hook vcs_info colors && colors
add-zsh-hook precmd vcs_info
# Enable git support only
zstyle ':vcs_info:*' enable git
# Display git branch
zstyle ':vcs_info:*' formats ' %F{red}%b%f'
# Add user@host when connected through SSH or using toolbox
local ssh_prefix
if [[ -n "$SSH_CLIENT" || -n "$SSH_TTY" || -n "$SSH_CONNECTION" || -n "$TOOLBOX_PATH" ]]; then
ssh_prefix="%F{green}%n@%m%f:"
fi
# Current directory, with the ~ symbol indicating the home directory
local -r pwd="%F{blue}%~%f"
# Color prompt symbol based on exit status. Inspired by
# https://solovyov.net/blog/2020/useful-shell-prompt/
local -r prompt_symbol="%(?.$.%F{yellow}$%f)"
# Define prompt
PROMPT="${ssh_prefix}${pwd}\${vcs_info_msg_0_}${prompt_symbol} "
}
autoload -Uz promptinit && promptinit
case "$TERM" in
dumb)
# Ignore dumb terminal, e.g. TRAMP.
prompt off
unsetopt ZLE
;;
*)
load-prompt
;;
esac
#
# Misc
#
# Change directory without cd
setopt AUTO_CD
# Interactive comments (like bash)
setopt INTERACTIVE_COMMENTS
# Correct commands
setopt CORRECT
# Make forward-word, backward-word etc. stop at path delimiter
export WORDCHARS=${WORDCHARS/\/}
# Print message if reboot is required
[[ -f "/var/run/reboot-required" ]] && echo "reboot required"
#
# Aliases
#
function cond-alias {
# Split on = and extract 1st word (alias name) and 2nd word (alias value)
local -r name=${1[(ws:=:)1]}
local -r value=${1[(ws:=:)2]}
# Command is the first word of the alias value
local cmd=${value[(w)1]}
# If command match any of the below, the actual command is the second word
case "$cmd" in
sudo|noglob|cd|cat)
# Remove $( and <( prefixes from command
cmd=${value[(w)2]}
cmd=${cmd:s/$\(//}
cmd=${cmd:s/<\(//}
;;
esac
if whence $cmd > /dev/null; then
alias $name=$value
fi
}
cond-alias aptup="sudo apt update && sudo apt upgrade"
cond-alias curl="noglob curl"
cond-alias ec="emacsclient -nq"
cond-alias find="noglob bfs"
cond-alias git-root='cd $(git rev-parse --show-toplevel)'
cond-alias grep="grep --color=auto"
cond-alias hstat="history 0 | awk '{print \$2}' | sort | uniq -c | sort -nr | head"
cond-alias mg="mg -n"
cond-alias ta='tmux new-session -AD -s $LOGNAME'
cond-alias week="date +%V"
cond-alias reload="exec zsh"
if (( $+commands[apt-mark] )); then
# This is the most precise method I've found for answering the question
# "which packages did I install explicitly?"
#
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=727799
# https://stackoverflow.com/q/58309013/22831
alias apt-leaves='sudo grep -oP "Unpacking \K[^: ]+" /var/log/installer/syslog | sort -u | comm -13 /dev/stdin <(apt-mark showmanual | sort)'
fi
# Display ANSI art typically found .nfo files correctly
function nfoless {
iconv -f 437 -t utf-8 "$@" | ${PAGER:-less}
}
# Show restic diff for the most recent snapshot. If offset is given, show the
# diff for the nth most recent snapshot instead
function restic-review {
local -r offset="${1:-0}"
if [[ $# -gt 1 || ! "$offset" =~ ^[0-9]+$ ]]; then
echo "usage: restic-review [OFFSET]" 1>&2
return 1
fi
restic snapshots --group-by host --host $(hostname -s) | \
grep -Eo "^[a-f0-9]{8,}" | \
tail -$(( 2 + $offset )) | \
head -2 | \
xargs -r restic diff
}
# Fuzzy-finding wrapper for brew install, info and uninstall
function brew-fzf {
case "$1" in
uninstall)
cat <(brew leaves) <(brew list --cask | sed "s/^/--cask /") | fzf --multi | xargs -r brew "$1"
;;
info|install)
cat <(brew formulae) <(brew casks | sed "s/^/--cask /") | fzf --multi | xargs -r brew "$1"
;;
*)
echo "usage: brew-fzf [ info | install | uninstall ]" 1>&2
return 1
;;
esac
}
# Use colors in diff output when supported
if diff --color=auto /dev/null /dev/null 2> /dev/null; then
alias diff="diff --color=auto"
fi
# Alias ls
ls_opts="--group-directories-first --color=auto"
case "$OSTYPE" in
darwin*|freebsd*)
if (( $+commands[gls] )); then
alias ls="gls ${ls_opts}"
alias ll="gls ${ls_opts} -lh"
elif (( $+commands[gnuls] )); then
alias ls="gnuls ${ls_opts}"
alias ll="gnuls ${ls_opts} -lh"
else
alias ls="ls -G"
alias ll="ls -Glh"
fi
;;
*)
alias ls="ls ${ls_opts}"
alias ll="ls ${ls_opts} -lh"
;;
esac
unset ls_opts
# Activate or deactivate a virtualenv
function venv {
local -r activate="${1:-.venv}/bin/activate"
if [[ -n "$VIRTUAL_ENV" ]]; then
echo "venv: deactivating $VIRTUAL_ENV" 1>&2
deactivate
elif [[ -f "$activate" ]]; then
echo "venv: activating $(realpath $activate/../..)" 1>&2
source "$activate"
else
echo "venv: $activate not found" 1>&2
return 1
fi
}
# A shell variant of the locate-dominating-file function found in Emacs
function locate-dominating-file {
local -r file="$1"
local -r name="$2"
local dir="$file"
# Resolve parent if we're not given a directory directly
if [[ ! -d "$dir" ]]; then
dir="${dir:h}" # h is dirname
if [[ ! -d "$dir" ]]; then
echo "locate-dominating-file: $dir is not a directory" 1>&2
return 1
fi
fi
local cur_dir="$dir"
while true; do
cur_dir="${cur_dir:P}" # P converts to realpath
if [[ -e "$cur_dir/$name" ]]; then
echo "$cur_dir"
break
elif [[ "$cur_dir" == "/" ]]; then
echo "locate-dominating-file: $name not found in $dir or any of its parents" 1>&2
return 1
fi
cur_dir="$cur_dir/.."
done
}
# Change directory to the nearest one containing the given file or directory
#
# Example:
#
# ~/project/some/deep/path$ cdn .git # or README.md, go.mod etc.
# ~/project$
#
function cdn {
cd "$(locate-dominating-file "$PWD" "$1")"
}
#
# Local configuration
#
source "$HOME/.zshrc.local" 2> /dev/null
#
# Extensions
#
# zsh-syntax-highlighting should be initialized as late as possible because it
# wraps ZLE widgets. Paths are tried in this order: Homebrew on macOS, dpkg on
# Debian and home directory. Fast variant of syntax highlighting is preferred.
source "$HOMEBREW_PREFIX/share/zsh-fast-syntax-highlighting/fast-syntax-highlighting.plugin.zsh" 2> /dev/null || \
source "$HOMEBREW_PREFIX/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" 2> /dev/null || \
source "/usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" 2> /dev/null || \
source "$HOME/.local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" 2> /dev/null
# Do not colorize comments. Default is too dark
[[ -n "$ZSH_HIGHLIGHT_STYLES" ]] && ZSH_HIGHLIGHT_STYLES[comment]="fg=none"
[[ -n "$FAST_HIGHLIGHT_STYLES" ]] && FAST_HIGHLIGHT_STYLES[comment]="fg=none"
# Load fzf keybindings and completion. E.g. C-r searches history using fzf.
# Paths are tried in this order: Homebrew on macOS, dpkg on Debian and rpm on
# Fedora.
source "$HOMEBREW_PREFIX/opt/fzf/shell/key-bindings.zsh" 2> /dev/null || \
source "/usr/share/doc/fzf/examples/key-bindings.zsh" 2> /dev/null || \
source "/usr/share/fzf/shell/key-bindings.zsh" 2> /dev/null
source "$HOMEBREW_PREFIX/opt/fzf/shell/completion.zsh" 2> /dev/null || \
source "/usr/share/doc/fzf/examples/completion.zsh" 2> /dev/null || \
source "/usr/share/zsh/site-functions/fzf" 2> /dev/null
# Cleanup
(( $+commands[brew] )) || unfunction brew-fzf
unfunction load-prompt set-terminal-title cond-alias