Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestions for autocompletion behavior #1

Closed
Closed
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
6 changes: 3 additions & 3 deletions docs_src/options_autocompletion/tutorial009.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import List

import typer
from click.core import Parameter
from rich.console import Console

valid_completion_items = [
Expand All @@ -12,9 +13,8 @@
err_console = Console(stderr=True)


def complete_name(ctx: typer.Context, args: List[str], incomplete: str):
err_console.print(f"{args}")
names = ctx.params.get("name") or []
def complete_name(ctx: typer.Context, param: Parameter, incomplete: str):
names = ctx.params.get(param.name) or []
for name, help_text in valid_completion_items:
if name.startswith(incomplete) and name not in names:
yield (name, help_text)
Expand Down
6 changes: 3 additions & 3 deletions docs_src/options_autocompletion/tutorial009_an.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import List

import typer
from click.core import Parameter
from rich.console import Console
from typing_extensions import Annotated

Expand All @@ -13,9 +14,8 @@
err_console = Console(stderr=True)


def complete_name(ctx: typer.Context, args: List[str], incomplete: str):
err_console.print(f"{args}")
names = ctx.params.get("name") or []
def complete_name(ctx: typer.Context, param: Parameter, incomplete: str):
names = ctx.params.get(param.name) or []
for name, help_text in valid_completion_items:
if name.startswith(incomplete) and name not in names:
yield (name, help_text)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_others.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def test_completion_untyped_parameters():
},
)
assert "info name is: completion_no_types.py" in result.stderr
assert "args is: []" in result.stderr
assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr
assert "incomplete is: Ca" in result.stderr
assert '"Camila":"The reader of books."' in result.stdout
assert '"Carlos":"The writer of scripts."' in result.stdout
Expand All @@ -199,7 +199,7 @@ def test_completion_untyped_parameters_different_order_correct_names():
},
)
assert "info name is: completion_no_types_order.py" in result.stderr
assert "args is: []" in result.stderr
assert "args is: ['--name', 'Sebastian', '--name']" in result.stderr
assert "incomplete is: Ca" in result.stderr
assert '"Camila":"The reader of books."' in result.stdout
assert '"Carlos":"The writer of scripts."' in result.stdout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_completion():
assert '"Camila":"The reader of books."' in result.stdout
assert '"Carlos":"The writer of scripts."' in result.stdout
assert '"Sebastian":"The type hints guy."' in result.stdout
assert "[]" in result.stderr
assert "--name" in result.stderr


def test_1():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_completion():
assert '"Camila":"The reader of books."' in result.stdout
assert '"Carlos":"The writer of scripts."' in result.stdout
assert '"Sebastian":"The type hints guy."' in result.stdout
assert "[]" in result.stderr
assert "--name" in result.stderr


def test_1():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def test_completion():
assert '"Camila":"The reader of books."' in result.stdout
assert '"Carlos":"The writer of scripts."' in result.stdout
assert '"Sebastian":"The type hints guy."' not in result.stdout
assert "[]" in result.stderr


def test_1():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def test_completion():
assert '"Camila":"The reader of books."' in result.stdout
assert '"Carlos":"The writer of scripts."' in result.stdout
assert '"Sebastian":"The type hints guy."' not in result.stdout
assert "[]" in result.stderr


def test_1():
Expand Down
18 changes: 18 additions & 0 deletions typer/_completion_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def get_completion_args(self) -> Tuple[List[str], str]:
except IndexError:
incomplete = ""

obj = self.ctx_args.setdefault("obj", {})
if isinstance(obj, dict):
obj.setdefault("args", args)
return args, incomplete

def format_completion(self, item: click.shell_completion.CompletionItem) -> str:
Expand Down Expand Up @@ -77,6 +80,11 @@ def get_completion_args(self) -> Tuple[List[str], str]:
args = args[:-1]
else:
incomplete = ""

