click-contrib / click-completion

Add or enhance bash, fish, zsh and powershell completion in Click
MIT License
288 stars 32 forks source link

Completion for file-paths/directories #6

Closed makkus closed 6 years ago

makkus commented 6 years ago

Is it possible to do filename completion without having to load all files of a matching folder in the complete method? That strikes me as very expensive if there are a lot of files.

Currently, I have something like:

    def complete(self, ctx, incomplete):

        if incomplete.startswith("/"):
            path_orig = incomplete
        else:
            path_orig = os.path.join(os.getcwd(), incomplete)

        path, file_prefix = path_orig.rsplit("/", 1)

        result = []
        if not path:
            path = "/"

        for filename in os.listdir(path):

            if not file_prefix or filename.startswith(file_prefix):

                file_path = os.path.join(path, filename)
                if os.path.isdir(file_path):
                    filename += "/"

                if incomplete.startswith("/"):
                    result.append(os.path.join(path, filename))
                else:
                    result.append(filename)

        return result

This will complete a folder with an appended '/', but also a whitespace, and there's no way to complete on the content of this folder? Am I missing something? Thanks!

glehmann commented 6 years ago

I don't think there is currently a great way to do that. That definitely something that would be nice, if someone wants to give it a try!

For now, in our private application, we are completing with both the directory name and the directory name with a trailing slash:

$ myapp d<tab>
dir1
dir1/
dir2
dir2/

because there are two entries that begin exactly the same, the completion is done up to the full dir name, without the slash, and without space, so it is possilbe to continue the completion process. Obviously the drawback is that the completion list is polluted by twice the amount of entries…

makkus commented 6 years ago

Ah, nice workaround, thanks! Will use that for now.

flokno commented 5 years ago

Hi @makkus and @glehmann , could you give a more complete example on how def complete .. is used to perform filename completion?

I use the fish shell and the autocompletion is currently useless to me since filenames are not completed :/

p.s. It seems that

from glob import glob
import click

complete_filenames = click.Choice(glob("**", recursive=True))

...
@click.argument("filename", type=complete_filenames)
...

does the job for me. Anyway if you have other suggestions, please let me know. Thanks!

p.p.s it does only when the directory tree is too large.

p.p.p.s this seems to be sufficient and doesn't require glob or anything:

complete_filenames = click.Path(exists=True)

p.p.p.p.s. only on fish 2.2.0 though, I noticed. I tried to understand changes in the fish completion API after 2.2.0, but did not come too far until now. Any ideas?