Files
flake/modules/cli/zsh/zsh-completion-sync.plugin.zsh

373 lines
16 KiB
Bash

# stolen from https://github.com/BronzeDeer/zsh-completion-sync/blob/master/zsh-completion-sync.plugin.zsh
_completion_sync:debug_log(){
if zstyle -t "$1" debug; then
echo "completion sync: $2"
fi
}
_completion_sync:delete_first_from_fpath(){
# There might be multiple instances of the same path on the fpath
# We will only delete the first instance to "reset" the priority order
# but leave later occurences
local idx="$fpath[(Ie)$1]"
if (( $idx != 0 )); then
_completion_sync:debug_log ':completion-sync:fpath:delete' "deleting '$1' from FPATH at index '$idx'"
fpath[$idx]=()
fi
}
_completion_sync:fpath_maybe_add_xdg(){
# There are two valid paths in an XDG_DATA_DIR, one from the zsh install and one from third party
# It is unclear if would ever make sense to add the ones from the zsh install,
# since they should be always on the fpath, but for now we test for both
local p="$1/zsh/$ZSH_VERSION/functions"
if [[ -d $p ]]; then
_completion_sync:debug_log ':completion-sync:xdg:add' "Added '$p' to FPATH"
fpath=("$p" $fpath)
fi
p="$1/zsh/site-functions"
if [[ -d $p ]]; then
_completion_sync:debug_log ':completion-sync:xdg:add' "Added '$p' to FPATH"
fpath=("$p" $fpath)
fi
}
_completion_sync:functions_from_xdg_data(){
local a=($(echo "$XDG_DATA_DIRS" | tr ':' "\n" | xargs -I{} realpath -e "{}/zsh/site-functions" "\n" realpath -e "{}/zsh/$ZSH_VERSION/functions" "\n" realpath -e "{}/zsh/vendor-completions" 2>/dev/null | tr "\n" ' '))
# unique the directories
echo "${(u)a[@]}"
}
# This function directly reads into the completion_sync_fpaths_from_path variable to avoid a subshell invocation
_completion_sync:find_fpaths_from_path(){
local rel_paths=()
if ! zstyle -g rel_paths ':completion-sync:path' rel_paths; then
rel_paths=("../share/zsh/$ZSH_VERSION/functions" '../share/zsh/site-functions')
fi
completion_sync_fpaths_from_path=()
for p in $path; do
for rp in $rel_paths; do
local maybe_fpath="$p/$rp"
# This is the equivalent of using readlink on the path (i.e. canonicalize it, and resolve symlinks)
_completion_sync:debug_log ':completion-sync:path:path2fpath:candidate' "fpath candidate: $maybe_fpath"
maybe_fpath=${maybe_fpath:A}
_completion_sync:debug_log ':completion-sync:path:path2fpath:candidate:canonicalize' "fpath candidate (canonicalized): $maybe_fpath"
if [[ -d "$maybe_fpath" ]]; then
_completion_sync:debug_log ':completion-sync:path:path2fpath' "found fpath on path: $maybe_fpath"
completion_sync_fpaths_from_path=("$maybe_fpath" $completion_sync_fpaths_from_path)
fi
done
done
# unique the directories
_completion_sync:debug_log ':completion-sync:path:path2fpath:unique' "fpaths on path (non-unique):\n${(F)completion_sync_fpaths_from_path}\n"
completion_sync_fpaths_from_path=(${(u)completion_sync_fpaths_from_path[@]})
_completion_sync:debug_log ':completion-sync:path:path2fpath:unique' "fpaths on path (unique):\n${(F)completion_sync_fpaths_from_path}\n"
}
_completion_sync:custom_compinit_isenabled(){
zstyle -t ':completion-sync:compinit:custom' enabled
return
}
_completion_sync:zsh_autocomplete_compat_isenabled(){
# set return code to !custom_isenabled && compat:zsh-autocomplete enabled
_completion_sync:custom_compinit_isenabled
if [[ $? -ne 0 ]]; then
zstyle -T ':completion-sync:compinit:compat:zsh-autocomplete' enabled
return
else
return 1
fi
}
_completion_sync:zsh_autocomplete_compat_disable(){
zstyle ':completion-sync:compinit:compat:zsh-autocomplete' enabled false
}
_completion_sync:zsh_autocomplete_compat_reload(){
# If zsh-autocomplete is loaded, it will either be in the fully initiliazed state, or the pre-initialized state
# In the pre-initilaized state, a precmd hook is waiting to execute, which will setup the plugin and among other things reload compinit and define a named-directory
# Instead of waiting for the pre-cmd hook to fire, we want it to execute right now.
# Find the autocomplete hook if present
local precmd=${(M)precmd_functions:#.autocomplete*precmd}
# If the plugin is already fully initialized, then the relevant functions have been undefined and we need to re-initialize it from the start.
if [[ -z $precmd ]]; then
# In this case the named directory "~zsh-autocomplete" will be present, which we can use to reload regardless of install location
if [[ ! -v nameddirs[zsh-autocomplete] ]]; then
_completion_sync:debug_log ':completion-sync:compinit:compat:zsh-autocomplete' "compat: zsh-autocomplete: unable to detect plugin setup functions or named directory, disabling compat"
_completion_sync:zsh_autocomplete_compat_disable
return 1
else
# Since the named directory exists we can source the plugin again, which will put the precmd hook back in the array
_completion_sync:debug_log ':completion-sync:compinit:compat:zsh-autocomplete' "compat: zsh-autocomplete: re-initializing zsh-autocomplete via 'source ~zsh-autocomplete/zsh-autocomplete.plugin.zsh'"
source ~zsh-autocomplete/zsh-autocomplete.plugin.zsh
precmd=${(M)precmd_functions:#.autocomplete*precmd}
if [[ -z $precmd ]]; then
echo "completion-sync: compat: zsh-autocomplete: unable to detect plugin setup functions even after reinitializing, disabling compat"
_completion_sync:zsh_autocomplete_compat_disable
return 1
fi
fi
fi
# Execute the precmd hook, this will re-initialize the compsys (since we are handling the zcompdump file)
_completion_sync:debug_log ':completion-sync:compinit:compat:zsh-autocomplete' "compat: zsh-autocomplete: executing precmd hook '$precmd' now"
_completion_sync:debug_log ':completion-sync:compinit:compat:zsh-autocomplete:diff' "compat: zsh-autocomplete: precmd hooks before"
_completion_sync:debug_log ':completion-sync:compinit:compat:zsh-autocomplete:diff' "$precmd_functions"
$precmd
_completion_sync:debug_log ':completion-sync:compinit:compat:zsh-autocomplete:diff' "compat: zsh-autocomplete: precmd hooks after"
_completion_sync:debug_log ':completion-sync:compinit:compat:zsh-autocomplete:diff' "$precmd_functions"
}
# Take a path (as first arg) to a zstyle which has two leaf keys "enabled" and "command"
# if enabled is true, eval the string inside command
_completion_sync:run_hook_if_enabled(){
local style="$1"
if zstyle -t "$style" enabled; then
local hook
zstyle -s "$style" command hook
eval "$hook"
fi
}
_completion_sync:compsys_reload(){
# Delete current cache
rm -rf "$_per_shell_compdump"
_completion_sync:run_hook_if_enabled ':completion-sync:compinit:custom:pre-hook'
if _completion_sync:custom_compinit_isenabled ; then
local custom
zstyle -s ':completion-sync:compinit:custom' command custom
_completion_sync:debug_log ':completion-sync:compinit:custom' "compinit: using custom command instead of compinit: ${(q)custom}"
eval $custom
elif _completion_sync:zsh_autocomplete_compat_isenabled ; then
# Reload will either reload zsh-autocomplete or disable the compat if it is not present
if ! _completion_sync:zsh_autocomplete_compat_reload; then
# zsh_autocomplete_compat could not properly reload, the plugin is likely not present, disable compat
_completion_sync:zsh_autocomplete_compat_disable
# Re-run the compsys logic with zac compat disabled
_completion_sync:compsys_reload
fi
else
# Ensure that we call compinit provided from the fpath (default: off)
if zstyle -t ':completion-sync:compinit:builtin-compinit' enabled; then
# Allow us to restore the previous compinit, to be a good citizen
functions -c compinit compinit_orig
# Remove the current compinit to allow for reloading
unfunction compinit
# restore original compinit
autoload +X compinit
_completion_sync:debug_log ':completion-sync:compinit:builtin-compinit' "previous compinit: $(whence -v compinit_orig)"
_completion_sync:debug_log ':completion-sync:compinit:builtin-compinit' "loaded compinit: $(whence -v compinit)"
fi
_completion_sync:debug_log ':completion-sync:compinit' "invoking compinit as 'compinit -d \"$_per_shell_compdump\"'"
compinit -d "$_per_shell_compdump"
if zstyle -t ':completion-sync:compinit:builtin-compinit' enabled; then
# restore original function
functions -c compinit_orig compinit
_completion_sync:debug_log ':completion-sync:compinit:builtin-compinit' "restored compinit: $(whence -v compinit)"
fi
fi
_completion_sync:run_hook_if_enabled ':completion-sync:compinit:custom:post-hook'
}
_completion_sync:path_hook(){
if [[ ! -v COMPLETION_SYNC_OLD_PATH ]]; then
_completion_sync:debug_log ':completion-sync:path:init' "Detecting FPATHs from PATH enabled"
_completion_sync:debug_log ':completion-sync:path:init:diff' "from path: old FPATH\n${(F)fpath}\n"
# Detect current fpaths from path and read it into the variable
_completion_sync:find_fpaths_from_path
# Prepend in reverse order to maintain their order in the final path
for idx in {${#completion_sync_fpaths_from_path}..1} ; do
local elem=${completion_sync_fpaths_from_path[$idx]}
# First time around, only add relevant XDG_DATA_DIRs, which are not on the FPATH yet
if (( ! ${fpath[(I)"$elem"]} )); then
_completion_sync:debug_log ':completion-sync:path:init:diff' "from path: $elem"
fpath=($elem $fpath)
completion_sync_fpath_changed_during_init=true
fi
done
_completion_sync:debug_log ':completion-sync:path:init:diff' "from path: new FPATH\n${(F)fpath}\n"
elif [[ "$COMPLETION_SYNC_OLD_PATH" != "$PATH" ]]; then
_completion_sync:debug_log ':completion-sync:path:onchange' "PATH CHANGED"
# Check if the fpath dirs changed
local completion_sync_old_fpaths_from_path=( "${(@f)completion_sync_fpaths_from_path}" )
if [[ "$completion_sync_old_fpaths_from_path" != "$completion_sync_fpaths_from_path" ]]; then
_completion_sync:debug_log ':completion-sync:path:onchange' "Need to update FPATH from PATH!"
local diff=( "${(@)$(diff <(echo "${(F)completion_sync_fpaths_from_path}") <( echo "${(F)completion_sync_old_fpaths_from_path}") | grep -E "<|>")}" )
_completion_sync:debug_log ':completion-sync:path:diff' "$diff"
# TODO: Generalize without subshell
# Prepend in reverse order to maintain their order in the final path
for idx in {${#diff}..1} ; do
local p=$diff[$idx]
case "${p[1]}" in
\<)
# path got added
local p_path="${p:2}"
_completion_sync:debug_log ':completion-sync:path:onchange:add' "Adding path '$p_path'"
_completion_sync:debug_log ':completion-sync:fpath:add' "Adding '$p_path' to FPATH"
fpath=("$p_path" $fpath)
;;
\>)
# path got removed
local p_path="${p:2}"
_completion_sync:debug_log ':completion-sync:path:onchange:delete' "Removing path '$p_path'"
_completion_sync:delete_first_from_fpath "$p_path"
;;
*)
# This should not happen
_completion_sync:debug_log ':completion-sync:path:onchange' "Invalid diff line $p"
_completion_sync:debug_log ':completion-sync:path:onchange' "Tried to match on character ${p[1]}"
;;
esac
done
else
_completion_sync:debug_log ':completion-sync:path:onchange' "No FPATH change needed"
fi
fi
COMPLETION_SYNC_OLD_PATH="$PATH"
}
_completion_sync:hook(){
if zstyle -t ':completion-sync:path' enabled; then
_completion_sync:path_hook
fi
if zstyle -T ':completion-sync:xdg' enabled; then
if [[ ! -v COMPLETION_SYNC_OLD_XDG_DATA_DIRS ]]; then
_completion_sync:debug_log ':completion-sync:xdg:init' "Syncing XDG_DATA_DIRS into FPATH enabled"
_completion_sync:debug_log ':completion-sync:xdg:init:diff' "old FPATH\n${(F)fpath}"
# First time around, only add relevant XDG_DATA_DIRs, which are not on the FPATH yet
# Find XDG_DATA_DIRS which have $ZSH function dirs under them
completion_sync_old_xdg_fpaths=( $(_completion_sync:functions_from_xdg_data) )
_completion_sync:debug_log ':completion-sync:xdg:init:diff' "adding from XDG"
# Prepend in reverse order to maintain their order in the final path
for idx in {${#completion_sync_old_xdg_fpaths}..1} ; do
local elem="${completion_sync_old_xdg_fpaths[$idx]}"
if (( ! ${fpath[(I)$elem]} )); then
_completion_sync:debug_log ':completion-sync:xdg:init:diff' $elem
fpath=($elem $fpath)
completion_sync_fpath_changed_during_init=true
fi
done
_completion_sync:debug_log ':completion-sync:xdg:init:diff' "New FPATH\n${(F)fpath}"
elif [[ "$COMPLETION_SYNC_OLD_XDG_DATA_DIRS" != "$XDG_DATA_DIRS" ]]; then
_completion_sync:debug_log ':completion-sync:xdg:onchange' "XDG_DATA_DIRS CHANGED"
# Check if the fpath dirs changed
local new_paths=( $(_completion_sync:functions_from_xdg_data) )
if [[ "$completion_sync_old_xdg_fpaths" != "$new_paths" ]]; then
_completion_sync:debug_log ':completion-sync:xdg:onchange' "Need to update FPATH from XDG_DATA_DIRS!"
local diff=( "${(f)$(diff <(echo "${(F)new_paths}") <( echo "${(F)completion_sync_old_xdg_fpaths}") | grep -E "<|>")}" )
_completion_sync:debug_log ':completion-sync:xdg:diff' "$diff"
# Prepend in reverse order to maintain their order in the final path
for idx in {${#diff}..1} ; do
local p=$diff[$idx]
case "${p[1]}" in
\<)
# path got added
local p_path="${p:2}"
_completion_sync:debug_log ':completion-sync:xdg:onchange:add' "Adding path '$p_path'"
_completion_sync:debug_log ':completion-sync:fpath:add' "Adding '$p_path' to FPATH"
fpath=("$p_path" $fpath)
;;
\>)
# path got removed
local p_path="${p:2}"
_completion_sync:debug_log ':completion-sync:xdg:onchange:delete' "Removing path '$p_path'"
_completion_sync:delete_first_from_fpath "$p_path"
;;
*)
# This should not happen
_completion_sync:debug_log ':completion-sync:xdg:onchange' "Invalid diff line $p"
_completion_sync:debug_log ':completion-sync:xdg:onchange' "Tried to match on character ${p[1]}"
;;
esac
done
completion_sync_old_xdg_fpaths=( "${(@f)new_paths}" )
else
_completion_sync:debug_log ':completion-sync:xdg:onchange' "No FPATH change needed"
fi
fi
COMPLETION_SYNC_OLD_XDG_DATA_DIRS="$XDG_DATA_DIRS"
fi
if [[ ! -v completion_sync_old_fpath ]]; then
_completion_sync:debug_log ':completion-sync:fpath:init' "Syncing completions to fpath enabled"
# Do not re-init the first time around unless other init code extended FPATH
if [[ "$completion_sync_fpath_changed_during_init" == "true" ]]; then
_completion_sync:debug_log ':completion-sync:fpath:init' "FPATH Changed during init, reloading compsys!"
_completion_sync:compsys_reload
fi
elif [[ "$completion_sync_old_fpath" != "$fpath" ]]; then
_completion_sync:debug_log ':completion-sync:fpath:onchange' "FPATH Changed!"
if zstyle -t ':completion-sync:fpath:onchange:diff' debug; then
diff <(echo "${(F)fpath}" | sort ) <(echo "${(F)completion_sync_old_fpath}" | sort) | grep -E "<|>"
fi
_completion_sync:compsys_reload
fi
completion_sync_old_fpath=( "${(@f)fpath}" )
}
local compDumpDir="${TMPDIR:-/tmp}/per-shell-zcompdumps"
mkdir -p $compDumpDir
# Create a specific zcompdump for this pid. This allows us to use a zcompdump per shell and identify it later if desired
_per_shell_compdump="$compDumpDir/$$.zcompdump"
# Set the relevant env vars for compdump location, various plugins/mechanisms will create the compdump there
ZSH_COMPDUMP="$_per_shell_compdump"
_comp_dumpfile="$_per_shell_compdump"
typeset -ag precmd_functions
if (( ! ${precmd_functions[(I)_completion_sync:hook]} )); then
# Add our hook last to go after _direnv_hook
precmd_functions=($precmd_functions _completion_sync:hook)
fi
typeset -ag chpwd_functions
if (( ! ${chpwd_functions[(I)_completion_sync:hook]} )); then
# Add our hook last to go after _direnv_hook
chpwd_functions=($chpwd_functions _completion_sync:hook)
fi