Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give instructions to user with zero possible completions #328

Open
mauvilsa opened this issue Nov 9, 2020 · 6 comments
Open

Give instructions to user with zero possible completions #328

mauvilsa opened this issue Nov 9, 2020 · 6 comments

Comments

@mauvilsa
Copy link

mauvilsa commented Nov 9, 2020

I would like to use the warn feature to give the user instructions even though there is no completion value that can be provided. The most simple case is an int argument. The value given to the argument could be already or not yet a valid int. But in either of those cases nothing can be completed, only a message can be given to help the user.

If I implement a completer that returns nothing, a double tab shows the warn message that I want but it also shows the files in the current directory which I don't want. I can overcome this by returning two completions just to print as message, but this is not a great solution since it is giving completion options that shouldn't be completed. Is there a way that the completer can indicate there are no completions available so it shouldn't use the fallback completer?

Furthermore, using the two completions workaround I have observed an issue which I am not sure if it is a bug. The example code is:

#!/usr/bin/env python3

import os
import argparse
import argcomplete

def completer(*args, prefix, **kwargs):
    if chr(int(os.environ['COMP_TYPE'])) == '?':
        try:
            int(prefix)
            msg = 'value already valid, expected type int'
        except:
            msg = 'value not yet valid, expected type int'
        argcomplete.warn(msg)
    return ['continue', 'please']

parser = argparse.ArgumentParser()
parser.add_argument('--val', type=int).completer = completer
argcomplete.autocomplete(parser)

If I double tab when the argument is given, but not yet a value, it works as expected printing the message and then restoring the prompt and command written, see below where the black rectangle () indicates where cursor ends up:

$ prog --val=<TAB><TAB>
 value not yet valid, expected type int

continue  please    
$ prog1 --val=▮

However, if a value is given, after double tab the prompt and command are not restored, which give a very bad user experience, see below again with the black rectangle () indicating the cursor:

$ prog1 --val=1<TAB><TAB>
 value already valid, expected type int
▮

Is this a bug, or is there a way to achieve what I want?

@evanunderscore
Copy link
Collaborator

This isn't a bug. In the first case, Bash is displaying the completions you supplied, and then redrawing the prompt. In the second case, Bash has determined that neither of your completions are valid (because they don't start with '1') and so does not bother to print anything or redraw the prompt. The important thing to note is that in both cases, Bash is unaware that you printed anything to the console; it only redraws the prompt when it thinks it needs to.

It's possible you could modify your wrapper to print the prompt yourself (e.g. like this) however I don't expect it will be simple and I doubt we'd want to add this functionality to argcomplete itself.

Displaying files when no matches are generated is a side effect of #284. You'd have to use the --complete-arguments option to register-python-argcomplete to override this.

@mauvilsa
Copy link
Author

@evanunderscore thank you very much for the response. It is very informative.

It's possible you could modify your wrapper to print the prompt yourself (e.g. like this) however I don't expect it will be simple and I doubt we'd want to add this functionality to argcomplete itself.

I looked at the link, did a test and it works perfectly. And it is very simple, a single line of python code. Though a single line installing the psutil package, which just for this would be a waste. The psutil package could be avoided if argcomplete provides the process id of the shell, which I assume wouldn't be difficult either. Could you please explain further the reasons why you wouldn't want to add this to argcomplete. To me it seems like a reasonable feature to have.

@evanunderscore
Copy link
Collaborator

evanunderscore commented Nov 27, 2020

My main hesitation is that the project is primarily intended to integrate with Bash completion rather than extend it. I was assuming the implementation would be complex, but if it isn't then perhaps there is no harm. If you already have a solution ready, feel free to raise a PR and we can discuss from there.

@mauvilsa
Copy link
Author

Sure I will look into creating a pull request. I could have this as part of my code. But it would be much better if it becomes part of argcomplete so that it benefits other people.

@mauvilsa
Copy link
Author

mauvilsa commented Nov 27, 2020

Just to give you a bit of more context. I am working on an extension of argparse and the idea is that argcomplete will work out of the box. No need to call argcomplete.autocomplete or implement or assign completer functions to added arguments unless some special behavior is wanted.

For you to try what I have been working on, install pip install "jsonargparse[signatures,argcomplete]==3.0.0rc3". Then an example command line tool would be:

#!/usr/bin/env python3

from enum import Enum
from typing import Optional, Dict
from jsonargparse import ArgumentParser, ActionConfigFile
from jsonargparse.typing import PositiveFloat, Email

class MyEnum(Enum):
    abc = 1
    xyz = 2

parser = ArgumentParser()
parser.add_argument('--config', action=ActionConfigFile)
parser.add_argument('--bool', type=Optional[bool])
parser.add_argument('--float', type=Optional[float])
parser.add_argument('--int', type=int)
parser.add_argument('--positive', type=PositiveFloat)
parser.add_argument('--email', type=Email)
parser.add_argument('--dict', type=Dict[str, int])
parser.add_argument('--enum', type=Optional[MyEnum])

parser.parse_args()  # argcomplete.autocomplete called inside

With this the completion (hopefully) already works as:

$ ./example.py --bool=<TAB><TAB>
false  null   true   
$ ./example.py --bool=f<TAB>
$ ./example.py --bool=false
$ ./example.py --int=<TAB><TAB>
 value not yet valid, expected type int
$ ./example.py --int=1<TAB><TAB>
 value already valid, expected type int
$ ./example.py --dict=<TAB><TAB>
 value not yet valid, expected type Dict[str, int]
$ ./example.py --dict='{"v": <TAB><TAB>
 value not yet valid, expected type Dict[str, int]
$ ./example.py --dict='{"v": 1}'<TAB><TAB>
 value already valid, expected type Dict[str, int]

I didn't want to add psutil as new requirement so for now this only works on linux and I guess also on mac. You can see the relevant code in jsonargparse/optionals.py#L143-L153

@mauvilsa
Copy link
Author

The argcomplete_warn_redraw_prompt function is kind of a hack. If this were to be integrated into argcomplete it would be different. First the process id could be provided for example in an environment variable _ARGCOMPLETE_SHELL_PID. Then implement a way to print message and redraw the prompt which would use the env var. Finally have the possibility that a completer not return any completions while not falling back to the bash completer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants