537 lines
12 KiB
Bash
537 lines
12 KiB
Bash
#!/bin/zsh
|
|
|
|
###
|
|
### Set global variables
|
|
###
|
|
|
|
: ${XDG_CONFIG_HOME:=$HOME/.config}
|
|
export XDG_CONFIG_HOME
|
|
|
|
|
|
###
|
|
### Zsh Configuration
|
|
###
|
|
|
|
# Customize spelling correction prompt.
|
|
SPROMPT='zsh: correct %F{red}%R%f to %F{green}%r%f [nyae]? '
|
|
|
|
# Characters considered part of a word by the line editor
|
|
WORDCHARS="*?_-.~$"
|
|
|
|
# Set the "main" keyboard so `bindkey` without `-M` will change the expected map
|
|
bindkey -A viins main
|
|
|
|
|
|
###
|
|
### Path
|
|
###
|
|
|
|
setopt nullglob
|
|
path=(
|
|
$HOME/bin
|
|
$HOME/dev_local/bin
|
|
$HOME/.local/bin
|
|
$HOME/.local/*/bin
|
|
$HOME/go/bin
|
|
/opt/*/bin
|
|
$path)
|
|
setopt nonullglob
|
|
|
|
fpath=($fpath /usr/share/zsh/site-functions)
|
|
|
|
# Remove duplicates
|
|
typeset -U path fpath
|
|
|
|
|
|
###
|
|
### Modules
|
|
###
|
|
|
|
### ASDF & direnv
|
|
#
|
|
if [[ -e "${XDG_CONFIG_HOME}/asdf-direnv/zshrc" ]]; then
|
|
# Don't modify next line, it is searched by "direnv s$etup"
|
|
source "${XDG_CONFIG_HOME:-$HOME/.config}/asdf-direnv/zshrc"
|
|
else
|
|
echo "asdf: direnv plugin not set!"
|
|
# asdf plugin add direnv
|
|
# asdf install direnv latest
|
|
# asdf direnv setup --version latest
|
|
# asdf global direnv latest
|
|
fi
|
|
|
|
### b4b4r07/enhancd
|
|
#
|
|
# See https://github.com/b4b4r07/enhancd
|
|
#
|
|
# Changes from default behaviour:
|
|
# - Use single-dot to show upper tree (default is sub-dir);
|
|
# (use Alt-C (provided by fzf) to go into sub-dir).
|
|
ENHANCD_DIR=$HOME/.zim
|
|
ENHANCD_ARG_DOUBLE_DOT='.'
|
|
ENHANCD_ENABLE_SINGLE_DOT=false
|
|
|
|
### fzf
|
|
#
|
|
# Scripts sourced by zvm_after_init()
|
|
#
|
|
# - https://github.com/junegunn/fzf#key-bindings-for-command-line
|
|
# - https://github.com/junegunn/fzf#fuzzy-completion-for-bash-and-zsh
|
|
|
|
#
|
|
# Adapted from https://github.com/zimfw/fzf
|
|
#
|
|
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse'
|
|
|
|
if (( ${+commands[fd]} )); then
|
|
export FZF_DEFAULT_COMMAND='command fd --color=always --hidden --no-ignore-vcs --exclude=.git --type=file --strip-cwd-prefix'
|
|
export FZF_ALT_C_COMMAND='command fd --color=always --hidden --no-ignore-vcs --exclude=.git --type=directory --strip-cwd-prefix'
|
|
|
|
# Trigger for completion: "**"
|
|
# Commands using _dir: FZF_COMPLETION_DIR_COMMANDS
|
|
_fzf_compgen_path() {
|
|
command fd --color=always --hidden --no-ignore-vcs --exclude=.git --type=file . "${1}"
|
|
}
|
|
_fzf_compgen_dir() {
|
|
command fd --color=always --hidden --no-ignore-vcs --exclude=.git --type=directory . "${1}"
|
|
}
|
|
export FZF_DEFAULT_OPTS="--ansi ${FZF_DEFAULT_OPTS}"
|
|
elif (( ${+commands[rg]} )); then
|
|
export FZF_DEFAULT_COMMAND="command rg -uu -g '!.git' --files"
|
|
_fzf_compgen_path() {
|
|
command rg -uu -g '!.git' --files "${1}"
|
|
}
|
|
fi
|
|
if (( ${+commands[bat]} )); then
|
|
export FZF_CTRL_T_OPTS="\
|
|
--preview='command bat --color=always --line-range :100 --style=plain {}'\
|
|
--preview-window=border-left\
|
|
${FZF_CTRL_T_OPTS}"
|
|
fi
|
|
|
|
if (( ${+FZF_DEFAULT_COMMAND} )) export FZF_CTRL_T_COMMAND=${FZF_DEFAULT_COMMAND}
|
|
|
|
# fzf: Debian Specific
|
|
#
|
|
FZF_CTRL_R_OPTS="--no-sort --exact"
|
|
|
|
# fzf: Alternate history (Ctrl-Win-R)
|
|
#
|
|
if [[ -f $ALT_HISTORY_FILE ]]; then
|
|
fzf-alt-history-widget() {
|
|
local selected num
|
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
|
selected=( $(cat ${ALT_HISTORY_FILE} |
|
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
|
local ret=$?
|
|
BUFFER=$selected
|
|
zle reset-prompt
|
|
return $ret
|
|
}
|
|
zle -N fzf-alt-history-widget
|
|
bindkey -M vicmd '^[[114;13u' fzf-alt-history-widget
|
|
bindkey -M viins '^[[114;13u' fzf-alt-history-widget
|
|
fi
|
|
|
|
### Ripgrep
|
|
#
|
|
export RIPGREP_CONFIG_PATH=$HOME/.ripgreprc
|
|
|
|
### ZVM — Zsh VI mode
|
|
#
|
|
# https://github.com/jeffreytse/zsh-vi-mode
|
|
|
|
function zvm_config() {
|
|
ZVM_LINE_INIT_MODE=$ZVM_MODE_INSERT
|
|
}
|
|
|
|
# bindkey for other modules
|
|
function zvm_after_init {
|
|
# fzf
|
|
source /usr/share/doc/fzf/examples/key-bindings.zsh
|
|
source /usr/share/doc/fzf/examples/completion.zsh
|
|
|
|
# Automatic expansion of global aliases
|
|
bindkey " " globalias
|
|
bindkey "^ " magic-space # control-space to bypass completion
|
|
bindkey -M isearch " " magic-space # normal space during searches
|
|
}
|
|
|
|
|
|
###
|
|
### Zim Framework
|
|
###
|
|
|
|
# Use degit instead of git as the default tool to install and update modules.
|
|
zstyle ':zim:zmodule' use 'degit'
|
|
|
|
|
|
ZIM_HOME=${ZDOTDIR:-${HOME}}/.zim
|
|
if [[ ! -e ${ZIM_HOME}/zimfw.zsh ]]; then
|
|
# Download zimfw script if missing.
|
|
if (( ${+commands[curl]} )); then
|
|
curl -fsSL --create-dirs -o ${ZIM_HOME}/zimfw.zsh https://github.com/zimfw/zimfw/releases/latest/download/zimfw.zsh
|
|
else
|
|
mkdir -p ${ZIM_HOME} && wget -nv -O ${ZIM_HOME}/zimfw.zsh https://github.com/zimfw/zimfw/releases/latest/download/zimfw.zsh
|
|
fi
|
|
fi
|
|
if [[ ! ${ZIM_HOME}/init.zsh -nt ${ZDOTDIR:-${HOME}}/.zimrc ]]; then
|
|
# Install missing modules, and update ${ZIM_HOME}/init.zsh if missing or outdated.
|
|
source ${ZIM_HOME}/zimfw.zsh init -q
|
|
fi
|
|
source ${ZIM_HOME}/init.zsh
|
|
|
|
|
|
###
|
|
### Aliases & Small Functions
|
|
###
|
|
#
|
|
# https://www.thorsten-hans.com/5-types-of-zsh-aliases
|
|
#
|
|
# https://unix.stackexchange.com/questions/250314/whats-the-intended-use-case-for-complete-aliases-in-zsh
|
|
# See also the Completion section below
|
|
|
|
alias cp='cp --interactive'
|
|
alias mv='mv --interactive'
|
|
alias rm='rm -I'
|
|
|
|
mkcd() { command mkdir -p "$@" && builtin cd "$@" }
|
|
|
|
autoload -U zmv
|
|
alias mmv='noglob zmv -W'
|
|
|
|
# https://github.com/lsd-rs/lsd
|
|
# ~/.config/lsd/config.yaml
|
|
alias ls='lsd'
|
|
|
|
alias l='lsd --total-size -l'
|
|
alias ll='lsd --total-size -lA'
|
|
|
|
alias lt='lsd -ltr'
|
|
alias llt='lsd -ltrA'
|
|
|
|
alias lx='lsd -l --blocks=permission,user,group,context,size,date,name'
|
|
alias llx='lx -A'
|
|
|
|
alias t='lsd --long --tree --depth=2 --no-symlink'
|
|
|
|
# Dev
|
|
#
|
|
alias make='colormake-short'
|
|
compdef colormake=make
|
|
compdef colormake-short=make
|
|
|
|
# Git
|
|
#
|
|
alias dotfiles='/usr/bin/git --git-dir=$HOME/.dotfiles --work-tree=$HOME'
|
|
|
|
lg()
|
|
{
|
|
export LAZYGIT_NEW_DIR_FILE="${XDG_CONFIG_HOME}/lazygit/cd-on-quit_$$"
|
|
lazygit "$@"
|
|
if [ -f $LAZYGIT_NEW_DIR_FILE ]; then
|
|
cd "$(cat $LAZYGIT_NEW_DIR_FILE)"
|
|
rm -f $LAZYGIT_NEW_DIR_FILE > /dev/null
|
|
fi
|
|
}
|
|
|
|
# Kitty
|
|
#
|
|
hg () { kitten hyperlinked_grep "$@" }
|
|
compdef hg=rg
|
|
|
|
# Neovim — nvim
|
|
#
|
|
# To choose an existing instance for nvr, use:
|
|
# `export NVIM=$(nvr --serverlist | fzf --exit-0 --select-1) && echo $NVIM`
|
|
|
|
|
|
# nnn - n³
|
|
#
|
|
|
|
local nnn_plug=(
|
|
'b:-!bat --paging=always "$nnn"*'
|
|
'd:fzcd' # fzf-select a file & cd to its folder
|
|
'z:fzopen' # fzf-select a file & open it
|
|
'm:mimelist' # List files corresponding to given mime type
|
|
'o:-!mimeopen "$nnn"*'
|
|
'O:-!mimeopen --ask "$nnn"*'
|
|
'U:umounttree' # Un-mount from any subdir
|
|
';:-!clear*'
|
|
|
|
't:-!trash-list | rg --color=always $PWD | less *' # Dump files trashed at PWD (date order)
|
|
'T:-!trash-list | rg --color=always $PWD | sort -k3 | less *' # Dump files trashed at PWD (name order)
|
|
)
|
|
|
|
n()
|
|
{
|
|
if [ ${NNNLVL:-0} -gt 0 ]; then
|
|
echo 'nnn is already running'
|
|
return
|
|
fi
|
|
|
|
local -x LESS=${LESS/F}
|
|
local -x PATH=$HOME/.config/nnn/plugins:$PATH
|
|
|
|
# See https://github.com/jarun/nnn/wiki/Usage#configuration
|
|
local -x GUI=1 # plugins: nuke, xdgdefault
|
|
local -x NNN_ARCHIVE='\.(7z|bz2|cbz|cbr|gz|tar|tbz|tgz|xz|zip|zst)$'
|
|
local -x NNN_BMS="d:~/Downloads;q:~/QubesIncoming"
|
|
local -x NNN_OPENER="${XDG_CONFIG_HOME}/nnn/plugins/nuke"
|
|
local -x NNN_OPTS='ABeGo'
|
|
local -x NNN_ORDER="t:$HOME/Downloads"
|
|
local -x NNN_PLUG=${(j:;:)nnn_plug}
|
|
local -x NNN_TMPFILE="${XDG_CONFIG_HOME}/nnn/cd-on-quit"
|
|
|
|
if (( ${+commands[fuse-archive]} )); then
|
|
local -x NNN_ARCHMNT='fuse-archive'
|
|
fi
|
|
if (( ${+commands[trash-put]} )); then
|
|
local -x NNN_TRASH=1
|
|
elif (( ${+commands[gio]} )); then
|
|
local -x NNN_TRASH=2
|
|
fi
|
|
|
|
command nnn "$@"
|
|
|
|
if [ -f $NNN_TMPFILE ]; then
|
|
# Tmp file contains a `cd` directive that will
|
|
# behave like a `pushd`, ignoring duplicates.
|
|
setopt autopushd pushdignoredups
|
|
. $NNN_TMPFILE
|
|
rm -f $NNN_TMPFILE > /dev/null
|
|
dirs -v
|
|
fi
|
|
}
|
|
compdef n=nnn
|
|
|
|
nnn-file-widget() {
|
|
# Extract "shell word" at cursor position (respecting quotes)
|
|
local _cursor=$CURSOR
|
|
zle .select-a-shell-word -N
|
|
|
|
# Helper
|
|
nnn-pick()
|
|
{
|
|
IFS=$'\n'
|
|
local items=( $(command nnn -p- $1 < "$TTY") )
|
|
if (( #items > 0 )); then
|
|
items=( ${(q-)items} )
|
|
print -n "${(j: :)items} "
|
|
fi
|
|
}
|
|
|
|
# Adapt behaviour to buffer content
|
|
if (($CURSOR < _cursor)); then
|
|
# Nothing at cursor, restore state before calling nnn
|
|
CURSOR=${_cursor}
|
|
REGION_ACTIVE=0
|
|
zle -U "$(nnn-pick)"
|
|
else
|
|
# Provide content under cursor to nnn
|
|
zle .kill-region
|
|
local result="$(nnn-pick ${CUTBUFFER## })"
|
|
if (( #result == 0 )); then
|
|
# Nothing selected, restore content
|
|
zle .yank
|
|
else
|
|
# Replace content with what nnn returned
|
|
zle -U " $result"
|
|
fi
|
|
fi
|
|
|
|
# TODO single-op undo
|
|
# TODO Handle vicmd properly
|
|
zle .redisplay
|
|
}
|
|
zle -N nnn-file-widget
|
|
bindkey -M vicmd '\en' nnn-file-widget
|
|
bindkey -M viins '\en' nnn-file-widget
|
|
|
|
|
|
# Whiptail helper
|
|
#
|
|
# see https://stackoverflow.com/questions/1970180/whiptail-how-to-redirect-output-to-environment-variable
|
|
|
|
whiptail_checklist() {
|
|
autoload zmathfunc; zmathfunc # min, max, sum
|
|
eval $(resize) # COLUMNS, LINES
|
|
|
|
local res title="$1" text="$2"
|
|
(( width=max(30, $#text) ))
|
|
shift 2
|
|
|
|
# Make tags compatible with whiptail
|
|
local -a tags
|
|
for tag; do
|
|
tags+=($tag $tag 0)
|
|
(( width=max(width, $#tag) ))
|
|
done
|
|
|
|
# Compute "best" width & height
|
|
(( list_height=min(20, ${#*}, LINES * .5) ))
|
|
(( height=min(list_height+8, LINES) ))
|
|
(( width=min(width + 9, COLUMNS * .75) ))
|
|
|
|
tag=$(whiptail --title $title \
|
|
--checklist $text $height $width $list_height $tags \
|
|
--separate-output --notags 3>&1 1>&2 2>&3 3>&-)
|
|
res=$?
|
|
|
|
# Use res=${(f)"$(whiptail_checklist...)"} to ignore spaces
|
|
echo $tag
|
|
return $res
|
|
}
|
|
|
|
whiptail_menu() {
|
|
autoload zmathfunc; zmathfunc # min, max, sum
|
|
eval $(resize) # COLUMNS, LINES
|
|
|
|
local res title="$1" text="$2"
|
|
(( width=max(30, $#text) ))
|
|
shift 2
|
|
|
|
# Make tags compatible with whiptail
|
|
local -a tags
|
|
for tag; do
|
|
tags+=($tag $tag)
|
|
(( width=max(width, $#tag) ))
|
|
done
|
|
|
|
# Compute "best" width & height
|
|
(( menu_height=min(20, ${#*}, LINES * .5) ))
|
|
(( height=min(menu_height+8, LINES) ))
|
|
(( width=min(width + 5, COLUMNS * .75) ))
|
|
|
|
tag=$(whiptail --title $title \
|
|
--menu $text $height $width $menu_height $tags \
|
|
--notags 3>&1 1>&2 2>&3 3>&-)
|
|
res=$?
|
|
|
|
echo $tag
|
|
return $res
|
|
}
|
|
|
|
|
|
###
|
|
### Aliases -- Global & automatic expansion
|
|
###
|
|
|
|
alias -g D='$(date +%Y-%m-%d_%H-%M-%S)'
|
|
alias -g E='2> /dev/null'
|
|
alias -g G='|& grep -i'
|
|
alias -g H='| head'
|
|
alias -g HL='--help |& less -r'
|
|
alias -g L='| less'
|
|
alias -g N='&> /dev/null'
|
|
alias -g P='| peco'
|
|
alias -g S='| sort'
|
|
alias -g T='| tail'
|
|
|
|
# Expand aliases inline - see http://blog.patshead.com/2012/11/automatically-expaning-zsh-global-aliases---simplified.html
|
|
globalias() {
|
|
if [[ $LBUFFER =~ ' [A-Z0-9]+$' ]]; then
|
|
zle _expand_alias
|
|
# If you want the content of the alias to be also expanded:
|
|
# zle expand-word
|
|
# (otherwise, <BS><Tab> do the job...)
|
|
fi
|
|
zle self-insert
|
|
}
|
|
zle -N globalias
|
|
|
|
|
|
###
|
|
### Colors
|
|
###
|
|
|
|
if [[ -r /etc/grc.zsh ]]; then
|
|
# ! Incompatible with P10K Transient
|
|
source /etc/grc.zsh
|
|
|
|
# I want the default behaviour for:
|
|
unset -f journalctl
|
|
unset -f ls
|
|
fi
|
|
|
|
|
|
###
|
|
### Exports
|
|
###
|
|
|
|
export BROWSER=firefox
|
|
export EDITOR=vi
|
|
export LESS=-iFRS
|
|
export SYSTEMD_LESS='iFRSXMK'
|
|
export VISUAL=bat
|
|
|
|
# KeePassXC SSH Agent
|
|
[[ -v SSH_AUTH_SOCK ]] || export SSH_AUTH_SOCK=$(echo /tmp/ssh-*/agent.*(U))
|
|
|
|
|
|
###
|
|
### History
|
|
###
|
|
|
|
setopt hist_ignore_all_dups
|
|
setopt hist_ignore_space
|
|
setopt hist_no_functions
|
|
setopt hist_reduce_blanks
|
|
setopt hist_save_no_dups
|
|
setopt hist_verify
|
|
setopt inc_append_history
|
|
|
|
HISTFILE=${ZDOTDIR:-${HOME}}/.zhistory
|
|
HISTSIZE=4000
|
|
SAVEHIST=3000
|
|
HISTORY_IGNORE="(?|??|???|cd [a-zA-Z]*|* --help|#*|builtin *|run-help *)"
|
|
|
|
|
|
###
|
|
### Key Binding
|
|
###
|
|
|
|
# `bindkey | grep -v self-insert` to print existing bindings
|
|
# `showkey -a` to print keys
|
|
|
|
# Undo (useful to cancel completion -- borrowed from emacs keymap)
|
|
bindkey -M vicmd "^_" undo
|
|
bindkey -M viins "^_" undo
|
|
|
|
|
|
###
|
|
### Zsh Options
|
|
###
|
|
|
|
## Changing Directories
|
|
setopt no_auto_cd
|
|
|
|
## Expansion and Globbing
|
|
setopt equals
|
|
|
|
## Input/Output
|
|
setopt no_clobber
|
|
setopt correct
|
|
setopt interactive_comments
|
|
setopt no_print_exit_value
|
|
|
|
## Job control
|
|
setopt auto_continue
|
|
setopt no_bg_nice
|
|
setopt check_jobs
|
|
setopt no_hup
|
|
|
|
|
|
# Remove path duplicates
|
|
typeset -U path fpath
|
|
|
|
|
|
###
|
|
### Completion Late Definitions
|
|
###
|
|
|
|
compdef locate=glocate
|
|
|
|
# Hide "parameters" (i.e. environment variables) when looking for a "command"
|
|
zstyle ':completion:*:-command-:*' tag-order '!parameters'
|