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

1import importlib.util 1abcdefghi

2import os 1abcdefghi

3import re 1abcdefghi

4import sys 1abcdefghi

5from typing import Any, Dict, List, Tuple 1abcdefghi

6 

7import click 1abcdefghi

8import click.parser 1abcdefghi

9import click.shell_completion 1abcdefghi

10 

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) 

18 

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 ) 

26 

27 

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

33 

34 return rich_utils.rich_render_text(text) 1abcdefghi

35 

36 

37class BashComplete(click.shell_completion.BashComplete): 1abcdefghi

38 name = Shells.bash.value 1abcdefghi

39 source_template = COMPLETION_SCRIPT_BASH 1abcdefghi

40 

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 } 

47 

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

52 

53 try: 1abcdefghi

54 incomplete = cwords[cword] 1abcdefghi

55 except IndexError: 1abcdefghi

56 incomplete = "" 1abcdefghi

57 

58 return args, incomplete 1abcdefghi

59 

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

65 

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

71 

72 

73class ZshComplete(click.shell_completion.ZshComplete): 1abcdefghi

74 name = Shells.zsh.value 1abcdefghi

75 source_template = COMPLETION_SCRIPT_ZSH 1abcdefghi

76 

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 } 

83 

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

94 

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 ) 

104 

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

112 

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

122 

123 

124class FishComplete(click.shell_completion.FishComplete): 1abcdefghi

125 name = Shells.fish.value 1abcdefghi

126 source_template = COMPLETION_SCRIPT_FISH 1abcdefghi

127 

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 } 

134 

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

145 

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

151 

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

158 

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 

175 

176 

177class PowerShellComplete(click.shell_completion.ShellComplete): 1abcdefghi

178 name = Shells.powershell.value 1abcdefghi

179 source_template = COMPLETION_SCRIPT_POWER_SHELL 1abcdefghi

180 

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 } 

187 

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

194 

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

197 

198 

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