Skip to content

Commit

Permalink
reorganize new completion
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Sep 29, 2020
1 parent ec6ff56 commit 2dc3348
Show file tree
Hide file tree
Showing 9 changed files with 701 additions and 1,088 deletions.
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ classifiers =
Programming Language :: Python

[options]
python_modules = example
packages = find:
package_dir = = src
include_package_data = true
Expand All @@ -32,6 +33,10 @@ python_requires = >= 3.6
[options.packages.find]
where = src

[options.entry_points]
console_scripts =
example = example:cli

[tool:pytest]
testpaths = tests
filterwarnings =
Expand Down
4 changes: 0 additions & 4 deletions src/click/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@
from .formatting import wrap_text
from .globals import get_current_context
from .parser import OptionParser
from .shell_completion import add_completion_class
from .shell_completion import resolve_ctx
from .shell_completion import resolve_partial_value
from .shell_completion import ShellComplete
from .termui import clear
from .termui import confirm
from .termui import echo_via_pager
Expand Down
194 changes: 108 additions & 86 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from .types import BOOL
from .types import convert_type
from .types import IntRange
from .types import ParamType
from .utils import _detect_program_name
from .utils import echo
from .utils import make_default_short_help
Expand All @@ -48,27 +47,30 @@ def _maybe_show_deprecated_notice(cmd):
echo(style(DEPRECATED_INVOKE_NOTICE.format(name=cmd.name), fg="red"), err=True)


def fast_exit(code):
"""Exit without garbage collection, this speeds up exit by about 10ms for
things like bash completion.
def _fast_exit(code):
"""Low-level exit that skips Python's cleanup but speeds up exit by
about 10ms for things like shell completion.
:param code: Exit code.
"""
sys.stdout.flush()
sys.stderr.flush()
os._exit(code)


def _shell_complete(cmd, prog_name, complete_var=None):
"""Internal handler for the bash completion support."""
if complete_var is None:
complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper()
complete_instr = os.environ.get(complete_var)
if not complete_instr:
return
def _complete_visible_commands(ctx, incomplete):
"""List all the subcommands of a group that start with the
incomplete value and aren't hidden.
from .shell_completion import shell_complete
:param ctx: Invocation context for the group.
:param incomplete: Value being completed. May be empty.
"""
for name in ctx.command.list_commands(ctx):
if name.startswith(incomplete):
command = ctx.command.get_command(ctx, name)

if shell_complete(cmd, prog_name, complete_var, complete_instr):
fast_exit(1)
if not command.hidden:
yield name, command


def _check_multicommand(base_command, cmd_name, cmd, register=False):
Expand Down Expand Up @@ -130,14 +132,6 @@ def sort_key(item):
return sorted(declaration_order, key=sort_key)


def _get_visible_commands(ctx, incomplete):
for name in ctx.command.list_commands(ctx):
if name.startswith(incomplete):
command = ctx.command.get_command(ctx, name)
if not command.hidden:
yield name, command


class ParameterSource:
"""This is an enum that indicates the source of a command line parameter.
Expand Down Expand Up @@ -870,26 +864,36 @@ def invoke(self, ctx):
"""
raise NotImplementedError("Base commands are not invokable by default")

def shell_complete(self, ctx, all_args, incomplete):
"""Given a partial value and current arguments, this returns a list of
commands to complete the partial value by walking the context tree in search
of chained :class:`MultiCommand`.
def shell_complete(self, ctx, args, incomplete):
"""Return a list of completions for the incomplete value. Looks
at the names of chained multi-commands.
Any command could be part of a chained multi-command, so sibling
commands are valid at any point during command completion. Other
command classes will return more completions.
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
"""
results = []

while ctx.parent is not None:
ctx = ctx.parent

if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
remaining_commands = [
commands = (
(name, command)
for name, command in _get_visible_commands(ctx, incomplete)
for name, command in _complete_visible_commands(ctx, incomplete)
if name not in ctx.protected_args
]
)
results.extend(
[
("plain", name, command.get_short_help_str())
for name, command in remaining_commands
]
("plain", name, command.get_short_help_str())
for name, command in commands
)

return results

