clap_complete/aot/shells/
elvish.rs

1use std::io::{Error, Write};
2
3use clap::builder::StyledStr;
4use clap::Command;
5
6use crate::generator::{utils, Generator};
7use crate::INTERNAL_ERROR_MSG;
8
9/// Generate elvish completion file
10#[derive(Copy, Clone, PartialEq, Eq, Debug)]
11pub struct Elvish;
12
13impl Generator for Elvish {
14    fn file_name(&self, name: &str) -> String {
15        format!("{name}.elv")
16    }
17
18    fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
19        self.try_generate(cmd, buf)
20            .expect("failed to write completion file");
21    }
22
23    fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
24        let bin_name = cmd
25            .get_bin_name()
26            .expect("crate::generate should have set the bin_name");
27
28        let subcommands_cases = generate_inner(cmd, "");
29
30        write!(
31            buf,
32            r#"
33use builtin;
34use str;
35
36set edit:completion:arg-completer[{bin_name}] = {{|@words|
37    fn spaces {{|n|
38        builtin:repeat $n ' ' | str:join ''
39    }}
40    fn cand {{|text desc|
41        edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
42    }}
43    var command = '{bin_name}'
44    for word $words[1..-1] {{
45        if (str:has-prefix $word '-') {{
46            break
47        }}
48        set command = $command';'$word
49    }}
50    var completions = [{subcommands_cases}
51    ]
52    $completions[$command]
53}}
54"#,
55        )
56    }
57}
58
59// Escape string inside single quotes
60fn escape_string(string: &str) -> String {
61    string.replace('\'', "''")
62}
63
64fn escape_help<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
65    match help {
66        Some(help) => escape_string(&help.to_string().replace('\n', " ")),
67        _ => data.to_string(),
68    }
69}
70
71fn generate_inner(p: &Command, previous_command_name: &str) -> String {
72    debug!("generate_inner");
73
74    let command_names = if previous_command_name.is_empty() {
75        vec![p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()]
76    } else {
77        p.get_name_and_visible_aliases()
78            .into_iter()
79            .map(|name| format!("{previous_command_name};{name}"))
80            .collect()
81    };
82
83    let mut completions = String::new();
84    let preamble = String::from("\n            cand ");
85
86    for option in p.get_opts() {
87        if let Some(shorts) = option.get_short_and_visible_aliases() {
88            let tooltip = escape_help(option.get_help(), shorts[0]);
89            for short in shorts {
90                completions.push_str(&preamble);
91                completions.push_str(format!("-{short} '{tooltip}'").as_str());
92            }
93        }
94
95        if let Some(longs) = option.get_long_and_visible_aliases() {
96            let tooltip = escape_help(option.get_help(), longs[0]);
97            for long in longs {
98                completions.push_str(&preamble);
99                completions.push_str(format!("--{long} '{tooltip}'").as_str());
100            }
101        }
102    }
103
104    for flag in utils::flags(p) {
105        if let Some(shorts) = flag.get_short_and_visible_aliases() {
106            let tooltip = escape_help(flag.get_help(), shorts[0]);
107            for short in shorts {
108                completions.push_str(&preamble);
109                completions.push_str(format!("-{short} '{tooltip}'").as_str());
110            }
111        }
112
113        if let Some(longs) = flag.get_long_and_visible_aliases() {
114            let tooltip = escape_help(flag.get_help(), longs[0]);
115            for long in longs {
116                completions.push_str(&preamble);
117                completions.push_str(format!("--{long} '{tooltip}'").as_str());
118            }
119        }
120    }
121
122    for subcommand in p.get_subcommands() {
123        for name in subcommand.get_name_and_visible_aliases() {
124            let tooltip = escape_help(subcommand.get_about(), name);
125
126            completions.push_str(&preamble);
127            completions.push_str(format!("{name} '{tooltip}'").as_str());
128        }
129    }
130
131    let mut subcommands_cases = String::new();
132    for command_name in &command_names {
133        subcommands_cases.push_str(&format!(
134            r"
135        &'{}'= {{{}
136        }}",
137            &command_name, completions
138        ));
139    }
140
141    for subcommand in p.get_subcommands() {
142        for command_name in &command_names {
143            let subcommand_subcommands_cases = generate_inner(subcommand, command_name);
144            subcommands_cases.push_str(&subcommand_subcommands_cases);
145        }
146    }
147
148    subcommands_cases
149}