zsh-users / zsh-autosuggestions

Fish-like autosuggestions for zsh
MIT License
30.33k stars 1.85k forks source link

Error on terminal startup #732

Open masakistan opened 1 year ago

masakistan commented 1 year ago

I get the following error on startup:

Last login: Tue Apr  4 15:02:38 on ttys001
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range
(eval):zle:1: not enough arguments for -C
_main_complete:compset:94: can only be called from completion function
_setup:37: compstate: assignment to invalid subscript range

I believe that the issue is related to this line: https://github.com/zsh-users/zsh-autosuggestions/blob/a411ef3e0992d4839f0732ebeb9823024afaaaa8/zsh-autosuggestions.zsh#L174

documentation indicates that it needs 3 arguments whereas the indicated line only has 2 arguments. The documentation reads as follows:

zle -C widget completion-widget function

thanks so much for the great plugin, please let me know if I can provide any additional information.

ericfreese commented 1 year ago

@masakistan Can you reproduce this by running zsh -df (to skip your .zshrc) and sourcing the plugin manually? If not, what is the minimal content from your .zshrc that you can add to start seeing the problem?

JamesWidman commented 10 months ago

i saw similar errors, and by reducing my ~/.zshrc with a binary search (after backing it up), i found that the culprit was this:

# somewhere in my ~/.zshrc:
IFS=$'\n'

i forget why i put it there (and i'm going to try to avoid using it in the future, and instead solve whatever problem i had with parameter expansion flags if possible), but the effect of setting IFS like this (as seen by setting setopt xtrace just above the zle -C line) is that i then see a bunch of evaluations like this:

+_zsh_autosuggest_bind_widget:33> eval $'zle -C autosuggest-orig-1-complete-word .complete-word\n_main_complete'
+(eval):1> zle -C autosuggest-orig-1-complete-word .complete-word
(eval):zle:1: not enough arguments for -C
+(eval):2> _main_complete
+_main_complete:0> -ftb-complete
+-ftb-complete:1> local -a _ftb_compcap
+-ftb-complete:2> local -Ua _ftb_groups
+-ftb-complete:3> local choice choices _ftb_curcontext continuous_trigger print_query accept_line bs=$'\C-B' nul=$'\C-@'
+-ftb-complete:4> local ret=0
+-ftb-complete:7> ((  0  ))

(note the errant literal \n in .complete-word\n_main_complete)

So _main_complete, the word that was intended to serve as the function argument in:

zle -C widget completion-widget function

is here instead being evaluated as a separate command, because eval translated the errant literal \n into a logical newline.

But if IFS has its default value, then ${${(s.:.)widgets[$widget]}[2,3]} gets expanded as intended:

+_zsh_autosuggest_bind_widget:33> eval 'zle -C autosuggest-orig-1-complete-word .complete-word _main_complete'
+(eval):1> zle -C autosuggest-orig-1-complete-word .complete-word _main_complete
JamesWidman commented 10 months ago

Side note: i am very lucky that i did not have any completion widgets set up for any commands that have lasting side-effects, or the downstream symptoms might have been very bad.

@ericfreese, do you think we can modify the expression ${${(s.:.)widgets[$widget]}[2,3]} so that it gives the result we want without depending on IFS? (I'm probably not the only zsh user who was silly enough to set it in my ~/.zshrc)

[edit: it seems like the expansion flag@ does the trick, as in: ${(@)${(s.:.)widgets[$widget]}[2,3]}]

JamesWidman commented 10 months ago

for anyone else who was confused by this (like i was), a key rule is rule 5 ("Double-quoted joining") of the parameter expansion rules:

If the value after this process is an array, and the substitution appears in double quotes, and neither an ‘(@)’ flag nor a ‘#’ length operator is present at the current level, then words of the value are joined with the first character of the parameter $IFS, by default a space, between each word (single word arrays are not modified). If the ‘(j)’ flag is present, that is used for joining instead of $IFS.

so, to apply this to the xtrace output from above: an expression like ${widgets[autosuggest-orig-1-complete-word]} produces a scalar value (not an array) and its value in this case happens to be completion:.complete-word:_main_complete. But in ${(s.:.)widgets[autosuggest-orig-1-complete-word]}, the flag (s.:.) means that the result of the expansion will be an array formed by splitting that scalar at each instance of ':'.

So our array value has 3 elements:

completion .complete-word _main_complete

After that, rule 5 kicks in.

[edit: well, we did evaluate rule 5 once before, when zsh was ticking through the expansion rules for the nested expansion ${(s.:.)...}, but at that time our input value was a scalar because we hadn't yet reached rule 11 (where the (s.:.) flag is applied, which replaces our scalar with array). And rule 5 applies only to arrays, not scalars, so this was a no-op. After that round of the rules, we then had to go through all of the rules again to process the outer expansion: ${<result from previous round>[2,3]} So now our current value is an array, so rule 5 applies.

(rule 3 (parameter subscripting) took effect in this round of the rules before we hit rule 5, so the input to rule 5 is an array with 2 elements instead of 3.)

--end edit]

This entire expansion appears inside a quoted string, and the (@) flag is not present, so the words of the array value "are joined with the first character of the parameter $IFS", which in my case (and presumably @masakistan's) happened to be a newline. (By default, it's a space character.)

So now i feel more confident that (@) should be used in this expression, because that way (it seems), the value of IFS does not affect the result.

[edit: alternatively, issue an error at the top of the script if IFS is set ([[ -v IFS ]]) and the first character is anything other than a space, since it looks like there may be other quoted array values in there.]