Skip to content
Open
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
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]:
"""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)
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:
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
Loading