obj = self.ctx_args.setdefault("obj", {})
if isinstance(obj, dict):
obj.setdefault("args", args)

return args, incomplete

def format_completion(self, item: click.shell_completion.CompletionItem) -> str:
Expand Down Expand Up @@ -127,6 +135,11 @@ def get_completion_args(self) -> Tuple[List[str], str]:
args = args[:-1]
else:
incomplete = ""

obj = self.ctx_args.setdefault("obj", {})
if isinstance(obj, dict):
obj.setdefault("args", args)

return args, incomplete

def format_completion(self, item: click.shell_completion.CompletionItem) -> str:
Expand Down Expand Up @@ -176,6 +189,11 @@ def get_completion_args(self) -> Tuple[List[str], str]:
incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "")
cwords = click.parser.split_arg_string(completion_args)
args = cwords[1:-1] if incomplete else cwords[1:]

obj = self.ctx_args.setdefault("obj", {})
if isinstance(obj, dict):
obj.setdefault("args", args)

return args, incomplete

def format_completion(self, item: click.shell_completion.CompletionItem) -> str:
Expand Down
9 changes: 7 additions & 2 deletions typer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ def _typer_param_setup_autocompletion_compat(
self: click.Parameter,
*,
autocompletion: Optional[
Callable[[click.Context, List[str], str], List[Union[Tuple[str, str], str]]]
Callable[
[click.Context, click.core.Parameter, str],
List[Union[Tuple[str, str], str, "click.shell_completion.CompletionItem"]],
]
] = None,
) -> None:
if self._custom_shell_complete is not None:
Expand All @@ -81,9 +84,11 @@ def compat_autocompletion(

out = []

for c in autocompletion(ctx, [], incomplete):
for c in autocompletion(ctx, param, incomplete):
if isinstance(c, tuple):
use_completion = CompletionItem(c[0], help=c[1])
elif isinstance(c, CompletionItem):
use_completion = c
else:
assert isinstance(c, str)
use_completion = CompletionItem(c)
Expand Down
16 changes: 14 additions & 2 deletions typer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@ def get_param_completion(
parameters = get_params_from_function(callback)
ctx_name = None
args_name = None
param_name = None
incomplete_name = None
unassigned_params = list(parameters.values())
for param_sig in unassigned_params[:]:
Expand All @@ -1040,6 +1041,9 @@ def get_param_completion(
elif lenient_issubclass(origin, List):
args_name = param_sig.name
unassigned_params.remove(param_sig)
elif lenient_issubclass(param_sig.annotation, click.core.Parameter):
param_name = param_sig.name
unassigned_params.remove(param_sig)
elif lenient_issubclass(param_sig.annotation, str):
incomplete_name = param_sig.name
unassigned_params.remove(param_sig)
Expand All @@ -1051,6 +1055,9 @@ def get_param_completion(
elif args_name is None and param_sig.name == "args":
args_name = param_sig.name
unassigned_params.remove(param_sig)
elif param_name is None and param_sig.name == "param":
param_name = param_sig.name
unassigned_params.remove(param_sig)
elif incomplete_name is None and param_sig.name == "incomplete":
incomplete_name = param_sig.name
unassigned_params.remove(param_sig)
Expand All @@ -1061,12 +1068,17 @@ def get_param_completion(
f"Invalid autocompletion callback parameters: {show_params}"
)

def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any:
def wrapper(
ctx: click.Context, param: click.core.Parameter, incomplete: Optional[str]
) -> Any:
use_params: Dict[str, Any] = {}
if ctx_name:
use_params[ctx_name] = ctx
if args_name:
use_params[args_name] = args
obj = ctx.obj or {}
use_params[args_name] = obj.get("args", []) if isinstance(obj, dict) else []
if param_name:
use_params[param_name] = param
if incomplete_name:
use_params[incomplete_name] = incomplete
return callback(**use_params)
Expand Down
Loading