dotfiles/config/zsh/.zshrc
Fernando Schauenburg 7464e383e3 zsh: add special ZLE widgets rather than replace them
Also took the opportunity to prefix all my functions with `fs-` to
differentiate from built-ins and avoid conflicts.
2024-07-29 00:40:33 +02:00

550 lines
17 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

setup_terminal() {
# Make ctrl-q and ctrl-s available to terminal applications.
stty start undef stop undef
}
setup_aliases() {
# ls: make `ls` group directories first if supported.
# lsc: force `ls` to use color output (e.g. for piping into `less`).
# Precendence of program to back the `ls` alias:
# 1. `eza`, if installed.
# 2. GNU ls
# 3. BSD ls (e.g. macOS)
if type -p eza >/dev/null; then
alias ls="eza --classify --group-directories-first --group --links --smart-group"
alias la="ls --all"
alias lt="ls --long --tree --ignore-glob='.git'"
alias lta="lt --all"
alias lsc="ls --color=always"
alias ltc="lt --color=always"
elif ls --group-directories-first --color=auto >/dev/null 2>&1; then
alias ls="ls --classify --human-readable --group-directories-first --color=auto"
alias la="ls --almost-all"
alias lt="tree --dirsfirst -FI '.git|Spotlight-V100|.fseventsd'"
alias lsc="ls --color=always"
alias ltc="tree -C --dirsfirst -FI '.git'"
else
alias ls="ls -hF -G"
alias la="ls -A"
alias lt="tree --dirsfirst -FI '.git|Spotlight-V100|.fseventsd'"
alias lsc="/usr/bin/env CLICOLOR_FORCE=1 ls"
alias ltc="tree -C --dirsfirst -FI '.git'"
fi
alias ll="ls -l"
alias lla="la -l"
alias ltl="lt -L"
alias dots="cd ~/.dotfiles"
alias grep="grep --color=auto"
alias egrep="egrep --color=auto"
alias fgrep="fgrep --color=auto"
if type -p nvim >/dev/null; then
alias v="nvim"
alias vi="nvim"
alias vim="nvim"
alias vimdiff="nvim -d"
fi
}
# source "$ZDOTDIR/completion.zsh"
setup_completion() {
# Enable additional completions from packages in `/usr/local`.
local vendor="/usr/local/share/zsh/vendor-completions"
[ -d "$vendor" ] && fpath=("$vendor" $fpath)
zmodload zsh/complist
autoload -Uz compinit
compinit -d "$XDG_CACHE_HOME/zsh/.zcompdump"
# Include hidden files when completing.
_comp_options+=(globdots)
# Completion context pattern:
# :completion:<function>:<completer>:<command>:<argument>:<tag>
# Which completion functions to use.
zstyle ':completion:*' completer _extensions _complete
# Match case-insensitively and on partial words.
# TODO: perhaps implement something like nvim's smartcase?
zstyle ':completion:*' matcher-list 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}'
# Activate caching for completions that may use it.
zstyle ':completion:*' use-cache true
zstyle ':completion:*' cache-path "$XDG_CACHE_HOME/zsh/.zcompcache"
# List files in a format similar to `ls -l`.
zmodload zsh/stat # required for `file-list all`
disable stat # don't shadow external `stat` executable
zstyle ':completion:*' file-list all
# Don't complete on all path components, which would complete /u/b/z to /usr/bin/zsh.
zstyle ':completion:*' path-completion false
# Use tags as the group names.
zstyle ':completion:*' group-name ''
# Custom order for words in the command position.
zstyle ':completion:*:*:-command-:*:*' group-order builtins aliases functions commands
# Colors!
zstyle ':completion:*:*:*:*:descriptions' format '%F{green} %d%f'
zstyle ':completion:*:*:*:*:corrections' format '%F{yellow} %d%f'
zstyle ':completion:*:*:*:*:original' format '%F{yellow} %d%f'
zstyle ':completion:*:*:*:*:messages' format '%F{cyan} %d%f'
zstyle ':completion:*:*:*:*:warnings' format '%F{red} no matches%f'
zstyle ':completion:*:*:*:*:default' list-colors true
##################
# Menu selection #
##################
zstyle ':completion:*' menu select
zstyle ':completion:*:*:*:*:default' select-prompt '%K{yellow}%F{black}%l (%p)%f%k'
# Navigate the list with `hjkl`.
bindkey -M menuselect 'h' vi-backward-char
bindkey -M menuselect 'j' vi-down-line-or-history
bindkey -M menuselect 'k' vi-up-line-or-history
bindkey -M menuselect 'l' vi-forward-char
# Accept selected match and keep menu open.
bindkey -M menuselect 'p' accept-and-hold
# Accept selected match and restart completion (allows drilling down directories).
bindkey -M menuselect '\t' accept-and-infer-next-history
# Remove previously inserted matches.
bindkey -M menuselect 'u' undo
# Jump to first/last line.
bindkey -M menuselect 'g' beginning-of-history
bindkey -M menuselect 'G' end-of-history
# Page down/up.
bindkey -M menuselect 'f' vi-forward-word
bindkey -M menuselect 'b' vi-backward-word
}
setup_history() {
setopt APPEND_HISTORY # Append history rather than overwrite.
setopt EXTENDED_HISTORY # Save beginning timestamp and duration.
setopt INC_APPEND_HISTORY # Don't wait until shell exits to save history.
setopt HIST_IGNORE_ALL_DUPS # Don't add duplicates to history.
HISTFILE="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/history"
HISTSIZE=1000000
SAVEHIST=1000000
}
setup_zle() {
bindkey -v # Use vi mode for line editing.
export KEYTIMEOUT=1 # 10ms delay for <esc> to switch to command mode.
#############################
# ZLE Widgets
#############################
fs-set-cursor-shape() {
local block='\e[1 q' # blinking block
local underline='\e[3 q' # blinking underline, 4 for steady
local bar='\e[5 q' # blinkind bar, 6 for steady
if [[ -n "$ITERM_SESSION_ID" && -z "$TMUX" ]] {
block='\e]1337;CursorShape=0\a'
bar='\e]1337;CursorShape=1\a'
underline='\e]1337;CursorShape=2\a'
}
case "$1" in
block) echo -n $block ;;
bar) echo -n $bar ;;
underline) echo -n $underline ;;
esac
}
autoload -Uz add-zle-hook-widget
# Start new prompts with bar shaped cursor.
add-zle-hook-widget line-init fs-zle-line-init
fs-zle-line-init() {
fs-set-cursor-shape bar
}
# Switch cursor shape depending on editing mode.
add-zle-hook-widget keymap-select fs-zle-keymap-select
fs-zle-keymap-select() {
case $KEYMAP in
vicmd) fs-set-cursor-shape block ;;
viins|main) fs-set-cursor-shape bar ;;
esac
}
# Clear completion menus and other status line text.
zle -N fs-zle-clear-status-line
fs-zle-clear-status-line() { zle -R -c; }
# Change root directory of the current git repository, if in one.
zle -N fs-zle-cd-git-root
fs-zle-cd-git-root() {
local top_level="$(git rev-parse --show-toplevel 2>/dev/null)"
[[ -z "$top_level" ]] && return 1
zle push-line
BUFFER="builtin cd -- ${(q)top_level}"
zle accept-line
}
autoload edit-command-line && zle -N edit-command-line # from zshcontrib
#############################
# Key mappings
#############################
# -------------- CTRL -------------------------------
bindkey -M viins '^a' vi-beginning-of-line
bindkey -M viins '^e' vi-end-of-line
bindkey -M viins '^g' fs-zle-cd-git-root
bindkey -M viins '^j' history-beginning-search-forward
bindkey -M viins '^k' history-beginning-search-backward
bindkey -M viins '^l' clear-screen
bindkey -M viins '^q' fs-zle-clear-status-line
# ^r history search using fzf
# ^t paste file/directory using fzf
bindkey -M viins '^u' kill-whole-line
bindkey -s '^v' '^unvim\r'
bindkey -M viins '^x^e' edit-command-line
bindkey -M viins '^y' yank
# -------------- ALT -------------------------------
bindkey -M viins '^[.' insert-last-word
bindkey -M viins '^[b' vi-backward-word
# ^[c cd using fzf
bindkey -M viins '^[f' vi-forward-word
# --------- Command mode -------------------------------
bindkey -M vicmd ' ' edit-command-line
#############################
# fzf
#############################
if type -p fzf >/dev/null; then
setup_zle_fzf
else
# Fall back to incremental search
bindkey -M viins '^r' history-incremental-search-backward
bindkey -M isearch '^j' history-incremental-search-forward
bindkey -M isearch '^k' history-incremental-search-backward
bindkey -M isearch '^y' accept-search
fi
}
setup_zle_fzf() {
local fzf_config="$ZDOTDIR/fzf/key-bindings.zsh"
[ -r "$fzf_config" ] || return
fzf_find() {
local excluded_dirs=("-name '.*'") # Exclude hidden directories.
if [ "$(uname -s)" = "Darwin" ] && [ "$(pwd)" = "$HOME" ]; then
# These macOS directories would make the find command unusable (too slow).
excluded_dirs+=(
"-name Library"
"-name Movies"
"-name Music"
"-name Pictures"
)
fi
local excluded_filesystems=(
"-fstype devfs"
"-fstype devtmpfs"
"-fstype proc"
"-fstype sysfs"
)
local excluded_items=(
"-type d \\( ${(j: -o :)excluded_dirs} \\)"
"${(j: -o :)excluded_filesystems}"
)
local excluded="\\( ${(j: -o :)excluded_items} \\) -prune"
local included="\\( ${(j: -o :)@} \\) -print"
eval "command find -L . -mindepth 1 $excluded -o $included 2>/dev/null | cut -c3-"
}
fzf_find_files() {
fzf_find "-type d" "-type f"
}
fzf_find_dirs() {
fzf_find "-type d"
}
source "$fzf_config"
export FZF_CTRL_T_COMMAND=fzf_find_files
export FZF_ALT_C_COMMAND=fzf_find_dirs
export FZF_CTRL_T_OPTS=--border-label='" Select file(s) "'
export FZF_CTRL_R_OPTS=--border-label='" History search "'
export FZF_ALT_C_OPTS=--border-label='" Change directory "'
}
# Colorful man pages.
setup_man_pages() {
# Foreground colors
typeset -A fg
fg[black]='\e[30m'
fg[red]='\e[31m'
fg[green]='\e[32m'
fg[yellow]='\e[33m'
fg[blue]='\e[34m'
fg[magenta]='\e[35m'
fg[cyan]='\e[36m'
fg[white]='\e[37m'
fg[br_black]='\e[90m'
fg[br_red]='\e[91m'
fg[br_green]='\e[92m'
fg[br_yellow]='\e[93m'
fg[br_blue]='\e[94m'
fg[br_magenta]='\e[95m'
fg[br_cyan]='\e[96m'
fg[br_white]='\e[97m'
# Background colors
typeset -A bg
bg[black]='\e[40m'
bg[red]='\e[41m'
bg[green]='\e[42m'
bg[yellow]='\e[43m'
bg[blue]='\e[44m'
bg[magenta]='\e[45m'
bg[cyan]='\e[46m'
bg[white]='\e[47m'
bg[br_black]='\e[100m'
bg[br_red]='\e[101m'
bg[br_green]='\e[102m'
bg[br_yellow]='\e[103m'
bg[br_blue]='\e[104m'
bg[br_magenta]='\e[105m'
bg[br_cyan]='\e[106m'
bg[br_white]='\e[107m'
# Other modifiers
local reset='\e[0m'
local bold='\e[1m'
local faint='\e[2m'
local italic='\e[3m'
local underline='\e[4m'
#######################
# Customize man pages #
#######################
# bold (md) and blink (mb)
export LESS_TERMCAP_md="$(printf %b $fg[blue])"
export LESS_TERMCAP_mb="$LESS_TERMCAP_md"
export LESS_TERMCAP_me="$(printf %b $reset)"
# underline
export LESS_TERMCAP_us="$(printf %b $fg[br_blue] $italic $underline)"
export LESS_TERMCAP_ue="$(printf %b $reset)"
# search
export LESS_TERMCAP_so="$(printf %b $fg[black] $bg[yellow] $bold)"
export LESS_TERMCAP_se="$(printf %b $reset)"
# Tell `groff` to not emit SGR sequences, since we are telling `less` to
# generate them as per the configurations above.
export GROFF_NO_SGR=1
}
setup_prompt() {
source "$ZDOTDIR/prompt.zsh"
}
setup_eza() {
type -p eza >/dev/null || return
local black=30
local red=31
local green=32
local yellow=33
local blue=34
local magenta=35
local cyan=36
local white=37
local br_black=90
local br_red=91
local br_green=92
local br_yellow=93
local br_blue=94
local br_magenta=95
local br_cyan=96
local br_white=97
local colors=(
di=$blue # directories
ex=$green # executable files
# fi # regular files
# pi # named pipes
# so # sockets
# bd # block devices
# cd # character devices
# ln # symlinks
# or # symlinks with no target
# oc # the permissions displayed as octal
ur=$yellow # the user-read permission bit
uw=$red # the user-write permission bit
ux=$green # the user-execute permission bit for regular files
ue=$green # the user-execute for other file kinds
# gr # the group-read permission bit
# gw # the group-write permission bit
# gx # the group-execute permission bit
# tr # the others-read permission bit
# tw # the others-write permission bit
# tx # the others-execute permission bit
# su # setuid, setgid, and sticky permission bits for files
# sf # setuid, setgid, and sticky for other file kinds
# xa # the extended attribute indicator
# sn # the numbers of a files size (sets nb, nk, nm, ng and nt)
# nb # the numbers of a files size if it is lower than 1 KB/Kib
nk=$green # the numbers of a files size if it is between 1 KB/KiB and 1 MB/MiB
# nm # the numbers of a files size if it is between 1 MB/MiB and 1 GB/GiB
# ng # the numbers of a files size if it is between 1 GB/GiB and 1 TB/TiB
# nt # the numbers of a files size if it is 1 TB/TiB or higher
# sb # the units of a files size (sets ub, uk, um, ug and ut)
# ub # the units of a files size if it is lower than 1 KB/Kib
# uk # the units of a files size if it is between 1 KB/KiB and 1 MB/MiB
# um # the units of a files size if it is between 1 MB/MiB and 1 GB/GiB
# ug # the units of a files size if it is between 1 GB/GiB and 1 TB/TiB
# ut # the units of a files size if it is 1 TB/TiB or higher
# df # a devices major ID
# ds # a devices minor ID
uu=$br_black # a user thats you
uR=$red # a user thats root
un=$yellow # a user thats someone else
gu=$br_black # a group that you belong to
gR=$red # a group related to root
gn=$yellow # a group you arent a member of
lc=$red # a number of hard links
# lm # a number of hard links for a regular file with at least two
# ga # a new flag in Git
# gm # a modified flag in Git
# gd # a deleted flag in Git
# gv # a renamed flag in Git
# gt # a modified metadata flag in Git
# gi # an ignored flag in Git
# gc # a conflicted flag in Git
# Gm # main branch of repo
# Go # other branch of repo
# Gc # clean branch of repo
# Gd # dirty branch of repo
# xx # “punctuation”, including many background UI elements
# da # a files date
# in # a files inode number
# bl # a files number of blocks
# hd # the header row of a table
# lp # the path of a symlink
# cc # an escaped character in a filename
# bO # the overlay style for broken symlink paths
# sp # special (not file, dir, mount, exec, pipe, socket, block device, char device, or link)
# mp # a mount point
# im # a regular file that is an image
# vi # a regular file that is a video
# mu # a regular file that is lossy music
# lo # a regular file that is lossless music
# cr # a regular file that is related to cryptography (ex: key or certificate)
# do # a regular file that is a document (ex: office suite document or PDF)
# co # a regular file that is compressed
# tm # a regular file that is temporary (ex: a text editors backup file)
# cm # a regular file that is a compilation artifact (ex: Java class file)
bu=$yellow # a regular file that is used to build a project (ex: Makefile)
sc=$yellow # a regular file that is source code
# Sn # No security context on a file
# Su # SELinux user
# Sr # SELinux role
# St # SELinux type
# Sl # SELinux level
# ff # BSD file flags
)
export EZA_COLORS="${(pj.:.)colors}"
}
setup_fzf() {
type -p fzf >/dev/null || return
local colors=(
fg:bright-black
fg+:bright-white:bold
bg+:-1
hl:yellow:regular
hl+:bright-yellow:bold
query:bright-white:regular
info:blue:regular
border:bright-black
scrollbar:white
separator:bright-black
label:yellow:bold
prompt:blue
pointer:red:bold
marker:green
spinner:yellow
)
local bindings=(
ctrl-f:page-down
ctrl-b:page-up
ctrl-o:toggle-up
ctrl-t:toggle-all
ctrl-x:deselect-all
)
local defaults=(
--reverse
--border=rounded
--scrollbar=
--prompt='" "'
--pointer='" "'
--marker='"󰄴 "'
--color="${(pj/,/)colors}"
--bind="${(pj/,/)bindings}"
)
export FZF_DEFAULT_OPTS="${(pj/ /)defaults}"
}
# Set up autoload for custom functions.
setup_functions() {
fpath=("$ZDOTDIR/functions" $fpath)
local filepath
for filepath in $ZDOTDIR/functions/*; do
autoload "${filepath##*/}"
done
}
# Load additional local configuration if present.
setup_local_config() {
local config="$HOME/.local/etc/zsh/zshrc"
[ -r "$config" ] && source "$config" || true
}
# Set up zsh for interactive use (options, prompt, aliases, etc.)
setup_zsh_interactive() {
setopt INTERACTIVE_COMMENTS # Allow comments in interactive use.
setup_terminal
setup_aliases
setup_completion
setup_history
setup_zle
setup_man_pages
setup_prompt
setup_functions
setup_eza
setup_fzf
setup_local_config
}
# zmodload zsh/zprof
setup_zsh_interactive
# zprof