]> git.sev.monster Git - dotfiles.git/blob - etc/zsh/.zshrc
vimrc: enable modeline explicitly
[dotfiles.git] / etc / zsh / .zshrc
1 ### options
2 setopts=(
3   ## 16.2.1  Changing Directories
4   AUTO_CD CDABLE_VARS
5   ## 16.2.2  Completion
6   COMPLETE_IN_WORD GLOB_COMPLETE REC_EXACT
7   ## 16.2.3  Expansion and Globbing
8   EXTENDED_GLOB GLOB_DOTS GLOB_STAR_SHORT MAGIC_EQUAL_SUBST MARK_DIRS
9   NUMERIC_GLOB_SORT
10   ## 16.2.4  History
11   # NOTE: NO_HIST_SAVE_BY_COPY to allow saving histfile if updating another
12   #       user's histfile. this is for compatibility with zsu.
13   HIST_FCNTL_LOCK HIST_IGNORE_DUPS HIST_IGNORE_SPACE HIST_LEX_WORDS
14   HIST_NO_STORE HIST_REDUCE_BLANKS NO_HIST_SAVE_BY_COPY SHARE_HISTORY
15   ## 16.2.6  Input/Output
16   NO_CLOBBER CLOBBER_EMPTY CORRECT_ALL INTERACTIVE_COMMENTS
17   HASH_EXECUTABLES_ONLY
18   ## 16.2.7  Job Control
19   AUTO_CONTINUE LONG_LIST_JOBS
20   ## 16.2.12 Zle
21   NO_BEEP
22 )
23 setopt $setopts
24 unset setopts
25
26 ### exports
27 ## common
28 export EDITOR=${$(whence -p nvim vim vi micro nano emacs)[(f)1]}
29 export PAGER=${$(whence -p less micro nano more)[(f)1]:s/micro/& -readonly true -multiopen tab/:s/nano/& --view}
30 ## grep
31 # XXX: deprecated in GNU
32 export GREP_OPTIONS=--color=auto
33 ## histfile
34 export HISTFILE=~/.histfile
35 export HISTSIZE=10000
36 export SAVEHIST=$HISTSIZE
37 ## python
38 export PYTHONSTARTUP=${XDG_CONFIG_HOME:-~/.config}/pythonrc
39 ## vim
40 export VIMINIT='let$MYVIMRC=($XDG_CONFIG_HOME??($HOME."/.config"))."/vim/.vimrc"|execute"source"$MYVIMRC'
41
42 ### imports
43 autoload -Uz zmv
44 autoload -Uz zmathfunc && zmathfunc
45
46 ## vcs
47 zstyle ':vcs_info:*' enable git
48 #zstyle ':vcs_info:git*' check-for-changes true #too slow
49 zstyle ':vcs_info:git*:dotfiles' check-for-changes true
50 zstyle ':vcs_info:git*' check-for-staged-changes true
51 autoload -Uz vcs_info
52
53 ## compinit
54 zstyle ':completion:*' auto-description '[arg] %d'
55 zstyle ':completion:*' expand suffix
56 zstyle ':completion:*' format '# %d'
57 zstyle ':completion:*' group-name ''
58 zstyle ':completion:*' ignore-parents parent
59 zstyle ':completion:*' insert-unambiguous false
60 zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
61 zstyle ':completion:*' list-prompt '%B%i%b'
62 zstyle ':completion:*' list-suffixes true
63 zstyle ':completion:*' matcher-list '' 'm:{[:lower:]}={[:upper:]}' 'm:{[:lower:][:upper:]}={[:upper:][:lower:]} r:|[._-]=* r:|=*' 'm:{[:lower:][:upper:]}={[:upper:][:lower:]} l:|=* r:|=*'
64 zstyle ':completion:*' menu select=1
65 zstyle ':completion:*' original false
66 zstyle ':completion:*' select-prompt '%B%l%b'
67 zstyle ':completion:*' verbose true
68 autoload -Uz compinit
69 cache=${XDG_CACHE_HOME:-~/.cache}/zsh
70 [[ -d $cache ]] || mkdir -p $cache
71 compinit -d $cache/.zcompdump
72 unset cache
73
74 ### keys
75 ## set up zkbd-style key array
76 if [[ ! -v _sev_force_zkbd && ( -v terminfo || -v termcap ) ]] {
77     # use application (keypad transmit) mode if the terminal supports it
78     # NOTE: we have to do this because termcap/terminfo keys are usually
79     #       defined in application mode. terminals that do not define these
80     #       capabilities are assumed to always be in application mode.
81     if [[ ( -v terminfo[smkx] && -v terminfo[rmkx] ) ||
82           ( -v termcap[ks]    && -v terminfo[ke] ) ]] {
83         autoload -Uz add-zle-hook-widget
84         function _enter-application-mode {
85             [[ -v terminfo[smkx] ]] && echoti smkx || echotc ks
86         }
87         add-zle-hook-widget line-init _enter-application-mode
88         function _exit-application-mode {
89             [[ -v terminfo[rmkx] ]] && echoti rmkx || echotc ke
90         }
91         add-zle-hook-widget line-finish _exit-application-mode
92         trap _exit-application-mode EXIT
93     }
94     # pull keys from terminfo/termcap
95     # TODO: Menu and more Shift- modifiers
96     typeset -gA key
97     typeset -lA newkey
98     if [[ -v terminfo ]] {
99         src=terminfo
100         newkey=(F1 kf1 F2 kf2 F3 kf3 F4 kf4 F5 kf5 F6 kf6 F7 kf7 F8 kf8 F9 kf9
101                 F10 kf10 F11 kf11 F12 kf12
102                 Backspace kbs
103                 Shift-Tab kcbt
104                 Insert    kich1
105                 Home      khome
106                 PageUp    kpp
107                 Delete    kdch1
108                 End       kend
109                 PageDown  knp
110                 Up        kcuu1
111                 Down      kcud1
112                 Left      kcub1
113                 Right     kcuf1
114         )
115     } elif [[ -v termcap ]] {
116         src=termcap
117         newkey=(F1 k1 F2 k2 F3 k3 F4 k4 F5 k5 F6 k6 F7 k7 F8 k8 F9 k9
118                 F10 k\; F11 F1 F12 F2
119                 Backspace kb
120                 Shift-Tab kB
121                 Insert    kI
122                 Home      kh
123                 PageUp    kP
124                 Delete    kD
125                 End       @7
126                 PageDown  kN
127                 Up        ku
128                 Down      kd
129                 Left      kl
130                 Right     kr
131         )
132     }
133     for k v (${(kv)newkey}) {
134         key[$k]=${${(P)src}[$v]}
135     }
136     unset k v src newkey
137 } else {
138     # use zkbd if termcap/terminfo unavailable
139     function find_keymap {
140         for f in ${ZDOTDIR:-~}/.zkbd/$TERM{-${DISPLAY:-$VENDOR-$OSTYPE},}
141             [[ -f $f ]] && keymap=$f && break
142     }
143     find_keymap
144     if [[ -z $keymap ]] {
145         if read -q "?Can't read terminfo. Add new zkbd keymap? [y/N]"; then
146             echo
147             autoload -Uz zkbd && zkbd
148             unfunction zkbd
149             find_keymap
150         fi
151         echo
152     }
153     if [[ -n $keymap ]] {
154         source $keymap
155     } else {
156         echo -E "Failed to source keymap file $keymap" >&2
157     }
158     unfunction find_keymap; unset keymap
159 }
160
161 ## load history search
162 autoload -Uz up-line-or-beginning-search down-line-or-beginning-search
163 zle -N up-line-or-beginning-search
164 zle -N down-line-or-beginning-search
165
166 ## bind keys in both viins and vicmd modes
167 # NOTE: cursor keys are bound to normal/raw/nontransmit mode strings by
168 #       default, and keys like Home and End are not bound at all, so we rebind
169 #       everything to cover all scenarios
170 typeset -A a
171 a=(
172     #key       viins                         vicmd
173     Backspace 'backward-delete-char          vi-backward-char'
174     Insert    'overwrite-mode                vi-insert'
175     Home      'beginning-of-line'
176     PageUp    'up-history                    -'
177     Delete    'delete-char'
178     End       'end-of-line'
179     PageDown  'down-history                  -'
180     Up        'up-line-or-beginning-search   vi-up-line-or-history'
181     Down      'down-line-or-beginning-search vi-down-line-or-history'
182     Left      'backward-char'
183     Right     'forward-char'
184 )
185 for k v (${(kv)a}) {
186     k=$key[$k]
187     if [[ -z $k ]] { continue }
188     v=($=v)
189     bindkey -- $k $v[1]
190     if [[ $v[2] == - ]] {
191         # copy viins to vicmd verbatim
192         bindkey -a -- $k $v[1]
193     } elif (( $#v != 1 )) {
194         # set vicmd to any other value
195         bindkey -a -- $k $v[2]
196     } else {
197         # copy viins to vicmd and prepend vi- to it
198         bindkey -a -- $k vi-$v[1]
199     }
200 }
201 unset a k v
202
203 ## fzf
204 # bash-style reverse-search-history (i.e. reverse-i-search)
205 if [[ -v commands[fzf] ]] {
206     function _history-incremental-pattern-search-fzf {
207         l=(${(f)"$(fc -li -1 0 | fzf -e -m -n 1 +s --preview-window=hidden ${BUFFER:+-q $BUFFER})"})
208         l=$(for x ("${l[@]}") { echo ${${=x}:3}; })
209         BUFFER="$l"
210     }
211     zle -N _history-incremental-pattern-search-fzf
212     bindkey '^R' _history-incremental-pattern-search-fzf
213 } else {
214     bindkey '^R' history-incremental-pattern-search-backward
215 }
216
217 ### aliases
218 ## builtins
219 alias rehash='_sev_setpath; rehash'
220 ## utils
221 alias h='fc -l -25'
222 alias j='jobs -l'
223 alias l='ls -AF'
224 if [[ "$OSTYPE" =~ '^(free|net)bsd' ]] {
225     alias ll='ls -lAFho'
226 } else {
227     alias ll='ls -lAFh'
228 }
229 alias p=\$PAGER
230 alias e=\$EDITOR
231 alias se=sudoedit
232 alias syncwatch='sync & watch -d grep -Fe Dirty: -e Writeback: /proc/meminfo'
233 if [[ -v commands[grep] ]] {
234     [[ -v commands[fgrep] ]] || alias fgrep='grep -F'
235     [[ -v commands[egrep] ]] || alias fgrep='grep -E'
236 }
237 for x (cat cmp diff grep test update) {
238     [[ -v commands[zutils-z$x] ]] || alias z$x=zutils-z$x
239 }
240 # be paranoid
241 alias cp='cp -ip'
242 alias mv='mv -i'
243 # zsh zmv with noglob wildcards
244 alias zm='noglob zmv -WiM'
245 alias zc='noglob zmv -WiC'
246 alias zl='noglob zmv -WiL'
247 alias sm='noglob zmv -Wip"sudo mv"'
248 alias sc='noglob zmv -Wip"sudo cp"'
249 alias sl='noglob zmv -Wip"sudo ln"'
250 if [[ "$OSTYPE" =~ '^freebsd' ]] {
251     # don't confirm if only a few files are deleted
252     alias rm='rm -I'
253 } else {
254     # TODO: similar behavior for non-freebsd, or impliment in zsh
255     alias rm='rm -i'
256 }
257 [[ -v commands[trash-put] ]] && alias t=trash-put
258 # ps
259 if [[ -v commands[pstree] && $commands[pstree]:A:t != busybox ]] {
260     # use pstree, but NOT busybox pstree because it kinda sucks
261     ps='pstree -wg3'
262 } elif [[ "$OSTYPE" =~ '^freebsd' ]] {
263     ps='ps -aSdfxwwouser=USR -ogroup=GRP -opid,nice=NI \
264        -o%cpu,%mem,tty,stat,start=START -oetime,command'
265 } elif [[ $commands[ps]:A:t == busybox ]] {
266     # busybox compatible
267     ps="ps -eouser='USR     ' -ogroup='GRP     ' \
268        -opid='  PID' -onice=' NI' -ovsz=' MEM' \
269        -otty,stat,etime,comm"
270 } else {
271     # XXX: untested, posix
272     # TODO: support gnu ps
273     ps='ps -eouser=USR -ogroup=GRP -opid,nice=NI \
274        -opcpu=CPU -ovsz=MEM -otty,stat,etime,comm'
275 }
276 if [[ "$(basename "$PAGER")" = "less" ]] {
277     ps="$ps | less -S"
278 } else {
279     ps="$ps | \"${PAGER:-more}\""
280 }
281 alias pa=$ps
282 alias spa="sudo $ps"
283 unset ps
284 ## py venv
285 alias va='source bin/activate'
286 alias vd=deactivate
287 alias vu="python3 -mvenv --upgrade"
288 alias svu="sudo python3 -mvenv --upgrade"
289 ## git
290 alias g=git
291 alias gd='git diff'
292 alias gdh='git diff HEAD'
293 alias gds='git diff --staged'
294 alias ga='git add'
295 alias ga.='git add .'
296 alias gai='git add -i'
297 alias gap='git add -p'
298 alias gc='git commit'
299 alias gca='git commit --amend'
300 alias gp='git push'
301 alias gu='git pull'
302 alias gl='git log'
303 alias gt='git tree' # from gitconfig
304 alias gsh='git show'
305 alias gst='git status'
306 alias gs='git stash'
307 alias gsp='git stash pop'
308 ## cd/zoxide
309 function up {
310     \cd $(printf '../%.0s' {1..${1:-1}})
311 }
312 alias u=up
313 if [[ -v commands[zoxide] ]] {
314     # https://github.com/ajeetdsouza/zoxide/issues/513
315     eval "${$(zoxide init zsh):s#_files -/#_cd#}"
316     alias cd=z
317 }
318 alias cd..=up
319 ## dotfiles
320 alias dfu='function {
321     pushd -q ${$(echo -E - ~/.zshenv):P:h:h}
322     git pull &&
323     git submodule init &&
324     git submodule sync &&
325     git submodule update
326     popd -q
327 }'
328 ## nocorrect
329 # zsh doesnt really handle sudo very well, so ignore it
330 alias sudo='nocorrect sudo'
331 ## docker compose
332 alias dcp='sudo docker compose pull'
333 alias dcu='sudo docker compose up -d'
334 alias dcr='sudo docker compose restart'
335 alias dcl='sudo docker compose logs -f'
336
337 ### hooks
338 autoload -Uz add-zsh-hook
339 typeset -gi _sev_exectime
340 function sev_preexec {
341     # change terminal title to show command
342     print -n "\e]2;$(print -P '%#')${SSH_CLIENT+$USER@$HOST:}$1\e\\"
343     # save last exec time for bell
344     # XXX: does not run for blank cmdline
345     _sev_exectime=$SECONDS
346     # update gpg forward, to always have unique filename and avoid clashes
347     if [[ -v _GNUPG_SOCK_DEST_EXT ]] {
348         export _GNUPG_SOCK_DEST_EXT=$(date +%s).$RANDOM
349         export _GNUPG_SOCK_DEST=$_GNUPG_SOCK_DEST_BASE.$_GNUPG_SOCK_DEST_EXT
350     }
351 }
352 add-zsh-hook preexec sev_preexec
353 function sev_precmd {
354     # change terminal title
355     # TODO: update and send BEL when job status changes
356     print -Pn "\e]2;%(1j,%j,)%#${SSH_CLIENT+$USER@$HOST:}%~\e\\"
357     # bell if exec takes 5s
358     if (( SECONDS - _sev_exectime >= 5 )) print "\a"
359     # we could update vcs_info here, but let prompt take care of it
360     # if it doesn't use vcs, it can be ignored safely
361 }
362 add-zsh-hook precmd sev_precmd
363 function sev_chpwd {
364     # echo dir on cwd change
365     ls -AF
366 }
367 add-zsh-hook chpwd sev_chpwd
368
369 ### system-specific configs and aliases
370 case $OSTYPE; in
371     freebsd*)
372         ## colors
373         export CLICOLOR=
374         export LS_COLORS='di=34:ln=35:so=32:pi=33:ex=31:bd=46;34:cd=43;34:su=41;30:sg=46;30:tw=42;30:ow=43;30'
375
376         ## sound
377         function s { sysctl hw.snd.default_unit${1:+\=$1} }
378         alias vol mixer
379
380         ## install port dependencies from pkg (like pkgsrc `bmake bin-install')
381         # XXX: should probably use package-depends where possible, breaks when
382         #      port name is different to package name
383         #      (eg. graphics/sdl20 == sdl2, devel/glib20 == glib2, etc)
384         function portpkg {
385             case "$1" {
386             build|run)
387                 sudo pkg install -AU $(make ${1}-depends-list |
388                            sed 's_/usr/ports/_ _' | tr -d '\n')
389                 ;;
390             *)  echo "Usage: \`portpkg <build|run>' in a port directory"
391                 return 1;;
392             }
393         };;
394     netbsd)
395         ## sound
396         function s {
397             if [[ -z "$1" ]] {
398                 ll /dev/mixer /dev/sound /dev/audio
399                 return
400             }
401             for x in mixer sound audio; do
402                 ln -sf /dev/$x"$1" /dev/$x
403             done
404         }
405         function vol {
406             if [[ -z "$1" ]] {
407                 for x in $(mixerctl -a | grep 'outputs\.master'); do
408                     echo -E - $x
409                 done
410                 return
411             }
412             mixerctl -w outputs.master"$2"="$1"
413         };;
414     *)
415         ## sound
416         # TODO: test alsa/oss/sndio/portaudio/pulse/pipewire in order of importance
417         function s {}
418         function vol {}
419 esac
420
421 ### prompt
422 autoload -Uz promptinit && promptinit
423 prompt arrows
424
425 ### plugins
426 load-plugins zshrc
427
428 ### load site-specific
429 load-site-dotfile zshrc
This page took 0.054304 seconds and 4 git commands to generate.