Coverage for typer / _completion_classes.py: 100%

114 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-26 21:46 +0000

1import importlib.util 1abcdefg

2import os 1abcdefg

3import re 1abcdefg

4import sys 1abcdefg

5from typing import Any 1abcdefg

6 

7import click 1abcdefg

8import click.parser 1abcdefg

9import click.shell_completion 1abcdefg

10from click.shell_completion import split_arg_string as click_split_arg_string 1abcdefg

11 

12from ._completion_shared import ( 1abcdefg

13 COMPLETION_SCRIPT_BASH, 

14 COMPLETION_SCRIPT_FISH, 

15 COMPLETION_SCRIPT_POWER_SHELL, 

16 COMPLETION_SCRIPT_ZSH, 

17 Shells, 

18) 

19 

20 

21def _sanitize_help_text(text: str) -> str: 1abcdefg

22 """Sanitizes the help text by removing rich tags""" 

23 if not importlib.util.find_spec("rich"): 1abcdefg

24 return text 1abcdefg

25 from . import rich_utils 1abcdefg

26 

27 return rich_utils.rich_render_text(text) 1abcdefg

28 

29 

30class BashComplete(click.shell_completion.BashComplete): 1abcdefg

31 name = Shells.bash.value 1abcdefg

32 source_template = COMPLETION_SCRIPT_BASH 1abcdefg

33 

34 def source_vars(self) -> dict[str, Any]: 1abcdefg

35 return { 1abcdefg

36 "complete_func": self.func_name, 

37 "autocomplete_var": self.complete_var, 

38 "prog_name": self.prog_name, 

39 } 

40 

41 def get_completion_args(self) -> tuple[list[str], str]: 1abcdefg

42 cwords = click_split_arg_string(os.environ["COMP_WORDS"]) 1abcdefg

43 cword = int(os.environ["COMP_CWORD"]) 1abcdefg

44 args = cwords[1:cword] 1abcdefg

45 

46 try: 1abcdefg

47 incomplete = cwords[cword] 1abcdefg

48 except IndexError: 1abcdefg

49 incomplete = "" 1abcdefg

50 

51 return args, incomplete 1abcdefg

52 

53 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefg

54 # TODO: Explore replicating the new behavior from Click, with item types and 

55 # triggering completion for files and directories 

56 # return f"{item.type},{item.value}" 

57 return f"{item.value}" 1abcdefg

58 

59 def complete(self) -> str: 1abcdefg

60 args, incomplete = self.get_completion_args() 1abcdefg

61 completions = self.get_completions(args, incomplete) 1abcdefg

62 out = [self.format_completion(item) for item in completions] 1abcdefg

63 return "\n".join(out) 1abcdefg

64 

65 

66class ZshComplete(click.shell_completion.ZshComplete): 1abcdefg

67 name = Shells.zsh.value 1abcdefg

68 source_template = COMPLETION_SCRIPT_ZSH 1abcdefg

69 

70 def source_vars(self) -> dict[str, Any]: 1abcdefg

71 return { 1abcdefg

72 "complete_func": self.func_name, 

73 "autocomplete_var": self.complete_var, 

74 "prog_name": self.prog_name, 

75 } 

76 

77 def get_completion_args(self) -> tuple[list[str], str]: 1abcdefg

78 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1abcdefg

79 cwords = click_split_arg_string(completion_args) 1abcdefg

80 args = cwords[1:] 1abcdefg

81 if args and not completion_args.endswith(" "): 1abcdefg

82 incomplete = args[-1] 1abcdefg

83 args = args[:-1] 1abcdefg

84 else: 

85 incomplete = "" 1abcdefg

86 return args, incomplete 1abcdefg

87 

88 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefg

89 def escape(s: str) -> str: 1abcdefg

90 return ( 1abcdefg

91 s.replace('"', '""') 

92 .replace("'", "''") 

93 .replace("$", "\\$") 

94 .replace("`", "\\`") 

95 .replace(":", r"\\:") 

96 ) 

97 

98 # TODO: Explore replicating the new behavior from Click, pay attention to 

99 # the difference with and without escape 

100 # return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" 

101 if item.help: 1abcdefg

102 return f'"{escape(item.value)}":"{_sanitize_help_text(escape(item.help))}"' 1abcdefg

103 else: 

104 return f'"{escape(item.value)}"' 1abcdefg

105 

