diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 5f71d98..7517822 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -2,76 +2,61 @@ #--------------------------------------------------------------------# # Completion Suggestion Strategy # #--------------------------------------------------------------------# -# Fetches suggestions from zsh's completion engine -# Based on https://github.com/Valodim/zsh-capture-completion +# Fetches a suggestion from the completion engine # -_zsh_autosuggest_capture_setup() { - zmodload zsh/zutil # For `zparseopts` - - # Ensure completions have been initialized - if ! whence compdef >/dev/null; then - autoload -Uz compinit && compinit - fi - - # There is a bug in zpty module (fixed in zsh/master) 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. - zshexit() { - kill -KILL $$ - sleep 1 # Block for long enough for the signal to come through - } +_zsh_autosuggest_capture_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 - # Never group stuff! - zstyle ':completion:*' list-grouped false + # Don't list completions + unset compstate[list] +} - # No list separator, this saves some stripping later on - zstyle ':completion:*' list-separator '' +_zsh_autosuggest_capture_completion_widget() { + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) - # Override compadd (this is our hook) - compadd () { - setopt localoptions norcexpandparam + # Only capture completions at the end of the buffer + CURSOR=$#BUFFER - # Just delegate and leave if any of -O, -A or -D are given - if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then - builtin compadd "$@" - return $? - fi + # 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]} - # Capture completions by injecting -A parameter into the compadd call. - # This takes care of matching for us. - typeset -a __hits - builtin compadd -A __hits "$@" + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' +} - # Exit if no completion results - [[ -n $__hits ]] || return +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget - # 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 +_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() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + fi - # Print the first match - echo -nE - $'\0'$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$'\0' - } + bindkey '^I' autosuggest-capture-completion } -_zsh_autosuggest_capture_widget() { +_zsh_autosuggest_capture_completion_sync() { _zsh_autosuggest_capture_setup - zle complete-word + zle autosuggest-capture-completion } -zle -N autosuggest-capture-completion _zsh_autosuggest_capture_widget - -_zsh_autosuggest_capture_buffer() { - local BUFFERCONTENT="$1" - +_zsh_autosuggest_capture_completion_async() { _zsh_autosuggest_capture_setup - zmodload zsh/parameter # For `$functions` + 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 @@ -83,46 +68,37 @@ _zsh_autosuggest_capture_buffer() { } # Open zle with buffer set so we can capture completions for it - vared BUFFERCONTENT + vared 1 } -_zsh_autosuggest_capture_completion() { - zmodload zsh/zpty 2>/dev/null || return - - typeset -g completion +_zsh_autosuggest_strategy_completion() { + 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 + # Zle will be inactive if we are in async mode if zle; then - zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zle autosuggest-capture-completion + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync else - zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_buffer "\$1" + 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' - - # On older versions of zsh, we sometimes get extra bytes after the - # second null byte, so trim those off the end - completion="${${${(M)line:#*$'\0'*$'\0'*}#*$'\0'}%%$'\0'*}" - - # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME -} - -_zsh_autosuggest_strategy_completion() { - typeset -g suggestion - local completion - - # Fetch the first completion result - _zsh_autosuggest_capture_completion "$1" - - [[ -z "$completion" ]] && return - - # Add the completion string to the buffer to build the full suggestion - local -i i=1 - while [[ "$completion" != "${1[$i,-1]}"* ]]; do ((i++)); done - suggestion="${1[1,$i-1]}$completion" + { + # 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' + + # On older versions of zsh, we sometimes get extra bytes after the + # second null byte, so trim those off the end + suggestion="${${${(M)line:#*$'\0'*$'\0'*}#*$'\0'}%%$'\0'*}" + } always { + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME + } } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 0190895..be784b6 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -472,76 +472,61 @@ 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 +# Fetches a suggestion from the completion engine # -_zsh_autosuggest_capture_setup() { - zmodload zsh/zutil # For `zparseopts` - - # Ensure completions have been initialized - if ! whence compdef >/dev/null; then - autoload -Uz compinit && compinit - fi +_zsh_autosuggest_capture_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 - # There is a bug in zpty module (fixed in zsh/master) 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. - zshexit() { - kill -KILL $$ - sleep 1 # Block for long enough for the signal to come through - } + # Don't list completions + unset compstate[list] +} - # Never group stuff! - zstyle ':completion:*' list-grouped false +_zsh_autosuggest_capture_completion_widget() { + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) - # No list separator, this saves some stripping later on - zstyle ':completion:*' list-separator '' + # Only capture completions at the end of the buffer + CURSOR=$#BUFFER - # Override compadd (this is our hook) - compadd () { - setopt localoptions norcexpandparam + # 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]} - # Just delegate and leave if any of -O, -A or -D are given - if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then - builtin compadd "$@" - return $? - fi - - # Capture completions by injecting -A parameter into the compadd call. - # This takes care of matching for us. - typeset -a __hits - builtin compadd -A __hits "$@" + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' +} - # Exit if no completion results - [[ -n $__hits ]] || return +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget - # 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 +_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() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + fi - # Print the first match - echo -nE - $'\0'$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$'\0' - } + bindkey '^I' autosuggest-capture-completion } -_zsh_autosuggest_capture_widget() { +_zsh_autosuggest_capture_completion_sync() { _zsh_autosuggest_capture_setup - zle complete-word + zle autosuggest-capture-completion } -zle -N autosuggest-capture-completion _zsh_autosuggest_capture_widget - -_zsh_autosuggest_capture_buffer() { - local BUFFERCONTENT="$1" - +_zsh_autosuggest_capture_completion_async() { _zsh_autosuggest_capture_setup - zmodload zsh/parameter # For `$functions` + 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 @@ -553,48 +538,39 @@ _zsh_autosuggest_capture_buffer() { } # Open zle with buffer set so we can capture completions for it - vared BUFFERCONTENT + vared 1 } -_zsh_autosuggest_capture_completion() { - zmodload zsh/zpty 2>/dev/null || return - - typeset -g completion +_zsh_autosuggest_strategy_completion() { + 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 + # Zle will be inactive if we are in async mode if zle; then - zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zle autosuggest-capture-completion + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_completion_sync else - zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_buffer "\$1" + 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' - - # On older versions of zsh, we sometimes get extra bytes after the - # second null byte, so trim those off the end - completion="${${${(M)line:#*$'\0'*$'\0'*}#*$'\0'}%%$'\0'*}" - - # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME -} - -_zsh_autosuggest_strategy_completion() { - typeset -g suggestion - local completion - - # Fetch the first completion result - _zsh_autosuggest_capture_completion "$1" - - [[ -z "$completion" ]] && return - - # Add the completion string to the buffer to build the full suggestion - local -i i=1 - while [[ "$completion" != "${1[$i,-1]}"* ]]; do ((i++)); done - suggestion="${1[1,$i-1]}$completion" + { + # 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' + + # On older versions of zsh, we sometimes get extra bytes after the + # second null byte, so trim those off the end + suggestion="${${${(M)line:#*$'\0'*$'\0'*}#*$'\0'}%%$'\0'*}" + } always { + # Destroy the pty + zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME + } } #--------------------------------------------------------------------#