Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Unreleased
- Fix reconciliation of `default`, `flag_value` and `type` parameters for
flag options, as well as parsing and normalization of environment variables.
:issue:`2952` :pr:`2956`
- Fix completion for the Z shell (``zsh``) for completion items containing
colons. :issue:`2703` :pr:`2846`

Version 8.2.1
-------------
Expand Down
22 changes: 21 additions & 1 deletion src/click/shell_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ def __getattr__(self, name: str) -> t.Any:
%(complete_func)s_setup;
"""

# See ZshComplete.format_completion below, and issue #2703, before
# changing this script.
#
# (TL;DR: _describe is picky about the format, but this Zsh script snippet
# is already widely deployed. So freeze this script, and use clever-ish
# handling of colons in ZshComplet.format_completion.)
_SOURCE_ZSH = """\
#compdef %(prog_name)s

Expand Down Expand Up @@ -373,7 +379,21 @@ def get_completion_args(self) -> tuple[list[str], str]:
return args, incomplete

def format_completion(self, item: CompletionItem) -> str:
return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
help_ = item.help or "_"
# The zsh completion script uses `_describe` on items with help
# texts (which splits the item help from the item value at the
# first unescaped colon) and `compadd` on items without help
# text (which uses the item value as-is and does not support
# colon escaping). So escape colons in the item value if and
# only if the item help is not the sentinel "_" value, as used
# by the completion script.
#
# (The zsh completion script is potentially widely deployed, and
# thus harder to fix than this method.)
#
# See issue #1812 and issue #2703 for further context.
value = item.value.replace(":", r"\:") if help_ != "_" else item.value
return f"{item.type}\n{value}\n{help_}"


class FishComplete(ShellComplete):
Expand Down
75 changes: 75 additions & 0 deletions tests/test_shell_completion.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import textwrap
import warnings
from collections.abc import Mapping

import pytest

Expand Down Expand Up @@ -354,6 +356,79 @@ def test_full_complete(runner, shell, env, expect):
assert result.output == expect


@pytest.mark.parametrize(
("env", "expect"),
[
(
{"COMP_WORDS": "", "COMP_CWORD": "0"},
textwrap.dedent(
"""\
plain
a
_
plain
b
bee
plain
c\\:d
cee:dee
plain
c:e
_
"""
),
),
(
{"COMP_WORDS": "a c", "COMP_CWORD": "1"},
textwrap.dedent(
"""\
plain
c\\:d
cee:dee
plain
c:e
_
"""
),
),
(
{"COMP_WORDS": "a c:", "COMP_CWORD": "1"},
textwrap.dedent(
"""\
plain
c\\:d
cee:dee
plain
c:e
_
"""
),
),
],
)
@pytest.mark.usefixtures("_patch_for_completion")
def test_zsh_full_complete_with_colons(
runner, env: Mapping[str, str], expect: str
) -> None:
cli = Group(
"cli",
commands=[
Command("a"),
Command("b", help="bee"),
Command("c:d", help="cee:dee"),
Command("c:e"),
],
)
result = runner.invoke(
cli,
env={
**env,
"_CLI_COMPLETE": "zsh_complete",
},
)
assert result.output == expect


@pytest.mark.usefixtures("_patch_for_completion")
def test_context_settings(runner):
def complete(ctx, param, incomplete):
Expand Down
Loading