From 245f5d2ba2a6da3305f6612e68828e24a36b7345 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 30 Jun 2018 18:15:38 -0600 Subject: [PATCH 1/4] Improve completion suggestions Just insert the first completion directly into the buffer and read the whole buffer from the zpty. --- src/strategies/completion.zsh | 95 +++++++++++------------------------ zsh-autosuggestions.zsh | 95 +++++++++++------------------------ 2 files changed, 56 insertions(+), 134 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 5f71d98..30139ba 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -2,18 +2,18 @@ #--------------------------------------------------------------------# # 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` +_zsh_autosuggest_capture_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 - # Ensure completions have been initialized - if ! whence compdef >/dev/null; then - autoload -Uz compinit && compinit - fi + # Don't list completions + unset compstate[list] +} +_zsh_autosuggest_capture_completion_widget() { # 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 @@ -24,54 +24,26 @@ _zsh_autosuggest_capture_setup() { sleep 1 # Block for long enough for the signal to come through } - # Never group stuff! - zstyle ':completion:*' list-grouped false + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) - # No list separator, this saves some stripping later on - zstyle ':completion:*' list-separator '' + # 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]} - # 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' - } + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' } -_zsh_autosuggest_capture_widget() { - _zsh_autosuggest_capture_setup - - zle complete-word -} - -zle -N autosuggest-capture-completion _zsh_autosuggest_capture_widget +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget _zsh_autosuggest_capture_buffer() { local BUFFERCONTENT="$1" - _zsh_autosuggest_capture_setup + zmodload zsh/parameter 2>/dev/null || return # For `$functions` - zmodload zsh/parameter # For `$functions` + bindkey '^I' autosuggest-capture-completion # Make vared completion work as if for a normal command line # https://stackoverflow.com/a/7057118/154703 @@ -86,12 +58,16 @@ _zsh_autosuggest_capture_buffer() { vared BUFFERCONTENT } -_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 @@ -106,23 +82,8 @@ _zsh_autosuggest_capture_completion() { # 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'*}" + suggestion="${${${(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" -} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 0190895..81cb2dc 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -472,18 +472,18 @@ 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` +_zsh_autosuggest_capture_postcompletion() { + # Always insert the first completion into the buffer + compstate[insert]=1 - # Ensure completions have been initialized - if ! whence compdef >/dev/null; then - autoload -Uz compinit && compinit - fi + # Don't list completions + unset compstate[list] +} +_zsh_autosuggest_capture_completion_widget() { # 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 @@ -494,54 +494,26 @@ _zsh_autosuggest_capture_setup() { sleep 1 # Block for long enough for the signal to come through } - # Never group stuff! - zstyle ':completion:*' list-grouped false + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) - # No list separator, this saves some stripping later on - zstyle ':completion:*' list-separator '' + # 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]} - # 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' - } + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' } -_zsh_autosuggest_capture_widget() { - _zsh_autosuggest_capture_setup - - zle complete-word -} - -zle -N autosuggest-capture-completion _zsh_autosuggest_capture_widget +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget _zsh_autosuggest_capture_buffer() { local BUFFERCONTENT="$1" - _zsh_autosuggest_capture_setup + zmodload zsh/parameter 2>/dev/null || return # For `$functions` - zmodload zsh/parameter # For `$functions` + bindkey '^I' autosuggest-capture-completion # Make vared completion work as if for a normal command line # https://stackoverflow.com/a/7057118/154703 @@ -556,12 +528,16 @@ _zsh_autosuggest_capture_buffer() { vared BUFFERCONTENT } -_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 @@ -576,27 +552,12 @@ _zsh_autosuggest_capture_completion() { # 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'*}" + suggestion="${${${(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" -} - #--------------------------------------------------------------------# # History Suggestion Strategy # #--------------------------------------------------------------------# From 302bd7c059c2e657ea741d8d5074e2a0f23f1628 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 1 Jul 2018 10:16:38 -0600 Subject: [PATCH 2/4] Setup zshexit hook immediately in both sync/async cases --- src/strategies/completion.zsh | 44 +++++++++++++++++++++-------------- zsh-autosuggestions.zsh | 44 +++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 30139ba..2f162dc 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -14,16 +14,6 @@ _zsh_autosuggest_capture_postcompletion() { } _zsh_autosuggest_capture_completion_widget() { - # 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 - } - local -a +h comppostfuncs comppostfuncs=(_zsh_autosuggest_capture_postcompletion) @@ -38,12 +28,32 @@ _zsh_autosuggest_capture_completion_widget() { zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget -_zsh_autosuggest_capture_buffer() { - local BUFFERCONTENT="$1" - - zmodload zsh/parameter 2>/dev/null || return # For `$functions` +_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 bindkey '^I' autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_sync() { + _zsh_autosuggest_capture_setup + + zle autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_async() { + _zsh_autosuggest_capture_setup + + 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 @@ -55,7 +65,7 @@ _zsh_autosuggest_capture_buffer() { } # Open zle with buffer set so we can capture completions for it - vared BUFFERCONTENT + vared 1 } _zsh_autosuggest_strategy_completion() { @@ -70,9 +80,9 @@ _zsh_autosuggest_strategy_completion() { # 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 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 81cb2dc..7a612f9 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -484,16 +484,6 @@ _zsh_autosuggest_capture_postcompletion() { } _zsh_autosuggest_capture_completion_widget() { - # 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 - } - local -a +h comppostfuncs comppostfuncs=(_zsh_autosuggest_capture_postcompletion) @@ -508,12 +498,32 @@ _zsh_autosuggest_capture_completion_widget() { zle -N autosuggest-capture-completion _zsh_autosuggest_capture_completion_widget -_zsh_autosuggest_capture_buffer() { - local BUFFERCONTENT="$1" - - zmodload zsh/parameter 2>/dev/null || return # For `$functions` +_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 bindkey '^I' autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_sync() { + _zsh_autosuggest_capture_setup + + zle autosuggest-capture-completion +} + +_zsh_autosuggest_capture_completion_async() { + _zsh_autosuggest_capture_setup + + 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 @@ -525,7 +535,7 @@ _zsh_autosuggest_capture_buffer() { } # Open zle with buffer set so we can capture completions for it - vared BUFFERCONTENT + vared 1 } _zsh_autosuggest_strategy_completion() { @@ -540,9 +550,9 @@ _zsh_autosuggest_strategy_completion() { # 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 From 4869a757c8b641007a405e6937db13e72eaca83d Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 1 Jul 2018 20:55:53 -0600 Subject: [PATCH 3/4] Ensure we always destroy the zpty If running in sync mode and a completion takes a long time, the user can ^C out of it. Without this patch, the pty will not be destroyed in this case and the next time we go to create it, it will fail, making the shell unusable. --- src/strategies/completion.zsh | 20 +++++++++++--------- zsh-autosuggestions.zsh | 20 +++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 2f162dc..9b6341c 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -86,14 +86,16 @@ _zsh_autosuggest_strategy_completion() { 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' + { + # 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'*}" - - # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME + # 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 7a612f9..68f1bb1 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -556,16 +556,18 @@ _zsh_autosuggest_strategy_completion() { 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' + { + # 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'*}" - - # Destroy the pty - zpty -d $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME + # 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 + } } #--------------------------------------------------------------------# From f1c3b98774bb52667fe3303ace477898aedd3b9b Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Mon, 2 Jul 2018 09:31:48 -0600 Subject: [PATCH 4/4] Only capture completions at the end of the buffer. To prevent the suggestion from not starting with the buffer string. Example: `ls / /[cursor left][cursor left]b` Before the patch, suggests `ls /b /ls /bin/ /` After the patch, suggests `ls /b /bin/`. https://github.com/zsh-users/zsh-autosuggestions/issues/343#issuecomment-401675712 --- src/strategies/completion.zsh | 3 +++ zsh-autosuggestions.zsh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 9b6341c..7517822 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -17,6 +17,9 @@ _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. diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 68f1bb1..be784b6 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -487,6 +487,9 @@ _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.