diff options
Diffstat (limited to '.config/zsh')
-rw-r--r-- | .config/zsh/.zshrc | 293 | ||||
-rw-r--r-- | .config/zsh/command-tools.zsh | 61 | ||||
m--------- | .config/zsh/plugins/fast-syntax-highlighting | 0 | ||||
-rw-r--r-- | .config/zsh/plugins/fzf-completion.zsh | 329 | ||||
-rw-r--r-- | .config/zsh/plugins/zsh-autosuggestions.zsh | 864 | ||||
-rw-r--r-- | .config/zsh/plugins/zsh-history-substring-search.zsh | 759 | ||||
m--------- | .config/zsh/plugins/zsh-syntax-highlighting | 0 | ||||
-rw-r--r-- | .config/zsh/zprofile | 21 |
8 files changed, 2327 insertions, 0 deletions
diff --git a/.config/zsh/.zshrc b/.config/zsh/.zshrc new file mode 100644 index 0000000..3f7ed30 --- /dev/null +++ b/.config/zsh/.zshrc @@ -0,0 +1,293 @@ +############################# +## SHELL CONFIGURATION ## +############################# + +bindkey -e +alias find="2> >(grep -v 'Permission denied' >&2) find" + +source ~/.profile +[ -n "$SDOTDIR" ] || SDOTDIR="${XDG_CONFIG_HOME:-$HOME/.config}/shell" +[ -n "$ZDOTDIR" ] || ZDOTDIR="${XDG_CONFIG_HOME:-HOME/.config}/zsh" + +source "$ZDOTDIR/command-tools.zsh" +source "$ZDOTDIR/plugins/fzf-completion.zsh" +# source "$ZDOTDIR/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" +source "$ZDOTDIR/plugins/zsh-autosuggestions.zsh" + +setopt correct # Auto correct mistakes +setopt extended_glob # Allows using regular expressions with * +setopt rc_expand_param # Array expension with parameters +setopt no_check_jobs # Don't warn about running processes when exiting +setopt rm_star_silent # Don't warn when using glob with rm +setopt numeric_glob_sort # Sort filenames numerically when it makes sense +setopt no_beep # No beep +setopt append_history # Immediately append history instead of overwriting +setopt extended_history # Add timestamps to history +setopt hist_ignore_all_dups # If a new command is a duplicate, remove the older one +setopt auto_cd # if only directory path is entered, cd there. +setopt auto_pushd # pushd on cd +setopt pushd_ignore_dups # truncate duplicate directories +setopt pushd_minus +setopt interactive_comments # enable comments on the command line +setopt histignorespace # ignore lines starting with a space + +# Enable colors and change prompt: +autoload -U colors && colors +PS1="%B%{$fg[red]%}[%{$fg[yellow]%}%n%{$fg[green]%}@%{$fg[blue]%}%M %{$fg[magenta]%}%1~%{$fg[red]%}]%{$reset_color%}$%b " +#PS1="%B%{$fg[red]%} %~ >%{$fg[yellow]%}%{$reset_color%}%b " +[ -n "$LF_LEVEL" ] && PS1="$PS1(lf: $LF_LEVEL) " + +# Change cursor shape for different vi modes. +function zle-keymap-select { + case $KEYMAP in + vicmd) echo -ne '\e[1 q';; # block + viins|main) echo -ne '\e[5 q';; # beam + esac +} +zle -N zle-keymap-select +zle-line-init() { echo -ne "\e[5 q"; } +zle -N zle-line-init +# echo -ne '\e[5 q' # Use beam shape cursor on startup. +preexec() { echo -ne '\e[5 q' ;} # Use beam shape cursor for each new prompt. + +# Completion. +autoload -Uz compinit +compinit -d ~/.cache/zshcompdump-$ZSH_VERSION +zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' # Case insensitive tab completion +zstyle ':completion:*' rehash true # automatically find new executables in path +zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}" # Colored completion (different colors for dirs/files/etc) +zstyle ':completion:*' completer _expand _complete _ignored _approximate +zstyle ':completion:*' menu select +zstyle ':completion:*' select-prompt '%SScrolling active: current selection at %p%s' +zstyle ':completion:*:descriptions' format '%U%F{cyan}%d%f%u' + +# Speed up completions +zstyle ':completion:*' accept-exact '*(N)' +zstyle ':completion:*' use-cache on +zstyle ':completion:*' cache-path ~/.cache/zsh + +# automatically load bash completion functions +autoload -U +X bashcompinit && bashcompinit +source "$ZDOTDIR/completion/arduino-cli.zsh" + +ZSH_AUTOSUGGEST_STRATEGY=(history completion) +ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(end-of-line vi-end-of-line vi-add-eol) +ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS+=(forward-char vi-forward-char) + +HISTFILE="${XDG_DATA_HOME:=$HOME/.local/share}/zsh/history" +HISTSIZE=100000 +SAVEHIST=50000 + +unset WORDCHARS +stty -ixon + +# Angular +# _ng_yargs_completions() +# { +# local reply +# local si=$IFS +# IFS=$' +# ' reply=($(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" ng --get-yargs-completions "${words[@]}")) +# IFS=$si +# _describe 'values' reply +# } +# compdef _ng_yargs_completions ng + + +_command_fail_hook() { + [ $? != 1 ] && return + if [ -n "$TMUX" ]; then + tmux send-keys C-p + elif [ -n "$XDOTOOL_WINDOW_ID" ]; then + xdotool key --window "$XDOTOOL_WINDOW_ID" ctrl+p + fi +} +add-zsh-hook precmd _command_fail_hook + +_cd_history_hook() { + sed -i "\|^$PWD$|d" "${XDG_DATA_HOME:-$HOME/.local/share}/cdhist" + echo "$PWD" >> "${XDG_DATA_HOME:-$HOME/.local/share}/cdhist" +} +add-zsh-hook -Uz chpwd _cd_history_hook + + +autoload -Uz up-line-or-beginning-search down-line-or-beginning-search +zle -N up-line-or-beginning-search +zle -N down-line-or-beginning-search + +bindkey -s '^[o' '^[q lfcd^M' +bindkey -s '^[j' '^[q lfcd "$(ff)"^M' +bindkey -s '^[l' '^[q l^M' +bindkey -s '^[L' '^[q lsblk^M' +bindkey -s '^[D' '^[q dirs -v^M' +bindkey -s '^[i' '^[q pushd -1^M' +bindkey -s '^[-' '^[q cd -^M' +bindkey -s '^[B' '^[q bindkey | less^M' + +bindkey jk vi-cmd-mode +bindkey "^j" autosuggest-execute +bindkey '^P' up-line-or-beginning-search +bindkey '^N' down-line-or-beginning-search +bindkey '^[p' up-line-or-history +bindkey '^[n' down-line-or-history +bindkey '^[w' backward-kill-word + +autoload -U edit-command-line; zle -N edit-command-line +bindkey '^[E' edit-command-line + +typeset -g -A key +if (( ${+terminfo[smkx]} && ${+terminfo[rmkx]} )); then + autoload -Uz add-zle-hook-widget + function zle_application_mode_start { echoti smkx } + function zle_application_mode_stop { echoti rmkx } + add-zle-hook-widget -Uz zle-line-init zle_application_mode_start + add-zle-hook-widget -Uz zle-line-finish zle_application_mode_stop +fi + + +[ -n "$DISPLAY" ] && command -V xdotool >/dev/null && + XDOTOOL_WINDOW_ID="$(xdotool getactivewindow)" + +[ -f "$BUFFER_CACHE" ] && { + { + if [ -n "$TMUX" ]; then + tmux send-keys -l "$(cat "$BUFFER_CACHE")" + elif [ -z "$TERMUX_VERSION" ]; then + xdotool type --window "$XDOTOOL_WINDOW_ID" "$(cat "$BUFFER_CACHE")" + fi + rm -f "$BUFFER_CACHE" + } & disown +} + +BUFFER_CACHE="${ZCACHEDIR:=$HOME/.cache/zsh}/previous-command-buffer-$$.tmp" +_exec-zsh() { + echo "$BUFFER" > "$BUFFER_CACHE" + BUFFER=' exec zsh' + zle accept-line +} +zle -N exec-zsh _exec-zsh +bindkey '^[r' exec-zsh + +backward-delete-word-to-slash() { + local WORDCHARS=${WORDCHARS/\//} + zle backward-delete-word +} +zle -N backward-delete-word-to-slash +bindkey '^W' backward-delete-word-to-slash + +_fzf-file-history() { + LBUFFER="${LBUFFER}$(sed "s|$HOME|~|" "${XDG_DATA_HOME:-$HOME/.local/share}/openhist" | fzf --tac --reverse --height 40% | sed "s/ /\\\ /")" + zle reset-prompt +} +zle -N fzf-file-history _fzf-file-history +bindkey '^T' fzf-file-history + +_fzf-cd-widget() { + cdpath="$(eval "${FZF_CD_COMMAND:-find -L . -type d ! -wholename '*.git*' -printf '%p/\n'}" | + FZF_DEFAULT_OPTS="$FZF_DEFAULT_OPTS --reverse --height 40% $FZF_CD_OPTS" fzf | + sed 's/"/\\\"/')" + [ -z "$cdpath" ] && { zle reset-prompt; return; } + zle push-line + BUFFER="cd -- \"$cdpath\"" + zle reset-prompt + zle accept-line +} +zle -N fzf-cd-widget _fzf-cd-widget +bindkey '^[c' fzf-cd-widget + +_cd-path-history() { + FZF_CD_COMMAND="< ${XDG_DATA_HOME:-$HOME/.local/share}/cdhist" \ + FZF_CD_OPTS="--tac --tiebreak=index --no-sort" \ + zle fzf-cd-widget +} +zle -N cd-path-history _cd-path-history +bindkey '^[k' cd-path-history + +_fzf-history-widget() { + zle vi-fetch-history -n "$(fc -rl 1 | fzf --height 40% -n2.. --tiebreak=index \ + --preview 'echo {2..}' --preview-window up:50%:hidden:wrap \ + --bind 'ctrl-y:execute-silent(echo -n {2..} | xsel --clipboard)' \ + --query="$LBUFFER")" + zle reset-prompt +} +zle -N fzf-history-widget _fzf-history-widget +bindkey '^R' fzf-history-widget + + +get-help() { + [ "$1" != sudo ] && cmd="$1" || cmd="$2" + cmdinfo="$(command -v "$cmd")" + case "${$(whence -w "$cmd")##*: }" in + builtin) MANPAGER="less +/'^ $cmd'" man zshbuiltins && HELP_FOUND=1 ;; + reserved) MANPAGER="less +/'^reserved' +/'$cmd'" man zshall && HELP_FOUND=1 ;; + alias) echo "$cmdinfo"; cmd="$(alias "$cmd" | sed "s/$cmd='\(\S*\) .*'/\1/")" ;; + esac + [ -n "$cmdinfo" ] && help "$cmd" || help "$1" +} +alias run-help='get-help' +alias which-command='where' + +_get-help() { + cmd=(${=BUFFER}) + zle push-line + case "${cmd[1]}" in + gh|hugo|npm|git) BUFFER="help ${cmd[1]}-${cmd[2]}" ;; + *) BUFFER="get-help $cmd" ;; + esac + zle accept-line +} +zle -N get-help _get-help +bindkey '^[H' get-help + + +[ -f "$SDOTDIR/aliasrc" ] && source "$SDOTDIR/aliasrc" +[ -f "$SDOTDIR/shortcutrc" ] && source "$SDOTDIR/shortcutrc" +[ -f "$ZDOTDIR/zshnameddirrc" ] && source "$ZDOTDIR/zshnameddirrc" + +case "$TERM" in *256*) + if [ -x /bin/eza ] || [ -x /usr/bin/eza ]; then + alias l='exa -aF --group-directories-first --color=always --icons' + lt() { exa --group-directories-first --color=always \ + --icons -FaT "$@" | less -rF; } + fi + + command -V starship >/dev/null && { + eval "$(starship init zsh)" + function set_win_title(){ + echo -ne "\033]0; $USER@$HOST:${PWD/$HOME/~} \007" + } + precmd_functions+=(set_win_title) + } + ;; +esac + +alias p='pacman' +alias sp='sudo pacman' +alias sv='sudo sv' +alias mmv='noglob zmv -W' +alias s='sudo ' +alias mirror='sudo reflector -f 30 -l 30 --number 10 --verbose --save /etc/pacman.d/mirrorlist' +alias mirrord='sudo reflector --latest 50 --number 20 --sort delay --save /etc/pacman.d/mirrorlist' +alias cleanup='sudo pacman -Rns $(pacman -Qtdq)' +alias fixpacman='sudo rm /var/lib/pacman/db.lck' +alias gtypist="gtypist $GTYPIST_OPTS" +alias typ='launch-gtypist -l "$(sed "/^gtypist lesson - \(.*\)$/!d; s//\1/" ~/Documents/Notes/QuickNote.md)"' +alias typa='launch-gtypist -e 3 -l "$(sed "/^gtypist lesson - \(.*\)$/!d; s//\1/" ~/Documents/Notes/ak47.txt)"' + +f() { + ret=$? + [ -n "$1" ] && { pacman -F "$@"; return; } + [ "$ret" != 127 ] && echo "Return code of last command is not 127" >&2 && return 2 + pacman -F "$(fc -s 2>&1 | tail -1 | cut -d\ -f5-)" +} + +# Execute functions with sudo +# sudofu() { +# [[ "$(type -w $1)" == "$1: function" ]] && +# ARGS="$@" && sudo bash -c "$(declare -f $1); $ARGS" +# } +# alias ssudo='sudofu ' + +# Load syntax highlighter; should be last. +source "$ZDOTDIR/plugins/fast-syntax-highlighting/fast-syntax-highlighting.plugin.zsh" + diff --git a/.config/zsh/command-tools.zsh b/.config/zsh/command-tools.zsh new file mode 100644 index 0000000..20126cb --- /dev/null +++ b/.config/zsh/command-tools.zsh @@ -0,0 +1,61 @@ +whereis_alias() { + location="$(zsh -xic exit 2>&1 | sed -E "/alias[ '$]*$1=/!d" | + fzf --exit-0 --select-1 --ansi --header-first --header='Select alias location' | + sed -n "s|^\+\(/.*:[0-9]*\)>.*$|\1|p")" + + if [ -z "$location" ]; then + echo "$0: '$1' not found in zsh debug log" >&2; return 1 + elif [ "$location" != "${location#*shortcutrc}" ]; then + awk "/^$1/ {print FILENAME\":\"NR}" ~/.config/shell/bm-{files,dirs} + else + echo "$location" + fi +} + +edit-command() { + [ -z "$1" ] && echo "USAGE: $0 <command>" >&2 && return 2 + cmd="$1" + cmdtype=$(whence -w "$cmd" | cut -d: -f2 | tr -d ' ') + + case "$cmdtype" in + alias) + query="alias ${cmd}=" + location="$(whereis_alias "$cmd")" || return + file="${location%%:*}" + line="${location##*:}" + [ "$file" != "${file#*/bm-}" ] && query="^$cmd" + ;; + function) + query="^\s*${cmd}\s*()" + file=$(whence -v "$cmd" | awk '{print $NF}') + ;; + command) + file=$(whence -ps "$cmd" | awk -F' -> ' '{print $NF}') + case "$(file --mime-type --brief "$file")" in + text/*) $EDITOR "$file" ;; + *) echo "$0: not a text file: $file" >&2; return 2 ;; + esac + return + ;; + builtin) echo "$0: $cmd is a builtin command" >&2; return 2 ;; + reserved) echo "$0: $cmd is a reserved command" >&2; return 2 ;; + none) echo "$0: $cmd not found" >&2; return 2 ;; + *) echo "$0: unknown command type: $cmdtype" >&2; return 2 ;; + esac + + if [ -n "$query" ]; then + $EDITOR +${line:-1} +/"$query" "$file" + else + $EDITOR "$file" + fi +} + +_edit-command() { + [ -z "$BUFFER" ] && return 2 + cmd=(${=BUFFER}); cmd="${cmd[1]}" + zle push-line + BUFFER="edit-command $cmd" + zle accept-line +} +zle -N edit-command _edit-command +bindkey '^[e' edit-command diff --git a/.config/zsh/plugins/fast-syntax-highlighting b/.config/zsh/plugins/fast-syntax-highlighting new file mode 160000 +Subproject cf318e06a9b7c9f2219d78f41b46fa6e06011fd diff --git a/.config/zsh/plugins/fzf-completion.zsh b/.config/zsh/plugins/fzf-completion.zsh new file mode 100644 index 0000000..f12afca --- /dev/null +++ b/.config/zsh/plugins/fzf-completion.zsh @@ -0,0 +1,329 @@ +# ____ ____ +# / __/___ / __/ +# / /_/_ / / /_ +# / __/ / /_/ __/ +# /_/ /___/_/ completion.zsh +# +# - $FZF_TMUX (default: 0) +# - $FZF_TMUX_OPTS (default: '-d 40%') +# - $FZF_COMPLETION_TRIGGER (default: '**') +# - $FZF_COMPLETION_OPTS (default: empty) + +# Both branches of the following `if` do the same thing -- define +# __fzf_completion_options such that `eval $__fzf_completion_options` sets +# all options to the same values they currently have. We'll do just that at +# the bottom of the file after changing options to what we prefer. +# +# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted +# *must* be quoted in order to prevent alias expansion. In addition, code must +# be written in a way works with any set of zsh options. This is very tricky, so +# careful when you change it. +# +# Start by loading the builtin zsh/parameter module. It provides `options` +# associative array that stores current shell options. +if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then + # This is the fast branch and it gets taken on virtually all Zsh installations. + # + # ${(kv)options[@]} expands to array of keys (option names) and values ("on" + # or "off"). The subsequent expansion# with (j: :) flag joins all elements + # together separated by spaces. __fzf_completion_options ends up with a value + # like this: "options=(shwordsplit off aliases on ...)". + __fzf_completion_options="options=(${(j: :)${(kv)options[@]}})" +else + # This branch is much slower because it forks to get the names of all + # zsh options. It's possible to eliminate this fork but it's not worth the + # trouble because this branch gets taken only on very ancient or broken + # zsh installations. + () { + # That `()` above defines an anonymous function. This is essentially a scope + # for local parameters. We use it to avoid polluting global scope. + 'local' '__fzf_opt' + __fzf_completion_options="setopt" + # `set -o` prints one line for every zsh option. Each line contains option + # name, some spaces, and then either "on" or "off". We just want option names. + # Expansion with (@f) flag splits a string into lines. The outer expansion + # removes spaces and everything that follow them on every line. __fzf_opt + # ends up iterating over option names: shwordsplit, aliases, etc. + for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do + if [[ -o "$__fzf_opt" ]]; then + # Option $__fzf_opt is currently on, so remember to set it back on. + __fzf_completion_options+=" -o $__fzf_opt" + else + # Option $__fzf_opt is currently off, so remember to set it back off. + __fzf_completion_options+=" +o $__fzf_opt" + fi + done + # The value of __fzf_completion_options here looks like this: + # "setopt +o shwordsplit -o aliases ..." + } +fi + +# Enable the default zsh options (those marked with <Z> in `man zshoptions`) +# but without `aliases`. Aliases in functions are expanded when functions are +# defined, so if we disable aliases here, we'll be sure to have no pesky +# aliases in any of our functions. This way we won't need prefix every +# command with `command` or to quote every word to defend against global +# aliases. Note that `aliases` is not the only option that's important to +# control. There are several others that could wreck havoc if they are set +# to values we don't expect. With the following `emulate` command we +# sidestep this issue entirely. +'emulate' 'zsh' '-o' 'no_aliases' + +# This brace is the start of try-always block. The `always` part is like +# `finally` in lesser languages. We use it to *always* restore user options. +{ + +# Bail out if not interactive shell. +[[ -o interactive ]] || return 0 + +# To use custom commands instead of find, override _fzf_compgen_{path,dir} +if ! declare -f _fzf_compgen_path > /dev/null; then + _fzf_compgen_path() { + echo "$1" + command find -L "$1" \ + -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \ + -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' + } +fi + +if ! declare -f _fzf_compgen_dir > /dev/null; then + _fzf_compgen_dir() { + command find -L "$1" \ + -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \ + -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' + } +fi + +########################################################### + +__fzf_comprun() { + if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then + _fzf_comprun "$@" + elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then + shift + if [ -n "$FZF_TMUX_OPTS" ]; then + fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@" + else + fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@" + fi + else + shift + fzf "$@" + fi +} + +# Extract the name of the command. e.g. foo=1 bar baz**<tab> +__fzf_extract_command() { + local token tokens + tokens=(${(z)1}) + for token in $tokens; do + token=${(Q)token} + if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then + echo "$token" + return + fi + done + echo "${tokens[1]}" +} + +__fzf_generic_path_completion() { + local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches + base=$1 + lbuf=$2 + cmd=$(__fzf_extract_command "$lbuf") + compgen=$3 + fzf_opts=$4 + suffix=$5 + tail=$6 + + setopt localoptions nonomatch + eval "base=$base" + [[ $base = *"/"* ]] && dir="$base" + while [ 1 ]; do + if [[ -z "$dir" || -d ${dir} ]]; then + leftover=${base/#"$dir"} + leftover=${leftover/#\/} + [ -z "$dir" ] && dir='.' + [ "$dir" != "/" ] && dir="${dir/%\//}" + matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do + echo -n "${(q)item}$suffix " + done) + matches=${matches% } + if [ -n "$matches" ]; then + LBUFFER="$lbuf$matches$tail" + fi + zle reset-prompt + break + fi + dir=$(dirname "$dir") + dir=${dir%/}/ + done +} + +_fzf_path_completion() { + __fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \ + "-m" "" " " +} + +_fzf_dir_completion() { + __fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \ + "" "/" "" +} + +_fzf_feed_fifo() ( + command rm -f "$1" + mkfifo "$1" + cat <&0 > "$1" & +) + +_fzf_complete() { + setopt localoptions ksh_arrays + # Split arguments around -- + local args rest str_arg i sep + args=("$@") + sep= + for i in {0..${#args[@]}}; do + if [[ "${args[$i]}" = -- ]]; then + sep=$i + break + fi + done + if [[ -n "$sep" ]]; then + str_arg= + rest=("${args[@]:$((sep + 1)):${#args[@]}}") + args=("${args[@]:0:$sep}") + else + str_arg=$1 + args=() + shift + rest=("$@") + fi + + local fifo lbuf cmd matches post + fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$" + lbuf=${rest[0]} + cmd=$(__fzf_extract_command "$lbuf") + post="${funcstack[1]}_post" + type $post > /dev/null 2>&1 || post=cat + + _fzf_feed_fifo "$fifo" + matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') + if [ -n "$matches" ]; then + LBUFFER="$lbuf$matches" + fi + command rm -f "$fifo" +} + +_fzf_complete_telnet() { + _fzf_complete +m -- "$@" < <( + command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' | + awk '{if (length($2) > 0) {print $2}}' | sort -u + ) +} + +_fzf_complete_ssh() { + _fzf_complete +m -- "$@" < <( + setopt localoptions nonomatch + command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \ + <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ + <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | + awk '{if (length($2) > 0) {print $2}}' | sort -u + ) +} + +_fzf_complete_export() { + _fzf_complete -m -- "$@" < <( + declare -xp | sed 's/=.*//' | sed 's/.* //' + ) +} + +_fzf_complete_unset() { + _fzf_complete -m -- "$@" < <( + declare -xp | sed 's/=.*//' | sed 's/.* //' + ) +} + +_fzf_complete_unalias() { + _fzf_complete +m -- "$@" < <( + alias | sed 's/=.*//' + ) +} + +_fzf_complete_kill() { + _fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <( + command ps -ef | sed 1d + ) +} + +_fzf_complete_kill_post() { + awk '{print $2}' +} + +fzf-completion() { + local tokens cmd prefix trigger tail matches lbuf d_cmds + setopt localoptions noshwordsplit noksh_arrays noposixbuiltins + + # http://zsh.sourceforge.net/FAQ/zshfaq03.html + # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags + tokens=(${(z)LBUFFER}) + if [ ${#tokens} -lt 1 ]; then + zle ${fzf_default_completion:-expand-or-complete} + return + fi + + cmd=$(__fzf_extract_command "$LBUFFER") + + # Explicitly allow for empty trigger. + trigger=${FZF_COMPLETION_TRIGGER-'**'} + [ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("") + + # When the trigger starts with ';', it becomes a separate token + if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then + tokens[-2]="${tokens[-2]}${tokens[-1]}" + tokens=(${tokens[0,-2]}) + fi + + lbuf=$LBUFFER + tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))} + # Kill completion (do not require trigger sequence) + if [ "$cmd" = kill -a ${LBUFFER[-1]} = ' ' ]; then + tail=$trigger + tokens+=$trigger + lbuf="$lbuf$trigger" + fi + + # Trigger sequence given + if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then + d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}) + + [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} + [ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}} + + if eval "type _fzf_complete_${cmd} > /dev/null"; then + prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf} + zle reset-prompt + elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then + _fzf_dir_completion "$prefix" "$lbuf" + else + _fzf_path_completion "$prefix" "$lbuf" + fi + # Fall back to default completion + else + zle ${fzf_default_completion:-expand-or-complete} + fi +} + +[ -z "$fzf_default_completion" ] && { + binding=$(bindkey '^I') + [[ $binding =~ 'undefined-key' ]] || fzf_default_completion=$binding[(s: :w)2] + unset binding +} + +zle -N fzf-completion +bindkey '^I' fzf-completion + +} always { + # Restore the original options. + eval $__fzf_completion_options + 'unset' '__fzf_completion_options' +} diff --git a/.config/zsh/plugins/zsh-autosuggestions.zsh b/.config/zsh/plugins/zsh-autosuggestions.zsh new file mode 100644 index 0000000..b19cac7 --- /dev/null +++ b/.config/zsh/plugins/zsh-autosuggestions.zsh @@ -0,0 +1,864 @@ +# Fish-like fast/unobtrusive autosuggestions for zsh. +# https://github.com/zsh-users/zsh-autosuggestions +# v0.7.0 +# Copyright (c) 2013 Thiago de Arruda +# Copyright (c) 2016-2021 Eric Freese +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +#--------------------------------------------------------------------# +# Global Configuration Variables # +#--------------------------------------------------------------------# + +# Color to use when highlighting suggestion +# Uses format of `region_highlight` +# More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets +(( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) && +typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' + +# Prefix to use when saving original versions of bound widgets +(( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) && +typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- + +# Strategies to use to fetch a suggestion +# Will try each strategy in order until a suggestion is returned +(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && { + typeset -ga ZSH_AUTOSUGGEST_STRATEGY + ZSH_AUTOSUGGEST_STRATEGY=(history) +} + +# Widgets that clear the suggestion +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS + ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-substring-search-up + history-substring-search-down + up-line-or-beginning-search + down-line-or-beginning-search + up-line-or-history + down-line-or-history + accept-line + copy-earlier-word + ) +} + +# Widgets that accept the entire suggestion +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS + ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( + forward-char + end-of-line + vi-forward-char + vi-end-of-line + vi-add-eol + ) +} + +# Widgets that accept the entire suggestion and execute it +(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS + ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( + ) +} + +# Widgets that accept the suggestion as far as the cursor moves +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS + ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( + forward-word + emacs-forward-word + vi-forward-word + vi-forward-word-end + vi-forward-blank-word + vi-forward-blank-word-end + vi-find-next-char + vi-find-next-char-skip + ) +} + +# Widgets that should be ignored (globbing supported but must be escaped) +(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank + yank-pop + zle-\* + ) +} + +# Pty name for capturing completions for completion suggestion strategy +(( ! ${+ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME} )) && +typeset -g ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty + +#--------------------------------------------------------------------# +# Utility Functions # +#--------------------------------------------------------------------# + +_zsh_autosuggest_escape_command() { + setopt localoptions EXTENDED_GLOB + + # Escape special chars in the string (requires EXTENDED_GLOB) + echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" +} + +#--------------------------------------------------------------------# +# Widget Helpers # +#--------------------------------------------------------------------# + +_zsh_autosuggest_incr_bind_count() { + typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1)) + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count +} + +# Bind a single widget to an autosuggest widget, saving a reference to the original widget +_zsh_autosuggest_bind_widget() { + typeset -gA _ZSH_AUTOSUGGEST_BIND_COUNTS + + local widget=$1 + local autosuggest_action=$2 + local prefix=$ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX + + local -i bind_count + + # Save a reference to the original widget + case $widgets[$widget] in + # Already bound + user:_zsh_autosuggest_(bound|orig)_*) + bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget])) + ;; + + # User-defined widget + user:*) + _zsh_autosuggest_incr_bind_count $widget + zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:} + ;; + + # Built-in widget + builtin) + _zsh_autosuggest_incr_bind_count $widget + eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" + zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget + ;; + + # Completion widget + completion:*) + _zsh_autosuggest_incr_bind_count $widget + eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + ;; + esac + + # Pass the original widget's name explicitly into the autosuggest + # function. Use this passed in widget name to call the original + # widget instead of relying on the $WIDGET variable being set + # correctly. $WIDGET cannot be trusted because other plugins call + # zle without the `-w` flag (e.g. `zle self-insert` instead of + # `zle self-insert -w`). + eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() { + _zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@ + }" + + # Create the bound widget + zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget +} + +# Map all configured widgets to the right autosuggest widgets +_zsh_autosuggest_bind_widgets() { + emulate -L zsh + + local widget + local ignore_widgets + + ignore_widgets=( + .\* + _\* + ${_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS/#/autosuggest-} + $ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX\* + $ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ) + + # Find every widget we might want to bind and bind it appropriately + for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do + if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget clear + elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget accept + elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget execute + elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then + _zsh_autosuggest_bind_widget $widget partial_accept + else + # Assume any unspecified widget might modify the buffer + _zsh_autosuggest_bind_widget $widget modify + fi + done +} + +# Given the name of an original widget and args, invoke it, if it exists +_zsh_autosuggest_invoke_original_widget() { + # Do nothing unless called with at least one arg + (( $# )) || return 0 + + local original_widget_name="$1" + + shift + + if (( ${+widgets[$original_widget_name]} )); then + zle $original_widget_name -- $@ + fi +} + +#--------------------------------------------------------------------# +# Highlighting # +#--------------------------------------------------------------------# + +# If there was a highlight, remove it +_zsh_autosuggest_highlight_reset() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then + region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}") + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} + +# If there's a suggestion, highlight it +_zsh_autosuggest_highlight_apply() { + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + + if (( $#POSTDISPLAY )); then + typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" + region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT") + else + unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT + fi +} + +#--------------------------------------------------------------------# +# Autosuggest Widget Implementations # +#--------------------------------------------------------------------# + +# Disable suggestions +_zsh_autosuggest_disable() { + typeset -g _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_clear +} + +# Enable suggestions +_zsh_autosuggest_enable() { + unset _ZSH_AUTOSUGGEST_DISABLED + + if (( $#BUFFER )); then + _zsh_autosuggest_fetch + fi +} + +# Toggle suggestions (enable/disable) +_zsh_autosuggest_toggle() { + if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then + _zsh_autosuggest_enable + else + _zsh_autosuggest_disable + fi +} + +# Clear the suggestion +_zsh_autosuggest_clear() { + # Remove the suggestion + unset POSTDISPLAY + + _zsh_autosuggest_invoke_original_widget $@ +} + +# Modify the buffer and get a new suggestion +_zsh_autosuggest_modify() { + local -i retval + + # Only available in zsh >= 5.4 + local -i KEYS_QUEUED_COUNT + + # Save the contents of the buffer/postdisplay + local orig_buffer="$BUFFER" + local orig_postdisplay="$POSTDISPLAY" + + # Clear suggestion while waiting for next one + unset POSTDISPLAY + + # Original widget may modify the buffer + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + emulate -L zsh + + # Don't fetch a new suggestion if there's more input to be read immediately + if (( $PENDING > 0 || $KEYS_QUEUED_COUNT > 0 )); then + POSTDISPLAY="$orig_postdisplay" + return $retval + fi + + # Optimize if manually typing in the suggestion or if buffer hasn't changed + if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then + POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}" + return $retval + fi + + # Bail out if suggestions are disabled + if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then + return $? + fi + + # Get a new suggestion if the buffer is not empty after modification + if (( $#BUFFER > 0 )); then + if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then + _zsh_autosuggest_fetch + fi + fi + + return $retval +} + +# Fetch a new suggestion based on what's currently in the buffer +_zsh_autosuggest_fetch() { + if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then + _zsh_autosuggest_async_request "$BUFFER" + else + local suggestion + _zsh_autosuggest_fetch_suggestion "$BUFFER" + _zsh_autosuggest_suggest "$suggestion" + fi +} + +# Offer a suggestion +_zsh_autosuggest_suggest() { + emulate -L zsh + + local suggestion="$1" + + if [[ -n "$suggestion" ]] && (( $#BUFFER )); then + POSTDISPLAY="${suggestion#$BUFFER}" + else + unset POSTDISPLAY + fi +} + +# Accept the entire suggestion +_zsh_autosuggest_accept() { + local -i retval max_cursor_pos=$#BUFFER + + # When vicmd keymap is active, the cursor can't move all the way + # to the end of the buffer + if [[ "$KEYMAP" = "vicmd" ]]; then + max_cursor_pos=$((max_cursor_pos - 1)) + fi + + # If we're not in a valid state to accept a suggestion, just run the + # original widget and bail out + if (( $CURSOR != $max_cursor_pos || !$#POSTDISPLAY )); then + _zsh_autosuggest_invoke_original_widget $@ + return + fi + + # Only accept if the cursor is at the end of the buffer + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Run the original widget before manually moving the cursor so that the + # cursor movement doesn't make the widget do something unexpected + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Move the cursor to the end of the buffer + if [[ "$KEYMAP" = "vicmd" ]]; then + CURSOR=$(($#BUFFER - 1)) + else + CURSOR=$#BUFFER + fi + + return $retval +} + +# Accept the entire suggestion and execute it +_zsh_autosuggest_execute() { + # Add the suggestion to the buffer + BUFFER="$BUFFER$POSTDISPLAY" + + # Remove the suggestion + unset POSTDISPLAY + + # Call the original `accept-line` to handle syntax highlighting or + # other potential custom behavior + _zsh_autosuggest_invoke_original_widget "accept-line" +} + +# Partially accept the suggestion +_zsh_autosuggest_partial_accept() { + local -i retval cursor_loc + + # Save the contents of the buffer so we can restore later if needed + local original_buffer="$BUFFER" + + # Temporarily accept the suggestion. + BUFFER="$BUFFER$POSTDISPLAY" + + # Original widget moves the cursor + _zsh_autosuggest_invoke_original_widget $@ + retval=$? + + # Normalize cursor location across vi/emacs modes + cursor_loc=$CURSOR + if [[ "$KEYMAP" = "vicmd" ]]; then + cursor_loc=$((cursor_loc + 1)) + fi + + # If we've moved past the end of the original buffer + if (( $cursor_loc > $#original_buffer )); then + # Set POSTDISPLAY to text right of the cursor + POSTDISPLAY="${BUFFER[$(($cursor_loc + 1)),$#BUFFER]}" + + # Clip the buffer at the cursor + BUFFER="${BUFFER[1,$cursor_loc]}" + else + # Restore the original buffer + BUFFER="$original_buffer" + fi + + return $retval +} + +() { + typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS + + _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=( + clear + fetch + suggest + accept + execute + enable + disable + toggle + ) + + local action + for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; do + eval "_zsh_autosuggest_widget_$action() { + local -i retval + + _zsh_autosuggest_highlight_reset + + _zsh_autosuggest_$action \$@ + retval=\$? + + _zsh_autosuggest_highlight_apply + + zle -R + + return \$retval + }" + done + + for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do + zle -N autosuggest-$action _zsh_autosuggest_widget_$action + done +} + +#--------------------------------------------------------------------# +# Completion Suggestion Strategy # +#--------------------------------------------------------------------# +# Fetches a suggestion from the completion engine +# + +_zsh_autosuggest_capture_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 + + # Don't list completions + unset 'compstate[list]' +} + +_zsh_autosuggest_capture_completion_widget() { + # Add a post-completion hook to be called after all completions have been + # gathered. The hook can modify compstate to affect what is done with the + # gathered completions. + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) + + # Only capture completions at the end of the buffer + CURSOR=$#BUFFER + + # Run the original widget wrapping `.complete-word` so we don't + # recursively try to fetch suggestions, since our pty is forked + # after autosuggestions is initialized. + zle -- ${(k)widgets[(r)completion:.complete-word:_main_complete]} + + if is-at-least 5.0.3; then + # Don't do any cr/lf transformations. We need to do this immediately before + # output because if we do it in setup, onlcr will be re-enabled when we enter + # vared in the async code path. There is a bug in zpty module in older versions + # where the tty is not properly attached to the pty slave, resulting in stty + # getting stopped with a SIGTTOU. See zsh-workers thread 31660 and upstream + # commit f75904a38 + stty -onlcr -ocrnl -F /dev/tty + fi + + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' +} + +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget + +_zsh_autosuggest_capture_setup() { + # There is a bug in zpty module in older zsh versions by which a + # zpty that exits will kill all zpty processes that were forked + # before it. Here we set up a zsh exit hook to SIGKILL the zpty + # process immediately, before it has a chance to kill any other + # zpty processes. + if ! is-at-least 5.4; then + zshexit() { + # The zsh builtin `kill` fails sometimes in older versions + # https://unix.stackexchange.com/a/477647/156673 + kill -KILL $$ 2>&- || command kill -KILL $$ + + # Block for long enough for the signal to come through + sleep 1 + } + fi + + # Try to avoid any suggestions that wouldn't match the prefix + zstyle ':completion:*' matcher-list '' + zstyle ':completion:*' path-completion false + zstyle ':completion:*' max-errors 0 not-numeric + + bindkey '^I' autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_sync() { + _zsh_autosuggest_capture_setup + + zle autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_async() { + _zsh_autosuggest_capture_setup + + zmodload zsh/parameter 2>/dev/null || return # For `$functions` + + # Make vared completion work as if for a normal command line + # https://stackoverflow.com/a/7057118/154703 + autoload +X _complete + functions[_original_complete]=$functions[_complete] + function _complete() { + unset 'compstate[vared]' + _original_complete "$@" + } + + # Open zle with buffer set so we can capture completions for it + vared 1 +} + +_zsh_autosuggest_strategy_completion() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable extended glob for completion ignore pattern + setopt EXTENDED_GLOB + + typeset -g suggestion + local line REPLY + + # Exit if we don't have completions + whence compdef >/dev/null || return + + # Exit if we don't have zpty + zmodload zsh/zpty 2>/dev/null || return + + # Exit if our search string matches the ignore pattern + [[ -n "$ZSH_AUTOSUGGEST_COMPLETION_IGNORE" ]] && [[ "$1" == $~ZSH_AUTOSUGGEST_COMPLETION_IGNORE ]] && return + + # Zle will be inactive if we are in async mode + if zle; then + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync + else + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_async "\$1" + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t' + fi + + { + # The completion result is surrounded by null bytes, so read the + # content between the first two null bytes. + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0''*'$'\0' + + # Extract the suggestion from between the null bytes. On older + # versions of zsh (older than 5.3), we sometimes get extra bytes after + # the second null byte, so trim those off the end. + # See http://www.zsh.org/mla/workers/2015/msg03290.html + suggestion="${${(@0)line}[2]}" + } always { + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME + } +} + +#--------------------------------------------------------------------# +# History Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix. +# + +_zsh_autosuggest_strategy_history() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) and (x~y) glob operator + setopt EXTENDED_GLOB + + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match the prefix, excluding those that match + # the ignore pattern + local pattern="$prefix*" + if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then + pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" + fi + + # Give the first history item matching the pattern as the suggestion + # - (r) subscript flag makes the pattern match on values + typeset -g suggestion="${history[(r)$pattern]}" +} + +#--------------------------------------------------------------------# +# Match Previous Command Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix and whose preceding history item also matches the most +# recently executed command. +# +# For example, suppose your history has the following entries: +# - pwd +# - ls foo +# - ls bar +# - pwd +# +# Given the history list above, when you type 'ls', the suggestion +# will be 'ls foo' rather than 'ls bar' because your most recently +# executed command (pwd) was previously followed by 'ls foo'. +# +# Note that this strategy won't work as expected with ZSH options that don't +# preserve the history order such as `HIST_IGNORE_ALL_DUPS` or +# `HIST_EXPIRE_DUPS_FIRST`. + +_zsh_autosuggest_strategy_match_prev_cmd() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) and (x~y) glob operator + setopt EXTENDED_GLOB + + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match the prefix, excluding those that match + # the ignore pattern + local pattern="$prefix*" + if [[ -n $ZSH_AUTOSUGGEST_HISTORY_IGNORE ]]; then + pattern="($pattern)~($ZSH_AUTOSUGGEST_HISTORY_IGNORE)" + fi + + # Get all history event numbers that correspond to history + # entries that match the pattern + local history_match_keys + history_match_keys=(${(k)history[(R)$~pattern]}) + + # By default we use the first history number (most recent history entry) + local histkey="${history_match_keys[1]}" + + # Get the previously executed command + local prev_cmd="$(_zsh_autosuggest_escape_command "${history[$((HISTCMD-1))]}")" + + # Iterate up to the first 200 history event numbers that match $prefix + for key in "${(@)history_match_keys[1,200]}"; do + # Stop if we ran out of history + [[ $key -gt 1 ]] || break + + # See if the history entry preceding the suggestion matches the + # previous command, and use it if it does + if [[ "${history[$((key - 1))]}" == "$prev_cmd" ]]; then + histkey="$key" + break + fi + done + + # Give back the matched history entry + typeset -g suggestion="$history[$histkey]" +} + +#--------------------------------------------------------------------# +# Fetch Suggestion # +#--------------------------------------------------------------------# +# Loops through all specified strategies and returns a suggestion +# from the first strategy to provide one. +# + +_zsh_autosuggest_fetch_suggestion() { + typeset -g suggestion + local -a strategies + local strategy + + # Ensure we are working with an array + strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) + + for strategy in $strategies; do + # Try to get a suggestion from this strategy + _zsh_autosuggest_strategy_$strategy "$1" + + # Ensure the suggestion matches the prefix + [[ "$suggestion" != "$1"* ]] && unset suggestion + + # Break once we've found a valid suggestion + [[ -n "$suggestion" ]] && break + done +} + +#--------------------------------------------------------------------# +# Async # +#--------------------------------------------------------------------# + +_zsh_autosuggest_async_request() { + zmodload zsh/system 2>/dev/null # For `$sysparams` + + typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID + + # If we've got a pending request, cancel it + if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then + # Close the file descriptor and remove the handler + exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD + + # We won't know the pid unless the user has zsh/system module installed + if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then + # Zsh will make a new process group for the child process only if job + # control is enabled (MONITOR option) + if [[ -o MONITOR ]]; then + # Send the signal to the process group to kill any processes that may + # have been forked by the suggestion strategy + kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + else + # Kill just the child process since it wasn't placed in a new process + # group. If the suggestion strategy forked any child processes they may + # be orphaned and left behind. + kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + fi + fi + fi + + # Fork a process to fetch a suggestion and open a pipe to read from it + exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( + # Tell parent process our pid + echo $sysparams[pid] + + # Fetch and print the suggestion + local suggestion + _zsh_autosuggest_fetch_suggestion "$1" + echo -nE "$suggestion" + ) + + # There's a weird bug here where ^C stops working unless we force a fork + # See https://github.com/zsh-users/zsh-autosuggestions/issues/364 + autoload -Uz is-at-least + is-at-least 5.8 || command true + + # Read the pid from the child process + read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD + + # When the fd is readable, call the response handler + zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response +} + +# Called when new data is ready to be read from the pipe +# First arg will be fd ready for reading +# Second arg will be passed in case of error +_zsh_autosuggest_async_response() { + emulate -L zsh + + local suggestion + + if [[ -z "$2" || "$2" == "hup" ]]; then + # Read everything from the fd and give it as a suggestion + IFS='' read -rd '' -u $1 suggestion + zle autosuggest-suggest -- "$suggestion" + + # Close the fd + exec {1}<&- + fi + + # Always remove the handler + zle -F "$1" +} + +#--------------------------------------------------------------------# +# Start # +#--------------------------------------------------------------------# + +# Start the autosuggestion widgets +_zsh_autosuggest_start() { + # By default we re-bind widgets on every precmd to ensure we wrap other + # wrappers. Specifically, highlighting breaks if our widgets are wrapped by + # zsh-syntax-highlighting widgets. This also allows modifications to the + # widget list variables to take effect on the next precmd. However this has + # a decent performance hit, so users can set ZSH_AUTOSUGGEST_MANUAL_REBIND + # to disable the automatic re-binding. + if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then + add-zsh-hook -d precmd _zsh_autosuggest_start + fi + + _zsh_autosuggest_bind_widgets +} + +# Mark for auto-loading the functions that we use +autoload -Uz add-zsh-hook is-at-least + +# Automatically enable asynchronous mode in newer versions of zsh. Disable for +# older versions because there is a bug when using async mode where ^C does not +# work immediately after fetching a suggestion. +# See https://github.com/zsh-users/zsh-autosuggestions/issues/364 +if is-at-least 5.0.8; then + typeset -g ZSH_AUTOSUGGEST_USE_ASYNC= +fi + +# Start the autosuggestion widgets on the next precmd +add-zsh-hook precmd _zsh_autosuggest_start diff --git a/.config/zsh/plugins/zsh-history-substring-search.zsh b/.config/zsh/plugins/zsh-history-substring-search.zsh new file mode 100644 index 0000000..a791cc4 --- /dev/null +++ b/.config/zsh/plugins/zsh-history-substring-search.zsh @@ -0,0 +1,759 @@ +#!/usr/bin/env zsh +############################################################################## +# +# Copyright (c) 2009 Peter Stephenson +# Copyright (c) 2011 Guido van Steen +# Copyright (c) 2011 Suraj N. Kurapati +# Copyright (c) 2011 Sorin Ionescu +# Copyright (c) 2011 Vincent Guerci +# Copyright (c) 2016 Geza Lore +# Copyright (c) 2017 Bengt Brodersen +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# * Neither the name of the FIZSH nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################## + +#----------------------------------------------------------------------------- +# declare global configuration variables +#----------------------------------------------------------------------------- + +typeset -g HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='bg=magenta,fg=white,bold' +typeset -g HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='bg=red,fg=white,bold' +typeset -g HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS='i' +typeset -g HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE='' +typeset -g HISTORY_SUBSTRING_SEARCH_FUZZY='' + +#----------------------------------------------------------------------------- +# declare internal global variables +#----------------------------------------------------------------------------- + +typeset -g BUFFER MATCH MBEGIN MEND CURSOR +typeset -g _history_substring_search_refresh_display +typeset -g _history_substring_search_query_highlight +typeset -g _history_substring_search_result +typeset -g _history_substring_search_query +typeset -g -a _history_substring_search_query_parts +typeset -g -a _history_substring_search_raw_matches +typeset -g -i _history_substring_search_raw_match_index +typeset -g -a _history_substring_search_matches +typeset -g -i _history_substring_search_match_index +typeset -g -A _history_substring_search_unique_filter + +#----------------------------------------------------------------------------- +# the main ZLE widgets +#----------------------------------------------------------------------------- + +history-substring-search-up() { + _history-substring-search-begin + + _history-substring-search-up-history || + _history-substring-search-up-buffer || + _history-substring-search-up-search + + _history-substring-search-end +} + +history-substring-search-down() { + _history-substring-search-begin + + _history-substring-search-down-history || + _history-substring-search-down-buffer || + _history-substring-search-down-search + + _history-substring-search-end +} + +zle -N history-substring-search-up +zle -N history-substring-search-down + +#----------------------------------------------------------------------------- +# implementation details +#----------------------------------------------------------------------------- + +zmodload -F zsh/parameter + +# +# We have to "override" some keys and widgets if the +# zsh-syntax-highlighting plugin has not been loaded: +# +# https://github.com/nicoulaj/zsh-syntax-highlighting +# +if [[ $+functions[_zsh_highlight] -eq 0 ]]; then + # + # Dummy implementation of _zsh_highlight() that + # simply removes any existing highlights when the + # user inserts printable characters into $BUFFER. + # + _zsh_highlight() { + if [[ $KEYS == [[:print:]] ]]; then + region_highlight=() + fi + } + + # + # The following snippet was taken from the zsh-syntax-highlighting project: + # + # https://github.com/zsh-users/zsh-syntax-highlighting/blob/56b134f5d62ae3d4e66c7f52bd0cc2595f9b305b/zsh-syntax-highlighting.zsh#L126-161 + # + # Copyright (c) 2010-2011 zsh-syntax-highlighting contributors + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are + # met: + # + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # + # * Neither the name of the zsh-syntax-highlighting contributors nor the + # names of its contributors may be used to endorse or promote products + # derived from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + # + #--------------8<-------------------8<-------------------8<----------------- + # Rebind all ZLE widgets to make them invoke _zsh_highlights. + _zsh_highlight_bind_widgets() + { + # Load ZSH module zsh/zleparameter, needed to override user defined widgets. + zmodload zsh/zleparameter 2>/dev/null || { + echo 'zsh-syntax-highlighting: failed loading zsh/zleparameter.' >&2 + return 1 + } + + # Override ZLE widgets to make them invoke _zsh_highlight. + local cur_widget + for cur_widget in ${${(f)"$(builtin zle -la)"}:#(.*|_*|orig-*|run-help|which-command|beep|yank*)}; do + case $widgets[$cur_widget] in + + # Already rebound event: do nothing. + user:$cur_widget|user:_zsh_highlight_widget_*);; + + # User defined widget: override and rebind old one with prefix "orig-". + user:*) eval "zle -N orig-$cur_widget ${widgets[$cur_widget]#*:}; \ + _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ + zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; + + # Completion widget: override and rebind old one with prefix "orig-". + completion:*) eval "zle -C orig-$cur_widget ${${widgets[$cur_widget]#*:}/:/ }; \ + _zsh_highlight_widget_$cur_widget() { builtin zle orig-$cur_widget -- \"\$@\" && _zsh_highlight }; \ + zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; + + # Builtin widget: override and make it call the builtin ".widget". + builtin) eval "_zsh_highlight_widget_$cur_widget() { builtin zle .$cur_widget -- \"\$@\" && _zsh_highlight }; \ + zle -N $cur_widget _zsh_highlight_widget_$cur_widget";; + + # Default: unhandled case. + *) echo "zsh-syntax-highlighting: unhandled ZLE widget '$cur_widget'" >&2 ;; + esac + done + } + #-------------->8------------------->8------------------->8----------------- + + _zsh_highlight_bind_widgets +fi + +_history-substring-search-begin() { + setopt localoptions extendedglob + + _history_substring_search_refresh_display= + _history_substring_search_query_highlight= + + # + # If the buffer is the same as the previously displayed history substring + # search result, then just keep stepping through the match list. Otherwise + # start a new search. + # + if [[ -n $BUFFER && $BUFFER == ${_history_substring_search_result:-} ]]; then + return; + fi + + # + # Clear the previous result. + # + _history_substring_search_result='' + + if [[ -z $BUFFER ]]; then + # + # If the buffer is empty, we will just act like up-history/down-history + # in ZSH, so we do not need to actually search the history. This should + # speed things up a little. + # + _history_substring_search_query= + _history_substring_search_query_parts=() + _history_substring_search_raw_matches=() + + else + # + # For the purpose of highlighting we keep a copy of the original + # query string. + # + _history_substring_search_query=$BUFFER + + # + # compose search pattern + # + if [[ -n $HISTORY_SUBSTRING_SEARCH_FUZZY ]]; then + # + # `=` split string in arguments + # + _history_substring_search_query_parts=(${=_history_substring_search_query}) + else + _history_substring_search_query_parts=(${==_history_substring_search_query}) + fi + + # + # Escape and join query parts with wildcard character '*' as seperator + # `(j:CHAR:)` join array to string with CHAR as seperator + # + local search_pattern="*${(j:*:)_history_substring_search_query_parts[@]//(#m)[\][()|\\*?#<>~^]/\\$MATCH}*" + + # + # Find all occurrences of the search pattern in the history file. + # + # (k) returns the "keys" (history index numbers) instead of the values + # (R) returns values in reverse older, so the index of the youngest + # matching history entry is at the head of the list. + # + _history_substring_search_raw_matches=(${(k)history[(R)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)${search_pattern}]}) + fi + + # + # In order to stay as responsive as possible, we will process the raw + # matches lazily (when the user requests the next match) to choose items + # that need to be displayed to the user. + # _history_substring_search_raw_match_index holds the index of the last + # unprocessed entry in _history_substring_search_raw_matches. Any items + # that need to be displayed will be added to + # _history_substring_search_matches. + # + # We use an associative array (_history_substring_search_unique_filter) as + # a 'set' data structure to ensure uniqueness of the results if desired. + # If an entry (key) is in the set (non-empty value), then we have already + # added that entry to _history_substring_search_matches. + # + _history_substring_search_raw_match_index=0 + _history_substring_search_matches=() + _history_substring_search_unique_filter=() + + # + # If $_history_substring_search_match_index is equal to + # $#_history_substring_search_matches + 1, this indicates that we + # are beyond the end of $_history_substring_search_matches and that we + # have also processed all entries in + # _history_substring_search_raw_matches. + # + # If $#_history_substring_search_match_index is equal to 0, this indicates + # that we are beyond the beginning of $_history_substring_search_matches. + # + # If we have initially pressed "up" we have to initialize + # $_history_substring_search_match_index to 0 so that it will be + # incremented to 1. + # + # If we have initially pressed "down" we have to initialize + # $_history_substring_search_match_index to 1 so that it will be + # decremented to 0. + # + if [[ $WIDGET == history-substring-search-down ]]; then + _history_substring_search_match_index=1 + else + _history_substring_search_match_index=0 + fi +} + +_history-substring-search-end() { + setopt localoptions extendedglob + + _history_substring_search_result=$BUFFER + + # the search was successful so display the result properly by clearing away + # existing highlights and moving the cursor to the end of the result buffer + if [[ $_history_substring_search_refresh_display -eq 1 ]]; then + region_highlight=() + CURSOR=${#BUFFER} + fi + + # highlight command line using zsh-syntax-highlighting + _zsh_highlight + + # highlight the search query inside the command line + if [[ -n $_history_substring_search_query_highlight ]]; then + # highlight first matching query parts + local highlight_start_index=0 + local highlight_end_index=0 + local query_part + for query_part in $_history_substring_search_query_parts; do + local escaped_query_part=${query_part//(#m)[\][()|\\*?#<>~^]/\\$MATCH} + # (i) get index of pattern + local query_part_match_index="${${BUFFER:$highlight_start_index}[(i)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)${escaped_query_part}]}" + if [[ $query_part_match_index -le ${#BUFFER:$highlight_start_index} ]]; then + highlight_start_index=$(( $highlight_start_index + $query_part_match_index )) + highlight_end_index=$(( $highlight_start_index + ${#query_part} )) + region_highlight+=("$(($highlight_start_index - 1)) $(($highlight_end_index - 1)) $_history_substring_search_query_highlight") + fi + done + fi + + # For debugging purposes: + # zle -R "mn: "$_history_substring_search_match_index" m#: "${#_history_substring_search_matches} + # read -k -t 200 && zle -U $REPLY + + # Exit successfully from the history-substring-search-* widgets. + return 0 +} + +_history-substring-search-up-buffer() { + # + # Check if the UP arrow was pressed to move the cursor within a multi-line + # buffer. This amounts to three tests: + # + # 1. $#buflines -gt 1. + # + # 2. $CURSOR -ne $#BUFFER. + # + # 3. Check if we are on the first line of the current multi-line buffer. + # If so, pressing UP would amount to leaving the multi-line buffer. + # + # We check this by adding an extra "x" to $LBUFFER, which makes + # sure that xlbuflines is always equal to the number of lines + # until $CURSOR (including the line with the cursor on it). + # + local buflines XLBUFFER xlbuflines + buflines=(${(f)BUFFER}) + XLBUFFER=$LBUFFER"x" + xlbuflines=(${(f)XLBUFFER}) + + if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xlbuflines -ne 1 ]]; then + zle up-line-or-history + return 0 + fi + + return 1 +} + +_history-substring-search-down-buffer() { + # + # Check if the DOWN arrow was pressed to move the cursor within a multi-line + # buffer. This amounts to three tests: + # + # 1. $#buflines -gt 1. + # + # 2. $CURSOR -ne $#BUFFER. + # + # 3. Check if we are on the last line of the current multi-line buffer. + # If so, pressing DOWN would amount to leaving the multi-line buffer. + # + # We check this by adding an extra "x" to $RBUFFER, which makes + # sure that xrbuflines is always equal to the number of lines + # from $CURSOR (including the line with the cursor on it). + # + local buflines XRBUFFER xrbuflines + buflines=(${(f)BUFFER}) + XRBUFFER="x"$RBUFFER + xrbuflines=(${(f)XRBUFFER}) + + if [[ $#buflines -gt 1 && $CURSOR -ne $#BUFFER && $#xrbuflines -ne 1 ]]; then + zle down-line-or-history + return 0 + fi + + return 1 +} + +_history-substring-search-up-history() { + # + # Behave like up in ZSH, except clear the $BUFFER + # when beginning of history is reached like in Fish. + # + if [[ -z $_history_substring_search_query ]]; then + + # we have reached the absolute top of history + if [[ $HISTNO -eq 1 ]]; then + BUFFER= + + # going up from somewhere below the top of history + else + zle up-line-or-history + fi + + return 0 + fi + + return 1 +} + +_history-substring-search-down-history() { + # + # Behave like down-history in ZSH, except clear the + # $BUFFER when end of history is reached like in Fish. + # + if [[ -z $_history_substring_search_query ]]; then + + # going down from the absolute top of history + if [[ $HISTNO -eq 1 && -z $BUFFER ]]; then + BUFFER=${history[1]} + _history_substring_search_refresh_display=1 + + # going down from somewhere above the bottom of history + else + zle down-line-or-history + fi + + return 0 + fi + + return 1 +} + +_history_substring_search_process_raw_matches() { + # + # Process more outstanding raw matches and append any matches that need to + # be displayed to the user to _history_substring_search_matches. + # Return whether there were any more results appended. + # + + # + # While we have more raw matches. Process them to see if there are any more + # matches that need to be displayed to the user. + # + while [[ $_history_substring_search_raw_match_index -lt $#_history_substring_search_raw_matches ]]; do + # + # Move on to the next raw entry and get its history index. + # + _history_substring_search_raw_match_index+=1 + local index=${_history_substring_search_raw_matches[$_history_substring_search_raw_match_index]} + + # + # If HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set to a non-empty value, + # then ensure that only unique matches are presented to the user. + # When HIST_IGNORE_ALL_DUPS is set, ZSH already ensures a unique history, + # so in this case we do not need to do anything. + # + if [[ ! -o HIST_IGNORE_ALL_DUPS && -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then + # + # Get the actual history entry at the new index, and check if we have + # already added it to _history_substring_search_matches. + # + local entry=${history[$index]} + + if [[ -z ${_history_substring_search_unique_filter[$entry]} ]]; then + # + # This is a new unique entry. Add it to the filter and append the + # index to _history_substring_search_matches. + # + _history_substring_search_unique_filter[$entry]=1 + _history_substring_search_matches+=($index) + + # + # Indicate that we did find a match. + # + return 0 + fi + + else + # + # Just append the new history index to the processed matches. + # + _history_substring_search_matches+=($index) + + # + # Indicate that we did find a match. + # + return 0 + fi + + done + + # + # We are beyond the end of the list of raw matches. Indicate that no + # more matches are available. + # + return 1 +} + +_history-substring-search-has-next() { + # + # Predicate function that returns whether any more older matches are + # available. + # + + if [[ $_history_substring_search_match_index -lt $#_history_substring_search_matches ]]; then + # + # We did not reach the end of the processed list, so we do have further + # matches. + # + return 0 + + else + # + # We are at the end of the processed list. Try to process further + # unprocessed matches. _history_substring_search_process_raw_matches + # returns whether any more matches were available, so just return + # that result. + # + _history_substring_search_process_raw_matches + return $? + fi +} + +_history-substring-search-has-prev() { + # + # Predicate function that returns whether any more younger matches are + # available. + # + + if [[ $_history_substring_search_match_index -gt 1 ]]; then + # + # We did not reach the beginning of the processed list, so we do have + # further matches. + # + return 0 + + else + # + # We are at the beginning of the processed list. We do not have any more + # matches. + # + return 1 + fi +} + +_history-substring-search-found() { + # + # A match is available. The index of the match is held in + # $_history_substring_search_match_index + # + # 1. Make $BUFFER equal to the matching history entry. + # + # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND + # to highlight the current buffer. + # + BUFFER=$history[$_history_substring_search_matches[$_history_substring_search_match_index]] + _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND +} + +_history-substring-search-not-found() { + # + # No more matches are available. + # + # 1. Make $BUFFER equal to $_history_substring_search_query so the user can + # revise it and search again. + # + # 2. Use $HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND + # to highlight the current buffer. + # + BUFFER=$_history_substring_search_query + _history_substring_search_query_highlight=$HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND +} + +_history-substring-search-up-search() { + _history_substring_search_refresh_display=1 + + # + # Select history entry during history-substring-down-search: + # + # The following variables have been initialized in + # _history-substring-search-up/down-search(): + # + # $_history_substring_search_matches is the current list of matches that + # need to be displayed to the user. + # $_history_substring_search_match_index is the index of the current match + # that is being displayed to the user. + # + # The range of values that $_history_substring_search_match_index can take + # is: [0, $#_history_substring_search_matches + 1]. A value of 0 + # indicates that we are beyond the beginning of + # $_history_substring_search_matches. A value of + # $#_history_substring_search_matches + 1 indicates that we are beyond + # the end of $_history_substring_search_matches and that we have also + # processed all entries in _history_substring_search_raw_matches. + # + # If $_history_substring_search_match_index equals + # $#_history_substring_search_matches and + # $_history_substring_search_raw_match_index is not greater than + # $#_history_substring_search_raw_matches, then we need to further process + # $_history_substring_search_raw_matches to see if there are any more + # entries that need to be displayed to the user. + # + # In _history-substring-search-up-search() the initial value of + # $_history_substring_search_match_index is 0. This value is set in + # _history-substring-search-begin(). _history-substring-search-up-search() + # will initially increment it to 1. + # + + if [[ $_history_substring_search_match_index -gt $#_history_substring_search_matches ]]; then + # + # We are beyond the end of $_history_substring_search_matches. This + # can only happen if we have also exhausted the unprocessed matches in + # _history_substring_search_raw_matches. + # + # 1. Update display to indicate search not found. + # + _history-substring-search-not-found + return + fi + + if _history-substring-search-has-next; then + # + # We do have older matches. + # + # 1. Move index to point to the next match. + # 2. Update display to indicate search found. + # + _history_substring_search_match_index+=1 + _history-substring-search-found + + else + # + # We do not have older matches. + # + # 1. Move the index beyond the end of + # _history_substring_search_matches. + # 2. Update display to indicate search not found. + # + _history_substring_search_match_index+=1 + _history-substring-search-not-found + fi + + # + # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from + # history should be matched, make sure the new and old results are different. + # + # However, if the HIST_IGNORE_ALL_DUPS shell option, or + # HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set, then we already have a + # unique history, so in this case we do not need to do anything. + # + if [[ -o HIST_IGNORE_ALL_DUPS || -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then + return + fi + + if [[ -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then + # + # Repeat the current search so that a different (unique) match is found. + # + _history-substring-search-up-search + fi +} + +_history-substring-search-down-search() { + _history_substring_search_refresh_display=1 + + # + # Select history entry during history-substring-down-search: + # + # The following variables have been initialized in + # _history-substring-search-up/down-search(): + # + # $_history_substring_search_matches is the current list of matches that + # need to be displayed to the user. + # $_history_substring_search_match_index is the index of the current match + # that is being displayed to the user. + # + # The range of values that $_history_substring_search_match_index can take + # is: [0, $#_history_substring_search_matches + 1]. A value of 0 + # indicates that we are beyond the beginning of + # $_history_substring_search_matches. A value of + # $#_history_substring_search_matches + 1 indicates that we are beyond + # the end of $_history_substring_search_matches and that we have also + # processed all entries in _history_substring_search_raw_matches. + # + # In _history-substring-search-down-search() the initial value of + # $_history_substring_search_match_index is 1. This value is set in + # _history-substring-search-begin(). _history-substring-search-down-search() + # will initially decrement it to 0. + # + + if [[ $_history_substring_search_match_index -lt 1 ]]; then + # + # We are beyond the beginning of $_history_substring_search_matches. + # + # 1. Update display to indicate search not found. + # + _history-substring-search-not-found + return + fi + + if _history-substring-search-has-prev; then + # + # We do have younger matches. + # + # 1. Move index to point to the previous match. + # 2. Update display to indicate search found. + # + _history_substring_search_match_index+=-1 + _history-substring-search-found + + else + # + # We do not have younger matches. + # + # 1. Move the index beyond the beginning of + # _history_substring_search_matches. + # 2. Update display to indicate search not found. + # + _history_substring_search_match_index+=-1 + _history-substring-search-not-found + fi + + # + # When HIST_FIND_NO_DUPS is set, meaning that only unique command lines from + # history should be matched, make sure the new and old results are different. + # + # However, if the HIST_IGNORE_ALL_DUPS shell option, or + # HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE is set, then we already have a + # unique history, so in this case we do not need to do anything. + # + if [[ -o HIST_IGNORE_ALL_DUPS || -n $HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE ]]; then + return + fi + + if [[ -o HIST_FIND_NO_DUPS && $BUFFER == $_history_substring_search_result ]]; then + # + # Repeat the current search so that a different (unique) match is found. + # + _history-substring-search-down-search + fi +} + +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et diff --git a/.config/zsh/plugins/zsh-syntax-highlighting b/.config/zsh/plugins/zsh-syntax-highlighting new file mode 160000 +Subproject e0165eaa730dd0fa321a6a6de74f092fe87630b diff --git a/.config/zsh/zprofile b/.config/zsh/zprofile new file mode 100644 index 0000000..46c5217 --- /dev/null +++ b/.config/zsh/zprofile @@ -0,0 +1,21 @@ +#!/bin/zsh +. ~/.profile + +[ -n "$TMUX" ] || [ -n "$TERMUX_VERSION" ] || [ "$SSH_CONNECTION" ] && + return + +on_ac_power="$(cat /sys/class/power_supply/ACAD/online)" +[ "$on_ac_power" = 0 ] && brightnessctl set 5% || brightnessctl set 20% + +case "$TTY" in + /dev/tty1) + [ "$on_ac_power" = 1 ] && { + echo "Waiting for nvidia card to load up..." + until [ -e /dev/dri/card0 ]; do; sleep 0.1; done + } + command -V startx >/dev/null && { pgrep startx || startx } + ;; + /dev/tty3) tmux new -s master ;; + /dev/tty[2-5]) tmux new -s "${TTY#/dev/}" ;; + # /dev/pts/*) exec tmux new -s "${TTY#/dev/}" ;; +esac |