Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
================


.. _version-unreleased:

Unreleased
==========

- Fix completion stopping after a boolean flag; remaining options and
arguments now complete (#116)


.. _version-0.4.1:

0.4.1
Expand Down
8 changes: 6 additions & 2 deletions click_repl/_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,12 @@ def _get_completion_for_cmd_args(
for option in opts:
# We want to make sure if this parameter was called
# If we are inside a parameter that was called, we want to show only
# relevant choices
if option in current_args: # noqa: E203
# relevant choices. A boolean flag consumes no value, so it must
# not short-circuit completion of the parameters that follow it.
# See https://github.com/click-contrib/click-repl/issues/116
if option in current_args and not getattr( # noqa: E203
param, "is_flag", False
):
param_called = True
break

Expand Down
56 changes: 45 additions & 11 deletions tests/test_completion/test_common_tests/test_option_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,33 +41,67 @@ def bool_option(foo):
def test_flag_option_offers_no_value():
# A flag (is_flag=True) does not consume a value, so once it has been
# typed the completer must not offer true/false as if it needed one.
# It must, however, keep completing the command's other parameters
# instead of stopping.
# See https://github.com/click-contrib/click-repl/issues/116
@root_command.command()
@click.argument("arg1", type=click.STRING)
@click.argument("arg1", type=click.Choice(("alpha", "beta")))
@click.option("-b", "--some-option", "some_option", is_flag=True)
def flag_option(arg1, some_option):
pass

completions = list(c.get_completions(Document("flag-option -b ")))
assert {x.text for x in completions} == set()
completions = {x.text for x in c.get_completions(Document("flag-option -b "))}
# No true/false value completion for the flag itself.
assert "true" not in completions and "false" not in completions
# Remaining argument still completes.
assert {"alpha", "beta"} <= completions

completions = list(c.get_completions(Document("flag-option --some-option ")))
assert {x.text for x in completions} == set()
completions = {x.text for x in c.get_completions(Document("flag-option --some-option "))}
assert "true" not in completions and "false" not in completions
assert {"alpha", "beta"} <= completions


def test_boolean_flag_with_secondary_opts_offers_no_value():
# Flags declared with a secondary option (--foo/--no-foo) are also flags
# and must not trigger true/false value completions.
# and must not trigger true/false value completions, while still letting
# the remaining parameters complete.
@root_command.command()
@click.argument("arg1", type=click.Choice(("alpha", "beta")))
@click.option("--shout/--no-shout", default=False)
def toggle_flag(shout):
def toggle_flag(arg1, shout):
pass

completions = list(c.get_completions(Document("toggle-flag --shout ")))
assert {x.text for x in completions} == set()
completions = {x.text for x in c.get_completions(Document("toggle-flag --shout "))}
assert "true" not in completions and "false" not in completions
assert {"alpha", "beta"} <= completions

completions = list(c.get_completions(Document("toggle-flag --no-shout ")))
assert {x.text for x in completions} == set()
completions = {x.text for x in c.get_completions(Document("toggle-flag --no-shout "))}
assert "true" not in completions and "false" not in completions
assert {"alpha", "beta"} <= completions


def test_completion_continues_after_boolean_flag():
# A flag consumes no value, so after it has been typed the completer must
# still offer the command's remaining options and arguments instead of
# stopping. See https://github.com/click-contrib/click-repl/issues/116
@root_command.command()
@click.argument("arg1", type=click.Choice(("foo", "bar")))
@click.option("--verbose", is_flag=True)
@click.option("--mode", type=click.Choice(("fast", "slow")))
def continue_after_flag(arg1, verbose, mode):
pass

# Remaining options are still offered after the flag.
completions = list(c.get_completions(Document("continue-after-flag --verbose --")))
assert {x.text for x in completions} >= {"--verbose", "--mode"}

# The positional argument still completes after the flag.
completions = list(c.get_completions(Document("continue-after-flag --verbose ")))
assert {"foo", "bar"} <= {x.text for x in completions}

# Still works after more than one flag.
completions = list(c.get_completions(Document("continue-after-flag --verbose ba")))
assert {x.text for x in completions} == {"bar"}


def test_only_unused_with_unique_option():
Expand Down
Loading