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

1import importlib.util 1defgabc

2import os 1defgabc

3import re 1defgabc

4import sys 1defgabc

5from typing import Any 1defgabc

6 

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) 

17 

18 

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

24 

25 return rich_utils.rich_render_text(text) 1defgabc

26 

27 

28class BashComplete(ShellComplete): 1defgabc

29 name = Shells.bash.value 1defgabc

30 source_template = COMPLETION_SCRIPT_BASH 1defgabc

31 

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 } 

38 

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

43 

44 try: 1defgabc

45 incomplete = cwords[cword] 1defgabc

46 except IndexError: 1defgabc

47 incomplete = "" 1defgabc

48 

49 return args, incomplete 1defgabc

50 

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

56 

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

62 

63 @staticmethod 1defgabc

64 def _check_version() -> None: 1defgabc

65 import shutil 1defgabc

66 import subprocess 1defgabc

67 

68 bash_exe = shutil.which("bash") 1defgabc

69 

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

78 

79 if match is not None: 1defgabc

80 major, minor = match.groups() 1defgabc

81 

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 

92 

93 def source(self) -> str: 1defgabc

94 self._check_version() 1defgabc

95 return super().source() 1defgabc

96 

97 

98class ZshComplete(ShellComplete): 1defgabc

99 name = Shells.zsh.value 1defgabc

100 source_template = COMPLETION_SCRIPT_ZSH 1defgabc

101 

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 } 

108 

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

119 

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 ) 

129 

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

137 

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

147 

148 

149class FishComplete(ShellComplete): 1defgabc

150 name = Shells.fish.value 1defgabc

151 source_template = COMPLETION_SCRIPT_FISH 1defgabc

152 

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 } 

159 

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

170 

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}" 

176 

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

183 

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 

200 

201 

202class PowerShellComplete(ShellComplete): 1defgabc

203 name = Shells.powershell.value 1defgabc

204 source_template = COMPLETION_SCRIPT_POWER_SHELL 1defgabc

205 

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 } 

212 

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

219 

220 def format_completion(self, item: CompletionItem) -> str: 1defgabc

221 return f"{item.value}:::{_sanitize_help_text(item.help) if item.help else ' '}" 1defgabc

222 

223 

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