cobra icon indicating copy to clipboard operation
cobra copied to clipboard

ValidArgsFunction not always called for completion of positional arguments

Open mipimipi opened this issue 2 years ago • 16 comments

Hi, I want to define a custom completion for the first argument of a sub command. That's my ValidArgsFunction (I defined it for the sub command update, my cli application is called crema):

func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    if len(args) == 0 {
        return repoNames(toComplete), cobra.ShellCompDirectiveNoFileComp
    }
    return []string{}, cobra.ShellCompDirectiveNoFileComp`
}

The function repoNames would deliver the valid values for the first argument.

The custom completion function is called as expected when I do:

crema update <tab>

It is not called when I do

crema update r<tab>

(I.e., when I type the first letter of a valid value for the first argument). Instead a file completion is done, but I expect that my custom completion function is called).

Also when I do

crema __complete update <tab>

to debug the whole thing, a file completion is done (instead, I expect that my custom completion function is called).

How can I get the desired behavior? BTW: I am using cobra v1.4.0 and zsh.

Thanks, mipi

mipimipi avatar May 30 '22 07:05 mipimipi

Hi @mipimipi.

When calling the __complete function you must not press <TAB> but actually execute the command (pressing <ENTER>). You should also explicitly specify the empty argument in that case only. So try comparing:

crema __complete update '' <enter>
crema __complete update r <enter>

I hope this helps.

marckhouzam avatar May 30 '22 11:05 marckhouzam

Hi @marckhouzam, thanks for the quick response. Now I was able to debug with __complete (btw: I had to do crema __complete update r <enter> instead of crema __complete update r <tab>), but strange things happen (just as background info: repoNames() returns values if it's called with toComplete="t", it returns an empty list if it is called with toComplete="r", which is both correct):

  1. crema update r<tab>: My custom completion function is not called, instead file completion is done.

  2. crema update r<space><tab>: My custom completion function is called, but now args=["r"] and thus, it assumes that the completion is called for the 2nd argument. But from the program logic, it is only possible to determine valid values for the 1st arg.

  3. crema update t<tab>: My custom completion function is called (!), the correct completion proposals are returned. No file completion is executed.

  4. crema update t<space><tab>: Same as 2.

  5. crema __complete update r<enter>: Output:

     :4
     Completion ended with directive: ShellCompDirectiveNoFileComp
    
  6. crema __complete update t<enter>: Output (test and test-m1 are the correct values):

    test test-m1 :4 Completion ended with directive: ShellCompDirectiveNoFileComp

First, I do not understand why in 1. a file completion is done though I set ShellCompDirectiveNoFileComp. Second, I do not understand why in 1. a file completion is done, but neither in 3. nor in 5. it is not done.

How do I check whether my custom completion function is called? - I have a print statement at the very beginning.

Could you shed some light on that?

Thanks, mipi

mipimipi avatar May 30 '22 12:05 mipimipi

Hi @marckhouzam, thanks for the quick response. Now I was able to debug with __complete (btw: I had to do crema __complete update r <enter> instead of crema __complete update r <tab>),

That was my mistake. I updated my comment to fix it. Sorry.

but strange things happen (just as background info: repoNames() returns values if it's called with toComplete="t", it returns an empty list if it is called with toComplete="r", which is both correct):

  1. crema update r<tab>: My custom completion function is not called, instead file completion is done.

This is strange. See below.

  1. crema update r<space><tab>: My custom completion function is called, but now args=["r"] and thus, it assumes that the completion is called for the 2nd argument. But from the program logic, it is only possible to determine valid values for the 1st arg.

This is normal as you have specified an argument of r and then asked for completions of the next argument.

  1. crema update t<tab>: My custom completion function is called (!), the correct completion proposals are returned. No file completion is executed.

🎉

  1. crema update t<space><tab>: Same as 2.

Normal as for point 2.

  1. crema __complete update r<enter>: Output:
     :4
     Completion ended with directive: ShellCompDirectiveNoFileComp
    

This is the expected output and I would have expected things to work properly when doing shell completion. Where is the print statement output you say you have at the beginning?

  1. crema __complete update t<enter>: Output (test and test-m1 are the correct values): test test-m1 :4 Completion ended with directive: ShellCompDirectiveNoFileComp

This is correct.

First, I do not understand why in 1. a file completion is done though I set ShellCompDirectiveNoFileComp. Second, I do not understand why in 1. a file completion is done, but neither in 3. nor in 5. it is not done.

Me neither 😄 The file completion for 1 is wrong. The other two cases are correct, as per your ValidArgsFunction.

How do I check whether my custom completion function is called? - I have a print statement at the very beginning.

Could the print statement be the problem? If you are printing to stdout it will affect shell completion. You should instead use cobra.CompError() or one of those methods described here: https://github.com/spf13/cobra/blob/master/shell_completions.md#debugging

marckhouzam avatar May 30 '22 14:05 marckhouzam

OK, I now used cobra.CompError instead of a print statement, but the output is still strange:

  1. crema update r<tab>: Custom completion function not called (i.e., no message to stderr), but file completion done.

  2. crema __complete update r<enter>: Output:

     [Debug] [Error] TOCOMPLETE: r
     :4
     Completion ended with directive: ShellCompDirectiveNoFileComp
    

    Looks OK to me, but it's different from 1 which is strange.

Here's the code:

ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    cobra.CompError(fmt.Sprintf("TOCOMPLETE: %s", toComplete))
    if len(args) == 0 {
        return repoNames(toComplete), cobra.ShellCompDirectiveNoFileComp
    }
    return []string{}, cobra.ShellCompDirectiveNoFileComp
}

mipimipi avatar May 30 '22 15:05 mipimipi

  1. crema update r<tab>: Custom completion function not called (i.e., no message to stderr), but file completion done.

You won't see the output when doing shell completion, it is normal; the shell completion script redirects this output to /dev/null. The fact that there is file completion is not normal.

How do you generate the completion script for crema? How to do you load it in the shell? Which version of zsh are you using?

You say that crema update t<tab> does shell completion properly but not crema update r<tab>, is that right?

marckhouzam avatar May 30 '22 15:05 marckhouzam

How to I generate and load the completion scripts?

  1. I defined the sub command gencomp which executes cmd.Root().GenZshCompletion(os.Stdout)
  2. I execute that sub command and pipe the result into /usr/share/zsh/site-functions/_crema
  3. I source /usr/share/zsh/site-functions/_crema

I am using zsh version 5.9

You say that crema update t does shell completion properly but not crema update r, is that right?

Yes, that's right. The reason is - as I assume - that there are valid values that start with "t", but there are no valid values that start with "r". Thus, the function repoNames() returns for "t" a string array that is not empty, but returns an empty string array for "r".

BTW: I created #1714 by accident. Sorry.

mipimipi avatar May 31 '22 05:05 mipimipi

We'll have to debug the behavior of the script.

In the shell where you will perform shell completion please set this variable: export BASH_COMP_DEBUG_FILE=/tmp/debug.txt

Then perform the shell completion test: crema update r<tab>

And look at the printouts that appeared in /tmp/debug.txt

marckhouzam avatar Jun 01 '22 10:06 marckhouzam

I did crema update r<tab>, and that's the content of /tmp/debug.txt:

========= starting completion logic ==========
CURRENT: 3, words[*]: crema update r
Truncated words[*]: crema update r,
lastParam: r, lastChar: r
About to call: eval crema __complete update r
[Debug] [Error] TOCOMPLETE: rcompletion output: :4
last line: :4
directive: 4
completions:
flagPrefix:
Calling _describe
_describe did not find completions.
Checking if we should do file completion.
deactivating file completion

As before, file completion was done.

mipimipi avatar Jun 01 '22 17:06 mipimipi

deactivating file completion

Hmm... What do you mean by "file completion was done". What do you see happening?

marckhouzam avatar Jun 01 '22 17:06 marckhouzam

crema update r<tab> leads to crema update README.md. README.md is the only file in the directory that starts with 'r'.

mipimipi avatar Jun 01 '22 18:06 mipimipi

BTW, that's the content of /tmp/debug.txt for crema update a<tab> (there are also valid values starting with 'a' - the only such valid value is 'archi3linux'):

========= starting completion logic ==========
CURRENT: 3, words[*]: crema update a
Truncated words[*]: crema update a,
lastParam: a, lastChar: a
About to call: eval crema __complete update a
[Debug] [Error] TOCOMPLETE: acompletion output: archi3linux
:4
last line: :4
directive: 4
completions: archi3linux
flagPrefix:
Adding completion: archi3linux
Calling _describe
_describe found some completions

mipimipi avatar Jun 01 '22 18:06 mipimipi

Up to now nothing explains why file completion is done. I see that zsh 5.9, which you are running, was released very recently. I wonder if some behavior changed?

I will find time to install this new version and try to reproduce the problem.

marckhouzam avatar Jun 01 '22 21:06 marckhouzam

OK, thanks a lot.

mipimipi avatar Jun 02 '22 04:06 mipimipi

@mipimipi I ran zsh 5.9 in docker and tested with helm's shell completion but was not able to reproduce the problem.

Below is what I did:

docker run --rm -it zshusers/zsh:5.9

# Install curl
apt update
apt install curl -y 

# Install helm
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

# Enable shell completion
autoload -Uz compinit
compinit
source <(helm completion zsh)

# Test
helm list <TAB> # No file completion as expected
helm package <TAB> # File completion as expected

So, I'm thinking it is either some issue with crema, or it is related to your particular zsh settings. What you could do is install helm in your own environment and try to see if file completion behaves as expected.

marckhouzam avatar Jun 05 '22 12:06 marckhouzam

@marckhouzam: Thanks for checking. I will investigate further.

mipimipi avatar Jun 06 '22 06:06 mipimipi

The Cobra project currently lacks enough contributors to adequately respond to all issues. This bot triages issues and PRs according to the following rules:

  • After 60d of inactivity, lifecycle/stale is applied. - After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied and the issue is closed. You can:
  • Make a comment to remove the stale label and show your support. The 60 days reset. - If an issue has lifecycle/rotten and is closed, comment and ask maintainers if they'd be interseted in reopening

github-actions[bot] avatar Aug 06 '22 00:08 github-actions[bot]