From 6ffaec725a29ff2a199ceb173f72eadc42254582 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 22 May 2018 16:13:56 -0600 Subject: [PATCH] Allow completion suggestions from current shell The `zsh -f` running in the PTY doesn't know about the non-exported variables and functions defined in the original shell, thus can't make suggestions for them. Run local functions in the PTY instead of a new `zsh` process. We have to handle things differently based on whether zle is active or not (async vs. sync mode). --- src/strategies/completion.zsh | 110 +++++++++++++++++++--------------- zsh-autosuggestions.zsh | 110 +++++++++++++++++++--------------- 2 files changed, 124 insertions(+), 96 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index aa87673..e8aac6c 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -3,90 +3,104 @@ # Completion Suggestion Strategy # #--------------------------------------------------------------------# # Fetches suggestions from zsh's completion engine +# Based on https://github.com/Valodim/zsh-capture-completion # -# Big thanks to https://github.com/Valodim/zsh-capture-completion -_zsh_autosuggest_capture_completion() { - zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zsh -f -i - - local line - - setopt rcquotes - () { - # Initialize the pty env, blocking until null byte is seen - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "source $1" - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' - } =( <<< ' - exec 2>/dev/null # Silence any error messages - - autoload compinit - compinit -d ~/.zcompdump_autosuggestions - - # Exit as soon as completion is finished - comppostfuncs=( exit ) +_zsh_autosuggest_capture_setup() { + zmodload zsh/zutil # For `zparseopts` # Never group stuff! - zstyle '':completion:*'' list-grouped false + zstyle ':completion:*' list-grouped false - # no list separator, this saves some stripping later on - zstyle '':completion:*'' list-separator '''' + # No list separator, this saves some stripping later on + zstyle ':completion:*' list-separator '' - # we use zparseopts - zmodload zsh/zutil - - # override compadd (this our hook) + # Override compadd (this is our hook) compadd () { + setopt localoptions norcexpandparam + # Just delegate and leave if any of -O, -A or -D are given if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then builtin compadd "$@" return $? fi - setopt localoptions norcexpandparam extendedglob - - typeset -a __hits - # Capture completions by injecting -A parameter into the compadd call. # This takes care of matching for us. + typeset -a __hits builtin compadd -A __hits "$@" # Exit if no completion results [[ -n $__hits ]] || return - # Extract prefixes and suffixes from compadd call. we can''t do zsh''s cool - # -r remove-func magic, but it''s better than nothing. + # Extract prefixes and suffixes from compadd call. we can't do zsh's cool + # -r remove-func magic, but it's better than nothing. typeset -A apre hpre hsuf asuf zparseopts -E P:=apre p:=hpre S:=asuf s:=hsuf # Print the first match - echo -nE - $''\0''$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$''\0'' + echo -nE - $'\0'$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$'\0' + } +} + +_zsh_autosuggest_capture_widget() { + _zsh_autosuggest_capture_setup + + zle complete-word +} + +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_widget + +_zsh_autosuggest_capture_buffer() { + local BUFFERCONTENT="$1" + + _zsh_autosuggest_capture_setup + + zmodload zsh/parameter # 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] + _complete () { + unset 'compstate[vared]' + _original_complete "$@" } - # Signal setup completion by sending null byte - echo $''\0'' - ') + # Open zle with buffer set so we can capture completions for it + vared BUFFERCONTENT +} - # Send the string and a tab to trigger completion - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' +_zsh_autosuggest_capture_completion() { + typeset -g completion + local line - # Read up to the start of the first result + # Zle will be inactive if we are in async mode + if zle; then + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zle autosuggest-capture-completion + else + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "_zsh_autosuggest_capture_buffer '$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' - - # Read the first result zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' + completion="${line%$'\0'}" - # Print it, removing the trailing null byte - echo -E - ${line%$'\0'} + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME } _zsh_autosuggest_strategy_completion() { - typeset -g suggestion=$(_zsh_autosuggest_capture_completion "$1" | head -n 1) + typeset -g suggestion completion - # Strip the trailing carriage return - suggestion="${suggestion%$'\r'}" + # Fetch the first completion result + _zsh_autosuggest_capture_completion "$1" # Add the completion string to the buffer to build the full suggestion local -i i=1 - while [[ "$suggestion" != "${1[$i,-1]}"* ]]; do ((i++)); done - suggestion="${1[1,$i-1]}$suggestion" + while [[ "$completion" != "${1[$i,-1]}"* ]]; do ((i++)); done + suggestion="${1[1,$i-1]}$completion" } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index c29eb9e..40f6f66 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -500,92 +500,106 @@ zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle # Completion Suggestion Strategy # #--------------------------------------------------------------------# # Fetches suggestions from zsh's completion engine +# Based on https://github.com/Valodim/zsh-capture-completion # -# Big thanks to https://github.com/Valodim/zsh-capture-completion -_zsh_autosuggest_capture_completion() { - zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zsh -f -i - - local line - - setopt rcquotes - () { - # Initialize the pty env, blocking until null byte is seen - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "source $1" - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' - } =( <<< ' - exec 2>/dev/null # Silence any error messages - - autoload compinit - compinit -d ~/.zcompdump_autosuggestions - - # Exit as soon as completion is finished - comppostfuncs=( exit ) +_zsh_autosuggest_capture_setup() { + zmodload zsh/zutil # For `zparseopts` # Never group stuff! - zstyle '':completion:*'' list-grouped false + zstyle ':completion:*' list-grouped false - # no list separator, this saves some stripping later on - zstyle '':completion:*'' list-separator '''' + # No list separator, this saves some stripping later on + zstyle ':completion:*' list-separator '' - # we use zparseopts - zmodload zsh/zutil - - # override compadd (this our hook) + # Override compadd (this is our hook) compadd () { + setopt localoptions norcexpandparam + # Just delegate and leave if any of -O, -A or -D are given if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then builtin compadd "$@" return $? fi - setopt localoptions norcexpandparam extendedglob - - typeset -a __hits - # Capture completions by injecting -A parameter into the compadd call. # This takes care of matching for us. + typeset -a __hits builtin compadd -A __hits "$@" # Exit if no completion results [[ -n $__hits ]] || return - # Extract prefixes and suffixes from compadd call. we can''t do zsh''s cool - # -r remove-func magic, but it''s better than nothing. + # Extract prefixes and suffixes from compadd call. we can't do zsh's cool + # -r remove-func magic, but it's better than nothing. typeset -A apre hpre hsuf asuf zparseopts -E P:=apre p:=hpre S:=asuf s:=hsuf # Print the first match - echo -nE - $''\0''$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$''\0'' + echo -nE - $'\0'$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$'\0' + } +} + +_zsh_autosuggest_capture_widget() { + _zsh_autosuggest_capture_setup + + zle complete-word +} + +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_widget + +_zsh_autosuggest_capture_buffer() { + local BUFFERCONTENT="$1" + + _zsh_autosuggest_capture_setup + + zmodload zsh/parameter # 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] + _complete () { + unset 'compstate[vared]' + _original_complete "$@" } - # Signal setup completion by sending null byte - echo $''\0'' - ') + # Open zle with buffer set so we can capture completions for it + vared BUFFERCONTENT +} - # Send the string and a tab to trigger completion - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' +_zsh_autosuggest_capture_completion() { + typeset -g completion + local line - # Read up to the start of the first result + # Zle will be inactive if we are in async mode + if zle; then + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zle autosuggest-capture-completion + else + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "_zsh_autosuggest_capture_buffer '$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' - - # Read the first result zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' + completion="${line%$'\0'}" - # Print it, removing the trailing null byte - echo -E - ${line%$'\0'} + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME } _zsh_autosuggest_strategy_completion() { - typeset -g suggestion=$(_zsh_autosuggest_capture_completion "$1" | head -n 1) + typeset -g suggestion completion - # Strip the trailing carriage return - suggestion="${suggestion%$'\r'}" + # Fetch the first completion result + _zsh_autosuggest_capture_completion "$1" # Add the completion string to the buffer to build the full suggestion local -i i=1 - while [[ "$suggestion" != "${1[$i,-1]}"* ]]; do ((i++)); done - suggestion="${1[1,$i-1]}$suggestion" + while [[ "$completion" != "${1[$i,-1]}"* ]]; do ((i++)); done + suggestion="${1[1,$i-1]}$completion" } #--------------------------------------------------------------------#