fastapi / typer

Typer, build great CLIs. Easy to code. Based on Python type hints.
https://typer.tiangolo.com/
MIT License
15.75k stars 671 forks source link

Autocompletion for `Path` #951

Open tiangolo opened 2 months ago

tiangolo commented 2 months ago

Privileged issue

Issue Content

It seems autocompletion for Path types is not working correctly.

from pathlib import Path

import typer

app = typer.Typer()

@app.command()
def f(p: Path):
    print(p)

if __name__ == "__main__":
    app()

Running that, for example with Typer CLI:

$ typer demo.py run <TAB>

that should show the available files in the current directory. In Zsh it doesn't show the files, in Bash it shows the files.

Then, let's say it's run from the same Typer directory, where the LICENSE file is located:

$ typer demo.py run L<TAB>

That should autocomplete LICENSE, but it only adds TAB/spaces characters.

svlandeg commented 2 months ago

Related reports: #682, #625

svlandeg commented 2 months ago

I've been looking into this for a bit (these are quite technical internals 🤓)

So one route we could consider is something that's already marked in the code in _completion_classes.py, is to have the format_completion functions return CompletionItem.type alongside CompletionItem.value, cf. the commented lines for BashComplete:

    def format_completion(self, item: click.shell_completion.CompletionItem) -> str:
        # TODO: Explore replicating the new behavior from Click, with item types and
        # triggering completion for files and directories
        # return f"{item.type},{item.value}"
        return f"{item.value}"

Once we have access to the type, we can do something like Click does, e.g. their Bash script:

_SOURCE_BASH = """
(...)
    response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \
%(complete_var)s=bash_complete $1)

    for completion in $response; do
        IFS=',' read type value <<< "$completion"

        if [[ $type == 'dir' ]]; then
            COMPREPLY=()
            compopt -o dirnames
        elif [[ $type == 'file' ]]; then
            COMPREPLY=()
            compopt -o default
        elif [[ $type == 'plain' ]]; then
            COMPREPLY+=($value)
        fi
    done

    return 0
}
(...)
"""

which has specific support for different types. For plain types, the behaviour would stay as before, but for dir and file we can trigger autocompletion here. A quick check on Bash on my system confirms that this should work - but it will require proper implementation & testing for all the shells (I'm sure the docker container will be super useful for this!)

@tiangolo: is this the direction we want to go in? Or did you have a different type of solution in mind?