Repository: pallets/click

Issue:
Completion of subcommand options after last argument does not work
* Install latest `HEAD` version of `click`:
```
$ pip3 install -e git+git://github.com/pallets/click.git@132d66ac7d69a2b0e8f218a4cd39e50be3e0bcb9#egg=click
```
* Save the following in a file called `foo` on your `PATH`, ensuring it's executable:
```python
#!/usr/bin/env python3

import click

def _complete(ctx, args, incomplete):
    return ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']

@click.group()
def entrypoint():
    pass

@click.command()
@click.option('--opt', autocompletion=_complete)
@click.argument('arg', nargs=-1)
def subcommand(opt, arg):
    pass

entrypoint.add_command(subcommand)

entrypoint()
```
* Source completion code
```sh
source <(_FOO_COMPLETE=source foo)
```
---
**Expected Behaviour**
```
$ foo subcommand --opt <TAB>
abc  def  ghi  jkl  mno  pqr  stu  vwx  yz

$ foo subcommand whatever --opt <TAB>
abc  def  ghi  jkl  mno  pqr  stu  vwx  yz
```
**Actual Behaviour**
```
# WORKS
$ foo subcommand --opt <TAB>
abc  def  ghi  jkl  mno  pqr  stu  vwx  yz

# BROKEN
$ foo subcommand whatever --opt <TAB>
<list of files in current directory (i.e. fallback when no results)>
```



Discussion:

