Coverage for typer / _completion_classes.py: 100%
128 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
1import importlib.util 1defgabc
2import os 1defgabc
3import re 1defgabc
4import sys 1defgabc
5from typing import Any 1defgabc
7from . import _click 1defgabc
8from ._click.shell_completion import CompletionItem, ShellComplete, add_completion_class 1defgabc
9from ._click.shell_completion import split_arg_string as click_split_arg_string 1defgabc
10from ._completion_shared import ( 1defgabc
11 COMPLETION_SCRIPT_BASH,
12 COMPLETION_SCRIPT_FISH,
13 COMPLETION_SCRIPT_POWER_SHELL,
14 COMPLETION_SCRIPT_ZSH,
15 Shells,
16)
19def _sanitize_help_text(text: str) -> str: 1defgabc
20 """Sanitizes the help text by removing rich tags"""
21 if not importlib.util.find_spec("rich"): 1defgabc
22 return text 1defgabc
23 from . import rich_utils 1defgabc
25 return rich_utils.rich_render_text(text) 1defgabc
28class BashComplete(ShellComplete): 1defgabc
29 name = Shells.bash.value 1defgabc
30 source_template = COMPLETION_SCRIPT_BASH 1defgabc
32 def source_vars(self) -> dict[str, Any]: 1defgabc
33 return { 1defgabc
34 "complete_func": self.func_name,
35 "autocomplete_var": self.complete_var,
36 "prog_name": self.prog_name,
37 }
39 def get_completion_args(self) -> tuple[list[str], str]: 1defgabc
40 cwords = click_split_arg_string(os.environ["COMP_WORDS"]) 1defgabc
41 cword = int(os.environ["COMP_CWORD"]) 1defgabc
42 args = cwords[1:cword] 1defgabc
44 try: 1defgabc
45 incomplete = cwords[cword] 1defgabc
46 except IndexError: 1defgabc
47 incomplete = "" 1defgabc
49 return args, incomplete 1defgabc
51 def format_completion(self, item: CompletionItem) -> str: 1defgabc
52 # TODO: Explore replicating the new behavior from Click, with item types and
53 # triggering completion for files and directories
54 # return f"{item.type},{item.value}"
55 return f"{item.value}" 1defgabc
57 def complete(self) -> str: 1defgabc
58 args, incomplete = self.get_completion_args() 1defgabc
59 completions = self.get_completions(args, incomplete) 1defgabc
60 out = [self.format_completion(item) for item in completions] 1defgabc
61 return "\n".join(out) 1defgabc
63 @staticmethod 1defgabc
64 def _check_version() -> None: 1defgabc
65 import shutil 1defgabc
66 import subprocess 1defgabc
68 bash_exe = shutil.which("bash") 1defgabc
70 if bash_exe is None: 1defgabc
71 match = None # pragma: no cover
72 else:
73 output = subprocess.run( 1defgabc
74 [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'],
75 stdout=subprocess.PIPE,
76 )
77 match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) 1defgabc
79 if match is not None: 1defgabc
80 major, minor = match.groups() 1defgabc
82 if major < "4" or major == "4" and minor < "4": 1defgabc
83 _click.utils.echo( 1abc
84 "Shell completion is not supported for Bash versions older than 4.4.",
85 err=True,
86 )
87 else:
88 _click.utils.echo(
89 "Couldn't detect Bash version, shell completion is not supported.",
90 err=True,
91 ) # pragma: no cover
93 def source(self) -> str: 1defgabc
94 self._check_version() 1defgabc
95 return super().source() 1defgabc
98class ZshComplete(ShellComplete): 1defgabc
99 name = Shells.zsh.value 1defgabc
100 source_template = COMPLETION_SCRIPT_ZSH 1defgabc
102 def source_vars(self) -> dict[str, Any]: 1defgabc
103 return { 1defgabc
104 "complete_func": self.func_name,
105 "autocomplete_var": self.complete_var,
106 "prog_name": self.prog_name,
107 }
109 def get_completion_args(self) -> tuple[list[str], str]: 1defgabc
110 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1defgabc
111 cwords = click_split_arg_string(completion_args) 1defgabc
112 args = cwords[1:] 1defgabc
113 if args and not completion_args.endswith(" "): 1defgabc
114 incomplete = args[-1] 1defgabc
115 args = args[:-1] 1defgabc
116 else:
117 incomplete = "" 1defgabc
118 return args, incomplete 1defgabc
120 def format_completion(self, item: CompletionItem) -> str: 1defgabc
121 def escape(s: str) -> str: 1defgabc
122 return ( 1defgabc
123 s.replace('"', '""')
124 .replace("'", "''")
125 .replace("$", "\\$")
126 .replace("`", "\\`")
127 .replace(":", r"\\:")
128 )
130 # TODO: Explore replicating the new behavior from Click, pay attention to
131 # the difference with and without escape
132 # return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
133 if item.help: 1defgabc
134 return f'"{escape(item.value)}":"{_sanitize_help_text(escape(item.help))}"' 1defgabc
135 else:
136 return f'"{escape(item.value)}"' 1defgabc
138 def complete(self) -> str: 1defgabc
139 args, incomplete = self.get_completion_args() 1defgabc
140 completions = self.get_completions(args, incomplete) 1defgabc
141 res = [self.format_completion(item) for item in completions] 1defgabc
142 if res: 1defgabc
143 args_str = "\n".join(res) 1defgabc
144 return f"_arguments '*: :(({args_str}))'" 1defgabc
145 else:
146 return "_files" 1defgabc
149class FishComplete(ShellComplete): 1defgabc
150 name = Shells.fish.value 1defgabc
151 source_template = COMPLETION_SCRIPT_FISH 1defgabc
153 def source_vars(self) -> dict[str, Any]: 1defgabc
154 return { 1defgabc
155 "complete_func": self.func_name,
156 "autocomplete_var": self.complete_var,
157 "prog_name": self.prog_name,
158 }
160 def get_completion_args(self) -> tuple[list[str], str]: 1defgabc
161 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1defgabc
162 cwords = click_split_arg_string(completion_args) 1defgabc
163 args = cwords[1:] 1defgabc
164 if args and not completion_args.endswith(" "): 1defgabc
165 incomplete = args[-1] 1defgabc
166 args = args[:-1] 1defgabc
167 else:
168 incomplete = "" 1defgabc
169 return args, incomplete 1defgabc
171 def format_completion(self, item: CompletionItem) -> str: 1defgabc
172 # TODO: Explore replicating the new behavior from Click, pay attention to
173 # the difference with and without formatted help
174 # if item.help:
175 # return f"{item.type},{item.value}\t{item.help}"
177 # return f"{item.type},{item.value}
178 if item.help: 1defgabc
179 formatted_help = re.sub(r"\s", " ", item.help) 1defgabc
180 return f"{item.value}\t{_sanitize_help_text(formatted_help)}" 1defgabc
181 else:
182 return f"{item.value}" 1defgabc
184 def complete(self) -> str: 1defgabc
185 complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "") 1defgabc
186 args, incomplete = self.get_completion_args() 1defgabc
187 completions = self.get_completions(args, incomplete) 1defgabc
188 show_args = [self.format_completion(item) for item in completions] 1defgabc
189 if complete_action == "get-args": 1defgabc
190 if show_args: 1defgabc
191 return "\n".join(show_args) 1defgabc
192 elif complete_action == "is-args": 1defgabc
193 if show_args: 1defgabc
194 # Activate complete args (no files)
195 sys.exit(0) 1defgabc
196 else:
197 # Deactivate complete args (allow files)
198 sys.exit(1) 1defgabc
199 return "" # pragma: no cover
202class PowerShellComplete(ShellComplete): 1defgabc
203 name = Shells.powershell.value 1defgabc
204 source_template = COMPLETION_SCRIPT_POWER_SHELL 1defgabc
206 def source_vars(self) -> dict[str, Any]: 1defgabc
207 return { 1defgabc
208 "complete_func": self.func_name,
209 "autocomplete_var": self.complete_var,
210 "prog_name": self.prog_name,
211 }
213 def get_completion_args(self) -> tuple[list[str], str]: 1defgabc
214 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1defgabc
215 incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") 1defgabc
216 cwords = click_split_arg_string(completion_args) 1defgabc
217 args = cwords[1:-1] if incomplete else cwords[1:] 1defgabc
218 return args, incomplete 1defgabc
220 def format_completion(self, item: CompletionItem) -> str: 1defgabc
221 return f"{item.value}:::{_sanitize_help_text(item.help) if item.help else ' '}" 1defgabc
224def completion_init() -> None: 1defgabc
225 add_completion_class(BashComplete, Shells.bash.value) 1defgabc
226 add_completion_class(ZshComplete, Shells.zsh.value) 1defgabc
227 add_completion_class(FishComplete, Shells.fish.value) 1defgabc
228 add_completion_class(PowerShellComplete, Shells.powershell.value) 1defgabc
229 add_completion_class(PowerShellComplete, Shells.pwsh.value) 1defgabc