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_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 + + # Don't list completions + unset compstate[list] +} + +_zsh_autosuggest_capture_completion_widget() { + 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]} + + # 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() { - 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 + # 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. - zshexit() { - kill -KILL $$ - sleep 1 # Block for long enough for the signal to come through - } + if ! is-at-least 5.4; then + zshexit() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + fi - # Never group stuff! - zstyle ':completion:*' list-grouped false - - # No list separator, this saves some stripping later on - zstyle ':completion:*' list-separator '' - - # 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 - - # 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. - 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' - } + 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 -} - -_zsh_autosuggest_capture_completion() { - zmodload zsh/zpty 2>/dev/null || return - - typeset -g completion - local line REPLY - - # 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''*'$'\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 + vared 1 } _zsh_autosuggest_strategy_completion() { typeset -g suggestion - local completion + local line REPLY - # Fetch the first completion result - _zsh_autosuggest_capture_completion "$1" + # Exit if we don't have completions + whence compdef >/dev/null || return - [[ -z "$completion" ]] && return + # Exit if we don't have zpty + zmodload zsh/zpty 2>/dev/null || 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" + # 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' + + # 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_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 + + # Don't list completions + unset compstate[list] +} + +_zsh_autosuggest_capture_completion_widget() { + 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]} + + # 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() { - 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 + # 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. - zshexit() { - kill -KILL $$ - sleep 1 # Block for long enough for the signal to come through - } + if ! is-at-least 5.4; then + zshexit() { + kill -KILL $$ + sleep 1 # Block for long enough for the signal to come through + } + fi - # Never group stuff! - zstyle ':completion:*' list-grouped false - - # No list separator, this saves some stripping later on - zstyle ':completion:*' list-separator '' - - # 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 - - # 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. - 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' - } + 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 -} - -_zsh_autosuggest_capture_completion() { - zmodload zsh/zpty 2>/dev/null || return - - typeset -g completion - local line REPLY - - # 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''*'$'\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 + vared 1 } _zsh_autosuggest_strategy_completion() { typeset -g suggestion - local completion + local line REPLY - # Fetch the first completion result - _zsh_autosuggest_capture_completion "$1" + # Exit if we don't have completions + whence compdef >/dev/null || return - [[ -z "$completion" ]] && return + # Exit if we don't have zpty + zmodload zsh/zpty 2>/dev/null || 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" + # 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' + + # 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 + } } #--------------------------------------------------------------------#