]> git.proxmox.com Git - rustc.git/blame - vendor/clap/src/completions/zsh.rs
New upstream version 1.59.0+dfsg1
[rustc.git] / vendor / clap / src / completions / zsh.rs
CommitLineData
8bb4bdeb 1// Std
8faf50e0 2#[allow(deprecated, unused_imports)]
a2a8927a 3use std::{ascii::AsciiExt, io::Write};
8bb4bdeb
XL
4
5// Internal
a2a8927a
XL
6use crate::{
7 app::{parser::Parser, App},
8 args::{AnyArg, ArgSettings},
9 completions, INTERNAL_ERROR_MSG,
10};
8bb4bdeb
XL
11
12pub struct ZshGen<'a, 'b>
ff7c6d11
XL
13where
14 'a: 'b,
8bb4bdeb
XL
15{
16 p: &'b Parser<'a, 'b>,
17}
18
19impl<'a, 'b> ZshGen<'a, 'b> {
20 pub fn new(p: &'b Parser<'a, 'b>) -> Self {
041b39d2 21 debugln!("ZshGen::new;");
a2a8927a 22 ZshGen { p }
8bb4bdeb
XL
23 }
24
25 pub fn generate_to<W: Write>(&self, buf: &mut W) {
041b39d2 26 debugln!("ZshGen::generate_to;");
ff7c6d11
XL
27 w!(
28 buf,
29 format!(
30 "\
8bb4bdeb
XL
31#compdef {name}
32
0531ce1d
XL
33autoload -U is-at-least
34
8bb4bdeb 35_{name}() {{
041b39d2 36 typeset -A opt_args
0531ce1d 37 typeset -a _arguments_options
041b39d2 38 local ret=1
8bb4bdeb 39
0531ce1d
XL
40 if is-at-least 5.2; then
41 _arguments_options=(-s -S -C)
42 else
43 _arguments_options=(-s -C)
44 fi
45
041b39d2 46 local context curcontext=\"$curcontext\" state line
8bb4bdeb
XL
47 {initial_args}
48 {subcommands}
49}}
50
51{subcommand_details}
52
53_{name} \"$@\"",
ff7c6d11
XL
54 name = self.p.meta.bin_name.as_ref().unwrap(),
55 initial_args = get_args_of(self.p),
56 subcommands = get_subcommands_of(self.p),
57 subcommand_details = subcommand_details(self.p)
3dfed10e
XL
58 )
59 .as_bytes()
ff7c6d11 60 );
8bb4bdeb
XL
61 }
62}
63
0531ce1d 64// Displays the commands of a subcommand
8bb4bdeb
XL
65// (( $+functions[_[bin_name_underscore]_commands] )) ||
66// _[bin_name_underscore]_commands() {
67// local commands; commands=(
68// '[arg_name]:[arg_help]'
69// )
70// _describe -t commands '[bin_name] commands' commands "$@"
71//
72// Where the following variables are present:
8faf50e0 73// [bin_name_underscore]: The full space delineated bin_name, where spaces have been replaced by
8bb4bdeb 74// underscore characters
0531ce1d
XL
75// [arg_name]: The name of the subcommand
76// [arg_help]: The help message of the subcommand
8faf50e0 77// [bin_name]: The full space delineated bin_name
8bb4bdeb
XL
78//
79// Here's a snippet from rustup:
80//
81// (( $+functions[_rustup_commands] )) ||
82// _rustup_commands() {
83// local commands; commands=(
84// 'show:Show the active and installed toolchains'
85// 'update:Update Rust toolchains'
86// # ... snip for brevity
87// 'help:Prints this message or the help of the given subcommand(s)'
88// )
89// _describe -t commands 'rustup commands' commands "$@"
90//
91fn subcommand_details(p: &Parser) -> String {
041b39d2 92 debugln!("ZshGen::subcommand_details;");
8bb4bdeb 93 // First we do ourself
3dfed10e
XL
94 let mut ret = vec![format!(
95 "\
8bb4bdeb
XL
96(( $+functions[_{bin_name_underscore}_commands] )) ||
97_{bin_name_underscore}_commands() {{
98 local commands; commands=(
99 {subcommands_and_args}
100 )
101 _describe -t commands '{bin_name} commands' commands \"$@\"
102}}",
3dfed10e
XL
103 bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"),
104 bin_name = p.meta.bin_name.as_ref().unwrap(),
105 subcommands_and_args = subcommands_of(p)
106 )];
8bb4bdeb
XL
107
108 // Next we start looping through all the children, grandchildren, etc.
109 let mut all_subcommands = completions::all_subcommands(p);
110 all_subcommands.sort();
111 all_subcommands.dedup();
112 for &(_, ref bin_name) in &all_subcommands {
041b39d2 113 debugln!("ZshGen::subcommand_details:iter: bin_name={}", bin_name);
ff7c6d11
XL
114 ret.push(format!(
115 "\
8bb4bdeb
XL
116(( $+functions[_{bin_name_underscore}_commands] )) ||
117_{bin_name_underscore}_commands() {{
118 local commands; commands=(
119 {subcommands_and_args}
120 )
121 _describe -t commands '{bin_name} commands' commands \"$@\"
122}}",
041b39d2 123 bin_name_underscore = bin_name.replace(" ", "__"),
8bb4bdeb 124 bin_name = bin_name,
0531ce1d 125 subcommands_and_args = subcommands_of(parser_of(p, bin_name))
ff7c6d11 126 ));
8bb4bdeb
XL
127 }
128
129 ret.join("\n")
130}
131
0531ce1d 132// Generates subcommand completions in form of
8bb4bdeb
XL
133//
134// '[arg_name]:[arg_help]'
135//
136// Where:
0531ce1d
XL
137// [arg_name]: the subcommand's name
138// [arg_help]: the help message of the subcommand
8bb4bdeb
XL
139//
140// A snippet from rustup:
141// 'show:Show the active and installed toolchains'
142// 'update:Update Rust toolchains'
0531ce1d
XL
143fn subcommands_of(p: &Parser) -> String {
144 debugln!("ZshGen::subcommands_of;");
8bb4bdeb
XL
145 let mut ret = vec![];
146 fn add_sc(sc: &App, n: &str, ret: &mut Vec<String>) {
041b39d2 147 debugln!("ZshGen::add_sc;");
ff7c6d11
XL
148 let s = format!(
149 "\"{name}:{help}\" \\",
150 name = n,
3dfed10e
XL
151 help =
152 sc.p.meta
153 .about
154 .unwrap_or("")
155 .replace("[", "\\[")
156 .replace("]", "\\]")
ff7c6d11 157 );
8bb4bdeb
XL
158 if !s.is_empty() {
159 ret.push(s);
160 }
161 }
162
0531ce1d 163 // The subcommands
8bb4bdeb 164 for sc in p.subcommands() {
3dfed10e 165 debugln!("ZshGen::subcommands_of:iter: subcommand={}", sc.p.meta.name);
8bb4bdeb
XL
166 add_sc(sc, &sc.p.meta.name, &mut ret);
167 if let Some(ref v) = sc.p.meta.aliases {
168 for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) {
169 add_sc(sc, alias, &mut ret);
170 }
171 }
172 }
173
8bb4bdeb
XL
174 ret.join("\n")
175}
176
177// Get's the subcommand section of a completion file
178// This looks roughly like:
179//
180// case $state in
181// ([bin_name]_args)
182// curcontext=\"${curcontext%:*:*}:[name_hyphen]-command-$words[1]:\"
183// case $line[1] in
184//
185// ([name])
186// _arguments -C -s -S \
187// [subcommand_args]
188// && ret=0
189//
190// [RECURSIVE_CALLS]
191//
192// ;;",
193//
194// [repeat]
195//
196// esac
197// ;;
198// esac",
199//
200// Where the following variables are present:
201// [name] = The subcommand name in the form of "install" for "rustup toolchain install"
8faf50e0
XL
202// [bin_name] = The full space delineated bin_name such as "rustup toolchain install"
203// [name_hyphen] = The full space delineated bin_name, but replace spaces with hyphens
8bb4bdeb
XL
204// [repeat] = From the same recursive calls, but for all subcommands
205// [subcommand_args] = The same as zsh::get_args_of
206fn get_subcommands_of(p: &Parser) -> String {
041b39d2 207 debugln!("get_subcommands_of;");
8bb4bdeb 208
ff7c6d11
XL
209 debugln!(
210 "get_subcommands_of: Has subcommands...{:?}",
211 p.has_subcommands()
212 );
8bb4bdeb
XL
213 if !p.has_subcommands() {
214 return String::new();
215 }
216
217 let sc_names = completions::subcommands_of(p);
218
219 let mut subcmds = vec![];
220 for &(ref name, ref bin_name) in &sc_names {
221 let mut v = vec![format!("({})", name)];
222 let subcommand_args = get_args_of(parser_of(p, &*bin_name));
223 if !subcommand_args.is_empty() {
224 v.push(subcommand_args);
225 }
226 let subcommands = get_subcommands_of(parser_of(p, &*bin_name));
227 if !subcommands.is_empty() {
228 v.push(subcommands);
229 }
230 v.push(String::from(";;"));
231 subcmds.push(v.join("\n"));
232 }
233
234 format!(
ff7c6d11 235 "case $state in
8bb4bdeb 236 ({name})
0531ce1d
XL
237 words=($line[{pos}] \"${{words[@]}}\")
238 (( CURRENT += 1 ))
239 curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\"
240 case $line[{pos}] in
8bb4bdeb
XL
241 {subcommands}
242 esac
243 ;;
ff7c6d11 244esac",
8bb4bdeb
XL
245 name = p.meta.name,
246 name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"),
0531ce1d
XL
247 subcommands = subcmds.join("\n"),
248 pos = p.positionals().len() + 1
ff7c6d11 249 )
8bb4bdeb
XL
250}
251
252fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
041b39d2 253 debugln!("parser_of: sc={}", sc);
8bb4bdeb
XL
254 if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) {
255 return p;
256 }
257 &p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p
258}
259
0531ce1d
XL
260// Writes out the args section, which ends up being the flags, opts and postionals, and a jump to
261// another ZSH function if there are subcommands.
8bb4bdeb
XL
262// The structer works like this:
263// ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)]
264// ^-- list '-v -h' ^--'*' ^--'+' ^-- list 'one two three'
265//
266// An example from the rustup command:
267//
268// _arguments -C -s -S \
269// '(-h --help --verbose)-v[Enable verbose output]' \
270// '(-V -v --version --verbose --help)-h[Prints help information]' \
271// # ... snip for brevity
0531ce1d
XL
272// ':: :_rustup_commands' \ # <-- displays subcommands
273// '*::: :->rustup' \ # <-- displays subcommand args and child subcommands
8bb4bdeb
XL
274// && ret=0
275//
276// The args used for _arguments are as follows:
277// -C: modify the $context internal variable
278// -s: Allow stacking of short args (i.e. -a -b -c => -abc)
279// -S: Do not complete anything after '--' and treat those as argument values
280fn get_args_of(p: &Parser) -> String {
041b39d2 281 debugln!("get_args_of;");
0531ce1d 282 let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")];
8bb4bdeb
XL
283 let opts = write_opts_of(p);
284 let flags = write_flags_of(p);
0531ce1d
XL
285 let positionals = write_positionals_of(p);
286 let sc_or_a = if p.has_subcommands() {
ff7c6d11 287 format!(
0531ce1d 288 "\":: :_{name}_commands\" \\",
ff7c6d11
XL
289 name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__")
290 )
8bb4bdeb
XL
291 } else {
292 String::new()
293 };
294 let sc = if p.has_subcommands() {
0531ce1d 295 format!("\"*::: :->{name}\" \\", name = p.meta.name)
8bb4bdeb
XL
296 } else {
297 String::new()
298 };
299
300 if !opts.is_empty() {
301 ret.push(opts);
302 }
303 if !flags.is_empty() {
304 ret.push(flags);
305 }
0531ce1d
XL
306 if !positionals.is_empty() {
307 ret.push(positionals);
308 }
8bb4bdeb
XL
309 if !sc_or_a.is_empty() {
310 ret.push(sc_or_a);
311 }
312 if !sc.is_empty() {
313 ret.push(sc);
314 }
315 ret.push(String::from("&& ret=0"));
316
317 ret.join("\n")
318}
319
0531ce1d
XL
320// Escape help string inside single quotes and brackets
321fn escape_help(string: &str) -> String {
ff7c6d11
XL
322 string
323 .replace("\\", "\\\\")
ea8adc8c
XL
324 .replace("'", "'\\''")
325 .replace("[", "\\[")
326 .replace("]", "\\]")
327}
328
0531ce1d
XL
329// Escape value string inside single quotes and parentheses
330fn escape_value(string: &str) -> String {
331 string
332 .replace("\\", "\\\\")
333 .replace("'", "'\\''")
334 .replace("(", "\\(")
335 .replace(")", "\\)")
336 .replace(" ", "\\ ")
337}
338
8bb4bdeb 339fn write_opts_of(p: &Parser) -> String {
041b39d2 340 debugln!("write_opts_of;");
8bb4bdeb
XL
341 let mut ret = vec![];
342 for o in p.opts() {
041b39d2 343 debugln!("write_opts_of:iter: o={}", o.name());
0531ce1d 344 let help = o.help().map_or(String::new(), escape_help);
8bb4bdeb
XL
345 let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG);
346 conflicts = if conflicts.is_empty() {
347 String::new()
348 } else {
349 format!("({})", conflicts)
350 };
351
352 let multiple = if o.is_set(ArgSettings::Multiple) {
353 "*"
354 } else {
355 ""
356 };
357 let pv = if let Some(pv_vec) = o.possible_vals() {
3dfed10e
XL
358 format!(
359 ": :({})",
360 pv_vec
361 .iter()
362 .map(|v| escape_value(*v))
363 .collect::<Vec<String>>()
364 .join(" ")
365 )
8bb4bdeb
XL
366 } else {
367 String::new()
368 };
369 if let Some(short) = o.short() {
ff7c6d11
XL
370 let s = format!(
371 "'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\",
8bb4bdeb
XL
372 conflicts = conflicts,
373 multiple = multiple,
374 arg = short,
375 possible_values = pv,
ff7c6d11
XL
376 help = help
377 );
8bb4bdeb 378
041b39d2 379 debugln!("write_opts_of:iter: Wrote...{}", &*s);
8bb4bdeb
XL
380 ret.push(s);
381 }
382 if let Some(long) = o.long() {
ff7c6d11 383 let l = format!(
0531ce1d 384 "'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\",
8bb4bdeb
XL
385 conflicts = conflicts,
386 multiple = multiple,
387 arg = long,
388 possible_values = pv,
ff7c6d11
XL
389 help = help
390 );
8bb4bdeb 391
041b39d2 392 debugln!("write_opts_of:iter: Wrote...{}", &*l);
8bb4bdeb
XL
393 ret.push(l);
394 }
395 }
396
397 ret.join("\n")
398}
399
400fn write_flags_of(p: &Parser) -> String {
041b39d2 401 debugln!("write_flags_of;");
8bb4bdeb
XL
402 let mut ret = vec![];
403 for f in p.flags() {
041b39d2 404 debugln!("write_flags_of:iter: f={}", f.name());
0531ce1d 405 let help = f.help().map_or(String::new(), escape_help);
8bb4bdeb
XL
406 let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG);
407 conflicts = if conflicts.is_empty() {
408 String::new()
409 } else {
410 format!("({})", conflicts)
411 };
412
413 let multiple = if f.is_set(ArgSettings::Multiple) {
414 "*"
415 } else {
416 ""
417 };
418 if let Some(short) = f.short() {
ff7c6d11
XL
419 let s = format!(
420 "'{conflicts}{multiple}-{arg}[{help}]' \\",
8bb4bdeb
XL
421 multiple = multiple,
422 conflicts = conflicts,
423 arg = short,
ff7c6d11
XL
424 help = help
425 );
8bb4bdeb 426
041b39d2 427 debugln!("write_flags_of:iter: Wrote...{}", &*s);
8bb4bdeb
XL
428 ret.push(s);
429 }
430
431 if let Some(long) = f.long() {
ff7c6d11
XL
432 let l = format!(
433 "'{conflicts}{multiple}--{arg}[{help}]' \\",
8bb4bdeb
XL
434 conflicts = conflicts,
435 multiple = multiple,
436 arg = long,
ff7c6d11
XL
437 help = help
438 );
8bb4bdeb 439
041b39d2 440 debugln!("write_flags_of:iter: Wrote...{}", &*l);
8bb4bdeb
XL
441 ret.push(l);
442 }
443 }
444
445 ret.join("\n")
446}
0531ce1d
XL
447
448fn write_positionals_of(p: &Parser) -> String {
449 debugln!("write_positionals_of;");
450 let mut ret = vec![];
451 for arg in p.positionals() {
452 debugln!("write_positionals_of:iter: arg={}", arg.b.name);
453 let a = format!(
454 "'{optional}:{name}{help}:{action}' \\",
3dfed10e
XL
455 optional = if !arg.b.is_set(ArgSettings::Required) {
456 ":"
457 } else {
458 ""
459 },
0531ce1d 460 name = arg.b.name,
3dfed10e
XL
461 help = arg
462 .b
0531ce1d
XL
463 .help
464 .map_or("".to_owned(), |v| " -- ".to_owned() + v)
465 .replace("[", "\\[")
466 .replace("]", "\\]"),
467 action = arg.possible_vals().map_or("_files".to_owned(), |values| {
3dfed10e
XL
468 format!(
469 "({})",
470 values
471 .iter()
472 .map(|v| escape_value(*v))
473 .collect::<Vec<String>>()
474 .join(" ")
475 )
0531ce1d
XL
476 })
477 );
478
479 debugln!("write_positionals_of:iter: Wrote...{}", a);
480 ret.push(a);
481 }
482
483 ret.join("\n")
484}