diff --git a/.clitest/complete-word.post.md b/.clitest/complete-word.post.md index 429a8561..5685a899 100644 --- a/.clitest/complete-word.post.md +++ b/.clitest/complete-word.post.md @@ -1,58 +1,86 @@ Setup: ```zsh +% zmodload zsh/param/private % autoload -Uz zmathfunc && zmathfunc % autoload -Uz $PWD/functions/widget/.autocomplete.complete-word.post % unset terminfo -% typeset -gA terminfo=() compstate=( old_list shown ) _lastcomp=() -% terminfo[kcbt]=BACKSPACE +% typeset -gA compstate=() _lastcomp=() terminfo=() % zstyle ':autocomplete:*' add-space 'FOO' 'TAG' 'BAR' +% zstyle ':autocomplete:(|shift-)tab:' widget-style complete-word +% KEYS=$'\t' WIDGET=complete-word terminfo[kcbt]=BACKTAB +% compstate[old_list]=keep compstate[nmatches]=0 _lastcomp[nmatches]=2 % ``` -Only `menu-select` widget sets `$MENUSELECT`: +If we have only 1 match, just insert it: ```zsh -% WIDGET=menu-select .autocomplete.complete-word.post -% print -r -- ${(q+)compstate[insert]} ${(q+)compstate[list]} $+MENUSELECT -1 '' 1 +% _lastcomp[nmatches]=1 +% .autocomplete.complete-word.post +% print -r -- ${(q+)compstate[insert]} $+compstate[list] $+MENUSELECT +1 0 0 +% _lastcomp[nmatches]=2 % ``` -Only `Shift-Tab` key sets `$compstate[insert]` to `*:0`: +If we have more than 1 match, but there's no old list, then show the list and don't insert: ```zsh -% KEYS=BACKSPACE .autocomplete.complete-word.post -% print -r -- ${(q+)compstate[insert]} ${(q+)compstate[list]} $+MENUSELECT -0 '' 0 +% compstate[old_list]= compstate[nmatches]=2 +% .autocomplete.complete-word.post +% print -r -- ${(q+)compstate[insert]} $+compstate[list] $+MENUSELECT +'' 1 0 +% compstate[old_list]=keep compstate[nmatches]=0 % ``` -`add-space` tag in current completion adds space: +`menu-*` widgets set `$compstate[insert]` to `menu:*`: ```zsh -% _comp_tags='LOREM TAG IPSUM' _lastcomp[tags]='OTHER' .autocomplete.complete-word.post -% print -r -- ${(q+)compstate[insert]} ${(q+)compstate[list]} $+MENUSELECT -'1 ' '' 0 +% zstyle ':autocomplete:shift-tab:' widget-style reverse-menu-complete +% KEYS=$terminfo[kcbt] +% .autocomplete.complete-word.post +% print -r -- ${(q+)compstate[insert]} $+compstate[list] $+MENUSELECT +menu:0 0 0 +% zstyle ':autocomplete:shift-tab:' widget-style complete-word +% KEYS=$'\t' % ``` -`add-space` tag in previous completion adds space, if current completion is not used: +Widgets default to `menu-select`, which sets `$MENUSELECT`, even without old list: ```zsh -% _comp_tags= _lastcomp[tags]='LOREM TAG IPSUM' .autocomplete.complete-word.post -% print -r -- ${(q+)compstate[insert]} ${(q+)compstate[list]} $+MENUSELECT -'1 ' '' 0 +% KEYS=OTHER compstate[old_list]= compstate[nmatches]=2 +% .autocomplete.complete-word.post +% print -r -- ${(q+)compstate[insert]} $+compstate[list] $+MENUSELECT +menu:1 0 1 +% KEYS=$'\t' compstate[old_list]=keep compstate[nmatches]=0 % ``` -`add-space` tag in previous completion does NOT add space, if current completion is used: +`Shift-Tab` key sets `$compstate[insert]` to `*0`: ```zsh -% _comp_tags='OTHER' _lastcomp[tags]='TAG' .autocomplete.complete-word.post -% print -r -- ${(q+)compstate[insert]} ${(q+)compstate[list]} $+MENUSELECT -1 '' 0 +% KEYS=$terminfo[kcbt] +% .autocomplete.complete-word.post +% print -r -- ${(q+)compstate[insert]} $+compstate[list] $+MENUSELECT +0 0 0 +% KEYS=$'\t' % ``` -If list is not yet shown, then insert unambiguous and show list: +If the list is showing and there's an `add-space` tag in the last completion, then add a space: ```zsh -% compstate[old_list]= _comp_tags= _lastcomp[tags]= .autocomplete.complete-word.post -% print -r -- ${(q+)compstate[insert]} ${(q+)compstate[list]} $+MENUSELECT -unambiguous 'list force packed rows' 0 +# % functions -T .autocomplete.complete-word.post +% _comp_tags='OTHER' _lastcomp[tags]='LOREM TAG IPSUM' +% .autocomplete.complete-word.post +% print -r -- ${(q+)compstate[insert]} $+compstate[list] $+MENUSELECT +'1 ' 0 0 +% compstate[old_list]= _lastcomp[tags]= +% +``` + +If the list is not showing and there's an `add-space` tag in the new completion, then add a space: +```zsh +% compstate[old_list]= _comp_tags='LOREM TAG IPSUM' _lastcomp[tags]='OTHER' +% .autocomplete.complete-word.post +% print -r -- ${(q+)compstate[insert]} $+compstate[list] $+MENUSELECT +'1 ' 0 0 +% compstate[old_list]=keep _comp_tags= _lastcomp[tags]= % ``` diff --git a/functions/widget/.autocomplete.complete-word.completion-widget b/functions/widget/.autocomplete.complete-word.completion-widget index 4ed9cee2..134efafd 100644 --- a/functions/widget/.autocomplete.complete-word.completion-widget +++ b/functions/widget/.autocomplete.complete-word.completion-widget @@ -1,6 +1,5 @@ #!/bin/zsh -unfunction .autocomplete.complete-word.post 2> /dev/null -builtin autoload -Uz .autocomplete.complete-word.post +zmodload -F zsh/terminfo p:terminfo private key_name= case $KEYS in @@ -10,18 +9,15 @@ esac local +h curcontext=${curcontext:-${WIDGET}:::} local +h -a comppostfuncs=( .autocomplete.complete-word.post "$comppostfuncs[@]" ) -if [[ $WIDGET == *menu-* && -v _autocomplete__partial_list ]] || - ( ( [[ $key_name == shift-tab ]] || - builtin zstyle -t ":autocomplete:${key_name}:" insert-unambiguous - ) && [[ -n $compstate[old_list] && - -v _autocomplete__unambiguous && -n $_autocomplete__unambiguous ]] ); then + +if [[ -z $compstate[old_list] ]] || [[ $WIDGET == *menu-* && -v _autocomplete__partial_list ]] || + ( builtin zstyle -t ":autocomplete:${key_name}:" insert-unambiguous && + [[ -n $_autocomplete__unambiguous ]] ); then _main_complete -elif [[ -n $compstate[old_list] ]]; then +else compstate[old_list]=keep [[ $key_name == shift-tab && $_lastcomp[tags] == *all-matches ]] && compstate[insert]=all _main_complete - -else - _main_complete fi -return 0 # Prevent beeping. +[[ _lastcomp[nmatches] -gt 0 && -n $compstate[insert] ]] diff --git a/functions/widget/.autocomplete.complete-word.post b/functions/widget/.autocomplete.complete-word.post index 95c3a163..9920b32a 100644 --- a/functions/widget/.autocomplete.complete-word.post +++ b/functions/widget/.autocomplete.complete-word.post @@ -1,7 +1,7 @@ #autoload builtin autoload -Uz is-at-least -unset MENUSELECT +unset MENUSELECT 'compstate[list]' compstate[insert]= private key_name= @@ -9,41 +9,42 @@ case $KEYS in ( $'\t' ) key_name=tab ;; ( $terminfo[kcbt] ) key_name=shift-tab ;; esac -local key_style= -builtin zstyle -s :autocomplete:${key_name}: widget-style key_style - -if [[ ! -v compstate[old_list] || -z $compstate[old_list] ]] || - ( - [[ -v _autocomplete__unambiguous && -n $_autocomplete__unambiguous ]] && - builtin zstyle -t ":autocomplete:${key_name}:" insert-unambiguous - ); then - - if ! is-at-least 5.8.1; then - # Work around a crashing bug in Zsh. - # See [zsh-workers 48936](https://www.zsh.org/mla/workers/2021/msg01162.html). - [[ $key_style == *menu-* && -n $key_name ]] && - compstate[insert]='automenu-' - fi +local widget_style= +builtin zstyle -s :autocomplete:${key_name}: widget-style widget_style || + widget_style=menu-select +if [[ -n $_autocomplete__unambiguous ]] && + builtin zstyle -t ":autocomplete:${key_name}:" insert-unambiguous; then + [[ $widget_style == (|*-)menu-* ]] && + compstate[insert]='automenu-' compstate[insert]+=unambiguous - compstate[list]='list force packed rows' unset _autocomplete__unambiguous return fi -if [[ $WIDGET == *menu-select ]]; then - # Determine which terminal line we're on (for async completion). - typeset -gHi _autocomplete__buffer_start_line=$(( - max( min( _autocomplete__buffer_start_line, LINES - compstate[list_lines] ), BUFFERLINES ) - )) - typeset -gHi MENUSELECT=0 +private -i nmatches=0 +if [[ $compstate[old_list] == keep ]]; then + nmatches=$_lastcomp[nmatches] +else + nmatches=$compstate[nmatches] fi -if ! is-at-least 5.8.1; then - # Work around a crashing bug in Zsh. - # See [zsh-workers 48936](https://www.zsh.org/mla/workers/2021/msg01162.html). - [[ $key_style == *menu-* && -n $key_name ]] && - compstate[insert]='menu:' +if (( nmatches > 1 )); then + if [[ $widget_style == (|*-)menu-select ]]; then + typeset -gHi MENUSELECT=0 + compstate[insert]='menu:' + + # Determine which terminal line we're on (for async completion). + typeset -gHi _autocomplete__buffer_start_line=$(( + max( min( _autocomplete__buffer_start_line, LINES - compstate[list_lines] ), BUFFERLINES ) + )) + + elif [[ $compstate[old_list] != keep && $_lastcomp[insert] != (|*-)unambiguous ]]; then + compstate[list]='list force' + return + elif [[ $widget_style == (|*-)menu-complete ]]; then + compstate[insert]='menu:' + fi fi if [[ $key_name == shift-tab ]]; then @@ -52,9 +53,16 @@ else compstate[insert]+='1' fi -local -a tags=() match=() mbegin=() mend=() -builtin zstyle -a :autocomplete: add-space tags || - tags=( executables aliases functions builtins reserved-words commands ) - -[[ $RBUFFER != [[:space:]]* && -n ${${=${_comp_tags:-$_lastcomp[tags]}}:*tags} ]] && - compstate[insert]+=' ' +if [[ $RBUFFER != [[:space:]]* ]]; then + local -a spacetags=() + builtin zstyle -a :autocomplete: add-space spacetags || + spacetags=( executables aliases functions builtins reserved-words commands ) + private -a comptags=() + if [[ $compstate[old_list] == keep ]]; then + comptags=( $=_lastcomp[tags] ) + else + comptags=( $=_comp_tags ) + fi + [[ -n ${comptags:*spacetags} ]] && + compstate[insert]+=' ' +fi diff --git a/functions/widget/.autocomplete.history-search.completion-widget b/functions/widget/.autocomplete.history-search.completion-widget index c1c3d3c4..509b82d9 100644 --- a/functions/widget/.autocomplete.history-search.completion-widget +++ b/functions/widget/.autocomplete.history-search.completion-widget @@ -9,7 +9,7 @@ $0() { _main_complete _autocomplete.history_lines unset curcontext - (( compstate[nmatches] > 0 )) + (( _lastcomp[nmatches] )) } $0.post() { diff --git a/functions/widget/.autocomplete.list-expand.completion-widget b/functions/widget/.autocomplete.list-expand.completion-widget index 72b37a2b..f37c1642 100644 --- a/functions/widget/.autocomplete.list-expand.completion-widget +++ b/functions/widget/.autocomplete.list-expand.completion-widget @@ -8,6 +8,7 @@ $0() { compstate[old_list]= local +h -a comppostfuncs=( $0.post "$comppostfuncs[@]" ) _main_complete + (( _lastcomp[nmatches] )) } $0.post() { diff --git a/scripts/.autocomplete.async b/scripts/.autocomplete.async index e792261e..f249cf56 100644 --- a/scripts/.autocomplete.async +++ b/scripts/.autocomplete.async @@ -207,6 +207,11 @@ builtin zle -N history-incremental-search-forward .autocomplete.async.history-in .autocomplete.async.start.inner() { { + local -F min_delay= + builtin zstyle -s :autocomplete: min-delay min_delay || + min_delay=0.01 + zselect -t "$(( [#10] 100 * max( 0, min_delay - SECONDS ) ))" + private hooks=( chpwd periodic precmd preexec zshaddhistory zshexit ) builtin unset ${^hooks}_functions &> /dev/null $hooks[@] () { : } @@ -224,11 +229,6 @@ builtin zle -N history-incremental-search-forward .autocomplete.async.history-in zpty -w AUTOCOMPLETE $'\t' - local -F min_delay= - builtin zstyle -s :autocomplete: min-delay min_delay || - min_delay=0.01 - zselect -t "$(( [#10] 100 * max( 0, min_delay - SECONDS ) ))" - local header= zpty -r AUTOCOMPLETE header $'*\C-B' @@ -279,7 +279,8 @@ log_functions+=( .autocomplete.async.pty ) setopt $_autocomplete__ctxt_opts[@] builtin zle .autocomplete.async.pty.completion-widget -w 2> /dev/null } always { - print -n -- '\C-C' + print -rNC1 -- \ + "$_autocomplete__list_lines" "$_autocomplete__mesg" "$_autocomplete__comp_mesg[@]" '\C-C' builtin kill $sysparams[pid] } } 2>>| $_autocomplete__log_file @@ -291,11 +292,11 @@ log_functions+=( .autocomplete.async.pty.zle-widget.inner ) .autocomplete.async.pty.completion-widget.inner() { if .autocomplete.async.insufficient-input; then - print -rNC1 -- "0" "" "" + typeset -gHi _autocomplete__list_lines=0 return fi if .autocomplete.async.same-state; then - print -rNC1 -- "$_lastcomp[list_lines]" "$_autocomplete__mesg" "$_autocomplete__comp_mesg[@]" + typeset -gHi _autocomplete__list_lines=$_lastcomp[list_lines] return fi unset _autocomplete__mesg _autocomplete__comp_mesg @@ -320,7 +321,7 @@ log_functions+=( .autocomplete.async.pty.zle-widget.inner ) local +h -a comppostfuncs=( .autocomplete.async.pty.message ) _main_complete } always { - print -rNC1 -- "$compstate[list_lines]" "$_autocomplete__mesg" "$_autocomplete__comp_mesg[@]" + typeset -gHi _autocomplete__list_lines=$compstate[list_lines] } } 2>>| $_autocomplete__log_file log_functions+=( .autocomplete.async.pty.completion-widget.inner ) diff --git a/zsh-autocomplete.plugin.zsh b/zsh-autocomplete.plugin.zsh index 484a6317..3603d9ee 100644 --- a/zsh-autocomplete.plugin.zsh +++ b/zsh-autocomplete.plugin.zsh @@ -8,7 +8,7 @@ .zinit-tmp-subst-off "${___mode:-load}" zmodload zsh/param/private -setopt NO_flowcontrol NO_singlelinezle +setopt NO_flowcontrol NO_listbeep NO_singlelinezle () { emulate -L zsh @@ -21,7 +21,7 @@ setopt NO_flowcontrol NO_singlelinezle ) setopt $_autocomplete__func_opts[@] - typeset -gHa _autocomplete__comp_opts=( localoptions NO_banghist NO_completeinword NO_listbeep ) + typeset -gHa _autocomplete__comp_opts=( localoptions NO_banghist NO_completeinword ) typeset -gHa _autocomplete__ctxt_opts=( completealiases completeinword ) private basedir=${${(%):-%x}:P:h}