diff --git a/src/click/shell_completion.py b/src/click/shell_completion.py index 34b564527..69551ef25 100644 --- a/src/click/shell_completion.py +++ b/src/click/shell_completion.py @@ -30,8 +30,10 @@ for completion in $response; do IFS=',' read type value <<< "$completion" + COMPREPLY+=($type) + COMPREPLY+=($value) if [[ $type == 'dir' ]]; then - COMREPLY=() + # COMREPLY=() compopt -o dirnames elif [[ $type == 'file' ]]; then COMREPLY=() @@ -86,12 +88,31 @@ compdef %(complete_func)s %(script_names)s """ -COMPLETION_SCRIPT_FISH = ( - "complete --no-files --command %(script_names)s --arguments" - ' "(env %(autocomplete_var)s=complete_fish' - " COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t)" - ' %(script_names)s)"' -) + +COMPLETION_SCRIPT_FISH = """ +function %(complete_func)s_complete; + set -l response; + for value in (env %(autocomplete_var)s=complete_fish \ + COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) \ + %(script_names)s); + set response $response $value; + end; + + for completion in $response; + set -l metadata (string split "," $completion); + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "none"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(script_names)s --arguments \ + "(%(complete_func)s_complete)" +""" _invalid_ident_char_re = re.compile(r"[^a-zA-Z0-9_]") @@ -133,7 +154,15 @@ def source(self): return COMPLETION_SCRIPT_BASH def complete(self): - completions = do_completion(self.cli, self.prog_name) + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + completions = do_complete(self.cli, self.prog_name, args, incomplete) for item in completions: echo(f"{item[0]},{item[1]}") @@ -147,7 +176,15 @@ def source(self): return COMPLETION_SCRIPT_ZSH def complete(self): - completions = do_completion(self.cli, self.prog_name) + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + completions = do_complete(self.cli, self.prog_name, args, incomplete) for item in completions: echo(item[1]) echo(item[2] if item[2] else "_") @@ -162,25 +199,26 @@ def source(self): return COMPLETION_SCRIPT_FISH def complete(self): - completions = do_completion(self.cli, self.prog_name) + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + # Fish is weird in that a partially completed path is registered in + # both `COMP_WORDS` and `COMP_CWORD`. + if incomplete and args and args[-1] == incomplete: + args.pop() + + completions = do_complete(self.cli, self.prog_name, args, incomplete) for item in completions: if item[2]: - echo(f"{item[1]}\t{item[2]}") + echo(f"{item[0]},{item[1]}\t{item[2]}") else: - echo(item[1]) + echo(f"{item[0]},{item[1]}") return True -def do_completion(cli, prog_name): - cwords = split_arg_string(os.environ["COMP_WORDS"]) - cword = int(os.environ["COMP_CWORD"]) - args = cwords[1:cword] - try: - incomplete = cwords[cword] - except IndexError: - incomplete = "" - +def do_complete(cli, prog_name, args, incomplete): all_args = copy.deepcopy(args) ctx = resolve_ctx(cli, prog_name, args) if ctx is None: