clap/completions/
elvish.rs

1// Std
2use std::io::Write;
3
4// Internal
5use crate::{app::parser::Parser, INTERNAL_ERROR_MSG};
6
7pub struct ElvishGen<'a, 'b>
8where
9    'a: 'b,
10{
11    p: &'b Parser<'a, 'b>,
12}
13
14impl<'a, 'b> ElvishGen<'a, 'b> {
15    pub fn new(p: &'b Parser<'a, 'b>) -> Self {
16        ElvishGen { p }
17    }
18
19    pub fn generate_to<W: Write>(&self, buf: &mut W) {
20        let bin_name = self.p.meta.bin_name.as_ref().unwrap();
21
22        let mut names = vec![];
23        let subcommands_cases = generate_inner(self.p, "", &mut names);
24
25        let result = format!(
26            r#"
27edit:completion:arg-completer[{bin_name}] = [@words]{{
28    fn spaces [n]{{
29        repeat $n ' ' | joins ''
30    }}
31    fn cand [text desc]{{
32        edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
33    }}
34    command = '{bin_name}'
35    for word $words[1:-1] {{
36        if (has-prefix $word '-') {{
37            break
38        }}
39        command = $command';'$word
40    }}
41    completions = [{subcommands_cases}
42    ]
43    $completions[$command]
44}}
45"#,
46            bin_name = bin_name,
47            subcommands_cases = subcommands_cases
48        );
49
50        w!(buf, result.as_bytes());
51    }
52}
53
54// Escape string inside single quotes
55fn escape_string(string: &str) -> String {
56    string.replace("'", "''")
57}
58
59fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
60    match help {
61        Some(help) => escape_string(help),
62        _ => data.to_string(),
63    }
64}
65
66fn generate_inner<'a, 'b, 'p>(
67    p: &'p Parser<'a, 'b>,
68    previous_command_name: &str,
69    names: &mut Vec<&'p str>,
70) -> String {
71    debugln!("ElvishGen::generate_inner;");
72    let command_name = if previous_command_name.is_empty() {
73        p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
74    } else {
75        format!("{};{}", previous_command_name, &p.meta.name)
76    };
77
78    let mut completions = String::new();
79    let preamble = String::from("\n            cand ");
80
81    for option in p.opts() {
82        if let Some(data) = option.s.short {
83            let tooltip = get_tooltip(option.b.help, data);
84            completions.push_str(&preamble);
85            completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
86        }
87        if let Some(data) = option.s.long {
88            let tooltip = get_tooltip(option.b.help, data);
89            completions.push_str(&preamble);
90            completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
91        }
92    }
93
94    for flag in p.flags() {
95        if let Some(data) = flag.s.short {
96            let tooltip = get_tooltip(flag.b.help, data);
97            completions.push_str(&preamble);
98            completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
99        }
100        if let Some(data) = flag.s.long {
101            let tooltip = get_tooltip(flag.b.help, data);
102            completions.push_str(&preamble);
103            completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
104        }
105    }
106
107    for subcommand in &p.subcommands {
108        let data = &subcommand.p.meta.name;
109        let tooltip = get_tooltip(subcommand.p.meta.about, data);
110        completions.push_str(&preamble);
111        completions.push_str(format!("{} '{}'", data, tooltip).as_str());
112    }
113
114    let mut subcommands_cases = format!(
115        r"
116        &'{}'= {{{}
117        }}",
118        &command_name, completions
119    );
120
121    for subcommand in &p.subcommands {
122        let subcommand_subcommands_cases = generate_inner(&subcommand.p, &command_name, names);
123        subcommands_cases.push_str(&subcommand_subcommands_cases);
124    }
125
126    subcommands_cases
127}