click-completion
click-completion copied to clipboard
No completion for choices with whitespace.
Hi there,
Thanks for the fantastic library! 😃 Small bug report: I am encountering some issues with completing arguments that contain whitespace:
@click.command()
@click.option("--foo", type=click.Choice(["Foo Bar", "Foo Qux"]))
def cli(foo):
print(foo)
λ cli --foo
<TAB>
λ cli --foo Foo\
<TAB>
(nothing changes and no options are displayed)
click-completion: 0.5.2 shell: fish
My suspicion was that the .endswith
check here could be culprit:
https://github.com/click-contrib/click-completion/blob/6e08a5fa43149c822152d40c07e00be5ec2c5c7e/click_completion/core.py#L191-L196 I patched split_args
to return an (args, incomplete)
tuple, but that did not cut it (edit: see below). Given that the changes got rather messy at this point, I stopped here hoping there might be an easier fix. :)
Ok, turns out my initial patching attempt was incomplete. This works and fixes the issue:
diff --git a/click_completion/core.py b/click_completion/core.py
index 867085e..4f83e48 100644
--- a/click_completion/core.py
+++ b/click_completion/core.py
@@ -188,12 +188,8 @@ def do_fish_complete(cli, prog_name):
True if the completion was successful, False otherwise
"""
commandline = os.environ['COMMANDLINE']
- args = split_args(commandline)[1:]
- if args and not commandline.endswith(' '):
- incomplete = args[-1]
- args = args[:-1]
- else:
- incomplete = ''
+ args, incomplete = split_args(commandline)
+ args = args[1:]
for item, help in get_choices(cli, prog_name, args, incomplete):
if help:
diff --git a/click_completion/lib.py b/click_completion/lib.py
index fc195cd..ddee5da 100644
--- a/click_completion/lib.py
+++ b/click_completion/lib.py
@@ -101,23 +101,35 @@ def split_args(line):
Returns
-------
- [str]
- The line split in separated arguments
+ [str], str
+ The line split in separated arguments, plus the last incomplete argument (if any)
"""
lex = shlex.shlex(line, posix=True)
lex.whitespace_split = True
lex.commenters = ''
res = []
+ last_state = lex.state
try:
while True:
+ last_state = lex.state
res.append(next(lex))
except ValueError: # No closing quotation
- pass
+ return res, lex.token
except StopIteration: # End of loop
- pass
- if lex.token:
- res.append(lex.token)
- return res
+ if last_state is None:
+ return res[:-1], res[-1]
+ else:
+ return res, ''
+
+
+def test_split_args():
+ assert split_args("foo bar") == (["foo"], "bar")
+ assert split_args("foo bar ") == (["foo", "bar"], "")
+ assert split_args("foo 'bar") == (["foo"], "bar")
+ assert split_args("foo 'bar ") == (["foo"], "bar ")
+ assert split_args("foo 'bar baz'") == (["foo"], "bar baz")
+ assert split_args("foo 'bar baz' ") == (["foo", "bar baz"], "")
+ assert split_args("foo bar\\ ") == (["foo"], "bar ")
There's the obvious question if the same sort of fix should be applied to the other shells as well. Any thoughts?