Skip to content

Commit

Permalink
Merge pull request #778 from stopthatcow/hotfix/fix_bashcompletion_on…
Browse files Browse the repository at this point in the history
…_chained_commands

Backport fixes for autocompletion on chained commands
  • Loading branch information
untitaker authored May 5, 2017
2 parents 5701c40 + 29ab0b0 commit 2ab5f7e
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Version 6.8
#728.
- Fix bug in test runner when calling ``sys.exit`` with ``None``. See #739.
- Fix crash on Windows console, see #744.
- Fix bashcompletion on chained commands. See #754.

Version 6.7
-----------
Expand Down
22 changes: 16 additions & 6 deletions click/_bashcomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,27 @@ def get_completion_script(prog_name, complete_var):

def resolve_ctx(cli, prog_name, args):
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand):
a = ctx.protected_args + ctx.args
cmd = ctx.command.get_command(ctx, a[0])
args_remaining = ctx.protected_args + ctx.args
while ctx is not None and args_remaining:
if isinstance(ctx.command, MultiCommand):
cmd = ctx.command.get_command(ctx, args_remaining[0])
if cmd is None:
return None
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
return ctx
ctx = cmd.make_context(args_remaining[0], args_remaining[1:], parent=ctx, resilient_parsing=True)
args_remaining = ctx.protected_args + ctx.args
else:
ctx = ctx.parent

return ctx

def get_choices(cli, prog_name, args, incomplete):
ctx = resolve_ctx(cli, prog_name, args)
if ctx is None:
return

choices = []
if incomplete and not incomplete[:1].isalnum():
incomplete_is_start_of_option = incomplete and not incomplete[:1].isalnum()
if incomplete_is_start_of_option:
for param in ctx.command.params:
if not isinstance(param, Option):
continue
Expand All @@ -54,6 +59,11 @@ def get_choices(cli, prog_name, args, incomplete):
elif isinstance(ctx.command, MultiCommand):
choices.extend(ctx.command.list_commands(ctx))

if not incomplete_is_start_of_option and ctx.parent is not None and isinstance(ctx.parent.command, MultiCommand) and ctx.parent.command.chain:
# completion for chained commands
remaining_comands = set(ctx.parent.command.list_commands(ctx.parent))-set(ctx.parent.protected_args)
choices.extend(remaining_comands)

for item in choices:
if item.startswith(incomplete):
yield item
Expand Down
25 changes: 25 additions & 0 deletions tests/test_bashcomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,28 @@ def csub(csub_opt):
assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '')) == ['csub']
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt']
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == []


def test_chaining():
@click.group('cli', chain=True)
@click.option('--cli-opt')
def cli(cli_opt):
pass

@cli.command('asub')
@click.option('--asub-opt')
def asub(asub_opt):
pass

@cli.command('bsub')
@click.option('--bsub-opt')
def bsub(bsub_opt, arg):
pass

assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt']
assert list(get_choices(cli, 'lol', [], '')) == ['asub', 'bsub']
assert list(get_choices(cli, 'lol', ['asub'], '-')) == ['--asub-opt']
assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub']
assert list(get_choices(cli, 'lol', ['bsub'], '')) == ['asub']
assert list(get_choices(cli, 'lol', ['asub', '--asub-opt', '5', 'bsub'], '-')) == ['--bsub-opt']
assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt']

0 comments on commit 2ab5f7e

Please sign in to comment.