Coverage for typer/_completion_classes.py: 100%
115 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-14 00:18 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-14 00:18 +0000
1import importlib.util 1iabcdefgh
2import os 1iabcdefgh
3import re 1iabcdefgh
4import sys 1iabcdefgh
5from typing import Any, Dict, List, Tuple 1iabcdefgh
7import click 1iabcdefgh
8import click.parser 1iabcdefgh
9import click.shell_completion 1iabcdefgh
11from ._completion_shared import ( 1iabcdefgh
12 COMPLETION_SCRIPT_BASH,
13 COMPLETION_SCRIPT_FISH,
14 COMPLETION_SCRIPT_POWER_SHELL,
15 COMPLETION_SCRIPT_ZSH,
16 Shells,
17)
19try: 1iabcdefgh
20 import shellingham 1iabcdefgh
21except ImportError: # pragma: no cover
22 shellingham = None
25def _sanitize_help_text(text: str) -> str: 1iabcdefgh
26 """Sanitizes the help text by removing rich tags"""
27 if not importlib.util.find_spec("rich"): 1iabcdefgh
28 return text 1iabcdefgh
29 from . import rich_utils 1iabcdefgh
31 return rich_utils.rich_render_text(text) 1iabcdefgh
34class BashComplete(click.shell_completion.BashComplete): 1iabcdefgh
35 name = Shells.bash.value 1iabcdefgh
36 source_template = COMPLETION_SCRIPT_BASH 1iabcdefgh
38 def source_vars(self) -> Dict[str, Any]: 1iabcdefgh
39 return { 1abcdefgh
40 "complete_func": self.func_name,
41 "autocomplete_var": self.complete_var,
42 "prog_name": self.prog_name,
43 }
45 def get_completion_args(self) -> Tuple[List[str], str]: 1iabcdefgh
46 cwords = click.parser.split_arg_string(os.environ["COMP_WORDS"]) 1iabcdefgh
47 cword = int(os.environ["COMP_CWORD"]) 1iabcdefgh
48 args = cwords[1:cword] 1iabcdefgh
50 try: 1iabcdefgh
51 incomplete = cwords[cword] 1iabcdefgh
52 except IndexError: 1iabcdefgh
53 incomplete = "" 1iabcdefgh
55 return args, incomplete 1iabcdefgh
57 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1iabcdefgh
58 # TODO: Explore replicating the new behavior from Click, with item types and
59 # triggering completion for files and directories
60 # return f"{item.type},{item.value}"
61 return f"{item.value}" 1iabcdefgh
63 def complete(self) -> str: 1iabcdefgh
64 args, incomplete = self.get_completion_args() 1iabcdefgh
65 completions = self.get_completions(args, incomplete) 1iabcdefgh
66 out = [self.format_completion(item) for item in completions] 1iabcdefgh
67 return "\n".join(out) 1iabcdefgh
70class ZshComplete(click.shell_completion.ZshComplete): 1iabcdefgh
71 name = Shells.zsh.value 1iabcdefgh
72 source_template = COMPLETION_SCRIPT_ZSH 1iabcdefgh
74 def source_vars(self) -> Dict[str, Any]: 1iabcdefgh
75 return { 1abcdefgh
76 "complete_func": self.func_name,
77 "autocomplete_var": self.complete_var,
78 "prog_name": self.prog_name,
79 }
81 def get_completion_args(self) -> Tuple[List[str], str]: 1iabcdefgh
82 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1iabcdefgh
83 cwords = click.parser.split_arg_string(completion_args) 1iabcdefgh
84 args = cwords[1:] 1iabcdefgh
85 if args and not completion_args.endswith(" "): 1iabcdefgh
86 incomplete = args[-1] 1iabcdefgh
87 args = args[:-1] 1iabcdefgh
88 else:
89 incomplete = "" 1iabcdefgh
90 return args, incomplete 1iabcdefgh
92 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1iabcdefgh
93 def escape(s: str) -> str: 1iabcdefgh
94 return ( 1abcdefgh
95 s.replace('"', '""')
96 .replace("'", "''")
97 .replace("$", "\\$")
98 .replace("`", "\\`")
99 .replace(":", r"\\:")
100 )
102 # TODO: Explore replicating the new behavior from Click, pay attention to
103 # the difference with and without escape
104 # return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
105 if item.help: 1iabcdefgh
106 return f'"{escape(item.value)}":"{_sanitize_help_text(escape(item.help))}"' 1iabcdefgh
107 else:
108 return f'"{escape(item.value)}"' 1iabcdefgh
110 def complete(self) -> str: 1iabcdefgh
111 args, incomplete = self.get_completion_args() 1iabcdefgh
112 completions = self.get_completions(args, incomplete) 1iabcdefgh
113 res = [self.format_completion(item) for item in completions] 1iabcdefgh
114 if res: 1iabcdefgh
115 args_str = "\n".join(res) 1iabcdefgh
116 return f"_arguments '*: :(({args_str}))'" 1iabcdefgh
117 else:
118 return "_files" 1iabcdefgh
121class FishComplete(click.shell_completion.FishComplete): 1iabcdefgh
122 name = Shells.fish.value 1iabcdefgh
123 source_template = COMPLETION_SCRIPT_FISH 1iabcdefgh
125 def source_vars(self) -> Dict[str, Any]: 1iabcdefgh
126 return { 1abcdefgh
127 "complete_func": self.func_name,
128 "autocomplete_var": self.complete_var,
129 "prog_name": self.prog_name,
130 }
132 def get_completion_args(self) -> Tuple[List[str], str]: 1iabcdefgh
133 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1iabcdefgh
134 cwords = click.parser.split_arg_string(completion_args) 1iabcdefgh
135 args = cwords[1:] 1iabcdefgh
136 if args and not completion_args.endswith(" "): 1iabcdefgh
137 incomplete = args[-1] 1iabcdefgh
138 args = args[:-1] 1iabcdefgh
139 else:
140 incomplete = "" 1iabcdefgh
141 return args, incomplete 1iabcdefgh
143 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1iabcdefgh
144 # TODO: Explore replicating the new behavior from Click, pay attention to
145 # the difference with and without formatted help
146 # if item.help:
147 # return f"{item.type},{item.value}\t{item.help}"
149 # return f"{item.type},{item.value}
150 if item.help: 1iabcdefgh
151 formatted_help = re.sub(r"\s", " ", item.help) 1iabcdefgh
152 return f"{item.value}\t{_sanitize_help_text(formatted_help)}" 1iabcdefgh
153 else:
154 return f"{item.value}" 1iabcdefgh
156 def complete(self) -> str: 1iabcdefgh
157 complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "") 1iabcdefgh
158 args, incomplete = self.get_completion_args() 1iabcdefgh
159 completions = self.get_completions(args, incomplete) 1iabcdefgh
160 show_args = [self.format_completion(item) for item in completions] 1iabcdefgh
161 if complete_action == "get-args": 1iabcdefgh
162 if show_args: 1iabcdefgh
163 return "\n".join(show_args) 1iabcdefgh
164 elif complete_action == "is-args": 1iabcdefgh
165 if show_args: 1iabcdefgh
166 # Activate complete args (no files)
167 sys.exit(0) 1iabcdefgh
168 else:
169 # Deactivate complete args (allow files)
170 sys.exit(1) 1iabcdefgh
171 return "" # pragma: no cover
174class PowerShellComplete(click.shell_completion.ShellComplete): 1iabcdefgh
175 name = Shells.powershell.value 1iabcdefgh
176 source_template = COMPLETION_SCRIPT_POWER_SHELL 1iabcdefgh
178 def source_vars(self) -> Dict[str, Any]: 1iabcdefgh
179 return { 1abcdefgh
180 "complete_func": self.func_name,
181 "autocomplete_var": self.complete_var,
182 "prog_name": self.prog_name,
183 }
185 def get_completion_args(self) -> Tuple[List[str], str]: 1iabcdefgh
186 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1iabcdefgh
187 incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") 1iabcdefgh
188 cwords = click.parser.split_arg_string(completion_args) 1iabcdefgh
189 args = cwords[1:-1] if incomplete else cwords[1:] 1iabcdefgh
190 return args, incomplete 1iabcdefgh
192 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1iabcdefgh
193 return f"{item.value}:::{_sanitize_help_text(item.help) if item.help else ' '}" 1iabcdefgh
196def completion_init() -> None: 1iabcdefgh
197 click.shell_completion.add_completion_class(BashComplete, Shells.bash.value) 1iabcdefgh
198 click.shell_completion.add_completion_class(ZshComplete, Shells.zsh.value) 1iabcdefgh
199 click.shell_completion.add_completion_class(FishComplete, Shells.fish.value) 1iabcdefgh
200 click.shell_completion.add_completion_class( 1iabcdefgh
201 PowerShellComplete, Shells.powershell.value
202 )
203 click.shell_completion.add_completion_class(PowerShellComplete, Shells.pwsh.value) 1iabcdefgh