From 63789e96b54d8e8acb91792216cc564f16d2cd07 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 26 May 2018 14:01:03 -0600 Subject: [PATCH 01/47] Fix handling of newline + carriage return in async pty (#333) --- spec/async_spec.rb | 39 +++++++++++++++++++++++++++++++++++++++ src/async.zsh | 5 ++++- zsh-autosuggestions.zsh | 5 ++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/spec/async_spec.rb b/spec/async_spec.rb index 0bcbaa1..10f1a74 100644 --- a/spec/async_spec.rb +++ b/spec/async_spec.rb @@ -27,6 +27,45 @@ context 'with asynchronous suggestions enabled' do end end + it 'should not add extra carriage returns before newlines' do + session. + send_string('echo "'). + send_keys('escape'). + send_keys('enter'). + send_string('"'). + send_keys('enter') + + session.clear_screen + + session.send_string('echo') + wait_for { session.content }.to eq("echo \"\n\"") + end + + it 'should treat carriage returns and newlines as separate characters' do + session. + send_string('echo "'). + send_keys('C-v'). + send_keys('enter'). + send_string('foo"'). + send_keys('enter') + + session. + send_string('echo "'). + send_keys('control'). + send_keys('enter'). + send_string('bar"'). + send_keys('enter') + + session.clear_screen + + session. + send_string('echo "'). + send_keys('C-v'). + send_keys('enter') + + wait_for { session.content }.to eq('echo "^Mfoo"') + end + describe 'exiting a subshell' do it 'should not cause error messages to be printed' do session.run_command('$(exit)') diff --git a/src/async.zsh b/src/async.zsh index 9a0cfaa..62e3329 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -17,9 +17,12 @@ _zsh_autosuggest_async_server() { sleep 1 # Block for long enough for the signal to come through } - # Output only newlines (not carriage return + newline) + # Don't add any extra carriage returns stty -onlcr + # Don't translate carriage returns to newlines + stty -icrnl + # Silence any error messages exec 2>/dev/null diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 1c3eab5..e2e06be 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -595,9 +595,12 @@ _zsh_autosuggest_async_server() { sleep 1 # Block for long enough for the signal to come through } - # Output only newlines (not carriage return + newline) + # Don't add any extra carriage returns stty -onlcr + # Don't translate carriage returns to newlines + stty -icrnl + # Silence any error messages exec 2>/dev/null From 5549b68e6edd285a50173c7360a935d91879f33a Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 26 May 2018 15:33:32 -0600 Subject: [PATCH 02/47] Async is less reliable in zsh versions < 5.0.8 `stty` occasionally hangs (always in CircleCI) inside the async pty. Disable the tests for now until we can figure out and fix/workaround this issue. --- spec/async_spec.rb | 4 ++++ spec/terminal_session.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/spec/async_spec.rb b/spec/async_spec.rb index 10f1a74..152adde 100644 --- a/spec/async_spec.rb +++ b/spec/async_spec.rb @@ -1,4 +1,8 @@ context 'with asynchronous suggestions enabled' do + before do + skip 'Async mode not supported below v5.0.8' if session.zsh_version < Gem::Version.new('5.0.8') + end + let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } describe '`up-line-or-beginning-search`' do diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 2d9468f..f91ee6c 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -18,6 +18,10 @@ class TerminalSession tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'") end + def zsh_version + @zsh_version ||= Gem::Version.new(`#{ZSH_BIN} -c 'echo -n $ZSH_VERSION'`) + end + def tmux_socket_name @tmux_socket_name ||= SecureRandom.hex(6) end From 82b08e2dc8c59a40d363cb0c12f3516937cb1733 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 16 May 2018 15:27:06 -0600 Subject: [PATCH 03/47] First pass at getting suggestions from completion engine (#111) Uses https://github.com/Valodim/zsh-capture-completion with some slight modifications. --- src/config.zsh | 5 +- src/strategies/completion.zsh | 132 ++++++++++++++++++++++++++++++++ zsh-autosuggestions.zsh | 137 +++++++++++++++++++++++++++++++++- 3 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 src/strategies/completion.zsh diff --git a/src/config.zsh b/src/config.zsh index 597307f..9b3dbae 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -68,4 +68,7 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= # Pty name for calculating autosuggestions asynchronously -ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty +ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_async_pty + +# Pty name for capturing completions for completion suggestion strategy +ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh new file mode 100644 index 0000000..db948a0 --- /dev/null +++ b/src/strategies/completion.zsh @@ -0,0 +1,132 @@ + +#--------------------------------------------------------------------# +# Completion Suggestion Strategy # +#--------------------------------------------------------------------# +# Fetches suggestions from zsh's completion engine +# + +# Big thanks to https://github.com/Valodim/zsh-capture-completion +_zsh_autosuggest_capture_completion() { + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zsh -f -i + + # line buffer for pty output + local line + + setopt rcquotes + () { + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME source $1 + repeat 4; do + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line + [[ $line == ok* ]] && return + done + echo 'error initializing.' >&2 + exit 2 + } =( <<< ' + # no prompt! + PROMPT= + # load completion system + autoload compinit + compinit -d ~/.zcompdump_autosuggestions + # never run a command + bindkey ''^M'' undefined + bindkey ''^J'' undefined + bindkey ''^I'' complete-word + # send a line with null-byte at the end before and after completions are output + null-line () { + echo -E - $''\0'' + } + compprefuncs=( null-line ) + comppostfuncs=( null-line exit ) + # never group stuff! + zstyle '':completion:*'' list-grouped false + # don''t insert tab when attempting completion on empty line + zstyle '':completion:*'' insert-tab false + # no list separator, this saves some stripping later on + zstyle '':completion:*'' list-separator '''' + # we use zparseopts + zmodload zsh/zutil + # override compadd (this our hook) + compadd () { + # check if any of -O, -A or -D are given + if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then + # if that is the case, just delegate and leave + builtin compadd "$@" + return $? + fi + # ok, this concerns us! + # echo -E - got this: "$@" + # be careful with namespacing here, we don''t want to mess with stuff that + # should be passed to compadd! + typeset -a __hits __dscr __tmp + # do we have a description parameter? + # note we don''t use zparseopts here because of combined option parameters + # with arguments like -default- confuse it. + if (( $@[(I)-d] )); then # kind of a hack, $+@[(r)-d] doesn''t work because of line noise overload + # next param after -d + __tmp=${@[$[${@[(i)-d]}+1]]} + # description can be given as an array parameter name, or inline () array + if [[ $__tmp == \(* ]]; then + eval "__dscr=$__tmp" + else + __dscr=( "${(@P)__tmp}" ) + fi + fi + # capture completions by injecting -A parameter into the compadd call. + # this takes care of matching for us. + builtin compadd -A __hits -D __dscr "$@" + # JESUS CHRIST IT TOOK ME FOREVER TO FIGURE OUT THIS OPTION WAS SET AND WAS MESSING WITH MY SHIT HERE + setopt localoptions norcexpandparam extendedglob + # 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 + # append / to directories? we are only emulating -f in a half-assed way + # here, but it''s better than nothing. + integer dirsuf=0 + # don''t be fooled by -default- >.> + if [[ -z $hsuf && "${${@//-default-/}% -# *}" == *-[[:alnum:]]#f* ]]; then + dirsuf=1 + fi + # just drop + [[ -n $__hits ]] || return + # this is the point where we have all matches in $__hits and all + # descriptions in $__dscr! + # display all matches + local dsuf dscr + for i in {1..$#__hits}; do + # add a dir suffix? + (( dirsuf )) && [[ -d $__hits[$i] ]] && dsuf=/ || dsuf= + # description to be displayed afterwards + (( $#__dscr >= $i )) && dscr=" -- ${${__dscr[$i]}##$__hits[$i] #}" || dscr= + echo -E - $IPREFIX$apre$hpre$__hits[$i]$dsuf$hsuf$asuf + done + } + # signal success! + echo ok') + + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' + + integer tog=0 + # read from the pty, and parse linewise + while zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME; do :; done | while IFS= read -r line; do + if [[ $line == *$'\0\r' ]]; then + (( tog++ )) && return 0 || continue + fi + # display between toggles + (( tog )) && echo -E - $line + done + + return 2 +} + +_zsh_autosuggest_strategy_completion() { + typeset -g suggestion=$(_zsh_autosuggest_capture_completion "$1" | head -n 1) + + # Strip the trailing carriage return + suggestion="${suggestion%$'\r'}" + + # 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" +} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index e2e06be..edd6d84 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -104,7 +104,10 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= # Pty name for calculating autosuggestions asynchronously -ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty +ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_async_pty + +# Pty name for capturing completions for completion suggestion strategy +ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty #--------------------------------------------------------------------# # Utility Functions # @@ -493,6 +496,138 @@ zle -N autosuggest-enable _zsh_autosuggest_widget_enable zle -N autosuggest-disable _zsh_autosuggest_widget_disable zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle +#--------------------------------------------------------------------# +# Completion Suggestion Strategy # +#--------------------------------------------------------------------# +# Fetches suggestions from zsh's completion engine +# + +# Big thanks to https://github.com/Valodim/zsh-capture-completion +_zsh_autosuggest_capture_completion() { + zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zsh -f -i + + # line buffer for pty output + local line + + setopt rcquotes + () { + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME source $1 + repeat 4; do + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line + [[ $line == ok* ]] && return + done + echo 'error initializing.' >&2 + exit 2 + } =( <<< ' + # no prompt! + PROMPT= + # load completion system + autoload compinit + compinit -d ~/.zcompdump_autosuggestions + # never run a command + bindkey ''^M'' undefined + bindkey ''^J'' undefined + bindkey ''^I'' complete-word + # send a line with null-byte at the end before and after completions are output + null-line () { + echo -E - $''\0'' + } + compprefuncs=( null-line ) + comppostfuncs=( null-line exit ) + # never group stuff! + zstyle '':completion:*'' list-grouped false + # don''t insert tab when attempting completion on empty line + zstyle '':completion:*'' insert-tab false + # no list separator, this saves some stripping later on + zstyle '':completion:*'' list-separator '''' + # we use zparseopts + zmodload zsh/zutil + # override compadd (this our hook) + compadd () { + # check if any of -O, -A or -D are given + if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then + # if that is the case, just delegate and leave + builtin compadd "$@" + return $? + fi + # ok, this concerns us! + # echo -E - got this: "$@" + # be careful with namespacing here, we don''t want to mess with stuff that + # should be passed to compadd! + typeset -a __hits __dscr __tmp + # do we have a description parameter? + # note we don''t use zparseopts here because of combined option parameters + # with arguments like -default- confuse it. + if (( $@[(I)-d] )); then # kind of a hack, $+@[(r)-d] doesn''t work because of line noise overload + # next param after -d + __tmp=${@[$[${@[(i)-d]}+1]]} + # description can be given as an array parameter name, or inline () array + if [[ $__tmp == \(* ]]; then + eval "__dscr=$__tmp" + else + __dscr=( "${(@P)__tmp}" ) + fi + fi + # capture completions by injecting -A parameter into the compadd call. + # this takes care of matching for us. + builtin compadd -A __hits -D __dscr "$@" + # JESUS CHRIST IT TOOK ME FOREVER TO FIGURE OUT THIS OPTION WAS SET AND WAS MESSING WITH MY SHIT HERE + setopt localoptions norcexpandparam extendedglob + # 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 + # append / to directories? we are only emulating -f in a half-assed way + # here, but it''s better than nothing. + integer dirsuf=0 + # don''t be fooled by -default- >.> + if [[ -z $hsuf && "${${@//-default-/}% -# *}" == *-[[:alnum:]]#f* ]]; then + dirsuf=1 + fi + # just drop + [[ -n $__hits ]] || return + # this is the point where we have all matches in $__hits and all + # descriptions in $__dscr! + # display all matches + local dsuf dscr + for i in {1..$#__hits}; do + # add a dir suffix? + (( dirsuf )) && [[ -d $__hits[$i] ]] && dsuf=/ || dsuf= + # description to be displayed afterwards + (( $#__dscr >= $i )) && dscr=" -- ${${__dscr[$i]}##$__hits[$i] #}" || dscr= + echo -E - $IPREFIX$apre$hpre$__hits[$i]$dsuf$hsuf$asuf + done + } + # signal success! + echo ok') + + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' + + integer tog=0 + # read from the pty, and parse linewise + while zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME; do :; done | while IFS= read -r line; do + if [[ $line == *$'\0\r' ]]; then + (( tog++ )) && return 0 || continue + fi + # display between toggles + (( tog )) && echo -E - $line + done + + return 2 +} + +_zsh_autosuggest_strategy_completion() { + typeset -g suggestion=$(_zsh_autosuggest_capture_completion "$1" | head -n 1) + + # Strip the trailing carriage return + suggestion="${suggestion%$'\r'}" + + # 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" +} + #--------------------------------------------------------------------# # Default Suggestion Strategy # #--------------------------------------------------------------------# From c5551daabcea48d11028036df88a43a064934d18 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 16 May 2018 15:52:46 -0600 Subject: [PATCH 04/47] Default strategy now tries history first and falls back to completion --- src/strategies/default.zsh | 22 +++++++--------------- src/strategies/history.zsh | 25 +++++++++++++++++++++++++ zsh-autosuggestions.zsh | 19 ++++++++++++++++++- 3 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 src/strategies/history.zsh diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh index 0e85fb5..68617ff 100644 --- a/src/strategies/default.zsh +++ b/src/strategies/default.zsh @@ -2,24 +2,16 @@ #--------------------------------------------------------------------# # Default Suggestion Strategy # #--------------------------------------------------------------------# -# Suggests the most recent history item that matches the given -# prefix. +# Will provide suggestions from your history. If no matches are found +# in history, will provide a suggestion from the completion engine. # _zsh_autosuggest_strategy_default() { - # Reset options to defaults and enable LOCAL_OPTIONS - emulate -L zsh + typeset -g suggestion - # Enable globbing flags so that we can use (#m) - setopt EXTENDED_GLOB + _zsh_autosuggest_strategy_history "$1" - # Escape backslashes and all of the glob operators so we can use - # this string as a pattern to search the $history associative array. - # - (#m) globbing flag enables setting references for match data - # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 - local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" - - # Get the history items that match - # - (r) subscript flag makes the pattern match on values - typeset -g suggestion="${history[(r)${prefix}*]}" + if [[ -z "$suggestion" ]]; then + _zsh_autosuggest_strategy_completion "$1" + fi } diff --git a/src/strategies/history.zsh b/src/strategies/history.zsh new file mode 100644 index 0000000..a2755a5 --- /dev/null +++ b/src/strategies/history.zsh @@ -0,0 +1,25 @@ + +#--------------------------------------------------------------------# +# History Suggestion Strategy # +#--------------------------------------------------------------------# +# Suggests the most recent history item that matches the given +# prefix. +# + +_zsh_autosuggest_strategy_history() { + # Reset options to defaults and enable LOCAL_OPTIONS + emulate -L zsh + + # Enable globbing flags so that we can use (#m) + setopt EXTENDED_GLOB + + # Escape backslashes and all of the glob operators so we can use + # this string as a pattern to search the $history associative array. + # - (#m) globbing flag enables setting references for match data + # TODO: Use (b) flag when we can drop support for zsh older than v5.0.8 + local prefix="${1//(#m)[\\*?[\]<>()|^~#]/\\$MATCH}" + + # Get the history items that match + # - (r) subscript flag makes the pattern match on values + typeset -g suggestion="${history[(r)${prefix}*]}" +} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index edd6d84..41336a7 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -631,11 +631,28 @@ _zsh_autosuggest_strategy_completion() { #--------------------------------------------------------------------# # Default Suggestion Strategy # #--------------------------------------------------------------------# +# Will provide suggestions from your history. If no matches are found +# in history, will provide a suggestion from the completion engine. +# + +_zsh_autosuggest_strategy_default() { + typeset -g suggestion + + _zsh_autosuggest_strategy_history "$1" + + if [[ -z "$suggestion" ]]; then + _zsh_autosuggest_strategy_completion "$1" + fi +} + +#--------------------------------------------------------------------# +# History Suggestion Strategy # +#--------------------------------------------------------------------# # Suggests the most recent history item that matches the given # prefix. # -_zsh_autosuggest_strategy_default() { +_zsh_autosuggest_strategy_history() { # Reset options to defaults and enable LOCAL_OPTIONS emulate -L zsh From f63afd5969a7c4fe098f686c005dee8d07b6b058 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 16 May 2018 16:29:01 -0600 Subject: [PATCH 05/47] Fix async pty name option spec --- spec/options/async_zpty_name_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/options/async_zpty_name_spec.rb b/spec/options/async_zpty_name_spec.rb index 407ee70..60be875 100644 --- a/spec/options/async_zpty_name_spec.rb +++ b/spec/options/async_zpty_name_spec.rb @@ -3,7 +3,7 @@ context 'when async suggestions are enabled' do describe 'the zpty for async suggestions' do it 'is created with the default name' do - session.run_command('zpty -t zsh_autosuggest_pty &>/dev/null; echo $?') + session.run_command('zpty -t zsh_autosuggest_async_pty &>/dev/null; echo $?') wait_for { session.content }.to end_with("\n0") end From 4cca26ec84e764b077175ae20c54c8d673061fc2 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 18 May 2018 15:05:45 -0600 Subject: [PATCH 06/47] Modify completion code to better fit our needs Only need the first completion result --- src/strategies/completion.zsh | 122 ++++++++++++--------------------- zsh-autosuggestions.zsh | 123 ++++++++++++---------------------- 2 files changed, 83 insertions(+), 162 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index db948a0..aa87673 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -9,114 +9,74 @@ _zsh_autosuggest_capture_completion() { zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zsh -f -i - # line buffer for pty output local line setopt rcquotes () { - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME source $1 - repeat 4; do - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line - [[ $line == ok* ]] && return - done - echo 'error initializing.' >&2 - exit 2 + # 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' } =( <<< ' - # no prompt! - PROMPT= - # load completion system + exec 2>/dev/null # Silence any error messages + autoload compinit compinit -d ~/.zcompdump_autosuggestions - # never run a command - bindkey ''^M'' undefined - bindkey ''^J'' undefined - bindkey ''^I'' complete-word - # send a line with null-byte at the end before and after completions are output - null-line () { - echo -E - $''\0'' - } - compprefuncs=( null-line ) - comppostfuncs=( null-line exit ) - # never group stuff! + + # Exit as soon as completion is finished + comppostfuncs=( exit ) + + # Never group stuff! zstyle '':completion:*'' list-grouped false - # don''t insert tab when attempting completion on empty line - zstyle '':completion:*'' insert-tab false + # no list separator, this saves some stripping later on zstyle '':completion:*'' list-separator '''' + # we use zparseopts zmodload zsh/zutil + # override compadd (this our hook) compadd () { - # check if any of -O, -A or -D are given + # Just delegate and leave if any of -O, -A or -D are given if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then - # if that is the case, just delegate and leave builtin compadd "$@" return $? fi - # ok, this concerns us! - # echo -E - got this: "$@" - # be careful with namespacing here, we don''t want to mess with stuff that - # should be passed to compadd! - typeset -a __hits __dscr __tmp - # do we have a description parameter? - # note we don''t use zparseopts here because of combined option parameters - # with arguments like -default- confuse it. - if (( $@[(I)-d] )); then # kind of a hack, $+@[(r)-d] doesn''t work because of line noise overload - # next param after -d - __tmp=${@[$[${@[(i)-d]}+1]]} - # description can be given as an array parameter name, or inline () array - if [[ $__tmp == \(* ]]; then - eval "__dscr=$__tmp" - else - __dscr=( "${(@P)__tmp}" ) - fi - fi - # capture completions by injecting -A parameter into the compadd call. - # this takes care of matching for us. - builtin compadd -A __hits -D __dscr "$@" - # JESUS CHRIST IT TOOK ME FOREVER TO FIGURE OUT THIS OPTION WAS SET AND WAS MESSING WITH MY SHIT HERE + setopt localoptions norcexpandparam extendedglob - # extract prefixes and suffixes from compadd call. we can''t do zsh''s cool + + typeset -a __hits + + # Capture completions by injecting -A parameter into the compadd call. + # This takes care of matching for us. + 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 - # append / to directories? we are only emulating -f in a half-assed way - # here, but it''s better than nothing. - integer dirsuf=0 - # don''t be fooled by -default- >.> - if [[ -z $hsuf && "${${@//-default-/}% -# *}" == *-[[:alnum:]]#f* ]]; then - dirsuf=1 - fi - # just drop - [[ -n $__hits ]] || return - # this is the point where we have all matches in $__hits and all - # descriptions in $__dscr! - # display all matches - local dsuf dscr - for i in {1..$#__hits}; do - # add a dir suffix? - (( dirsuf )) && [[ -d $__hits[$i] ]] && dsuf=/ || dsuf= - # description to be displayed afterwards - (( $#__dscr >= $i )) && dscr=" -- ${${__dscr[$i]}##$__hits[$i] #}" || dscr= - echo -E - $IPREFIX$apre$hpre$__hits[$i]$dsuf$hsuf$asuf - done + + # Print the first match + echo -nE - $''\0''$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$''\0'' } - # signal success! - echo ok') + # Signal setup completion by sending null byte + echo $''\0'' + ') + + # Send the string and a tab to trigger completion zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' - integer tog=0 - # read from the pty, and parse linewise - while zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME; do :; done | while IFS= read -r line; do - if [[ $line == *$'\0\r' ]]; then - (( tog++ )) && return 0 || continue - fi - # display between toggles - (( tog )) && echo -E - $line - done + # Read up to the start of the first result + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' + + # Read the first result + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' - return 2 + # Print it, removing the trailing null byte + echo -E - ${line%$'\0'} } _zsh_autosuggest_strategy_completion() { diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 41336a7..a2134da 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -506,114 +506,75 @@ zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle _zsh_autosuggest_capture_completion() { zpty $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME zsh -f -i - # line buffer for pty output local line setopt rcquotes () { - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME source $1 - repeat 4; do - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line - [[ $line == ok* ]] && return - done - echo 'error initializing.' >&2 - exit 2 + # Setup, blocking until null byte to signal completion + zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "source $1" + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' } =( <<< ' - # no prompt! - PROMPT= - # load completion system + exec 2>/dev/null # Silence any error messages + autoload compinit compinit -d ~/.zcompdump_autosuggestions - # never run a command - bindkey ''^M'' undefined - bindkey ''^J'' undefined - bindkey ''^I'' complete-word - # send a line with null-byte at the end before and after completions are output - null-line () { - echo -E - $''\0'' - } - compprefuncs=( null-line ) - comppostfuncs=( null-line exit ) - # never group stuff! + + # Exit as soon as completion is finished + comppostfuncs=( exit ) + + # Never group stuff! zstyle '':completion:*'' list-grouped false - # don''t insert tab when attempting completion on empty line - zstyle '':completion:*'' insert-tab false + # no list separator, this saves some stripping later on zstyle '':completion:*'' list-separator '''' + # we use zparseopts zmodload zsh/zutil + # override compadd (this our hook) compadd () { - # check if any of -O, -A or -D are given + # Just delegate and leave if any of -O, -A or -D are given if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then - # if that is the case, just delegate and leave builtin compadd "$@" return $? fi - # ok, this concerns us! - # echo -E - got this: "$@" - # be careful with namespacing here, we don''t want to mess with stuff that - # should be passed to compadd! - typeset -a __hits __dscr __tmp - # do we have a description parameter? - # note we don''t use zparseopts here because of combined option parameters - # with arguments like -default- confuse it. - if (( $@[(I)-d] )); then # kind of a hack, $+@[(r)-d] doesn''t work because of line noise overload - # next param after -d - __tmp=${@[$[${@[(i)-d]}+1]]} - # description can be given as an array parameter name, or inline () array - if [[ $__tmp == \(* ]]; then - eval "__dscr=$__tmp" - else - __dscr=( "${(@P)__tmp}" ) - fi - fi - # capture completions by injecting -A parameter into the compadd call. - # this takes care of matching for us. - builtin compadd -A __hits -D __dscr "$@" - # JESUS CHRIST IT TOOK ME FOREVER TO FIGURE OUT THIS OPTION WAS SET AND WAS MESSING WITH MY SHIT HERE + setopt localoptions norcexpandparam extendedglob - # extract prefixes and suffixes from compadd call. we can''t do zsh''s cool + + typeset -a __hits + + # Capture completions by injecting -A parameter into the compadd call. + # This takes care of matching for us. + 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 - # append / to directories? we are only emulating -f in a half-assed way - # here, but it''s better than nothing. - integer dirsuf=0 - # don''t be fooled by -default- >.> - if [[ -z $hsuf && "${${@//-default-/}% -# *}" == *-[[:alnum:]]#f* ]]; then - dirsuf=1 - fi - # just drop - [[ -n $__hits ]] || return - # this is the point where we have all matches in $__hits and all - # descriptions in $__dscr! - # display all matches - local dsuf dscr - for i in {1..$#__hits}; do - # add a dir suffix? - (( dirsuf )) && [[ -d $__hits[$i] ]] && dsuf=/ || dsuf= - # description to be displayed afterwards - (( $#__dscr >= $i )) && dscr=" -- ${${__dscr[$i]}##$__hits[$i] #}" || dscr= - echo -E - $IPREFIX$apre$hpre$__hits[$i]$dsuf$hsuf$asuf - done + + # Print the first match + echo -nE - $''\0''$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$''\0'' } - # signal success! - echo ok') + # Signal setup completion by sending null byte + echo $''\0'' + ') + + + # Send the string and a tab to trigger completion zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' - integer tog=0 - # read from the pty, and parse linewise - while zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME; do :; done | while IFS= read -r line; do - if [[ $line == *$'\0\r' ]]; then - (( tog++ )) && return 0 || continue - fi - # display between toggles - (( tog )) && echo -E - $line - done + # Read up to the start of the first result + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' + + # Read the first result + zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' - return 2 + # Print it, removing the trailing null byte + echo -E - ${line%$'\0'} } _zsh_autosuggest_strategy_completion() { From 0a548c62f4f57bb68cb6c45ff75b4781bb39b451 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 18 May 2018 15:24:48 -0600 Subject: [PATCH 07/47] Forgot to make after small tweak --- zsh-autosuggestions.zsh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index a2134da..c29eb9e 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -510,7 +510,7 @@ _zsh_autosuggest_capture_completion() { setopt rcquotes () { - # Setup, blocking until null byte to signal completion + # 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' } =( <<< ' @@ -563,7 +563,6 @@ _zsh_autosuggest_capture_completion() { echo $''\0'' ') - # Send the string and a tab to trigger completion zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' From 6ffaec725a29ff2a199ceb173f72eadc42254582 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 22 May 2018 16:13:56 -0600 Subject: [PATCH 08/47] 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 - - # no list separator, this saves some stripping later on - zstyle '':completion:*'' list-separator '''' + zstyle ':completion:*' list-grouped false - # we use zparseopts - zmodload zsh/zutil + # No list separator, this saves some stripping later on + zstyle ':completion:*' list-separator '' - # 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' } +} - # Signal setup completion by sending null byte - echo $''\0'' - ') +_zsh_autosuggest_capture_widget() { + _zsh_autosuggest_capture_setup - # Send the string and a tab to trigger completion - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' + zle complete-word +} - # Read up to the start of the first result - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' +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` - # Read the first result + # 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 "$@" + } + + # Open zle with buffer set so we can capture completions for it + vared BUFFERCONTENT +} + +_zsh_autosuggest_capture_completion() { + typeset -g completion + local line + + # 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' 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' } +} - # Signal setup completion by sending null byte - echo $''\0'' - ') +_zsh_autosuggest_capture_widget() { + _zsh_autosuggest_capture_setup - # Send the string and a tab to trigger completion - zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME "$*"$'\t' + zle complete-word +} - # Read up to the start of the first result - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' +zle -N autosuggest-capture-completion _zsh_autosuggest_capture_widget - # Read the first result +_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 "$@" + } + + # Open zle with buffer set so we can capture completions for it + vared BUFFERCONTENT +} + +_zsh_autosuggest_capture_completion() { + typeset -g completion + local line + + # 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' 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" } #--------------------------------------------------------------------# From 3dbd9afaec0a5f3c1c63779d665b1dcf93b60c7f Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 22 May 2018 17:16:45 -0600 Subject: [PATCH 09/47] Fix completion strategy killing other pty's Only a problem in synchronous mode --- spec/integrations/client_zpty_spec.rb | 21 +++++++++++++++++---- src/strategies/completion.zsh | 10 ++++++++++ zsh-autosuggestions.zsh | 10 ++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/spec/integrations/client_zpty_spec.rb b/spec/integrations/client_zpty_spec.rb index 8f1550e..e364759 100644 --- a/spec/integrations/client_zpty_spec.rb +++ b/spec/integrations/client_zpty_spec.rb @@ -1,10 +1,23 @@ describe 'a running zpty command' do let(:before_sourcing) { -> { session.run_command('zmodload zsh/zpty && zpty -b kitty cat') } } - it 'is not affected by running zsh-autosuggestions' do - sleep 1 # Give a little time for precmd hooks to run - session.run_command('zpty -t kitty; echo $?') + context 'when sourcing the plugin' do + it 'is not affected' do + sleep 1 # Give a little time for precmd hooks to run + session.run_command('zpty -t kitty; echo $?') - wait_for { session.content }.to end_with("\n0") + wait_for { session.content }.to end_with("\n0") + end + end + + context 'when using `completion` strategy' do + let(:options) { ["ZSH_AUTOSUGGEST_STRATEGY=completion"] } + + it 'is not affected' do + session.send_keys('a').send_keys('C-h') + session.run_command('zpty -t kitty; echo $?') + + wait_for { session.content }.to end_with("\n0") + end end end diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index e8aac6c..6517444 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -9,6 +9,16 @@ _zsh_autosuggest_capture_setup() { zmodload zsh/zutil # For `zparseopts` + # 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 + } + # Never group stuff! zstyle ':completion:*' list-grouped false diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 40f6f66..644bacf 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -506,6 +506,16 @@ zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle _zsh_autosuggest_capture_setup() { zmodload zsh/zutil # For `zparseopts` + # 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 + } + # Never group stuff! zstyle ':completion:*' list-grouped false From cf458d2a3bcc494a1ab63fdb542f528b42b71546 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 23 May 2018 14:50:56 -0600 Subject: [PATCH 10/47] Fix completion suggestions when compinit is not enabled Need to make sure compinit is called in the pty or the shell hangs --- src/strategies/completion.zsh | 2 ++ zsh-autosuggestions.zsh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 6517444..c8b176b 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -9,6 +9,8 @@ _zsh_autosuggest_capture_setup() { zmodload zsh/zutil # For `zparseopts` + autoload compinit && compinit + # 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 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 644bacf..756cfbc 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -506,6 +506,8 @@ zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle _zsh_autosuggest_capture_setup() { zmodload zsh/zutil # For `zparseopts` + autoload compinit && compinit + # 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 From 7d19f8f9b2bf03b92372f95d37f5b55a6e941684 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 23 May 2018 22:04:16 -0600 Subject: [PATCH 11/47] Rename default spec to history spec --- spec/strategies/{default_spec.rb => history_spec.rb} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename spec/strategies/{default_spec.rb => history_spec.rb} (85%) diff --git a/spec/strategies/default_spec.rb b/spec/strategies/history_spec.rb similarity index 85% rename from spec/strategies/default_spec.rb rename to spec/strategies/history_spec.rb index 89321f3..f8ae526 100644 --- a/spec/strategies/default_spec.rb +++ b/spec/strategies/history_spec.rb @@ -1,6 +1,6 @@ require 'strategies/special_characters_helper' -describe 'the default suggestion strategy' do +describe 'the `history` suggestion strategy' do it 'suggests the last matching history entry' do with_history('ls foo', 'ls bar', 'echo baz') do session.send_string('ls') From 973205005cca59f0701e6379474bf24eef805ac1 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 23 May 2018 22:04:47 -0600 Subject: [PATCH 12/47] Add spec for `completion` strategy --- spec/strategies/completion_spec.rb | 30 ++++++++++++++++++++++++++ spec/strategies/match_prev_cmd_spec.rb | 2 +- src/strategies/completion.zsh | 5 ++++- zsh-autosuggestions.zsh | 5 ++++- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 spec/strategies/completion_spec.rb diff --git a/spec/strategies/completion_spec.rb b/spec/strategies/completion_spec.rb new file mode 100644 index 0000000..62cf0e5 --- /dev/null +++ b/spec/strategies/completion_spec.rb @@ -0,0 +1,30 @@ +describe 'the `completion` suggestion strategy' do + let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=completion'] } + let(:before_sourcing) do + -> do + session. + run_command('autoload compinit && compinit'). + run_command('_foo() { compadd bar }'). + run_command('compdef _foo baz') + end + end + + it 'suggests the first completion result' do + session.send_string('baz ') + wait_for { session.content }.to eq('baz bar') + end + + context 'when async mode is enabled' do + before do + skip 'Async mode not supported below v5.0.8' if session.zsh_version < Gem::Version.new('5.0.8') + end + + let(:options) { ['ZSH_AUTOSUGGEST_USE_ASYNC=true', 'ZSH_AUTOSUGGEST_STRATEGY=completion'] } + + it 'suggests the first completion result' do + session.send_string('baz ') + wait_for { session.content }.to eq('baz bar') + end + end +end + diff --git a/spec/strategies/match_prev_cmd_spec.rb b/spec/strategies/match_prev_cmd_spec.rb index f1596ba..5a143b8 100644 --- a/spec/strategies/match_prev_cmd_spec.rb +++ b/spec/strategies/match_prev_cmd_spec.rb @@ -1,6 +1,6 @@ require 'strategies/special_characters_helper' -describe 'the match_prev_cmd strategy' do +describe 'the `match_prev_cmd` strategy' do let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=match_prev_cmd'] } it 'suggests the last matching history entry after the previous command' do diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index c8b176b..0808575 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -9,7 +9,10 @@ _zsh_autosuggest_capture_setup() { zmodload zsh/zutil # For `zparseopts` - autoload compinit && compinit + # 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 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 756cfbc..da10235 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -506,7 +506,10 @@ zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle _zsh_autosuggest_capture_setup() { zmodload zsh/zutil # For `zparseopts` - autoload compinit && compinit + # 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 From 949c37419544e6fab313d5139723fa315d645cab Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Thu, 24 May 2018 16:45:20 -0600 Subject: [PATCH 13/47] Fix `completion` strategy on older versions of zsh `zpty -r` with a pattern seems to have some funky behavior on older versions, giving unpredictable results --- src/strategies/completion.zsh | 8 +++++--- zsh-autosuggestions.zsh | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 0808575..a23b630 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -100,9 +100,11 @@ _zsh_autosuggest_capture_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' - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' - completion="${line%$'\0'}" + 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 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index da10235..6683262 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -597,9 +597,11 @@ _zsh_autosuggest_capture_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' - zpty -r $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME line '*'$'\0' - completion="${line%$'\0'}" + 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 From bcbdad83e940917db9bbd0afd62057229446c00f Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 6 Jun 2018 21:49:03 -0600 Subject: [PATCH 14/47] Support fallback strategies by setting array in config --- Makefile | 1 + README.md | 11 ++++-- spec/integrations/zle_input_stack_spec.rb | 2 +- spec/options/strategy_spec.rb | 45 ++++++++++++++++----- src/async.zsh | 2 +- src/config.zsh | 4 +- src/fetch.zsh | 23 +++++++++++ src/strategies/default.zsh | 17 -------- src/widgets.zsh | 2 +- zsh-autosuggestions.zsh | 48 +++++++++++++---------- 10 files changed, 100 insertions(+), 55 deletions(-) create mode 100644 src/fetch.zsh delete mode 100644 src/strategies/default.zsh diff --git a/Makefile b/Makefile index d5d162c..b89ff04 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ SRC_FILES := \ $(SRC_DIR)/highlight.zsh \ $(SRC_DIR)/widgets.zsh \ $(SRC_DIR)/strategies/*.zsh \ + $(SRC_DIR)/fetch.zsh \ $(SRC_DIR)/async.zsh \ $(SRC_DIR)/start.zsh diff --git a/README.md b/README.md index 4ad07d8..5039b53 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ _[Fish](http://fishshell.com/)-like fast/unobtrusive autosuggestions for zsh._ -It suggests commands as you type, based on command history. +It suggests commands as you type. Requirements: Zsh v4.3.11 or later @@ -39,10 +39,13 @@ Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion ### Suggestion Strategy -Set `ZSH_AUTOSUGGEST_STRATEGY` to choose the strategy for generating suggestions. There are currently two to choose from: +`ZSH_AUTOSUGGEST_STRATEGY` is an array that specifies how suggestions should be generated. The strategies in the array are tried successively until a suggestion is found. There are currently three built-in strategies to choose from: -- `default`: Chooses the most recent match. -- `match_prev_cmd`: Chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). Note that this strategy won't work as expected with ZSH options that don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`. +- `history`: Chooses the most recent match from history. +- `match_prev_cmd`: Like `history`, but chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). Note that this strategy won't work as expected with ZSH options that don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`. +- `completion`: (experimental) Chooses a suggestion based on what tab-completion would suggest. + +For example, setting `ZSH_AUTOSUGGEST_STRATEGY=(history completion)` will first try to find a suggestion from your history, but, if it can't find a match, will find a suggestion from the completion engine. ### Widget Mapping diff --git a/spec/integrations/zle_input_stack_spec.rb b/spec/integrations/zle_input_stack_spec.rb index 8a2c990..12cfbc7 100644 --- a/spec/integrations/zle_input_stack_spec.rb +++ b/spec/integrations/zle_input_stack_spec.rb @@ -2,7 +2,7 @@ describe 'using `zle -U`' do let(:before_sourcing) do -> do session. - run_command('_zsh_autosuggest_strategy_test() { sleep 1; _zsh_autosuggest_strategy_default "$1" }'). + run_command('_zsh_autosuggest_strategy_test() { sleep 1; _zsh_autosuggest_strategy_history "$1" }'). run_command('foo() { zle -U - "echo hello" }; zle -N foo; bindkey ^B foo') end end diff --git a/spec/options/strategy_spec.rb b/spec/options/strategy_spec.rb index c9f01e1..378d01e 100644 --- a/spec/options/strategy_spec.rb +++ b/spec/options/strategy_spec.rb @@ -1,20 +1,45 @@ describe 'a suggestion for a given prefix' do - let(:options) { ['_zsh_autosuggest_strategy_default() { suggestion="echo foo" }'] } + let(:history_strategy) { '_zsh_autosuggest_strategy_history() { suggestion="history" }' } + let(:foobar_strategy) { '_zsh_autosuggest_strategy_foobar() { [[ "foobar baz" = $1* ]] && suggestion="foobar baz" }' } + let(:foobaz_strategy) { '_zsh_autosuggest_strategy_foobaz() { [[ "foobaz bar" = $1* ]] && suggestion="foobaz bar" }' } - it 'is determined by calling the default strategy function' do - session.send_string('e') - wait_for { session.content }.to eq('echo foo') + let(:options) { [ history_strategy ] } + + it 'by default is determined by calling the `history` strategy function' do + session.send_string('h') + wait_for { session.content }.to eq('history') + end + + context 'when ZSH_AUTOSUGGEST_STRATEGY is set to an array' do + let(:options) { [ + foobar_strategy, + foobaz_strategy, + 'ZSH_AUTOSUGGEST_STRATEGY=(foobar foobaz)' + ] } + + it 'is determined by the first strategy function to return a suggestion' do + session.send_string('foo') + wait_for { session.content }.to eq('foobar baz') + + session.send_string('baz') + wait_for { session.content }.to eq('foobaz bar') + end end - context 'when ZSH_AUTOSUGGEST_STRATEGY is set' do + context 'when ZSH_AUTOSUGGEST_STRATEGY is set to a string' do let(:options) { [ - '_zsh_autosuggest_strategy_custom() { suggestion="echo foo" }', - 'ZSH_AUTOSUGGEST_STRATEGY=custom' + foobar_strategy, + foobaz_strategy, + 'ZSH_AUTOSUGGEST_STRATEGY="foobar foobaz"' ] } - it 'is determined by calling the specified strategy function' do - session.send_string('e') - wait_for { session.content }.to eq('echo foo') + it 'is determined by the first strategy function to return a suggestion' do + session.send_string('foo') + wait_for { session.content }.to eq('foobar baz') + + session.send_string('baz') + wait_for { session.content }.to eq('foobaz bar') end end end + diff --git a/src/async.zsh b/src/async.zsh index 62e3329..dd54c24 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -35,7 +35,7 @@ _zsh_autosuggest_async_server() { # Run suggestion search in the background ( local suggestion - _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query" + _zsh_autosuggest_fetch_suggestion "$query" echo -n -E "$suggestion"$'\0' ) & diff --git a/src/config.zsh b/src/config.zsh index 9b3dbae..4598191 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -11,7 +11,9 @@ ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- -ZSH_AUTOSUGGEST_STRATEGY=default +# Strategies to use to fetch a suggestion +# Will try each strategy in order until a suggestion is returned +ZSH_AUTOSUGGEST_STRATEGY=(history) # Widgets that clear the suggestion ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( diff --git a/src/fetch.zsh b/src/fetch.zsh new file mode 100644 index 0000000..f94e66d --- /dev/null +++ b/src/fetch.zsh @@ -0,0 +1,23 @@ + +#--------------------------------------------------------------------# +# Fetch Suggestion # +#--------------------------------------------------------------------# +# Loops through all specified strategies and returns a suggestion +# from the first strategy to provide one. +# + +_zsh_autosuggest_fetch_suggestion() { + typeset -g suggestion + local -a strategies + + # Ensure we are working with an array + strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) + + for strategy in $strategies; do + # Try to get a suggestion from this strategy + _zsh_autosuggest_strategy_$strategy "$1" + + # Break once we've found a suggestion + [[ -n "$suggestion" ]] && break + done +} diff --git a/src/strategies/default.zsh b/src/strategies/default.zsh deleted file mode 100644 index 68617ff..0000000 --- a/src/strategies/default.zsh +++ /dev/null @@ -1,17 +0,0 @@ - -#--------------------------------------------------------------------# -# Default Suggestion Strategy # -#--------------------------------------------------------------------# -# Will provide suggestions from your history. If no matches are found -# in history, will provide a suggestion from the completion engine. -# - -_zsh_autosuggest_strategy_default() { - typeset -g suggestion - - _zsh_autosuggest_strategy_history "$1" - - if [[ -z "$suggestion" ]]; then - _zsh_autosuggest_strategy_completion "$1" - fi -} diff --git a/src/widgets.zsh b/src/widgets.zsh index 87bb62e..89af395 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -97,7 +97,7 @@ _zsh_autosuggest_fetch() { _zsh_autosuggest_async_request "$BUFFER" else local suggestion - _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$BUFFER" + _zsh_autosuggest_fetch_suggestion "$BUFFER" _zsh_autosuggest_suggest "$suggestion" fi } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 6683262..02d4b2f 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -47,7 +47,9 @@ ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- -ZSH_AUTOSUGGEST_STRATEGY=default +# Strategies to use to fetch a suggestion +# Will try each strategy in order until a suggestion is returned +ZSH_AUTOSUGGEST_STRATEGY=(history) # Widgets that clear the suggestion ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( @@ -381,7 +383,7 @@ _zsh_autosuggest_fetch() { _zsh_autosuggest_async_request "$BUFFER" else local suggestion - _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$BUFFER" + _zsh_autosuggest_fetch_suggestion "$BUFFER" _zsh_autosuggest_suggest "$suggestion" fi } @@ -619,23 +621,6 @@ _zsh_autosuggest_strategy_completion() { suggestion="${1[1,$i-1]}$completion" } -#--------------------------------------------------------------------# -# Default Suggestion Strategy # -#--------------------------------------------------------------------# -# Will provide suggestions from your history. If no matches are found -# in history, will provide a suggestion from the completion engine. -# - -_zsh_autosuggest_strategy_default() { - typeset -g suggestion - - _zsh_autosuggest_strategy_history "$1" - - if [[ -z "$suggestion" ]]; then - _zsh_autosuggest_strategy_completion "$1" - fi -} - #--------------------------------------------------------------------# # History Suggestion Strategy # #--------------------------------------------------------------------# @@ -720,6 +705,29 @@ _zsh_autosuggest_strategy_match_prev_cmd() { typeset -g suggestion="$history[$histkey]" } +#--------------------------------------------------------------------# +# Fetch Suggestion # +#--------------------------------------------------------------------# +# Loops through all specified strategies and returns a suggestion +# from the first strategy to provide one. +# + +_zsh_autosuggest_fetch_suggestion() { + typeset -g suggestion + local -a strategies + + # Ensure we are working with an array + strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) + + for strategy in $strategies; do + # Try to get a suggestion from this strategy + _zsh_autosuggest_strategy_$strategy "$1" + + # Break once we've found a suggestion + [[ -n "$suggestion" ]] && break + done +} + #--------------------------------------------------------------------# # Async # #--------------------------------------------------------------------# @@ -756,7 +764,7 @@ _zsh_autosuggest_async_server() { # Run suggestion search in the background ( local suggestion - _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query" + _zsh_autosuggest_fetch_suggestion "$query" echo -n -E "$suggestion"$'\0' ) & From 4e466f0e4e412607d6354e2be716d4e4efdd4351 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 10 Jun 2018 22:39:58 -0600 Subject: [PATCH 15/47] Support widgets starting with dashes (ex: `-a-widget`) Fixes #337 --- src/bind.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bind.zsh b/src/bind.zsh index 42a0dd0..f538379 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -69,7 +69,7 @@ _zsh_autosuggest_bind_widget() { }" # Create the bound widget - zle -N $widget _zsh_autosuggest_bound_${bind_count}_$widget + zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget } # Map all configured widgets to the right autosuggest widgets diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index e2e06be..0a1f9c6 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -206,7 +206,7 @@ _zsh_autosuggest_bind_widget() { }" # Create the bound widget - zle -N $widget _zsh_autosuggest_bound_${bind_count}_$widget + zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget } # Map all configured widgets to the right autosuggest widgets From b0ffc34fb83136b29e6ece5028f0fda6b1e00ee7 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 10 Jun 2018 23:35:22 -0600 Subject: [PATCH 16/47] completion should be a local var --- src/strategies/completion.zsh | 3 ++- zsh-autosuggestions.zsh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index a23b630..847f22b 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -111,7 +111,8 @@ _zsh_autosuggest_capture_completion() { } _zsh_autosuggest_strategy_completion() { - typeset -g suggestion completion + typeset -g suggestion + local completion # Fetch the first completion result _zsh_autosuggest_capture_completion "$1" diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 02d4b2f..823f541 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -610,7 +610,8 @@ _zsh_autosuggest_capture_completion() { } _zsh_autosuggest_strategy_completion() { - typeset -g suggestion completion + typeset -g suggestion + local completion # Fetch the first completion result _zsh_autosuggest_capture_completion "$1" From 9cb010151204926aee24e71d54879092d89c66b4 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Mon, 11 Jun 2018 02:06:18 -0600 Subject: [PATCH 17/47] Refactor async mode to no longer use zpty See technique used in `fast-syntax-highlighting`: - https://github.com/zdharma/fast-syntax-highlighting/commit/ca2e18bbc9e27b9264206c257d2ab68838162c70 - http://www.zsh.org/mla/users/2018/msg00424.html --- spec/async_spec.rb | 53 ------------ spec/integrations/client_zpty_spec.rb | 10 --- spec/options/async_zpty_name_spec.rb | 19 ---- spec/terminal_session.rb | 4 - src/async.zsh | 114 +++++------------------- src/start.zsh | 4 - src/widgets.zsh | 2 +- zsh-autosuggestions.zsh | 120 +++++--------------------- 8 files changed, 42 insertions(+), 284 deletions(-) delete mode 100644 spec/integrations/client_zpty_spec.rb delete mode 100644 spec/options/async_zpty_name_spec.rb diff --git a/spec/async_spec.rb b/spec/async_spec.rb index 152adde..9405fb2 100644 --- a/spec/async_spec.rb +++ b/spec/async_spec.rb @@ -1,8 +1,4 @@ context 'with asynchronous suggestions enabled' do - before do - skip 'Async mode not supported below v5.0.8' if session.zsh_version < Gem::Version.new('5.0.8') - end - let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } describe '`up-line-or-beginning-search`' do @@ -30,55 +26,6 @@ context 'with asynchronous suggestions enabled' do end end end - - it 'should not add extra carriage returns before newlines' do - session. - send_string('echo "'). - send_keys('escape'). - send_keys('enter'). - send_string('"'). - send_keys('enter') - - session.clear_screen - - session.send_string('echo') - wait_for { session.content }.to eq("echo \"\n\"") - end - - it 'should treat carriage returns and newlines as separate characters' do - session. - send_string('echo "'). - send_keys('C-v'). - send_keys('enter'). - send_string('foo"'). - send_keys('enter') - - session. - send_string('echo "'). - send_keys('control'). - send_keys('enter'). - send_string('bar"'). - send_keys('enter') - - session.clear_screen - - session. - send_string('echo "'). - send_keys('C-v'). - send_keys('enter') - - wait_for { session.content }.to eq('echo "^Mfoo"') - end - - describe 'exiting a subshell' do - it 'should not cause error messages to be printed' do - session.run_command('$(exit)') - - sleep 1 - - expect(session.content).to eq('$(exit)') - end - end end diff --git a/spec/integrations/client_zpty_spec.rb b/spec/integrations/client_zpty_spec.rb deleted file mode 100644 index 8f1550e..0000000 --- a/spec/integrations/client_zpty_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -describe 'a running zpty command' do - let(:before_sourcing) { -> { session.run_command('zmodload zsh/zpty && zpty -b kitty cat') } } - - it 'is not affected by running zsh-autosuggestions' do - sleep 1 # Give a little time for precmd hooks to run - session.run_command('zpty -t kitty; echo $?') - - wait_for { session.content }.to end_with("\n0") - end -end diff --git a/spec/options/async_zpty_name_spec.rb b/spec/options/async_zpty_name_spec.rb deleted file mode 100644 index 407ee70..0000000 --- a/spec/options/async_zpty_name_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -context 'when async suggestions are enabled' do - let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } - - describe 'the zpty for async suggestions' do - it 'is created with the default name' do - session.run_command('zpty -t zsh_autosuggest_pty &>/dev/null; echo $?') - wait_for { session.content }.to end_with("\n0") - end - - context 'when ZSH_AUTOSUGGEST_ASYNC_PTY_NAME is set' do - let(:options) { super() + ['ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=foo_pty'] } - - it 'is created with the specified name' do - session.run_command('zpty -t foo_pty &>/dev/null; echo $?') - wait_for { session.content }.to end_with("\n0") - end - end - end -end diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index f91ee6c..2d9468f 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -18,10 +18,6 @@ class TerminalSession tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'") end - def zsh_version - @zsh_version ||= Gem::Version.new(`#{ZSH_BIN} -c 'echo -n $ZSH_VERSION'`) - end - def tmux_socket_name @tmux_socket_name ||= SecureRandom.hex(6) end diff --git a/src/async.zsh b/src/async.zsh index 62e3329..8111496 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -3,107 +3,33 @@ # Async # #--------------------------------------------------------------------# -# Zpty process is spawned running this function -_zsh_autosuggest_async_server() { - emulate -R zsh - - # 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 add any extra carriage returns - stty -onlcr - - # Don't translate carriage returns to newlines - stty -icrnl - - # Silence any error messages - exec 2>/dev/null - - local last_pid - - while IFS='' read -r -d $'\0' query; do - # Kill last bg process - kill -KILL $last_pid &>/dev/null +_zsh_autosuggest_async_request() { + typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD - # Run suggestion search in the background - ( - local suggestion - _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query" - echo -n -E "$suggestion"$'\0' - ) & + # Close the last fd to invalidate old suggestions + if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then + exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + fi - last_pid=$! - done -} + # Fork a process to fetch a suggestion and open a pipe to read from it + exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( + local suggestion + _zsh_autosuggest_fetch_suggestion "$1" + echo -nE "$suggestion" + ) -_zsh_autosuggest_async_request() { - # Write the query to the zpty process to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' + # When the fd is readable, call the response handler + zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response } -# Called when new data is ready to be read from the pty +# Called when new data is ready to be read from the pipe # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { - setopt LOCAL_OPTIONS EXTENDED_GLOB - - local suggestion - - zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle autosuggest-suggest -- "${suggestion%%$'\0'##}" -} - -_zsh_autosuggest_async_pty_create() { - # With newer versions of zsh, REPLY stores the fd to read from - typeset -h REPLY - - # If we won't get a fd back from zpty, try to guess it - if (( ! $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD )); then - integer -l zptyfd - exec {zptyfd}>&1 # Open a new file descriptor (above 10). - exec {zptyfd}>&- # Close it so it's free to be used by zpty. - fi - - # Fork a zpty process running the server function - zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server - - # Store the fd so we can remove the handler later - if (( REPLY )); then - _ZSH_AUTOSUGGEST_PTY_FD=$REPLY - else - _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd - fi - - # Set up input handler from the zpty - zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response -} - -_zsh_autosuggest_async_pty_destroy() { - # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null - - # Destroy the zpty - zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null -} - -_zsh_autosuggest_async_pty_recreate() { - _zsh_autosuggest_async_pty_destroy - _zsh_autosuggest_async_pty_create -} - -_zsh_autosuggest_async_start() { - typeset -g _ZSH_AUTOSUGGEST_PTY_FD - - _zsh_autosuggest_feature_detect_zpty_returns_fd - _zsh_autosuggest_async_pty_recreate + # Read everything from the fd and give it as a suggestion + zle autosuggest-suggest -- "$(<&$1)" - # We recreate the pty to get a fresh list of history events - add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate + # Remove the handler and close the fd + zle -F "$1" + exec {1}<&- } diff --git a/src/start.zsh b/src/start.zsh index 6f48ab6..a73ee3b 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -14,10 +14,6 @@ _zsh_autosuggest_start() { # zsh-syntax-highlighting widgets. This also allows modifications # to the widget list variables to take effect on the next precmd. add-zsh-hook precmd _zsh_autosuggest_bind_widgets - - if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then - _zsh_autosuggest_async_start - fi } # Start the autosuggestion widgets on the next precmd diff --git a/src/widgets.zsh b/src/widgets.zsh index 87bb62e..4cd8ca8 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -93,7 +93,7 @@ _zsh_autosuggest_modify() { # Fetch a new suggestion based on what's currently in the buffer _zsh_autosuggest_fetch() { - if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then + if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then _zsh_autosuggest_async_request "$BUFFER" else local suggestion diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 0a1f9c6..3106709 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -374,7 +374,7 @@ _zsh_autosuggest_modify() { # Fetch a new suggestion based on what's currently in the buffer _zsh_autosuggest_fetch() { - if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then + if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then _zsh_autosuggest_async_request "$BUFFER" else local suggestion @@ -581,109 +581,35 @@ _zsh_autosuggest_strategy_match_prev_cmd() { # Async # #--------------------------------------------------------------------# -# Zpty process is spawned running this function -_zsh_autosuggest_async_server() { - emulate -R zsh - - # 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 add any extra carriage returns - stty -onlcr - - # Don't translate carriage returns to newlines - stty -icrnl - - # Silence any error messages - exec 2>/dev/null - - local last_pid - - while IFS='' read -r -d $'\0' query; do - # Kill last bg process - kill -KILL $last_pid &>/dev/null +_zsh_autosuggest_async_request() { + typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD - # Run suggestion search in the background - ( - local suggestion - _zsh_autosuggest_strategy_$ZSH_AUTOSUGGEST_STRATEGY "$query" - echo -n -E "$suggestion"$'\0' - ) & + # Close the last fd to invalidate old suggestions + if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then + exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + fi - last_pid=$! - done -} + # Fork a process to fetch a suggestion and open a pipe to read from it + exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( + local suggestion + _zsh_autosuggest_fetch_suggestion "$1" + echo -nE "$suggestion" + ) -_zsh_autosuggest_async_request() { - # Write the query to the zpty process to fetch a suggestion - zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' + # When the fd is readable, call the response handler + zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response } -# Called when new data is ready to be read from the pty +# Called when new data is ready to be read from the pipe # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { - setopt LOCAL_OPTIONS EXTENDED_GLOB - - local suggestion - - zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null - zle autosuggest-suggest -- "${suggestion%%$'\0'##}" -} - -_zsh_autosuggest_async_pty_create() { - # With newer versions of zsh, REPLY stores the fd to read from - typeset -h REPLY + # Read everything from the fd and give it as a suggestion + zle autosuggest-suggest -- "$(<&$1)" - # If we won't get a fd back from zpty, try to guess it - if (( ! $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD )); then - integer -l zptyfd - exec {zptyfd}>&1 # Open a new file descriptor (above 10). - exec {zptyfd}>&- # Close it so it's free to be used by zpty. - fi - - # Fork a zpty process running the server function - zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server - - # Store the fd so we can remove the handler later - if (( REPLY )); then - _ZSH_AUTOSUGGEST_PTY_FD=$REPLY - else - _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd - fi - - # Set up input handler from the zpty - zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response -} - -_zsh_autosuggest_async_pty_destroy() { - # Remove the input handler - zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null - - # Destroy the zpty - zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null -} - -_zsh_autosuggest_async_pty_recreate() { - _zsh_autosuggest_async_pty_destroy - _zsh_autosuggest_async_pty_create -} - -_zsh_autosuggest_async_start() { - typeset -g _ZSH_AUTOSUGGEST_PTY_FD - - _zsh_autosuggest_feature_detect_zpty_returns_fd - _zsh_autosuggest_async_pty_recreate - - # We recreate the pty to get a fresh list of history events - add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate + # Remove the handler and close the fd + zle -F "$1" + exec {1}<&- } #--------------------------------------------------------------------# @@ -701,10 +627,6 @@ _zsh_autosuggest_start() { # zsh-syntax-highlighting widgets. This also allows modifications # to the widget list variables to take effect on the next precmd. add-zsh-hook precmd _zsh_autosuggest_bind_widgets - - if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then - _zsh_autosuggest_async_start - fi } # Start the autosuggestion widgets on the next precmd From 4a268da1df268fd29f3ff8f6e9309499656b473b Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Mon, 11 Jun 2018 02:39:00 -0600 Subject: [PATCH 18/47] Fix readme- async no longer uses zpty --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ad07d8..2636936 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ This can be useful when pasting large amount of text in the terminal, to avoid t ### Enable Asynchronous Mode -As of `v0.4.0`, suggestions can be fetched asynchronously using the `zsh/zpty` module. To enable this behavior, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable (it can be set to anything). +As of `v0.4.0`, suggestions can be fetched asynchronously. To enable this behavior, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable (it can be set to anything). ### Key Bindings From cd81522b30d394dda40cff7b4e38fa056c2a2ba9 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Tue, 12 Jun 2018 23:45:29 -0600 Subject: [PATCH 19/47] Attempt to kill async worker process when new request comes in See http://www.zsh.org/mla/users/2018/msg00432.html --- src/async.zsh | 18 ++++++++++++++++-- zsh-autosuggestions.zsh | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 8111496..ee39332 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -3,21 +3,35 @@ # Async # #--------------------------------------------------------------------# +zmodload zsh/system + _zsh_autosuggest_async_request() { - typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD + typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID - # Close the last fd to invalidate old suggestions + # If we've got a pending request, cancel it if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then + # Close the file descriptor exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + + # Assume the child process created a new process group and send + # TERM to the group to attempt to kill all descendent processes + kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null fi # Fork a process to fetch a suggestion and open a pipe to read from it exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( + # Tell parent process our pid + echo $sysparams[pid] + + # Fetch and print the suggestion local suggestion _zsh_autosuggest_fetch_suggestion "$1" echo -nE "$suggestion" ) + # Read the pid from the child process + read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD + # When the fd is readable, call the response handler zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 3106709..4c14666 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -581,21 +581,35 @@ _zsh_autosuggest_strategy_match_prev_cmd() { # Async # #--------------------------------------------------------------------# +zmodload zsh/system + _zsh_autosuggest_async_request() { - typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD + typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID - # Close the last fd to invalidate old suggestions + # If we've got a pending request, cancel it if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then + # Close the file descriptor exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + + # Assume the child process created a new process group and send + # TERM to the group to attempt to kill all descendent processes + kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null fi # Fork a process to fetch a suggestion and open a pipe to read from it exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( + # Tell parent process our pid + echo $sysparams[pid] + + # Fetch and print the suggestion local suggestion _zsh_autosuggest_fetch_suggestion "$1" echo -nE "$suggestion" ) + # Read the pid from the child process + read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD + # When the fd is readable, call the response handler zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response } From 43a011026ffd9c7e9dba4cf022922a4877f1d4e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20V=C3=A4th?= Date: Mon, 18 Jun 2018 19:47:27 +0200 Subject: [PATCH 20/47] Do not leak global variables REPLY and strategy https://github.com/zsh-users/zsh-autosuggestions/issues/341 --- src/fetch.zsh | 1 + src/strategies/completion.zsh | 2 +- zsh-autosuggestions.zsh | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fetch.zsh b/src/fetch.zsh index f94e66d..1517018 100644 --- a/src/fetch.zsh +++ b/src/fetch.zsh @@ -9,6 +9,7 @@ _zsh_autosuggest_fetch_suggestion() { typeset -g suggestion local -a strategies + local strategy # Ensure we are working with an array strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 847f22b..30542ee 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -88,7 +88,7 @@ _zsh_autosuggest_capture_buffer() { _zsh_autosuggest_capture_completion() { typeset -g completion - local line + local line REPLY # Zle will be inactive if we are in async mode if zle; then diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 8aad4c8..17e0d52 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -587,7 +587,7 @@ _zsh_autosuggest_capture_buffer() { _zsh_autosuggest_capture_completion() { typeset -g completion - local line + local line REPLY # Zle will be inactive if we are in async mode if zle; then @@ -716,6 +716,7 @@ _zsh_autosuggest_strategy_match_prev_cmd() { _zsh_autosuggest_fetch_suggestion() { typeset -g suggestion local -a strategies + local strategy # Ensure we are working with an array strategies=(${=ZSH_AUTOSUGGEST_STRATEGY}) From 1ec43c7291db3327391360e466907329b36c6770 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 29 Jun 2018 22:08:33 -0600 Subject: [PATCH 21/47] Fix error when single quote entered into buffer Error looked something like: ``` % echo 'f(zpty):8: unmatched ' _zsh_autosuggest_capture_completion:zpty:9: no such pty command: zsh_autosuggest_completion_pty _zsh_autosuggest_capture_completion:zpty:14: no such pty command: zsh_autosuggest_completion_pty _zsh_autosuggest_capture_completion:zpty:21: no such pty command: zsh_autosuggest_completion_pty ``` According to `man zshmodules`, the args to `zpty` are "concatenated with spaces between, then executed as a command, as if passed to the eval builtin." So we need to escape the `$` so that `$1` is passed to eval instead of the value of `$1`. --- src/strategies/completion.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 30542ee..8bbd7c8 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -94,7 +94,7 @@ _zsh_autosuggest_capture_completion() { 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 $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_buffer "\$1" zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t' fi diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 17e0d52..6f27260 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -593,7 +593,7 @@ _zsh_autosuggest_capture_completion() { 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 $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME _zsh_autosuggest_capture_buffer "\$1" zpty -w $ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME $'\t' fi From 7d968869e39df762a69b61510dc0b2207c3f9871 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 30 Jun 2018 15:03:14 -0600 Subject: [PATCH 22/47] Return if no completion found --- src/strategies/completion.zsh | 2 ++ zsh-autosuggestions.zsh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 8bbd7c8..422f4cc 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -117,6 +117,8 @@ _zsh_autosuggest_strategy_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 diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index cad2847..4b617cc 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -616,6 +616,8 @@ _zsh_autosuggest_strategy_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 From dad6be4d5ec5c5446b7d67ada8b81ccd7074a9d4 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 30 Jun 2018 15:04:56 -0600 Subject: [PATCH 23/47] Remove unused feature detection Not needed after move away from zpty for async --- Makefile | 1 - src/features.zsh | 19 ------------------- zsh-autosuggestions.zsh | 19 ------------------- 3 files changed, 39 deletions(-) delete mode 100644 src/features.zsh diff --git a/Makefile b/Makefile index b89ff04..93b8d94 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ SRC_FILES := \ $(SRC_DIR)/setup.zsh \ $(SRC_DIR)/config.zsh \ $(SRC_DIR)/util.zsh \ - $(SRC_DIR)/features.zsh \ $(SRC_DIR)/bind.zsh \ $(SRC_DIR)/highlight.zsh \ $(SRC_DIR)/widgets.zsh \ diff --git a/src/features.zsh b/src/features.zsh deleted file mode 100644 index 7a5248f..0000000 --- a/src/features.zsh +++ /dev/null @@ -1,19 +0,0 @@ - -#--------------------------------------------------------------------# -# Feature Detection # -#--------------------------------------------------------------------# - -_zsh_autosuggest_feature_detect_zpty_returns_fd() { - typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD - typeset -h REPLY - - zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' - - if (( REPLY )); then - _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 - else - _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 - fi - - zpty -d zsh_autosuggest_feature_detect -} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 4b617cc..83b2f7d 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -122,25 +122,6 @@ _zsh_autosuggest_escape_command() { echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" } -#--------------------------------------------------------------------# -# Feature Detection # -#--------------------------------------------------------------------# - -_zsh_autosuggest_feature_detect_zpty_returns_fd() { - typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD - typeset -h REPLY - - zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' - - if (( REPLY )); then - _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 - else - _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 - fi - - zpty -d zsh_autosuggest_feature_detect -} - #--------------------------------------------------------------------# # Widget Helpers # #--------------------------------------------------------------------# From 5529102afc618a3bff5fae75a969a1bb150fe270 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 30 Jun 2018 15:06:19 -0600 Subject: [PATCH 24/47] zpty module is only needed for `completion` strategy --- Makefile | 1 - README.md | 2 +- src/setup.zsh | 10 ---------- src/start.zsh | 1 + src/strategies/completion.zsh | 2 ++ zsh-autosuggestions.zsh | 13 +++---------- 6 files changed, 7 insertions(+), 22 deletions(-) delete mode 100644 src/setup.zsh diff --git a/Makefile b/Makefile index 93b8d94..f6d13a7 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ SRC_DIR := ./src SRC_FILES := \ - $(SRC_DIR)/setup.zsh \ $(SRC_DIR)/config.zsh \ $(SRC_DIR)/util.zsh \ $(SRC_DIR)/bind.zsh \ diff --git a/README.md b/README.md index 4507c6b..dc7d21f 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion - `history`: Chooses the most recent match from history. - `match_prev_cmd`: Like `history`, but chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). Note that this strategy won't work as expected with ZSH options that don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`. -- `completion`: (experimental) Chooses a suggestion based on what tab-completion would suggest. +- `completion`: (experimental) Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` and `zutil` modules) For example, setting `ZSH_AUTOSUGGEST_STRATEGY=(history completion)` will first try to find a suggestion from your history, but, if it can't find a match, will find a suggestion from the completion engine. diff --git a/src/setup.zsh b/src/setup.zsh deleted file mode 100644 index c74489f..0000000 --- a/src/setup.zsh +++ /dev/null @@ -1,10 +0,0 @@ - -#--------------------------------------------------------------------# -# Setup # -#--------------------------------------------------------------------# - -# Precmd hooks for initializing the library and starting pty's -autoload -Uz add-zsh-hook - -# Asynchronous suggestions are generated in a pty -zmodload zsh/zpty diff --git a/src/start.zsh b/src/start.zsh index a73ee3b..ff93fdf 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -17,4 +17,5 @@ _zsh_autosuggest_start() { } # Start the autosuggestion widgets on the next precmd +autoload -Uz add-zsh-hook add-zsh-hook precmd _zsh_autosuggest_start diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index 422f4cc..ff13b81 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -87,6 +87,8 @@ _zsh_autosuggest_capture_buffer() { } _zsh_autosuggest_capture_completion() { + zmodload -s zsh/zpty || return + typeset -g completion local line REPLY diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 83b2f7d..0052c69 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -25,16 +25,6 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -#--------------------------------------------------------------------# -# Setup # -#--------------------------------------------------------------------# - -# Precmd hooks for initializing the library and starting pty's -autoload -Uz add-zsh-hook - -# Asynchronous suggestions are generated in a pty -zmodload zsh/zpty - #--------------------------------------------------------------------# # Global Configuration Variables # #--------------------------------------------------------------------# @@ -567,6 +557,8 @@ _zsh_autosuggest_capture_buffer() { } _zsh_autosuggest_capture_completion() { + zmodload -s zsh/zpty || return + typeset -g completion local line REPLY @@ -780,4 +772,5 @@ _zsh_autosuggest_start() { } # Start the autosuggestion widgets on the next precmd +autoload -Uz add-zsh-hook add-zsh-hook precmd _zsh_autosuggest_start From c0315e96d84a9f992d236131783aaecc9117004f Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 30 Jun 2018 16:54:33 -0600 Subject: [PATCH 25/47] Don't use `-s` option to `zmodload` It is not available in zsh versions older than 5.3 --- src/strategies/completion.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh index ff13b81..5f71d98 100644 --- a/src/strategies/completion.zsh +++ b/src/strategies/completion.zsh @@ -87,7 +87,7 @@ _zsh_autosuggest_capture_buffer() { } _zsh_autosuggest_capture_completion() { - zmodload -s zsh/zpty || return + zmodload zsh/zpty 2>/dev/null || return typeset -g completion local line REPLY diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 0052c69..41c659f 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -557,7 +557,7 @@ _zsh_autosuggest_capture_buffer() { } _zsh_autosuggest_capture_completion() { - zmodload -s zsh/zpty || return + zmodload zsh/zpty 2>/dev/null || return typeset -g completion local line REPLY From e97d132b3bef126b3f9c8f61b8a0bbefb9b29a9a Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 30 Jun 2018 15:38:05 -0600 Subject: [PATCH 26/47] Add install directions for Antigen --- INSTALL.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index 945cec7..4c54637 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -16,6 +16,16 @@ 3. Start a new terminal session. +### Antigen + +1. Add the following to your `.zshrc`: + + ```sh + antigen bundle zsh-users/zsh-autosuggestions + ``` + +2. Start a new terminal session. + ### Oh My Zsh 1. Clone this repository into `$ZSH_CUSTOM/plugins` (by default `~/.oh-my-zsh/custom/plugins`) From 8ae0283c9034dbd3f479871c590d9b853b3bc84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20V=C3=A4th?= Date: Mon, 2 Jul 2018 19:26:06 +0200 Subject: [PATCH 27/47] Do not rely on implicit NULLCMD=cat --- src/async.zsh | 2 +- zsh-autosuggestions.zsh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index ee39332..eefdf4a 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -41,7 +41,7 @@ _zsh_autosuggest_async_request() { # Second arg will be passed in case of error _zsh_autosuggest_async_response() { # Read everything from the fd and give it as a suggestion - zle autosuggest-suggest -- "$(<&$1)" + zle autosuggest-suggest -- "$(cat <&$1)" # Remove the handler and close the fd zle -F "$1" diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 41c659f..0190895 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -747,7 +747,7 @@ _zsh_autosuggest_async_request() { # Second arg will be passed in case of error _zsh_autosuggest_async_response() { # Read everything from the fd and give it as a suggestion - zle autosuggest-suggest -- "$(<&$1)" + zle autosuggest-suggest -- "$(cat <&$1)" # Remove the handler and close the fd zle -F "$1" From 245f5d2ba2a6da3305f6612e68828e24a36b7345 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 30 Jun 2018 18:15:38 -0600 Subject: [PATCH 28/47] 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 - - # 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 + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) - # 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 + # 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]} - # Print the first match - echo -nE - $'\0'$IPREFIX$apre$hpre$__hits[1]$dsuf$hsuf$asuf$'\0' - } -} - -_zsh_autosuggest_capture_widget() { - _zsh_autosuggest_capture_setup - - zle complete-word + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' } -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 - - # 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 "$@" + local -a +h comppostfuncs + comppostfuncs=(_zsh_autosuggest_capture_postcompletion) - # Exit if no completion results - [[ -n $__hits ]] || return + # 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]} - # 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' - } -} - -_zsh_autosuggest_capture_widget() { - _zsh_autosuggest_capture_setup - - zle complete-word + # The completion has been added, print the buffer as the suggestion + echo -nE - $'\0'$BUFFER$'\0' } -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 29/47] 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 30/47] 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 | 22 ++++++++++++---------- zsh-autosuggestions.zsh | 22 ++++++++++++---------- 2 files changed, 24 insertions(+), 20 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' - - # 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 + { + # 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 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' - - # 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 + { + # 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 + } } #--------------------------------------------------------------------# From f1c3b98774bb52667fe3303ace477898aedd3b9b Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Mon, 2 Jul 2018 09:31:48 -0600 Subject: [PATCH 31/47] 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. From bd1fd9773838d28e565f9abdb31b1d367443fff3 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Mon, 2 Jul 2018 22:25:36 -0600 Subject: [PATCH 32/47] Cleanup unused async pty name --- src/config.zsh | 3 --- zsh-autosuggestions.zsh | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/config.zsh b/src/config.zsh index 4598191..4c489df 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -69,8 +69,5 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= -# Pty name for calculating autosuggestions asynchronously -ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_async_pty - # Pty name for capturing completions for completion suggestion strategy ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index be784b6..8169cc8 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -95,9 +95,6 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( # Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= -# Pty name for calculating autosuggestions asynchronously -ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_async_pty - # Pty name for capturing completions for completion suggestion strategy ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty From 0ee5b0a5c94d43cd41a3f4c23eaf31bcb8c96f4d Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Mon, 2 Jul 2018 22:28:16 -0600 Subject: [PATCH 33/47] Completion strategy no longer requires zutil module --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc7d21f..96bbcb4 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion - `history`: Chooses the most recent match from history. - `match_prev_cmd`: Like `history`, but chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). Note that this strategy won't work as expected with ZSH options that don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`. -- `completion`: (experimental) Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` and `zutil` modules) +- `completion`: (experimental) Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` module) For example, setting `ZSH_AUTOSUGGEST_STRATEGY=(history completion)` will first try to find a suggestion from your history, but, if it can't find a match, will find a suggestion from the completion engine. From 93877f6b765fc25f52d4104a8049783cdc38b418 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 13 Jul 2018 11:25:59 -0600 Subject: [PATCH 34/47] We also need to remove the handler when cancelling async request Should fix GitHub #353 --- src/async.zsh | 3 ++- zsh-autosuggestions.zsh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index eefdf4a..296c1c2 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -10,8 +10,9 @@ _zsh_autosuggest_async_request() { # If we've got a pending request, cancel it if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then - # Close the file descriptor + # Close the file descriptor and remove the handler exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD # Assume the child process created a new process group and send # TERM to the group to attempt to kill all descendent processes diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 8169cc8..3e7560a 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -689,8 +689,9 @@ _zsh_autosuggest_async_request() { # If we've got a pending request, cancel it if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then - # Close the file descriptor + # Close the file descriptor and remove the handler exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- + zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD # Assume the child process created a new process group and send # TERM to the group to attempt to kill all descendent processes From 88fe824ddfe1bb635f93e95098346725d864284a Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 13 Jul 2018 11:26:57 -0600 Subject: [PATCH 35/47] Add some error handling to async response handler We only want to read data in case of POLLIN or POLLHUP. Not POLLNVAL or select error. We always want to remove the handler, so it doesn't get called in an infinite loop when error is nval or err. In zsh source, see main zle event loop in zle_main.c raw_getbyte function. --- src/async.zsh | 12 ++++++++---- zsh-autosuggestions.zsh | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index 296c1c2..5c19a81 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -41,10 +41,14 @@ _zsh_autosuggest_async_request() { # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { - # Read everything from the fd and give it as a suggestion - zle autosuggest-suggest -- "$(cat <&$1)" + if [[ -z "$2" || "$2" == "hup" ]]; then + # Read everything from the fd and give it as a suggestion + zle autosuggest-suggest -- "$(cat <&$1)" - # Remove the handler and close the fd + # Close the fd + exec {1}<&- + fi + + # Always remove the handler zle -F "$1" - exec {1}<&- } diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 3e7560a..27c42c6 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -720,12 +720,16 @@ _zsh_autosuggest_async_request() { # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { - # Read everything from the fd and give it as a suggestion - zle autosuggest-suggest -- "$(cat <&$1)" + if [[ -z "$2" || "$2" == "hup" ]]; then + # Read everything from the fd and give it as a suggestion + zle autosuggest-suggest -- "$(cat <&$1)" - # Remove the handler and close the fd + # Close the fd + exec {1}<&- + fi + + # Always remove the handler zle -F "$1" - exec {1}<&- } #--------------------------------------------------------------------# From 7ab212490470870bdf86074becc10d9954bfd5a0 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 13 Jul 2018 21:48:25 -0600 Subject: [PATCH 36/47] Kill async process by id when job control disabled --- src/async.zsh | 15 ++++++++++++--- zsh-autosuggestions.zsh | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/async.zsh b/src/async.zsh index eefdf4a..814a185 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -13,9 +13,18 @@ _zsh_autosuggest_async_request() { # Close the file descriptor exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- - # Assume the child process created a new process group and send - # TERM to the group to attempt to kill all descendent processes - kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + # Zsh will make a new process group for the child process only if job + # control is enabled (MONITOR option) + if [[ -o MONITOR ]]; then + # Send the signal to the process group to kill any processes that may + # have been forked by the suggestion strategy + kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + else + # Kill just the child process since it wasn't placed in a new process + # group. If the suggestion strategy forked any child processes they may + # be orphaned and left behind. + kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + fi fi # Fork a process to fetch a suggestion and open a pipe to read from it diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 8169cc8..191cda1 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -692,9 +692,18 @@ _zsh_autosuggest_async_request() { # Close the file descriptor exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- - # Assume the child process created a new process group and send - # TERM to the group to attempt to kill all descendent processes - kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + # Zsh will make a new process group for the child process only if job + # control is enabled (MONITOR option) + if [[ -o MONITOR ]]; then + # Send the signal to the process group to kill any processes that may + # have been forked by the suggestion strategy + kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + else + # Kill just the child process since it wasn't placed in a new process + # group. If the suggestion strategy forked any child processes they may + # be orphaned and left behind. + kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null + fi fi # Fork a process to fetch a suggestion and open a pipe to read from it From 681ffc7b2891e97801a2104997ed8524fa2c4556 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 13 Jul 2018 22:16:53 -0600 Subject: [PATCH 37/47] Reset opts in some functions affected by GLOB_SUBST Should fix GitHub #334 --- src/bind.zsh | 4 +++- src/widgets.zsh | 4 ++++ zsh-autosuggestions.zsh | 8 +++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/bind.zsh b/src/bind.zsh index f538379..bb41ef8 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -74,7 +74,9 @@ _zsh_autosuggest_bind_widget() { # Map all configured widgets to the right autosuggest widgets _zsh_autosuggest_bind_widgets() { - local widget + emulate -L zsh + + local widget local ignore_widgets ignore_widgets=( diff --git a/src/widgets.zsh b/src/widgets.zsh index 746944d..3312579 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -37,6 +37,8 @@ _zsh_autosuggest_clear() { # Modify the buffer and get a new suggestion _zsh_autosuggest_modify() { + emulate -L zsh + local -i retval # Only available in zsh >= 5.4 @@ -104,6 +106,8 @@ _zsh_autosuggest_fetch() { # Offer a suggestion _zsh_autosuggest_suggest() { + emulate -L zsh + local suggestion="$1" if [[ -n "$suggestion" ]] && (( $#BUFFER )); then diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index f94859e..4b39dda 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -184,7 +184,9 @@ _zsh_autosuggest_bind_widget() { # Map all configured widgets to the right autosuggest widgets _zsh_autosuggest_bind_widgets() { - local widget + emulate -L zsh + + local widget local ignore_widgets ignore_widgets=( @@ -291,6 +293,8 @@ _zsh_autosuggest_clear() { # Modify the buffer and get a new suggestion _zsh_autosuggest_modify() { + emulate -L zsh + local -i retval # Only available in zsh >= 5.4 @@ -358,6 +362,8 @@ _zsh_autosuggest_fetch() { # Offer a suggestion _zsh_autosuggest_suggest() { + emulate -L zsh + local suggestion="$1" if [[ -n "$suggestion" ]] && (( $#BUFFER )); then From 4b28d92e01517ef944e93523594920c8c3752e29 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sat, 10 Nov 2018 13:56:31 -0700 Subject: [PATCH 38/47] Add `after_sourcing` hook for tests Is executed immediately after sourcing the plugin --- spec/options/strategy_spec.rb | 32 +++++++++++++++++++++----------- spec/spec_helper.rb | 2 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/spec/options/strategy_spec.rb b/spec/options/strategy_spec.rb index 378d01e..58562d0 100644 --- a/spec/options/strategy_spec.rb +++ b/spec/options/strategy_spec.rb @@ -3,7 +3,11 @@ describe 'a suggestion for a given prefix' do let(:foobar_strategy) { '_zsh_autosuggest_strategy_foobar() { [[ "foobar baz" = $1* ]] && suggestion="foobar baz" }' } let(:foobaz_strategy) { '_zsh_autosuggest_strategy_foobaz() { [[ "foobaz bar" = $1* ]] && suggestion="foobaz bar" }' } - let(:options) { [ history_strategy ] } + let(:after_sourcing) do + -> do + session.run_command(history_strategy) + end + end it 'by default is determined by calling the `history` strategy function' do session.send_string('h') @@ -11,11 +15,14 @@ describe 'a suggestion for a given prefix' do end context 'when ZSH_AUTOSUGGEST_STRATEGY is set to an array' do - let(:options) { [ - foobar_strategy, - foobaz_strategy, - 'ZSH_AUTOSUGGEST_STRATEGY=(foobar foobaz)' - ] } + let(:after_sourcing) do + -> do + session. + run_command(foobar_strategy). + run_command(foobaz_strategy). + run_command('ZSH_AUTOSUGGEST_STRATEGY=(foobar foobaz)') + end + end it 'is determined by the first strategy function to return a suggestion' do session.send_string('foo') @@ -27,11 +34,14 @@ describe 'a suggestion for a given prefix' do end context 'when ZSH_AUTOSUGGEST_STRATEGY is set to a string' do - let(:options) { [ - foobar_strategy, - foobaz_strategy, - 'ZSH_AUTOSUGGEST_STRATEGY="foobar foobaz"' - ] } + let(:after_sourcing) do + -> do + session. + run_command(foobar_strategy). + run_command(foobaz_strategy). + run_command('ZSH_AUTOSUGGEST_STRATEGY="foobar foobaz"') + end + end it 'is determined by the first strategy function to return a suggestion' do session.send_string('foo') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 64115d2..bfcb706 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,12 +6,14 @@ RSpec.shared_context 'terminal session' do let(:term_opts) { {} } let(:session) { TerminalSession.new(term_opts) } let(:before_sourcing) { -> {} } + let(:after_sourcing) { -> {} } let(:options) { [] } around do |example| before_sourcing.call session.run_command((['source zsh-autosuggestions.zsh'] + options).join('; ')) + after_sourcing.call session.clear_screen example.run From e61442161e8978fa2ed18c57232ade819b5139b5 Mon Sep 17 00:00:00 2001 From: Eric Nielsen Date: Mon, 15 Oct 2018 16:37:02 -0500 Subject: [PATCH 39/47] Don't overwrite config with default values otherwise users are obliged to set the config values *after* sourcing the plugin. They're not able to do it before. Also, re-sourcing the plugin will reset the values to the defaults again. See zimfw/zimfw#301 Fixes #335 --- README.md | 2 +- spec/spec_helper.rb | 4 ++-- src/config.zsh | 22 +++++++++++----------- zsh-autosuggestions.zsh | 22 +++++++++++----------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 96bbcb4..65274e8 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ If you invoke the `forward-word` widget, it will partially accept the suggestion ## Configuration -You may want to override the default global config variables after sourcing the plugin. Default values of these variables can be found [here](src/config.zsh). +You may want to override the default global config variables. Default values of these variables can be found [here](src/config.zsh). **Note:** If you are using Oh My Zsh, you can put this configuration in a file in the `$ZSH_CUSTOM` directory. See their comments on [overriding internals](https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals). diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bfcb706..abea917 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,8 +11,8 @@ RSpec.shared_context 'terminal session' do around do |example| before_sourcing.call - - session.run_command((['source zsh-autosuggestions.zsh'] + options).join('; ')) + session.run_command(options.join('; ')) + session.run_command('source zsh-autosuggestions.zsh') after_sourcing.call session.clear_screen diff --git a/src/config.zsh b/src/config.zsh index 4c489df..dc01b7c 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -6,17 +6,17 @@ # Color to use when highlighting suggestion # Uses format of `region_highlight` # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets -ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' +: ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'} # Prefix to use when saving original versions of bound widgets -ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- +: ${ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-} # Strategies to use to fetch a suggestion # Will try each strategy in order until a suggestion is returned -ZSH_AUTOSUGGEST_STRATEGY=(history) +(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && ZSH_AUTOSUGGEST_STRATEGY=(history) # Widgets that clear the suggestion -ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( history-search-forward history-search-backward history-beginning-search-forward @@ -31,7 +31,7 @@ ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( ) # Widgets that accept the entire suggestion -ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( forward-char end-of-line vi-forward-char @@ -40,11 +40,11 @@ ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( ) # Widgets that accept the entire suggestion and execute it -ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( ) # Widgets that accept the suggestion as far as the cursor moves -ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( forward-word emacs-forward-word vi-forward-word @@ -56,7 +56,7 @@ ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( ) # Widgets that should be ignored (globbing supported but must be escaped) -ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( orig-\* beep run-help @@ -66,8 +66,8 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( yank-pop ) -# Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. -ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= +# Max size of buffer to trigger autosuggestion. Leave null for no upper bound. +: ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} # Pty name for capturing completions for completion suggestion strategy -ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty +: ${ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 4b39dda..5127015 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -32,17 +32,17 @@ # Color to use when highlighting suggestion # Uses format of `region_highlight` # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets -ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' +: ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'} # Prefix to use when saving original versions of bound widgets -ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- +: ${ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-} # Strategies to use to fetch a suggestion # Will try each strategy in order until a suggestion is returned -ZSH_AUTOSUGGEST_STRATEGY=(history) +(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && ZSH_AUTOSUGGEST_STRATEGY=(history) # Widgets that clear the suggestion -ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( history-search-forward history-search-backward history-beginning-search-forward @@ -57,7 +57,7 @@ ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( ) # Widgets that accept the entire suggestion -ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( forward-char end-of-line vi-forward-char @@ -66,11 +66,11 @@ ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( ) # Widgets that accept the entire suggestion and execute it -ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( ) # Widgets that accept the suggestion as far as the cursor moves -ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( forward-word emacs-forward-word vi-forward-word @@ -82,7 +82,7 @@ ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( ) # Widgets that should be ignored (globbing supported but must be escaped) -ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( +(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( orig-\* beep run-help @@ -92,11 +92,11 @@ ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( yank-pop ) -# Max size of buffer to trigger autosuggestion. Leave undefined for no upper bound. -ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= +# Max size of buffer to trigger autosuggestion. Leave null for no upper bound. +: ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} # Pty name for capturing completions for completion suggestion strategy -ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty +: ${ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty} #--------------------------------------------------------------------# # Utility Functions # From 41657e35659864f21b24d3179e8890857d243e88 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 16 Dec 2018 20:43:25 -0700 Subject: [PATCH 40/47] Revert async process substitution & completion strategy They're not quite ready. Keep them on a feature branch for now. --- Makefile | 2 + README.md | 9 +- spec/async_spec.rb | 53 +++++ spec/integrations/client_zpty_spec.rb | 12 +- spec/options/async_zpty_name_spec.rb | 19 ++ spec/strategies/completion_spec.rb | 26 --- spec/terminal_session.rb | 4 + src/async.zsh | 134 +++++++++---- src/config.zsh | 4 +- src/features.zsh | 19 ++ src/setup.zsh | 10 + src/start.zsh | 5 +- src/strategies/completion.zsh | 104 ---------- src/widgets.zsh | 2 +- zsh-autosuggestions.zsh | 278 ++++++++++++-------------- 15 files changed, 337 insertions(+), 344 deletions(-) create mode 100644 spec/options/async_zpty_name_spec.rb delete mode 100644 spec/strategies/completion_spec.rb create mode 100644 src/features.zsh create mode 100644 src/setup.zsh delete mode 100644 src/strategies/completion.zsh diff --git a/Makefile b/Makefile index f6d13a7..b89ff04 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ SRC_DIR := ./src SRC_FILES := \ + $(SRC_DIR)/setup.zsh \ $(SRC_DIR)/config.zsh \ $(SRC_DIR)/util.zsh \ + $(SRC_DIR)/features.zsh \ $(SRC_DIR)/bind.zsh \ $(SRC_DIR)/highlight.zsh \ $(SRC_DIR)/widgets.zsh \ diff --git a/README.md b/README.md index 59ee831..7e3e674 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ _[Fish](http://fishshell.com/)-like fast/unobtrusive autosuggestions for zsh._ -It suggests commands as you type. +It suggests commands as you type, based on command history. Requirements: Zsh v4.3.11 or later @@ -39,13 +39,10 @@ Set `ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE` to configure the style that the suggestion ### Suggestion Strategy -`ZSH_AUTOSUGGEST_STRATEGY` is an array that specifies how suggestions should be generated. The strategies in the array are tried successively until a suggestion is found. There are currently three built-in strategies to choose from: +`ZSH_AUTOSUGGEST_STRATEGY` is an array that specifies how suggestions should be generated. The strategies in the array are tried successively until a suggestion is found. There are currently two built-in strategies to choose from: - `history`: Chooses the most recent match from history. - `match_prev_cmd`: Like `history`, but chooses the most recent match whose preceding history item matches the most recently executed command ([more info](src/strategies/match_prev_cmd.zsh)). Note that this strategy won't work as expected with ZSH options that don't preserve the history order such as `HIST_IGNORE_ALL_DUPS` or `HIST_EXPIRE_DUPS_FIRST`. -- `completion`: (experimental) Chooses a suggestion based on what tab-completion would suggest. (requires `zpty` module) - -For example, setting `ZSH_AUTOSUGGEST_STRATEGY=(history completion)` will first try to find a suggestion from your history, but, if it can't find a match, will find a suggestion from the completion engine. ### Widget Mapping @@ -70,7 +67,7 @@ This can be useful when pasting large amount of text in the terminal, to avoid t ### Enable Asynchronous Mode -As of `v0.4.0`, suggestions can be fetched asynchronously. To enable this behavior, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable (it can be set to anything). +As of `v0.4.0`, suggestions can be fetched asynchronously using the `zsh/zpty` module. To enable this behavior, set the `ZSH_AUTOSUGGEST_USE_ASYNC` variable (it can be set to anything). ### Key Bindings diff --git a/spec/async_spec.rb b/spec/async_spec.rb index 9405fb2..152adde 100644 --- a/spec/async_spec.rb +++ b/spec/async_spec.rb @@ -1,4 +1,8 @@ context 'with asynchronous suggestions enabled' do + before do + skip 'Async mode not supported below v5.0.8' if session.zsh_version < Gem::Version.new('5.0.8') + end + let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } describe '`up-line-or-beginning-search`' do @@ -26,6 +30,55 @@ context 'with asynchronous suggestions enabled' do end end end + + it 'should not add extra carriage returns before newlines' do + session. + send_string('echo "'). + send_keys('escape'). + send_keys('enter'). + send_string('"'). + send_keys('enter') + + session.clear_screen + + session.send_string('echo') + wait_for { session.content }.to eq("echo \"\n\"") + end + + it 'should treat carriage returns and newlines as separate characters' do + session. + send_string('echo "'). + send_keys('C-v'). + send_keys('enter'). + send_string('foo"'). + send_keys('enter') + + session. + send_string('echo "'). + send_keys('control'). + send_keys('enter'). + send_string('bar"'). + send_keys('enter') + + session.clear_screen + + session. + send_string('echo "'). + send_keys('C-v'). + send_keys('enter') + + wait_for { session.content }.to eq('echo "^Mfoo"') + end + + describe 'exiting a subshell' do + it 'should not cause error messages to be printed' do + session.run_command('$(exit)') + + sleep 1 + + expect(session.content).to eq('$(exit)') + end + end end diff --git a/spec/integrations/client_zpty_spec.rb b/spec/integrations/client_zpty_spec.rb index b8abb37..8f1550e 100644 --- a/spec/integrations/client_zpty_spec.rb +++ b/spec/integrations/client_zpty_spec.rb @@ -1,14 +1,10 @@ describe 'a running zpty command' do let(:before_sourcing) { -> { session.run_command('zmodload zsh/zpty && zpty -b kitty cat') } } - context 'when using `completion` strategy' do - let(:options) { ["ZSH_AUTOSUGGEST_STRATEGY=completion"] } + it 'is not affected by running zsh-autosuggestions' do + sleep 1 # Give a little time for precmd hooks to run + session.run_command('zpty -t kitty; echo $?') - it 'is not affected' do - session.send_keys('a').send_keys('C-h') - session.run_command('zpty -t kitty; echo $?') - - wait_for { session.content }.to end_with("\n0") - end + wait_for { session.content }.to end_with("\n0") end end diff --git a/spec/options/async_zpty_name_spec.rb b/spec/options/async_zpty_name_spec.rb new file mode 100644 index 0000000..407ee70 --- /dev/null +++ b/spec/options/async_zpty_name_spec.rb @@ -0,0 +1,19 @@ +context 'when async suggestions are enabled' do + let(:options) { ["ZSH_AUTOSUGGEST_USE_ASYNC="] } + + describe 'the zpty for async suggestions' do + it 'is created with the default name' do + session.run_command('zpty -t zsh_autosuggest_pty &>/dev/null; echo $?') + wait_for { session.content }.to end_with("\n0") + end + + context 'when ZSH_AUTOSUGGEST_ASYNC_PTY_NAME is set' do + let(:options) { super() + ['ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=foo_pty'] } + + it 'is created with the specified name' do + session.run_command('zpty -t foo_pty &>/dev/null; echo $?') + wait_for { session.content }.to end_with("\n0") + end + end + end +end diff --git a/spec/strategies/completion_spec.rb b/spec/strategies/completion_spec.rb deleted file mode 100644 index bd2c72d..0000000 --- a/spec/strategies/completion_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -describe 'the `completion` suggestion strategy' do - let(:options) { ['ZSH_AUTOSUGGEST_STRATEGY=completion'] } - let(:before_sourcing) do - -> do - session. - run_command('autoload compinit && compinit'). - run_command('_foo() { compadd bar }'). - run_command('compdef _foo baz') - end - end - - it 'suggests the first completion result' do - session.send_string('baz ') - wait_for { session.content }.to eq('baz bar') - end - - context 'when async mode is enabled' do - let(:options) { ['ZSH_AUTOSUGGEST_USE_ASYNC=true', 'ZSH_AUTOSUGGEST_STRATEGY=completion'] } - - it 'suggests the first completion result' do - session.send_string('baz ') - wait_for { session.content }.to eq('baz bar') - end - end -end - diff --git a/spec/terminal_session.rb b/spec/terminal_session.rb index 2d9468f..f91ee6c 100644 --- a/spec/terminal_session.rb +++ b/spec/terminal_session.rb @@ -18,6 +18,10 @@ class TerminalSession tmux_command("new-session -d -x #{opts[:width]} -y #{opts[:height]} '#{cmd}'") end + def zsh_version + @zsh_version ||= Gem::Version.new(`#{ZSH_BIN} -c 'echo -n $ZSH_VERSION'`) + end + def tmux_socket_name @tmux_socket_name ||= SecureRandom.hex(6) end diff --git a/src/async.zsh b/src/async.zsh index f1877d3..dd54c24 100644 --- a/src/async.zsh +++ b/src/async.zsh @@ -3,61 +3,107 @@ # Async # #--------------------------------------------------------------------# -zmodload zsh/system +# Zpty process is spawned running this function +_zsh_autosuggest_async_server() { + emulate -R zsh -_zsh_autosuggest_async_request() { - typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID - - # If we've got a pending request, cancel it - if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then - # Close the file descriptor and remove the handler - exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- - zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD - - # Zsh will make a new process group for the child process only if job - # control is enabled (MONITOR option) - if [[ -o MONITOR ]]; then - # Send the signal to the process group to kill any processes that may - # have been forked by the suggestion strategy - kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null - else - # Kill just the child process since it wasn't placed in a new process - # group. If the suggestion strategy forked any child processes they may - # be orphaned and left behind. - kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null - fi - 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 + } + + # Don't add any extra carriage returns + stty -onlcr + + # Don't translate carriage returns to newlines + stty -icrnl + + # Silence any error messages + exec 2>/dev/null + + local last_pid - # Fork a process to fetch a suggestion and open a pipe to read from it - exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( - # Tell parent process our pid - echo $sysparams[pid] + while IFS='' read -r -d $'\0' query; do + # Kill last bg process + kill -KILL $last_pid &>/dev/null - # Fetch and print the suggestion - local suggestion - _zsh_autosuggest_fetch_suggestion "$1" - echo -nE "$suggestion" - ) + # Run suggestion search in the background + ( + local suggestion + _zsh_autosuggest_fetch_suggestion "$query" + echo -n -E "$suggestion"$'\0' + ) & - # Read the pid from the child process - read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD + last_pid=$! + done +} - # When the fd is readable, call the response handler - zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response +_zsh_autosuggest_async_request() { + # Write the query to the zpty process to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' } -# Called when new data is ready to be read from the pipe +# Called when new data is ready to be read from the pty # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { - if [[ -z "$2" || "$2" == "hup" ]]; then - # Read everything from the fd and give it as a suggestion - zle autosuggest-suggest -- "$(cat <&$1)" + setopt LOCAL_OPTIONS EXTENDED_GLOB + + local suggestion + + zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zle autosuggest-suggest -- "${suggestion%%$'\0'##}" +} + +_zsh_autosuggest_async_pty_create() { + # With newer versions of zsh, REPLY stores the fd to read from + typeset -h REPLY - # Close the fd - exec {1}<&- + # If we won't get a fd back from zpty, try to guess it + if (( ! $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD )); then + integer -l zptyfd + exec {zptyfd}>&1 # Open a new file descriptor (above 10). + exec {zptyfd}>&- # Close it so it's free to be used by zpty. fi - # Always remove the handler - zle -F "$1" + # Fork a zpty process running the server function + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server + + # Store the fd so we can remove the handler later + if (( REPLY )); then + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + else + _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd + fi + + # Set up input handler from the zpty + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response +} + +_zsh_autosuggest_async_pty_destroy() { + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null + + # Destroy the zpty + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null +} + +_zsh_autosuggest_async_pty_recreate() { + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_pty_create +} + +_zsh_autosuggest_async_start() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + _zsh_autosuggest_feature_detect_zpty_returns_fd + _zsh_autosuggest_async_pty_recreate + + # We recreate the pty to get a fresh list of history events + add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate } diff --git a/src/config.zsh b/src/config.zsh index dc01b7c..9ac1484 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -69,5 +69,5 @@ # Max size of buffer to trigger autosuggestion. Leave null for no upper bound. : ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} -# Pty name for capturing completions for completion suggestion strategy -: ${ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty} +# Pty name for calculating autosuggestions asynchronously +: ${ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty} diff --git a/src/features.zsh b/src/features.zsh new file mode 100644 index 0000000..7a5248f --- /dev/null +++ b/src/features.zsh @@ -0,0 +1,19 @@ + +#--------------------------------------------------------------------# +# Feature Detection # +#--------------------------------------------------------------------# + +_zsh_autosuggest_feature_detect_zpty_returns_fd() { + typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD + typeset -h REPLY + + zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' + + if (( REPLY )); then + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 + else + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 + fi + + zpty -d zsh_autosuggest_feature_detect +} diff --git a/src/setup.zsh b/src/setup.zsh new file mode 100644 index 0000000..c74489f --- /dev/null +++ b/src/setup.zsh @@ -0,0 +1,10 @@ + +#--------------------------------------------------------------------# +# Setup # +#--------------------------------------------------------------------# + +# Precmd hooks for initializing the library and starting pty's +autoload -Uz add-zsh-hook + +# Asynchronous suggestions are generated in a pty +zmodload zsh/zpty diff --git a/src/start.zsh b/src/start.zsh index ff93fdf..6f48ab6 100644 --- a/src/start.zsh +++ b/src/start.zsh @@ -14,8 +14,11 @@ _zsh_autosuggest_start() { # zsh-syntax-highlighting widgets. This also allows modifications # to the widget list variables to take effect on the next precmd. add-zsh-hook precmd _zsh_autosuggest_bind_widgets + + if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then + _zsh_autosuggest_async_start + fi } # Start the autosuggestion widgets on the next precmd -autoload -Uz add-zsh-hook add-zsh-hook precmd _zsh_autosuggest_start diff --git a/src/strategies/completion.zsh b/src/strategies/completion.zsh deleted file mode 100644 index 7517822..0000000 --- a/src/strategies/completion.zsh +++ /dev/null @@ -1,104 +0,0 @@ - -#--------------------------------------------------------------------# -# Completion Suggestion Strategy # -#--------------------------------------------------------------------# -# 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() { - # 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 - autoload +X _complete - functions[_original_complete]=$functions[_complete] - _complete () { - unset 'compstate[vared]' - _original_complete "$@" - } - - # Open zle with buffer set so we can capture completions for it - vared 1 -} - -_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 _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/src/widgets.zsh b/src/widgets.zsh index 3312579..6a2be4a 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -95,7 +95,7 @@ _zsh_autosuggest_modify() { # Fetch a new suggestion based on what's currently in the buffer _zsh_autosuggest_fetch() { - if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then + if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then _zsh_autosuggest_async_request "$BUFFER" else local suggestion diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 5127015..1407559 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -25,6 +25,16 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. +#--------------------------------------------------------------------# +# Setup # +#--------------------------------------------------------------------# + +# Precmd hooks for initializing the library and starting pty's +autoload -Uz add-zsh-hook + +# Asynchronous suggestions are generated in a pty +zmodload zsh/zpty + #--------------------------------------------------------------------# # Global Configuration Variables # #--------------------------------------------------------------------# @@ -95,8 +105,8 @@ # Max size of buffer to trigger autosuggestion. Leave null for no upper bound. : ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} -# Pty name for capturing completions for completion suggestion strategy -: ${ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty} +# Pty name for calculating autosuggestions asynchronously +: ${ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty} #--------------------------------------------------------------------# # Utility Functions # @@ -109,6 +119,25 @@ _zsh_autosuggest_escape_command() { echo -E "${1//(#m)[\"\'\\()\[\]|*?~]/\\$MATCH}" } +#--------------------------------------------------------------------# +# Feature Detection # +#--------------------------------------------------------------------# + +_zsh_autosuggest_feature_detect_zpty_returns_fd() { + typeset -g _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD + typeset -h REPLY + + zpty zsh_autosuggest_feature_detect '{ zshexit() { kill -KILL $$; sleep 1 } }' + + if (( REPLY )); then + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=1 + else + _ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD=0 + fi + + zpty -d zsh_autosuggest_feature_detect +} + #--------------------------------------------------------------------# # Widget Helpers # #--------------------------------------------------------------------# @@ -351,7 +380,7 @@ _zsh_autosuggest_modify() { # Fetch a new suggestion based on what's currently in the buffer _zsh_autosuggest_fetch() { - if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then + if zpty -t "$ZSH_AUTOSUGGEST_ASYNC_PTY_NAME" &>/dev/null; then _zsh_autosuggest_async_request "$BUFFER" else local suggestion @@ -472,110 +501,6 @@ zle -N autosuggest-enable _zsh_autosuggest_widget_enable zle -N autosuggest-disable _zsh_autosuggest_widget_disable zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle -#--------------------------------------------------------------------# -# Completion Suggestion Strategy # -#--------------------------------------------------------------------# -# 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() { - # 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 - autoload +X _complete - functions[_original_complete]=$functions[_complete] - _complete () { - unset 'compstate[vared]' - _original_complete "$@" - } - - # Open zle with buffer set so we can capture completions for it - vared 1 -} - -_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 _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 - } -} - #--------------------------------------------------------------------# # History Suggestion Strategy # #--------------------------------------------------------------------# @@ -688,63 +613,109 @@ _zsh_autosuggest_fetch_suggestion() { # Async # #--------------------------------------------------------------------# -zmodload zsh/system +# Zpty process is spawned running this function +_zsh_autosuggest_async_server() { + emulate -R zsh -_zsh_autosuggest_async_request() { - typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID - - # If we've got a pending request, cancel it - if [[ -n "$_ZSH_AUTOSUGGEST_ASYNC_FD" ]] && { true <&$_ZSH_AUTOSUGGEST_ASYNC_FD } 2>/dev/null; then - # Close the file descriptor and remove the handler - exec {_ZSH_AUTOSUGGEST_ASYNC_FD}<&- - zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD - - # Zsh will make a new process group for the child process only if job - # control is enabled (MONITOR option) - if [[ -o MONITOR ]]; then - # Send the signal to the process group to kill any processes that may - # have been forked by the suggestion strategy - kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null - else - # Kill just the child process since it wasn't placed in a new process - # group. If the suggestion strategy forked any child processes they may - # be orphaned and left behind. - kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null - fi - 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 + } - # Fork a process to fetch a suggestion and open a pipe to read from it - exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <( - # Tell parent process our pid - echo $sysparams[pid] + # Don't add any extra carriage returns + stty -onlcr - # Fetch and print the suggestion - local suggestion - _zsh_autosuggest_fetch_suggestion "$1" - echo -nE "$suggestion" - ) + # Don't translate carriage returns to newlines + stty -icrnl + + # Silence any error messages + exec 2>/dev/null + + local last_pid + + while IFS='' read -r -d $'\0' query; do + # Kill last bg process + kill -KILL $last_pid &>/dev/null + + # Run suggestion search in the background + ( + local suggestion + _zsh_autosuggest_fetch_suggestion "$query" + echo -n -E "$suggestion"$'\0' + ) & - # Read the pid from the child process - read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD + last_pid=$! + done +} - # When the fd is readable, call the response handler - zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response +_zsh_autosuggest_async_request() { + # Write the query to the zpty process to fetch a suggestion + zpty -w -n $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME "${1}"$'\0' } -# Called when new data is ready to be read from the pipe +# Called when new data is ready to be read from the pty # First arg will be fd ready for reading # Second arg will be passed in case of error _zsh_autosuggest_async_response() { - if [[ -z "$2" || "$2" == "hup" ]]; then - # Read everything from the fd and give it as a suggestion - zle autosuggest-suggest -- "$(cat <&$1)" + setopt LOCAL_OPTIONS EXTENDED_GLOB + + local suggestion + + zpty -rt $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME suggestion '*'$'\0' 2>/dev/null + zle autosuggest-suggest -- "${suggestion%%$'\0'##}" +} - # Close the fd - exec {1}<&- +_zsh_autosuggest_async_pty_create() { + # With newer versions of zsh, REPLY stores the fd to read from + typeset -h REPLY + + # If we won't get a fd back from zpty, try to guess it + if (( ! $_ZSH_AUTOSUGGEST_ZPTY_RETURNS_FD )); then + integer -l zptyfd + exec {zptyfd}>&1 # Open a new file descriptor (above 10). + exec {zptyfd}>&- # Close it so it's free to be used by zpty. + fi + + # Fork a zpty process running the server function + zpty -b $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME _zsh_autosuggest_async_server + + # Store the fd so we can remove the handler later + if (( REPLY )); then + _ZSH_AUTOSUGGEST_PTY_FD=$REPLY + else + _ZSH_AUTOSUGGEST_PTY_FD=$zptyfd fi - # Always remove the handler - zle -F "$1" + # Set up input handler from the zpty + zle -F $_ZSH_AUTOSUGGEST_PTY_FD _zsh_autosuggest_async_response +} + +_zsh_autosuggest_async_pty_destroy() { + # Remove the input handler + zle -F $_ZSH_AUTOSUGGEST_PTY_FD &>/dev/null + + # Destroy the zpty + zpty -d $ZSH_AUTOSUGGEST_ASYNC_PTY_NAME &>/dev/null +} + +_zsh_autosuggest_async_pty_recreate() { + _zsh_autosuggest_async_pty_destroy + _zsh_autosuggest_async_pty_create +} + +_zsh_autosuggest_async_start() { + typeset -g _ZSH_AUTOSUGGEST_PTY_FD + + _zsh_autosuggest_feature_detect_zpty_returns_fd + _zsh_autosuggest_async_pty_recreate + + # We recreate the pty to get a fresh list of history events + add-zsh-hook precmd _zsh_autosuggest_async_pty_recreate } #--------------------------------------------------------------------# @@ -762,8 +733,11 @@ _zsh_autosuggest_start() { # zsh-syntax-highlighting widgets. This also allows modifications # to the widget list variables to take effect on the next precmd. add-zsh-hook precmd _zsh_autosuggest_bind_widgets + + if [[ -n "${ZSH_AUTOSUGGEST_USE_ASYNC+x}" ]]; then + _zsh_autosuggest_async_start + fi } # Start the autosuggestion widgets on the next precmd -autoload -Uz add-zsh-hook add-zsh-hook precmd _zsh_autosuggest_start From e937e89267afa1110b99e2fab0a4c42c2ea6dda7 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Sun, 9 Dec 2018 10:14:19 -0700 Subject: [PATCH 41/47] Respect user's set options when running original widget Fixes GitHub #379 --- spec/integrations/auto_cd_spec.rb | 14 ++++++++++++++ spec/integrations/glob_subst_spec.rb | 12 ++++++++++++ src/widgets.zsh | 4 ++-- zsh-autosuggestions.zsh | 4 ++-- 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 spec/integrations/auto_cd_spec.rb create mode 100644 spec/integrations/glob_subst_spec.rb diff --git a/spec/integrations/auto_cd_spec.rb b/spec/integrations/auto_cd_spec.rb new file mode 100644 index 0000000..94bd24b --- /dev/null +++ b/spec/integrations/auto_cd_spec.rb @@ -0,0 +1,14 @@ +describe 'with `AUTO_CD` option set' do + let(:after_sourcing) do + -> { + session.run_command('setopt AUTO_CD') + session.run_command('autoload compinit && compinit') + } + end + + it 'directory names are still completed' do + session.send_string('sr') + session.send_keys('C-i') + wait_for { session.content }.to eq('src/') + end +end diff --git a/spec/integrations/glob_subst_spec.rb b/spec/integrations/glob_subst_spec.rb new file mode 100644 index 0000000..c3dd671 --- /dev/null +++ b/spec/integrations/glob_subst_spec.rb @@ -0,0 +1,12 @@ +describe 'with `GLOB_SUBST` option set' do + let(:after_sourcing) do + -> { + session.run_command('setopt GLOB_SUBST') + } + end + + it 'error messages are not printed' do + session.send_string('[[') + wait_for { session.content }.to eq('[[') + end +end diff --git a/src/widgets.zsh b/src/widgets.zsh index 6a2be4a..874bf46 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -37,8 +37,6 @@ _zsh_autosuggest_clear() { # Modify the buffer and get a new suggestion _zsh_autosuggest_modify() { - emulate -L zsh - local -i retval # Only available in zsh >= 5.4 @@ -55,6 +53,8 @@ _zsh_autosuggest_modify() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + emulate -L zsh + # Don't fetch a new suggestion if there's more input to be read immediately if (( $PENDING > 0 )) || (( $KEYS_QUEUED_COUNT > 0 )); then POSTDISPLAY="$orig_postdisplay" diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 514d957..c1cc14a 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -322,8 +322,6 @@ _zsh_autosuggest_clear() { # Modify the buffer and get a new suggestion _zsh_autosuggest_modify() { - emulate -L zsh - local -i retval # Only available in zsh >= 5.4 @@ -340,6 +338,8 @@ _zsh_autosuggest_modify() { _zsh_autosuggest_invoke_original_widget $@ retval=$? + emulate -L zsh + # Don't fetch a new suggestion if there's more input to be read immediately if (( $PENDING > 0 )) || (( $KEYS_QUEUED_COUNT > 0 )); then POSTDISPLAY="$orig_postdisplay" From aee1b10db6ab6a3cdc592ad2d2a3982cec997af9 Mon Sep 17 00:00:00 2001 From: dana Date: Wed, 19 Dec 2018 01:20:57 -0600 Subject: [PATCH 42/47] Avoid warn_create_global warnings --- src/config.zsh | 114 +++++++++++++++++++------------- src/widgets.zsh | 25 ++++---- zsh-autosuggestions.zsh | 139 ++++++++++++++++++++++++---------------- 3 files changed, 164 insertions(+), 114 deletions(-) diff --git a/src/config.zsh b/src/config.zsh index 9ac1484..3487230 100644 --- a/src/config.zsh +++ b/src/config.zsh @@ -6,68 +6,90 @@ # Color to use when highlighting suggestion # Uses format of `region_highlight` # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets -: ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'} +(( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) && +typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets -: ${ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-} +(( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) && +typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- # Strategies to use to fetch a suggestion # Will try each strategy in order until a suggestion is returned -(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && ZSH_AUTOSUGGEST_STRATEGY=(history) +(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && { + typeset -ga ZSH_AUTOSUGGEST_STRATEGY + ZSH_AUTOSUGGEST_STRATEGY=(history) +} # Widgets that clear the suggestion -(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( - history-search-forward - history-search-backward - history-beginning-search-forward - history-beginning-search-backward - history-substring-search-up - history-substring-search-down - up-line-or-beginning-search - down-line-or-beginning-search - up-line-or-history - down-line-or-history - accept-line -) +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS + ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-substring-search-up + history-substring-search-down + up-line-or-beginning-search + down-line-or-beginning-search + up-line-or-history + down-line-or-history + accept-line + ) +} # Widgets that accept the entire suggestion -(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( - forward-char - end-of-line - vi-forward-char - vi-end-of-line - vi-add-eol -) +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS + ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( + forward-char + end-of-line + vi-forward-char + vi-end-of-line + vi-add-eol + ) +} # Widgets that accept the entire suggestion and execute it -(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( -) +(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS + ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( + ) +} # Widgets that accept the suggestion as far as the cursor moves -(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( - forward-word - emacs-forward-word - vi-forward-word - vi-forward-word-end - vi-forward-blank-word - vi-forward-blank-word-end - vi-find-next-char - vi-find-next-char-skip -) +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS + ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( + forward-word + emacs-forward-word + vi-forward-word + vi-forward-word-end + vi-forward-blank-word + vi-forward-blank-word-end + vi-find-next-char + vi-find-next-char-skip + ) +} # Widgets that should be ignored (globbing supported but must be escaped) -(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( - orig-\* - beep - run-help - set-local-history - which-command - yank - yank-pop -) +(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank + yank-pop + ) +} # Max size of buffer to trigger autosuggestion. Leave null for no upper bound. -: ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} +(( ! ${+ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE} )) && +typeset -g ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= # Pty name for calculating autosuggestions asynchronously -: ${ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty} +(( ! ${+ZSH_AUTOSUGGEST_ASYNC_PTY_NAME} )) && +typeset -g ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty diff --git a/src/widgets.zsh b/src/widgets.zsh index 874bf46..4a4ce24 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -190,22 +190,25 @@ _zsh_autosuggest_partial_accept() { return $retval } -for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do - eval "_zsh_autosuggest_widget_$action() { - local -i retval +() { + local action + for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do + eval "_zsh_autosuggest_widget_$action() { + local -i retval - _zsh_autosuggest_highlight_reset + _zsh_autosuggest_highlight_reset - _zsh_autosuggest_$action \$@ - retval=\$? + _zsh_autosuggest_$action \$@ + retval=\$? - _zsh_autosuggest_highlight_apply + _zsh_autosuggest_highlight_apply - zle -R + zle -R - return \$retval - }" -done + return \$retval + }" + done +} zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index c1cc14a..535c82e 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -42,71 +42,93 @@ zmodload zsh/zpty # Color to use when highlighting suggestion # Uses format of `region_highlight` # More info: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Widgets -: ${ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'} +(( ! ${+ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE} )) && +typeset -g ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # Prefix to use when saving original versions of bound widgets -: ${ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig-} +(( ! ${+ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX} )) && +typeset -g ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- # Strategies to use to fetch a suggestion # Will try each strategy in order until a suggestion is returned -(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && ZSH_AUTOSUGGEST_STRATEGY=(history) +(( ! ${+ZSH_AUTOSUGGEST_STRATEGY} )) && { + typeset -ga ZSH_AUTOSUGGEST_STRATEGY + ZSH_AUTOSUGGEST_STRATEGY=(history) +} # Widgets that clear the suggestion -(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( - history-search-forward - history-search-backward - history-beginning-search-forward - history-beginning-search-backward - history-substring-search-up - history-substring-search-down - up-line-or-beginning-search - down-line-or-beginning-search - up-line-or-history - down-line-or-history - accept-line -) +(( ! ${+ZSH_AUTOSUGGEST_CLEAR_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_CLEAR_WIDGETS + ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + history-search-forward + history-search-backward + history-beginning-search-forward + history-beginning-search-backward + history-substring-search-up + history-substring-search-down + up-line-or-beginning-search + down-line-or-beginning-search + up-line-or-history + down-line-or-history + accept-line + ) +} # Widgets that accept the entire suggestion -(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( - forward-char - end-of-line - vi-forward-char - vi-end-of-line - vi-add-eol -) +(( ! ${+ZSH_AUTOSUGGEST_ACCEPT_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_ACCEPT_WIDGETS + ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( + forward-char + end-of-line + vi-forward-char + vi-end-of-line + vi-add-eol + ) +} # Widgets that accept the entire suggestion and execute it -(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( -) +(( ! ${+ZSH_AUTOSUGGEST_EXECUTE_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_EXECUTE_WIDGETS + ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( + ) +} # Widgets that accept the suggestion as far as the cursor moves -(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( - forward-word - emacs-forward-word - vi-forward-word - vi-forward-word-end - vi-forward-blank-word - vi-forward-blank-word-end - vi-find-next-char - vi-find-next-char-skip -) +(( ! ${+ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS + ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( + forward-word + emacs-forward-word + vi-forward-word + vi-forward-word-end + vi-forward-blank-word + vi-forward-blank-word-end + vi-find-next-char + vi-find-next-char-skip + ) +} # Widgets that should be ignored (globbing supported but must be escaped) -(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( - orig-\* - beep - run-help - set-local-history - which-command - yank - yank-pop -) +(( ! ${+ZSH_AUTOSUGGEST_IGNORE_WIDGETS} )) && { + typeset -ga ZSH_AUTOSUGGEST_IGNORE_WIDGETS + ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( + orig-\* + beep + run-help + set-local-history + which-command + yank + yank-pop + ) +} # Max size of buffer to trigger autosuggestion. Leave null for no upper bound. -: ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=} +(( ! ${+ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE} )) && +typeset -g ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE= # Pty name for calculating autosuggestions asynchronously -: ${ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty} +(( ! ${+ZSH_AUTOSUGGEST_ASYNC_PTY_NAME} )) && +typeset -g ZSH_AUTOSUGGEST_ASYNC_PTY_NAME=zsh_autosuggest_pty #--------------------------------------------------------------------# # Utility Functions # @@ -475,22 +497,25 @@ _zsh_autosuggest_partial_accept() { return $retval } -for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do - eval "_zsh_autosuggest_widget_$action() { - local -i retval +() { + local action + for action in clear modify fetch suggest accept partial_accept execute enable disable toggle; do + eval "_zsh_autosuggest_widget_$action() { + local -i retval - _zsh_autosuggest_highlight_reset + _zsh_autosuggest_highlight_reset - _zsh_autosuggest_$action \$@ - retval=\$? + _zsh_autosuggest_$action \$@ + retval=\$? - _zsh_autosuggest_highlight_apply + _zsh_autosuggest_highlight_apply - zle -R + zle -R - return \$retval - }" -done + return \$retval + }" + done +} zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest From 50579b33716f2b64251f6f192b2a89612c77caf8 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Fri, 21 Dec 2018 23:20:08 -0700 Subject: [PATCH 43/47] Move widget definitions inside anonymous function --- src/widgets.zsh | 18 +++++++++--------- zsh-autosuggestions.zsh | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/widgets.zsh b/src/widgets.zsh index 4a4ce24..1912064 100644 --- a/src/widgets.zsh +++ b/src/widgets.zsh @@ -208,13 +208,13 @@ _zsh_autosuggest_partial_accept() { return \$retval }" done -} -zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch -zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest -zle -N autosuggest-accept _zsh_autosuggest_widget_accept -zle -N autosuggest-clear _zsh_autosuggest_widget_clear -zle -N autosuggest-execute _zsh_autosuggest_widget_execute -zle -N autosuggest-enable _zsh_autosuggest_widget_enable -zle -N autosuggest-disable _zsh_autosuggest_widget_disable -zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle + zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch + zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest + zle -N autosuggest-accept _zsh_autosuggest_widget_accept + zle -N autosuggest-clear _zsh_autosuggest_widget_clear + zle -N autosuggest-execute _zsh_autosuggest_widget_execute + zle -N autosuggest-enable _zsh_autosuggest_widget_enable + zle -N autosuggest-disable _zsh_autosuggest_widget_disable + zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle +} diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 535c82e..5bb5cd7 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -515,16 +515,16 @@ _zsh_autosuggest_partial_accept() { return \$retval }" done -} -zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch -zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest -zle -N autosuggest-accept _zsh_autosuggest_widget_accept -zle -N autosuggest-clear _zsh_autosuggest_widget_clear -zle -N autosuggest-execute _zsh_autosuggest_widget_execute -zle -N autosuggest-enable _zsh_autosuggest_widget_enable -zle -N autosuggest-disable _zsh_autosuggest_widget_disable -zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle + zle -N autosuggest-fetch _zsh_autosuggest_widget_fetch + zle -N autosuggest-suggest _zsh_autosuggest_widget_suggest + zle -N autosuggest-accept _zsh_autosuggest_widget_accept + zle -N autosuggest-clear _zsh_autosuggest_widget_clear + zle -N autosuggest-execute _zsh_autosuggest_widget_execute + zle -N autosuggest-enable _zsh_autosuggest_widget_enable + zle -N autosuggest-disable _zsh_autosuggest_widget_disable + zle -N autosuggest-toggle _zsh_autosuggest_widget_toggle +} #--------------------------------------------------------------------# # History Suggestion Strategy # From f76472272e9a40a27a5b589f7ed3cf605c2993c4 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Mon, 1 Apr 2019 14:36:31 -0600 Subject: [PATCH 44/47] cleanup: Remove unnecessary braces --- src/bind.zsh | 6 +++--- zsh-autosuggestions.zsh | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bind.zsh b/src/bind.zsh index bb41ef8..ec762ef 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -39,20 +39,20 @@ _zsh_autosuggest_bind_widget() { # User-defined widget user:*) _zsh_autosuggest_incr_bind_count $widget - zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:} + zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:} ;; # Built-in widget builtin) _zsh_autosuggest_incr_bind_count $widget eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" - zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget + zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget ;; # Completion widget completion:*) _zsh_autosuggest_incr_bind_count $widget - eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" ;; esac diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 5bb5cd7..67cb5c9 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -200,20 +200,20 @@ _zsh_autosuggest_bind_widget() { # User-defined widget user:*) _zsh_autosuggest_incr_bind_count $widget - zle -N $prefix${bind_count}-$widget ${widgets[$widget]#*:} + zle -N $prefix$bind_count-$widget ${widgets[$widget]#*:} ;; # Built-in widget builtin) _zsh_autosuggest_incr_bind_count $widget eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }" - zle -N $prefix${bind_count}-$widget _zsh_autosuggest_orig_$widget + zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget ;; # Completion widget completion:*) _zsh_autosuggest_incr_bind_count $widget - eval "zle -C $prefix${bind_count}-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" + eval "zle -C $prefix$bind_count-${(q)widget} ${${(s.:.)widgets[$widget]}[2,3]}" ;; esac From 4a82ff1ead68db25645b2e920f17774a66e44e31 Mon Sep 17 00:00:00 2001 From: romkatv Date: Mon, 25 Feb 2019 12:59:31 +0100 Subject: [PATCH 45/47] speed up widget rebinding by removing redundant array subscripts --- src/bind.zsh | 23 +++++------------------ zsh-autosuggestions.zsh | 23 +++++------------------ 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/src/bind.zsh b/src/bind.zsh index ec762ef..a2e86e1 100644 --- a/src/bind.zsh +++ b/src/bind.zsh @@ -4,21 +4,8 @@ #--------------------------------------------------------------------# _zsh_autosuggest_incr_bind_count() { - if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then - ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++)) - else - _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1 - fi - - typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] -} - -_zsh_autosuggest_get_bind_count() { - if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then - typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] - else - typeset -gi bind_count=0 - fi + typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1)) + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count } # Bind a single widget to an autosuggest widget, saving a reference to the original widget @@ -34,7 +21,9 @@ _zsh_autosuggest_bind_widget() { # Save a reference to the original widget case $widgets[$widget] in # Already bound - user:_zsh_autosuggest_(bound|orig)_*);; + user:_zsh_autosuggest_(bound|orig)_*) + bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget])) + ;; # User-defined widget user:*) @@ -56,8 +45,6 @@ _zsh_autosuggest_bind_widget() { ;; esac - _zsh_autosuggest_get_bind_count $widget - # Pass the original widget's name explicitly into the autosuggest # function. Use this passed in widget name to call the original # widget instead of relying on the $WIDGET variable being set diff --git a/zsh-autosuggestions.zsh b/zsh-autosuggestions.zsh index 67cb5c9..a2edce6 100644 --- a/zsh-autosuggestions.zsh +++ b/zsh-autosuggestions.zsh @@ -165,21 +165,8 @@ _zsh_autosuggest_feature_detect_zpty_returns_fd() { #--------------------------------------------------------------------# _zsh_autosuggest_incr_bind_count() { - if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then - ((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]++)) - else - _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=1 - fi - - typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] -} - -_zsh_autosuggest_get_bind_count() { - if ((${+_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]})); then - typeset -gi bind_count=$_ZSH_AUTOSUGGEST_BIND_COUNTS[$1] - else - typeset -gi bind_count=0 - fi + typeset -gi bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$1]+1)) + _ZSH_AUTOSUGGEST_BIND_COUNTS[$1]=$bind_count } # Bind a single widget to an autosuggest widget, saving a reference to the original widget @@ -195,7 +182,9 @@ _zsh_autosuggest_bind_widget() { # Save a reference to the original widget case $widgets[$widget] in # Already bound - user:_zsh_autosuggest_(bound|orig)_*);; + user:_zsh_autosuggest_(bound|orig)_*) + bind_count=$((_ZSH_AUTOSUGGEST_BIND_COUNTS[$widget])) + ;; # User-defined widget user:*) @@ -217,8 +206,6 @@ _zsh_autosuggest_bind_widget() { ;; esac - _zsh_autosuggest_get_bind_count $widget - # Pass the original widget's name explicitly into the autosuggest # function. Use this passed in widget name to call the original # widget instead of relying on the $WIDGET variable being set From 3ee91c731cfddf8203121cfc76af71d843a09294 Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 3 Apr 2019 10:51:48 -0600 Subject: [PATCH 46/47] Update changelog for v0.5.1 release --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 387071b..64a81f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.5.1 +- Speed up widget rebinding (#413) +- Clean up global variable creations (#403) +- Respect user's set options when running original widget (#402) + ## v0.5.0 - Don't overwrite config with default values (#335) - Support fallback strategies by supplying array to suggestion config var From f94e667f59b8d8a030b4b45477d5e8b57bac102c Mon Sep 17 00:00:00 2001 From: Eric Freese Date: Wed, 3 Apr 2019 10:52:43 -0600 Subject: [PATCH 47/47] v0.5.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b043aa6..992ac75 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.5.0 +v0.5.1