Coverage for typer/_completion_classes.py: 100%
115 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-19 20:29 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-12-19 20:29 +0000
1import importlib.util 1abcdefghi
2import os 1abcdefghi
3import re 1abcdefghi
4import sys 1abcdefghi
5from typing import Any, Dict, List, Tuple 1abcdefghi
7import click 1abcdefghi
8import click.parser 1abcdefghi
9import click.shell_completion 1abcdefghi
11from ._completion_shared import ( 1abcdefghi
12 COMPLETION_SCRIPT_BASH,
13 COMPLETION_SCRIPT_FISH,
14 COMPLETION_SCRIPT_POWER_SHELL,
15 COMPLETION_SCRIPT_ZSH,
16 Shells,
17)
19try: 1abcdefghi
20 from click.shell_completion import split_arg_string as click_split_arg_string 1abcdefghi
21except ImportError: # pragma: no cover
22 # TODO: when removing support for Click < 8.2, remove this import
23 from click.parser import ( # type: ignore[no-redef]
24 split_arg_string as click_split_arg_string,
25 )
28def _sanitize_help_text(text: str) -> str: 1abcdefghi
29 """Sanitizes the help text by removing rich tags"""
30 if not importlib.util.find_spec("rich"): 1abcdefghi
31 return text 1abcdefghi
32 from . import rich_utils 1abcdefghi
34 return rich_utils.rich_render_text(text) 1abcdefghi
37class BashComplete(click.shell_completion.BashComplete): 1abcdefghi
38 name = Shells.bash.value 1abcdefghi
39 source_template = COMPLETION_SCRIPT_BASH 1abcdefghi
41 def source_vars(self) -> Dict[str, Any]: 1abcdefghi
42 return { 1abcdefghi
43 "complete_func": self.func_name,
44 "autocomplete_var": self.complete_var,
45 "prog_name": self.prog_name,
46 }
48 def get_completion_args(self) -> Tuple[List[str], str]: 1abcdefghi
49 cwords = click_split_arg_string(os.environ["COMP_WORDS"]) 1abcdefghi
50 cword = int(os.environ["COMP_CWORD"]) 1abcdefghi
51 args = cwords[1:cword] 1abcdefghi
53 try: 1abcdefghi
54 incomplete = cwords[cword] 1abcdefghi
55 except IndexError: 1abcdefghi
56 incomplete = "" 1abcdefghi
58 return args, incomplete 1abcdefghi
60 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefghi
61 # TODO: Explore replicating the new behavior from Click, with item types and
62 # triggering completion for files and directories
63 # return f"{item.type},{item.value}"
64 return f"{item.value}" 1abcdefghi
66 def complete(self) -> str: 1abcdefghi
67 args, incomplete = self.get_completion_args() 1abcdefghi
68 completions = self.get_completions(args, incomplete) 1abcdefghi
69 out = [self.format_completion(item) for item in completions] 1abcdefghi
70 return "\n".join(out) 1abcdefghi
73class ZshComplete(click.shell_completion.ZshComplete): 1abcdefghi
74 name = Shells.zsh.value 1abcdefghi
75 source_template = COMPLETION_SCRIPT_ZSH 1abcdefghi
77 def source_vars(self) -> Dict[str, Any]: 1abcdefghi
78 return { 1abcdefghi
79 "complete_func": self.func_name,
80 "autocomplete_var": self.complete_var,
81 "prog_name": self.prog_name,
82 }
84 def get_completion_args(self) -> Tuple[List[str], str]: 1abcdefghi
85 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1abcdefghi
86 cwords = click_split_arg_string(completion_args) 1abcdefghi
87 args = cwords[1:] 1abcdefghi
88 if args and not completion_args.endswith(" "): 1abcdefghi
89 incomplete = args[-1] 1abcdefghi
90 args = args[:-1] 1abcdefghi
91 else:
92 incomplete = "" 1abcdefghi
93 return args, incomplete 1abcdefghi
95 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefghi
96 def escape(s: str) -> str: 1abcdefghi
97 return ( 1abcdefghi
98 s.replace('"', '""')
99 .replace("'", "''")
100 .replace("$", "\\$")
101 .replace("`", "\\`")
102 .replace(":", r"\\:")
103 )
105 # TODO: Explore replicating the new behavior from Click, pay attention to
106 # the difference with and without escape
107 # return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
108 if item.help: 1abcdefghi
109 return f'"{escape(item.value)}":"{_sanitize_help_text(escape(item.help))}"' 1abcdefghi
110 else:
111 return f'"{escape(item.value)}"' 1abcdefghi
113 def complete(self) -> str: 1abcdefghi
114 args, incomplete = self.get_completion_args() 1abcdefghi
115 completions = self.get_completions(args, incomplete) 1abcdefghi
116 res = [self.format_completion(item) for item in completions] 1abcdefghi
117 if res: 1abcdefghi
118 args_str = "\n".join(res) 1abcdefghi
119 return f"_arguments '*: :(({args_str}))'" 1abcdefghi
120 else:
121 return "_files" 1abcdefghi
124class FishComplete(click.shell_completion.FishComplete): 1abcdefghi
125 name = Shells.fish.value 1abcdefghi
126 source_template = COMPLETION_SCRIPT_FISH 1abcdefghi
128 def source_vars(self) -> Dict[str, Any]: 1abcdefghi
129 return { 1abcdefghi
130 "complete_func": self.func_name,
131 "autocomplete_var": self.complete_var,
132 "prog_name": self.prog_name,
133 }
135 def get_completion_args(self) -> Tuple[List[str], str]: 1abcdefghi
136 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1abcdefghi
137 cwords = click_split_arg_string(completion_args) 1abcdefghi
138 args = cwords[1:] 1abcdefghi
139 if args and not completion_args.endswith(" "): 1abcdefghi
140 incomplete = args[-1] 1abcdefghi
141 args = args[:-1] 1abcdefghi
142 else:
143 incomplete = "" 1abcdefghi
144 return args, incomplete 1abcdefghi
146 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefghi
147 # TODO: Explore replicating the new behavior from Click, pay attention to
148 # the difference with and without formatted help
149 # if item.help:
150 # return f"{item.type},{item.value}\t{item.help}"
152 # return f"{item.type},{item.value}
153 if item.help: 1abcdefghi
154 formatted_help = re.sub(r"\s", " ", item.help) 1abcdefghi
155 return f"{item.value}\t{_sanitize_help_text(formatted_help)}" 1abcdefghi
156 else:
157 return f"{item.value}" 1abcdefghi
159 def complete(self) -> str: 1abcdefghi
160 complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "") 1abcdefghi
161 args, incomplete = self.get_completion_args() 1abcdefghi
162 completions = self.get_completions(args, incomplete) 1abcdefghi
163 show_args = [self.format_completion(item) for item in completions] 1abcdefghi
164 if complete_action == "get-args": 1abcdefghi
165 if show_args: 1abcdefghi
166 return "\n".join(show_args) 1abcdefghi
167 elif complete_action == "is-args": 1abcdefghi
168 if show_args: 1abcdefghi
169 # Activate complete args (no files)
170 sys.exit(0) 1abcdefghi
171 else:
172 # Deactivate complete args (allow files)
173 sys.exit(1) 1abcdefghi
174 return "" # pragma: no cover
177class PowerShellComplete(click.shell_completion.ShellComplete): 1abcdefghi
178 name = Shells.powershell.value 1abcdefghi
179 source_template = COMPLETION_SCRIPT_POWER_SHELL 1abcdefghi
181 def source_vars(self) -> Dict[str, Any]: 1abcdefghi
182 return { 1abcdefghi
183 "complete_func": self.func_name,
184 "autocomplete_var": self.complete_var,
185 "prog_name": self.prog_name,
186 }
188 def get_completion_args(self) -> Tuple[List[str], str]: 1abcdefghi
189 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1abcdefghi
190 incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") 1abcdefghi
191 cwords = click_split_arg_string(completion_args) 1abcdefghi
192 args = cwords[1:-1] if incomplete else cwords[1:] 1abcdefghi
193 return args, incomplete 1abcdefghi
195 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefghi
196 return f"{item.value}:::{_sanitize_help_text(item.help) if item.help else ' '}" 1abcdefghi
199def completion_init() -> None: 1abcdefghi
200 click.shell_completion.add_completion_class(BashComplete, Shells.bash.value) 1abcdefghi
201 click.shell_completion.add_completion_class(ZshComplete, Shells.zsh.value) 1abcdefghi
202 click.shell_completion.add_completion_class(FishComplete, Shells.fish.value) 1abcdefghi
203 click.shell_completion.add_completion_class( 1abcdefghi
204 PowerShellComplete, Shells.powershell.value
205 )
206 click.shell_completion.add_completion_class(PowerShellComplete, Shells.pwsh.value) 1abcdefghi