click-completion icon indicating copy to clipboard operation
click-completion copied to clipboard

No completion for choices with whitespace.

Open mhils opened this issue 4 years ago • 1 comments

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. :)

mhils avatar Oct 18 '19 20:10 mhils

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?

mhils avatar Oct 18 '19 23:10 mhils