def main(
Expand Down Expand Up @@ -944,10 +948,18 @@ def main(
if prog_name is None:
prog_name = _detect_program_name()

# Hook for the Bash completion. This only activates if the Bash
# completion is actually enabled, otherwise this is quite a fast
# noop.
_shell_complete(self, prog_name, complete_var)
if complete_var is None:
complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper()

comp_instr = os.environ.get(complete_var)

# If the shell is asking for tab completion, process that. If
# the instruction was valid, exit early.
if comp_instr:
from .shell_completion import shell_complete

if shell_complete(self, prog_name, complete_var, comp_instr):
_fast_exit(1)

try:
try:
Expand Down Expand Up @@ -1255,29 +1267,35 @@ def invoke(self, ctx):
if self.callback is not None:
return ctx.invoke(self.callback, **ctx.params)

def shell_complete(self, ctx, all_args, incomplete):
"""Given a partial value and current arguments, this returns a list of
options that complete the partial value (if it begins with "-"), otherwise,
attempts to complete the partial value with chained commands.
def shell_complete(self, ctx, args, incomplete):
"""Return a list of completions for the incomplete value. Looks
at the names of options and chained multi-commands.
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
"""
results = []
if incomplete and incomplete[:1] == "-":

if incomplete and not incomplete[0].isalnum():
for param in self.get_params(ctx):
if isinstance(param, Option) and not param.hidden:
param_opts = [
param_opt
for param_opt in param.opts + param.secondary_opts
if param_opt not in all_args or param.multiple
]
results.extend(
[
("plain", o, param.help)
for o in param_opts
if o.startswith(incomplete)
]
)
else:
results.extend(BaseCommand.shell_complete(self, ctx, all_args, incomplete))
if not isinstance(param, Option) or param.hidden:
continue

names = (
name
for name in param.opts + param.secondary_opts
if name not in args or param.multiple
)
results.extend(
("plain", name, param.help)
for name in names
if name.startswith(incomplete)
)

results.extend(super().shell_complete(ctx, args, incomplete))
return results


Expand Down Expand Up @@ -1551,23 +1569,22 @@ def list_commands(self, ctx):
"""
return []

def shell_complete(self, ctx, all_args, incomplete):
"""Given a partial value and current arguments, this returns a list of
options that complete the partial value (if it begins with "-"), otherwise,
attempts to complete the partial value with subcommands and chained commands.
"""
if incomplete and incomplete[:1] == "-":
return Command.shell_complete(self, ctx, all_args, incomplete)
def shell_complete(self, ctx, args, incomplete):
"""Return a list of completions for the incomplete value. Looks
at the names of options, subcommands, and chained
multi-commands.
results = []
results.extend(
[
("plain", name, command.get_short_help_str())
for name, command in _get_visible_commands(ctx, incomplete)
]
)
results.extend(BaseCommand.shell_complete(self, ctx, all_args, incomplete))
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
"""
results = [
("plain", name, command.get_short_help_str())
for name, command in _complete_visible_commands(ctx, incomplete)
]
results.extend(super().shell_complete(ctx, args, incomplete))
return results


Expand Down Expand Up @@ -1751,6 +1768,9 @@ class Parameter:
order of processing.
:param envvar: a string or list of strings that are environment variables
that should be checked.
:param autocompletion: A function that returns custom shell
completions. Used instead of the param's type completion if
given.
.. versionchanged:: 7.1
Empty environment variables are ignored rather than taking the
Expand Down Expand Up @@ -1991,23 +2011,25 @@ def get_error_hint(self, ctx):
hint_list = self.opts or [self.human_readable_name]
return " / ".join(repr(x) for x in hint_list)

def shell_complete(self, ctx, all_args, incomplete):
"""Given a partial value and current arguments, this returns a list of
completions based on :attr:`type` if successful, otherwise uses
:attr:`autocompletion` if instantiated with one.
def shell_complete(self, ctx, args, incomplete):
"""Return a list of completions for the incomplete value. If an
:attr:`autocompletion` function was given, it is used.
Otherwise, the :attr:`type`
:meth:`~click.types.ParamType.shell_complete` function is used.
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
"""
results = []
if isinstance(self.type, ParamType):
results = self.type.shell_complete(ctx, all_args, incomplete)
if not results and self.autocompletion is not None:
dynamic_completions = self.autocompletion(
ctx=ctx, args=all_args, incomplete=incomplete
)
results = [
if self.autocompletion is not None:
return [
("plain",) + c if isinstance(c, tuple) else ("plain", c, None)
for c in dynamic_completions
for c in self.autocompletion(ctx=ctx, args=args, incomplete=incomplete)
]
return results

return self.type.shell_complete(ctx, args, incomplete)


class Option(Parameter):
Expand Down
Loading

0 comments on commit 2dc3348

Please sign in to comment.