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
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.. currentmodule:: click

Version 8.4.dev
Version 8.4.1
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not include version changes I think?

------------------

Unreleased
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "click"
version = "8.4.dev"
version = "8.4.1.dev"
description = "Composable command line interface toolkit"
readme = "README.md"
license = "BSD-3-Clause"
Expand Down
56 changes: 40 additions & 16 deletions src/click/shell_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def shell_complete(
prog_name: str,
complete_var: str,
instruction: str,
) -> int:
) -> t.Literal[0, 1]:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should perhaps create a type for ExitCode, but at the same time, I'm not sure there's that much value. Kind of good to know the specific exit codes as well. 🤷️

"""Perform shell completion for the given CLI program.

:param cli: Command being called.
Expand Down Expand Up @@ -54,7 +54,16 @@ def shell_complete(
return 1


class CompletionItem:
if t.TYPE_CHECKING:
from typing_extensions import TypeVar

# `Any` is used as default for backwards compatibility (instead of e.g. `str`)
_ValueT_co = TypeVar("_ValueT_co", covariant=True, default=t.Any)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the underscore prefix, or at least I don't think we should as we don't do it elsewhere.

else:
_ValueT_co = t.TypeVar("_ValueT_co", covariant=True)


class CompletionItem(t.Generic[_ValueT_co]):
"""Represents a completion value and metadata about the value. The
default metadata is ``type`` to indicate special shell handling,
and ``help`` if a shell supports showing a help string next to the
Expand All @@ -77,12 +86,12 @@ class CompletionItem:

def __init__(
self,
value: t.Any,
value: _ValueT_co,
type: str = "plain",
help: str | None = None,
**kwargs: t.Any,
) -> None:
self.value: t.Any = value
self.value: _ValueT_co = value
self.type: str = type
self.help: str | None = help
self._info = kwargs
Expand Down Expand Up @@ -201,6 +210,12 @@ def __getattr__(self, name: str) -> t.Any:
"""


class _SourceVarsDict(t.TypedDict):
complete_func: str
complete_var: str
prog_name: str


class ShellComplete:
"""Base class for providing shell completion support. A subclass for
a given shell will override attributes and methods to implement the
Expand Down Expand Up @@ -245,7 +260,7 @@ def func_name(self) -> str:
safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII)
return f"_{safe_name}_completion"

def source_vars(self) -> dict[str, t.Any]:
def source_vars(self) -> _SourceVarsDict:
"""Vars for formatting :attr:`source_template`.

By default this provides ``complete_func``, ``complete_var``,
Expand All @@ -272,7 +287,9 @@ def get_completion_args(self) -> tuple[list[str], str]:
"""
raise NotImplementedError

def get_completions(self, args: list[str], incomplete: str) -> list[CompletionItem]:
def get_completions(
self, args: list[str], incomplete: str
) -> list[CompletionItem[str]]:
"""Determine the context and last complete command or parameter
from the complete args. Call that object's ``shell_complete``
method to get the completions for the incomplete value.
Expand All @@ -284,7 +301,7 @@ def get_completions(self, args: list[str], incomplete: str) -> list[CompletionIt
obj, incomplete = _resolve_incomplete(ctx, args, incomplete)
return obj.shell_complete(ctx, incomplete)

def format_completion(self, item: CompletionItem) -> str:
def format_completion(self, item: CompletionItem[str]) -> str:
"""Format a completion item into the form recognized by the
shell script. This must be implemented by subclasses.

Expand Down Expand Up @@ -360,7 +377,7 @@ def get_completion_args(self) -> tuple[list[str], str]:

return args, incomplete

def format_completion(self, item: CompletionItem) -> str:
def format_completion(self, item: CompletionItem[t.Any]) -> str:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's Any here instead of str?

return f"{item.type},{item.value}"


Expand All @@ -382,7 +399,7 @@ def get_completion_args(self) -> tuple[list[str], str]:

return args, incomplete

def format_completion(self, item: CompletionItem) -> str:
def format_completion(self, item: CompletionItem[str]) -> str:
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
Expand Down Expand Up @@ -420,7 +437,7 @@ def get_completion_args(self) -> tuple[list[str], str]:

return args, incomplete

def format_completion(self, item: CompletionItem) -> str:
def format_completion(self, item: CompletionItem[str]) -> str:
"""
.. versionchanged:: 8.4
Escape newlines in value and help to fix completion errors with
Expand All @@ -437,19 +454,18 @@ def format_completion(self, item: CompletionItem) -> str:
return f"{item.type}\n{value}\n{help_escaped}"


ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]")


_available_shells: dict[str, type[ShellComplete]] = {
_available_shells: t.Final[dict[str, type[ShellComplete]]] = {
"bash": BashComplete,
"fish": FishComplete,
"zsh": ZshComplete,
}

_ShellCompleteT = t.TypeVar("_ShellCompleteT", bound="ShellComplete")


def add_completion_class(
cls: ShellCompleteType, name: str | None = None
) -> ShellCompleteType:
cls: type[_ShellCompleteT], name: str | None = None
) -> type[_ShellCompleteT]:
"""Register a :class:`ShellComplete` subclass under the given name.
The name will be provided by the completion instruction environment
variable during completion.
Expand All @@ -467,6 +483,14 @@ def add_completion_class(
return cls


@t.overload
def get_completion_class(shell: t.Literal["bash"]) -> type[BashComplete]: ...
@t.overload
def get_completion_class(shell: t.Literal["fish"]) -> type[FishComplete]: ...
@t.overload
def get_completion_class(shell: t.Literal["zsh"]) -> type[ZshComplete]: ...
@t.overload
def get_completion_class(shell: str) -> type[ShellComplete] | None: ...
def get_completion_class(shell: str) -> type[ShellComplete] | None:
"""Look up a registered :class:`ShellComplete` subclass by the name
provided by the completion instruction environment variable. If the
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading