Marco Ricci commited on 2025-01-17 20:51:03
Zeige 2 geänderte Dateien mit 77 Einfügungen und 16 Löschungen.
The new Zsh serialization handler in bba4bd075ab5e1d6a6a76d90b129ad0d58425b96 was tested only against completion items with descriptions, and it did not properly take into account that items *without* descriptions would be passed directly to Zsh as unescaped completion entries, i.e., no interpretation of escape sequences would occur. We fix this in both the code and the tests, and include a safeguard against applying this against the wrong Zsh completion script version (at least as far as we can programmatically determine from within Python). References: [click#2703](https://github.com/pallets/click/issues/2703)
... | ... |
@@ -1203,11 +1203,19 @@ class ZshComplete(click.shell_completion.ZshComplete): |
1203 | 1203 |
"""Zsh completion class that supports colons. |
1204 | 1204 |
|
1205 | 1205 |
`click`'s Zsh completion class (at least v8.1.7 and v8.1.8) uses |
1206 |
- completion helper functions (provided by Zsh) that parse each |
|
1206 |
+ some completion helper functions (provided by Zsh) that parse each |
|
1207 | 1207 |
completion item into value-description pairs, separated by a colon. |
1208 |
- Correspondingly, any internal colons in the completion item's value |
|
1209 |
- need to be escaped. `click` doesn't do this. So, this subclass |
|
1210 |
- overrides those parts, and adds the missing escaping. |
|
1208 |
+ Other completion helper functions don't. Correspondingly, any |
|
1209 |
+ internal colons in the completion item's value sometimes need to be |
|
1210 |
+ escaped, and sometimes don't. |
|
1211 |
+ |
|
1212 |
+ The "right" way to fix this is to modify the Zsh completion script |
|
1213 |
+ to only use one type of serialization: either escaped, or unescaped. |
|
1214 |
+ However, the Zsh completion script itself may already be installed |
|
1215 |
+ in the user's Zsh settings, and we have no way of knowing that. |
|
1216 |
+ Therefore, it is better to change the `format_completion` method to |
|
1217 |
+ adaptively and "smartly" emit colon-escaped output or not, based on |
|
1218 |
+ whether the completion script will be using it. |
|
1211 | 1219 |
|
1212 | 1220 |
""" |
1213 | 1221 |
|
... | ... |
@@ -1219,17 +1227,69 @@ class ZshComplete(click.shell_completion.ZshComplete): |
1219 | 1227 |
"""Return a suitable serialization of the CompletionItem. |
1220 | 1228 |
|
1221 | 1229 |
This serialization ensures colons in the item value are properly |
1222 |
- escaped. |
|
1230 |
+ escaped if and only if the completion script will attempt to |
|
1231 |
+ pass a colon-separated key/description pair to the underlying |
|
1232 |
+ Zsh machinery. This is the case if and only if the help text is |
|
1233 |
+ non-degenerate. |
|
1223 | 1234 |
|
1224 | 1235 |
""" |
1225 |
- type, value, help = ( # noqa: A001 |
|
1226 |
- item.type, |
|
1227 |
- item.value.replace(':', '\\:'), |
|
1228 |
- item.help or '_', |
|
1229 |
- ) |
|
1230 |
- return f'{type}\n{value}\n{help}' |
|
1236 |
+ help_ = item.help or '_' |
|
1237 |
+ value = item.value.replace(':', r'\:' if help_ != '_' else ':') |
|
1238 |
+ return f'{item.type}\n{value}\n{help_}' |
|
1231 | 1239 |
|
1232 | 1240 |
|
1241 |
+# Our ZshComplete class depends crucially on the exact shape of the Zsh |
|
1242 |
+# completion script. So only fix the completion formatter if the |
|
1243 |
+# completion script is still the same. |
|
1244 |
+# |
|
1245 |
+# (This Zsh script is part of click, and available under the |
|
1246 |
+# 3-clause-BSD license.) |
|
1247 |
+_ORIG_SOURCE_TEMPLATE = """\ |
|
1248 |
+#compdef %(prog_name)s |
|
1249 |
+ |
|
1250 |
+%(complete_func)s() { |
|
1251 |
+ local -a completions |
|
1252 |
+ local -a completions_with_descriptions |
|
1253 |
+ local -a response |
|
1254 |
+ (( ! $+commands[%(prog_name)s] )) && return 1 |
|
1255 |
+ |
|
1256 |
+ response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ |
|
1257 |
+%(complete_var)s=zsh_complete %(prog_name)s)}") |
|
1258 |
+ |
|
1259 |
+ for type key descr in ${response}; do |
|
1260 |
+ if [[ "$type" == "plain" ]]; then |
|
1261 |
+ if [[ "$descr" == "_" ]]; then |
|
1262 |
+ completions+=("$key") |
|
1263 |
+ else |
|
1264 |
+ completions_with_descriptions+=("$key":"$descr") |
|
1265 |
+ fi |
|
1266 |
+ elif [[ "$type" == "dir" ]]; then |
|
1267 |
+ _path_files -/ |
|
1268 |
+ elif [[ "$type" == "file" ]]; then |
|
1269 |
+ _path_files -f |
|
1270 |
+ fi |
|
1271 |
+ done |
|
1272 |
+ |
|
1273 |
+ if [ -n "$completions_with_descriptions" ]; then |
|
1274 |
+ _describe -V unsorted completions_with_descriptions -U |
|
1275 |
+ fi |
|
1276 |
+ |
|
1277 |
+ if [ -n "$completions" ]; then |
|
1278 |
+ compadd -U -V unsorted -a completions |
|
1279 |
+ fi |
|
1280 |
+} |
|
1281 |
+ |
|
1282 |
+if [[ $zsh_eval_context[-1] == loadautofunc ]]; then |
|
1283 |
+ # autoload from fpath, call function directly |
|
1284 |
+ %(complete_func)s "$@" |
|
1285 |
+else |
|
1286 |
+ # eval/source/. command, register function for later |
|
1287 |
+ compdef %(complete_func)s %(prog_name)s |
|
1288 |
+fi |
|
1289 |
+""" |
|
1290 |
+if ( |
|
1291 |
+ click.shell_completion.ZshComplete.source_template == _ORIG_SOURCE_TEMPLATE |
|
1292 |
+): # pragma: no cover |
|
1233 | 1293 |
click.shell_completion.add_completion_class(ZshComplete) |
1234 | 1294 |
|
1235 | 1295 |
|
... | ... |
@@ -3538,12 +3538,13 @@ def fish_format(item: click.shell_completion.CompletionItem) -> str: |
3538 | 3538 |
|
3539 | 3539 |
|
3540 | 3540 |
def zsh_format(item: click.shell_completion.CompletionItem) -> str: |
3541 |
- type, value, help = ( # noqa: A001 |
|
3542 |
- item.type, |
|
3543 |
- item.value.replace(':', r'\:'), |
|
3544 |
- item.help or '_', |
|
3541 |
+ empty_help = '_' |
|
3542 |
+ help_, value = ( |
|
3543 |
+ (item.help, item.value.replace(':', r'\:')) |
|
3544 |
+ if item.help and item.help == empty_help |
|
3545 |
+ else (empty_help, item.value) |
|
3545 | 3546 |
) |
3546 |
- return f'{type}\n{value}\n{help}' |
|
3547 |
+ return f'{item.type}\n{value}\n{help_}' |
|
3547 | 3548 |
|
3548 | 3549 |
|
3549 | 3550 |
def completion_item( |
3550 | 3551 |