From 57c08d925f050bb955028992a6435c4341e1056a Mon Sep 17 00:00:00 2001 From: LangLangBart <92653266+LangLangBart@users.noreply.github.com> Date: Sun, 10 Nov 2024 09:40:59 +0100 Subject: [PATCH] Enhance command extraction in zsh completion (#4082) Fix #1992 --- shell/completion.zsh | 53 +++++++++++++++++++++++------------------- shell/key-bindings.zsh | 2 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/shell/completion.zsh b/shell/completion.zsh index 217404f..a6d686e 100644 --- a/shell/completion.zsh +++ b/shell/completion.zsh @@ -120,25 +120,19 @@ __fzf_comprun() { fi } -# Extract the name of the command. e.g. foo=1 bar baz** +# Extract the name of the command. e.g. ls; foo=1 ssh ** __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]}" + setopt localoptions noksh_arrays + # Control completion with the "compstate" parameter, insert and list noting + compstate[insert]= + compstate[list]= + cmd_word="${words[1]}" } __fzf_generic_path_completion() { - local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches + local base lbuf compgen fzf_opts suffix tail dir leftover matches base=$1 lbuf=$2 - cmd=$(__fzf_extract_command "$lbuf") compgen=$3 fzf_opts=$4 suffix=$5 @@ -161,7 +155,7 @@ __fzf_generic_path_completion() { FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}") unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE if declare -f "$compgen" > /dev/null; then - eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" + eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" else if [[ $compgen =~ dir ]]; then walker=dir,follow @@ -170,7 +164,7 @@ __fzf_generic_path_completion() { walker=file,dir,follow,hidden rest=${FZF_COMPLETION_PATH_OPTS-} fi - __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty + __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty fi | while read -r item; do item="${item%$suffix}$suffix" echo -n -E "${(q)item} " @@ -227,10 +221,9 @@ _fzf_complete() { rest=("$@") fi - local fifo lbuf cmd matches post + local fifo lbuf 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 @@ -238,7 +231,7 @@ _fzf_complete() { matches=$( FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \ FZF_DEFAULT_OPTS_FILE='' \ - __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') + __fzf_comprun "$cmd_word" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') if [ -n "$matches" ]; then LBUFFER="$lbuf$matches" fi @@ -310,9 +303,16 @@ _fzf_complete_kill_post() { } fzf-completion() { - local tokens cmd prefix trigger tail matches lbuf d_cmds + trap 'unset cmd_word' EXIT + local tokens prefix trigger tail matches lbuf d_cmds setopt localoptions noshwordsplit noksh_arrays noposixbuiltins + # Check if at least one completion system (old or new) is active + if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( $+functions[compdef] )); then + if ! zmodload -e zsh/compctl; then + zmodload -i zsh/compctl + fi + fi # http://zsh.sourceforge.net/FAQ/zshfaq03.html # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags tokens=(${(z)LBUFFER}) @@ -321,8 +321,6 @@ fzf-completion() { return fi - cmd=$(__fzf_extract_command "$LBUFFER") - # Explicitly allow for empty trigger. trigger=${FZF_COMPLETION_TRIGGER-'**'} [ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("") @@ -340,16 +338,20 @@ fzf-completion() { if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}) + # Make the 'cmd_word' global + zle __fzf_extract_command || : + [[ -z "$cmd_word" ]] && return + [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then return fi [ -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} + if eval "type _fzf_complete_${cmd_word} > /dev/null"; then + prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf} zle reset-prompt - elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then + elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then _fzf_dir_completion "$prefix" "$lbuf" else _fzf_path_completion "$prefix" "$lbuf" @@ -366,6 +368,9 @@ fzf-completion() { unset binding } +# Completion widget to gain access to the 'words' array (man zshcompwid) +zle -C __fzf_extract_command .complete-word __fzf_extract_command +# Normal widget zle -N fzf-completion bindkey '^I' fzf-completion fi diff --git a/shell/key-bindings.zsh b/shell/key-bindings.zsh index 3015f12..c8c2076 100644 --- a/shell/key-bindings.zsh +++ b/shell/key-bindings.zsh @@ -111,7 +111,7 @@ fzf-history-widget() { # Ensure the module is loaded if not already, and the required features, such # as the associative 'history' array, which maps event numbers to full history # lines, are set. Also, make sure Perl is installed for multi-line output. - if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${#commands[perl]} )); then + if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( $+commands[perl] )); then # Import commands from other shells if SHARE_HISTORY is enabled, as the # 'history' array only updates after executing a non-empty command. selected="$(