106 def complete(self) -> str: 1abcdefg

107 args, incomplete = self.get_completion_args() 1abcdefg

108 completions = self.get_completions(args, incomplete) 1abcdefg

109 res = [self.format_completion(item) for item in completions] 1abcdefg

110 if res: 1abcdefg

111 args_str = "\n".join(res) 1abcdefg

112 return f"_arguments '*: :(({args_str}))'" 1abcdefg

113 else: 

114 return "_files" 1abcdefg

115 

116 

117class FishComplete(click.shell_completion.FishComplete): 1abcdefg

118 name = Shells.fish.value 1abcdefg

119 source_template = COMPLETION_SCRIPT_FISH 1abcdefg

120 

121 def source_vars(self) -> dict[str, Any]: 1abcdefg

122 return { 1abcdefg

123 "complete_func": self.func_name, 

124 "autocomplete_var": self.complete_var, 

125 "prog_name": self.prog_name, 

126 } 

127 

128 def get_completion_args(self) -> tuple[list[str], str]: 1abcdefg

129 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1abcdefg

130 cwords = click_split_arg_string(completion_args) 1abcdefg

131 args = cwords[1:] 1abcdefg

132 if args and not completion_args.endswith(" "): 1abcdefg

133 incomplete = args[-1] 1abcdefg

134 args = args[:-1] 1abcdefg

135 else: 

136 incomplete = "" 1abcdefg

137 return args, incomplete 1abcdefg

138 

139 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefg

140 # TODO: Explore replicating the new behavior from Click, pay attention to 

141 # the difference with and without formatted help 

142 # if item.help: 

143 # return f"{item.type},{item.value}\t{item.help}" 

144 

145 # return f"{item.type},{item.value} 

146 if item.help: 1abcdefg

147 formatted_help = re.sub(r"\s", " ", item.help) 1abcdefg

148 return f"{item.value}\t{_sanitize_help_text(formatted_help)}" 1abcdefg

149 else: 

150 return f"{item.value}" 1abcdefg

151 

152 def complete(self) -> str: 1abcdefg

153 complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "") 1abcdefg

154 args, incomplete = self.get_completion_args() 1abcdefg

155 completions = self.get_completions(args, incomplete) 1abcdefg

156 show_args = [self.format_completion(item) for item in completions] 1abcdefg

157 if complete_action == "get-args": 1abcdefg

158 if show_args: 1abcdefg

159 return "\n".join(show_args) 1abcdefg

160 elif complete_action == "is-args": 1abcdefg

161 if show_args: 1abcdefg

162 # Activate complete args (no files) 

163 sys.exit(0) 1abcdefg

164 else: 

165 # Deactivate complete args (allow files) 

166 sys.exit(1) 1abcdefg

167 return "" # pragma: no cover 

168 

169 

170class PowerShellComplete(click.shell_completion.ShellComplete): 1abcdefg

171 name = Shells.powershell.value 1abcdefg

172 source_template = COMPLETION_SCRIPT_POWER_SHELL 1abcdefg

173 

174 def source_vars(self) -> dict[str, Any]: 1abcdefg

175 return { 1abcdefg

176 "complete_func": self.func_name, 

177 "autocomplete_var": self.complete_var, 

178 "prog_name": self.prog_name, 

179 } 

180 

181 def get_completion_args(self) -> tuple[list[str], str]: 1abcdefg

182 completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") 1abcdefg

183 incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") 1abcdefg

184 cwords = click_split_arg_string(completion_args) 1abcdefg

185 args = cwords[1:-1] if incomplete else cwords[1:] 1abcdefg

186 return args, incomplete 1abcdefg

187 

188 def format_completion(self, item: click.shell_completion.CompletionItem) -> str: 1abcdefg

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

190 

191 

192def completion_init() -> None: 1abcdefg

193 click.shell_completion.add_completion_class(BashComplete, Shells.bash.value) 1abcdefg

194 click.shell_completion.add_completion_class(ZshComplete, Shells.zsh.value) 1abcdefg

195 click.shell_completion.add_completion_class(FishComplete, Shells.fish.value) 1abcdefg

196 click.shell_completion.add_completion_class( 1abcdefg

197 PowerShellComplete, Shells.powershell.value 

198 ) 

199 click.shell_completion.add_completion_class(PowerShellComplete, Shells.pwsh.value) 1abcdefg