clap_complete/aot/shells/
elvish.rs

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