Resolving patch:
diff --git a/click/_bashcomplete.py b/click/_bashcomplete.py
--- a/click/_bashcomplete.py
+++ b/click/_bashcomplete.py
@@ -73,18 +73,31 @@ def resolve_ctx(cli, prog_name, args):
     :return: the final context/command parsed
     """
     ctx = cli.make_context(prog_name, args, resilient_parsing=True)
-    args_remaining = ctx.protected_args + ctx.args
-    while ctx is not None and args_remaining:
+    args = ctx.protected_args + ctx.args
+    while args:
         if isinstance(ctx.command, MultiCommand):
-            cmd = ctx.command.get_command(ctx, args_remaining[0])
-            if cmd is None:
-                return None
-            ctx = cmd.make_context(
-                args_remaining[0], args_remaining[1:], parent=ctx, resilient_parsing=True)
-            args_remaining = ctx.protected_args + ctx.args
+            if not ctx.command.chain:
+                cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
+                if cmd is None:
+                    return ctx
+                ctx = cmd.make_context(cmd_name, args, parent=ctx,
+                                       resilient_parsing=True)
+                args = ctx.protected_args + ctx.args
+            else:
+                # Walk chained subcommand contexts saving the last one.
+                while args:
+                    cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
+                    if cmd is None:
+                        return ctx
+                    sub_ctx = cmd.make_context(cmd_name, args, parent=ctx,
+                                               allow_extra_args=True,
+                                               allow_interspersed_args=False,
+                                               resilient_parsing=True)
+                    args = sub_ctx.args
+                ctx = sub_ctx
+                args = sub_ctx.protected_args + sub_ctx.args
         else:
-            ctx = ctx.parent
-
+            break
     return ctx
 
 
@@ -216,12 +229,7 @@ def get_choices(cli, prog_name, args, incomplete):
     # completion for argument values from user supplied values
     for param in ctx.command.params:
         if is_incomplete_argument(ctx.params, param):
-            completions.extend(get_user_autocompletions(
-                ctx, all_args, incomplete, param))
-            # Stop looking for other completions only if this argument is required.
-            if param.required:
-                return completions
-            break
+            return get_user_autocompletions(ctx, all_args, incomplete, param)
 
     add_subcommand_completions(ctx, incomplete, completions)
     return completions
diff --git a/click/core.py b/click/core.py
--- a/click/core.py
+++ b/click/core.py
@@ -182,7 +182,8 @@ class Context(object):
                               add some safety mapping on the right.
     :param resilient_parsing: if this flag is enabled then Click will
                               parse without any interactivity or callback
-                              invocation.  This is useful for implementing
+                              invocation.  Default values will also be
+                              ignored.  This is useful for implementing
                               things such as completion support.
     :param allow_extra_args: if this is set to `True` then extra arguments
                              at the end will not raise an error and will be
@@ -312,7 +313,8 @@ def __init__(self, command, parent=None, info_name=None, obj=None,
         self.token_normalize_func = token_normalize_func
 
         #: Indicates if resilient parsing is enabled.  In that case Click
-        #: will do its best to not cause any failures.
+        #: will do its best to not cause any failures and default values
+        #: will be ignored. Useful for completion.
         self.resilient_parsing = resilient_parsing
 
         # If there is no envvar prefix yet, but the parent has one and
@@ -1177,7 +1179,7 @@ def resolve_command(self, ctx, args):
         # an option we want to kick off parsing again for arguments to
         # resolve things like --help which now should go to the main
         # place.
-        if cmd is None:
+        if cmd is None and not ctx.resilient_parsing:
             if split_opt(cmd_name)[0]:
                 self.parse_args(ctx, ctx.args)
             ctx.fail('No such command "%s".' % original_cmd_name)
@@ -1433,7 +1435,7 @@ def value_is_missing(self, value):
     def full_process_value(self, ctx, value):
         value = self.process_value(ctx, value)
 
-        if value is None:
+        if value is None and not ctx.resilient_parsing:
             value = self.get_default(ctx)
 
         if self.required and self.value_is_missing(value):


Test patch:
diff --git a/tests/test_bashcomplete.py b/tests/test_bashcomplete.py
--- a/tests/test_bashcomplete.py
+++ b/tests/test_bashcomplete.py
@@ -132,7 +132,8 @@ def csub(csub_opt, color):
 def test_chaining():
     @click.group('cli', chain=True)
     @click.option('--cli-opt')
-    def cli(cli_opt):
+    @click.argument('arg', type=click.Choice(['cliarg1', 'cliarg2']))
+    def cli(cli_opt, arg):
         pass
 
     @cli.command()
@@ -142,33 +143,35 @@ def asub(asub_opt):
 
     @cli.command(help='bsub help')
     @click.option('--bsub-opt')
-    @click.argument('arg', type=click.Choice(['arg1', 'arg2']), required=True)
+    @click.argument('arg', type=click.Choice(['arg1', 'arg2']))
     def bsub(bsub_opt, arg):
         pass
 
     @cli.command()
     @click.option('--csub-opt')
-    @click.argument('arg', type=click.Choice(['carg1', 'carg2']), required=False)
+    @click.argument('arg', type=click.Choice(['carg1', 'carg2']), default='carg1')
     def csub(csub_opt, arg):
         pass
 
     assert choices_without_help(cli, [], '-') == ['--cli-opt']
-    assert choices_without_help(cli, [], '') == ['asub', 'bsub', 'csub']
-    assert choices_without_help(cli, ['asub'], '-') == ['--asub-opt']
-    assert choices_without_help(cli, ['asub'], '') == ['bsub', 'csub']
-    assert choices_without_help(cli, ['bsub'], '') == ['arg1', 'arg2']
-    assert choices_without_help(cli, ['asub', '--asub-opt'], '') == []
-    assert choices_without_help(cli, ['asub', '--asub-opt', '5', 'bsub'], '-') == ['--bsub-opt']
-    assert choices_without_help(cli, ['asub', 'bsub'], '-') == ['--bsub-opt']
-    assert choices_with_help(cli, ['asub'], 'b') == [('bsub', 'bsub help')]
-    assert choices_without_help(cli, ['asub', 'csub'], '-') == ['--csub-opt']
+    assert choices_without_help(cli, [], '') == ['cliarg1', 'cliarg2']
+    assert choices_without_help(cli, ['cliarg1', 'asub'], '-') == ['--asub-opt']
+    assert choices_without_help(cli, ['cliarg1', 'asub'], '') == ['bsub', 'csub']
+    assert choices_without_help(cli, ['cliarg1', 'bsub'], '') == ['arg1', 'arg2']
+    assert choices_without_help(cli, ['cliarg1', 'asub', '--asub-opt'], '') == []
+    assert choices_without_help(cli, ['cliarg1', 'asub', '--asub-opt', '5', 'bsub'], '-') == ['--bsub-opt']
+    assert choices_without_help(cli, ['cliarg1', 'asub', 'bsub'], '-') == ['--bsub-opt']
+    assert choices_without_help(cli, ['cliarg1', 'asub', 'csub'], '') == ['carg1', 'carg2']
+    assert choices_without_help(cli, ['cliarg1', 'bsub', 'arg1', 'csub'], '') == ['carg1', 'carg2']
+    assert choices_without_help(cli, ['cliarg1', 'asub', 'csub'], '-') == ['--csub-opt']
+    assert choices_with_help(cli, ['cliarg1', 'asub'], 'b') == [('bsub', 'bsub help')]
 
 
 def test_argument_choice():
     @click.command()
-    @click.argument('arg1', required=False, type=click.Choice(['arg11', 'arg12']))
-    @click.argument('arg2', required=False, type=click.Choice(['arg21', 'arg22']))
-    @click.argument('arg3', required=False, type=click.Choice(['arg', 'argument']))
+    @click.argument('arg1', required=True, type=click.Choice(['arg11', 'arg12']))
+    @click.argument('arg2', type=click.Choice(['arg21', 'arg22']), default='arg21')
+    @click.argument('arg3', type=click.Choice(['arg', 'argument']), default='arg')
     def cli():
         pass
 
@@ -182,7 +185,7 @@ def cli():
 def test_option_choice():
     @click.command()
     @click.option('--opt1', type=click.Choice(['opt11', 'opt12']), help='opt1 help')
-    @click.option('--opt2', type=click.Choice(['opt21', 'opt22']))
+    @click.option('--opt2', type=click.Choice(['opt21', 'opt22']), default='opt21')
     @click.option('--opt3', type=click.Choice(['opt', 'option']))
     def cli():
         pass
@@ -218,6 +221,8 @@ def cli():
     assert choices_without_help(cli, [''], '--opt1=') == ['opt11', 'opt12']
     assert choices_without_help(cli, [], '') == ['arg11', 'arg12']
     assert choices_without_help(cli, ['--opt2'], '') == ['opt21', 'opt22']
+    assert choices_without_help(cli, ['arg11'], '--opt') == ['--opt1', '--opt2']
+    assert choices_without_help(cli, [], '--opt') == ['--opt1', '--opt2']
 
 
 def test_boolean_flag_choice():
@@ -258,11 +263,37 @@ def cli(local_opt):
 
 def test_variadic_argument_choice():
     @click.command()
+    @click.option('--opt', type=click.Choice(['opt1', 'opt2']))
     @click.argument('src', nargs=-1, type=click.Choice(['src1', 'src2']))
     def cli(local_opt):
         pass
 
     assert choices_without_help(cli, ['src1', 'src2'], '') == ['src1', 'src2']
+    assert choices_without_help(cli, ['src1', 'src2'], '--o') == ['--opt']
+    assert choices_without_help(cli, ['src1', 'src2', '--opt'], '') == ['opt1', 'opt2']
+    assert choices_without_help(cli, ['src1', 'src2'], '') == ['src1', 'src2']
+
+
+def test_variadic_argument_complete():
+
+    def _complete(ctx, args, incomplete):
+        return ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
+
+    @click.group()
+    def entrypoint():
+        pass
+
+    @click.command()
+    @click.option('--opt', autocompletion=_complete)
+    @click.argument('arg', nargs=-1)
+    def subcommand(opt, arg):
+        pass
+
+    entrypoint.add_command(subcommand)
+
+    assert choices_without_help(entrypoint, ['subcommand', '--opt'], '') == _complete(0,0,0)
+    assert choices_without_help(entrypoint, ['subcommand', 'whatever', '--opt'], '') == _complete(0,0,0)
+    assert choices_without_help(entrypoint, ['subcommand', 'whatever', '--opt', 'abc'], '') == []
 
 
 def test_long_chain_choice():
@@ -273,7 +304,7 @@ def cli():
     @cli.group()
     @click.option('--sub-opt', type=click.Choice(['subopt1', 'subopt2']))
     @click.argument('sub-arg', required=False, type=click.Choice(['subarg1', 'subarg2']))
-    def sub(sub_opt):
+    def sub(sub_opt, sub_arg):
         pass
 
     @sub.command(short_help='bsub help')
@@ -283,15 +314,60 @@ def sub(sub_opt):
     def bsub(bsub_opt):
         pass
 
-    assert choices_with_help(cli, ['sub'], '') == [('subarg1', None), ('subarg2', None), ('bsub', 'bsub help')]
+    @sub.group('csub')
+    def csub():
+        pass
+
+    @csub.command()
+    def dsub():
+        pass
+
+    assert choices_with_help(cli, ['sub', 'subarg1'], '') == [('bsub', 'bsub help'), ('csub', '')]
+    assert choices_without_help(cli, ['sub'], '') == ['subarg1', 'subarg2']
     assert choices_without_help(cli, ['sub', '--sub-opt'], '') == ['subopt1', 'subopt2']
     assert choices_without_help(cli, ['sub', '--sub-opt', 'subopt1'], '') == \
-        ['subarg1', 'subarg2', 'bsub']
+           ['subarg1', 'subarg2']
+    assert choices_without_help(cli,
+                                ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub'], '-') == ['--bsub-opt']
+    assert choices_without_help(cli,
+                                ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub'], '') == ['bsubarg1', 'bsubarg2']
     assert choices_without_help(cli,
-        ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub'], '-') == ['--bsub-opt']
+                                ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt'], '') == \
+           ['bsubopt1', 'bsubopt2']
     assert choices_without_help(cli,
-        ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt'], '') == \
-        ['bsubopt1', 'bsubopt2']
+                                ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt', 'bsubopt1', 'bsubarg1'],
+                                '') == ['bbsubarg1', 'bbsubarg2']
     assert choices_without_help(cli,
-        ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub', '--bsub-opt', 'bsubopt1', 'bsubarg1'],
-                            '') == ['bbsubarg1', 'bbsubarg2']
+                                ['sub', '--sub-opt', 'subopt1', 'subarg1', 'csub'],
+                                '') == ['dsub']
+
+
+def test_chained_multi():
+    @click.group()
+    def cli():
+        pass
+
+    @cli.group()
+    def sub():
+        pass
+
+    @sub.group()
+    def bsub():
+        pass
+
+    @sub.group(chain=True)
+    def csub():
+        pass
+
+    @csub.command()
+    def dsub():
+        pass
+
+    @csub.command()
+    def esub():
+        pass
+
+    assert choices_without_help(cli, ['sub'], '') == ['bsub', 'csub']
+    assert choices_without_help(cli, ['sub'], 'c') == ['csub']
+    assert choices_without_help(cli, ['sub', 'csub'], '') == ['dsub', 'esub']
+    assert choices_without_help(cli, ['sub', 'csub', 'dsub'], '') == ['esub']