1use std::io::Write;
2
3use clap::builder::StyledStr;
4use clap::*;
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        let result = format!(
26            r#"
27use builtin;
28use str;
29
30set edit:completion:arg-completer[{bin_name}] = {{|@words|
31    fn spaces {{|n|
32        builtin:repeat $n ' ' | str:join ''
33    }}
34    fn cand {{|text desc|
35        edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
36    }}
37    var command = '{bin_name}'
38    for word $words[1..-1] {{
39        if (str:has-prefix $word '-') {{
40            break
41        }}
42        set command = $command';'$word
43    }}
44    var completions = [{subcommands_cases}
45    ]
46    $completions[$command]
47}}
48"#,
49        );
50
51        w!(buf, result.as_bytes());
52    }
53}
54
55// Escape string inside single quotes
56fn escape_string(string: &str) -> String {
57    string.replace('\'', "''")
58}
59
60fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
61    match help {
62        Some(help) => escape_string(&help.to_string()),
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_name = if previous_command_name.is_empty() {
71        p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()
72    } else {
73        format!("{};{}", previous_command_name, &p.get_name())
74    };
75
76    let mut completions = String::new();
77    let preamble = String::from("\n            cand ");
78
79    for option in p.get_opts() {
80        if let Some(shorts) = option.get_short_and_visible_aliases() {
81            let tooltip = get_tooltip(option.get_help(), shorts[0]);
82            for short in shorts {
83                completions.push_str(&preamble);
84                completions.push_str(format!("-{short} '{tooltip}'").as_str());
85            }
86        }
87
88        if let Some(longs) = option.get_long_and_visible_aliases() {
89            let tooltip = get_tooltip(option.get_help(), longs[0]);
90            for long in longs {
91                completions.push_str(&preamble);
92                completions.push_str(format!("--{long} '{tooltip}'").as_str());
93            }
94        }
95    }
96
97    for flag in utils::flags(p) {
98        if let Some(shorts) = flag.get_short_and_visible_aliases() {
99            let tooltip = get_tooltip(flag.get_help(), shorts[0]);
100            for short in shorts {
101                completions.push_str(&preamble);
102                completions.push_str(format!("-{short} '{tooltip}'").as_str());
103            }
104        }
105
106        if let Some(longs) = flag.get_long_and_visible_aliases() {
107            let tooltip = get_tooltip(flag.get_help(), longs[0]);
108            for long in longs {
109                completions.push_str(&preamble);
110                completions.push_str(format!("--{long} '{tooltip}'").as_str());
111            }
112        }
113    }
114
115    for subcommand in p.get_subcommands() {
116        let data = &subcommand.get_name();
117        let tooltip = get_tooltip(subcommand.get_about(), data);
118
119        completions.push_str(&preamble);
120        completions.push_str(format!("{data} '{tooltip}'").as_str());
121    }
122
123    let mut subcommands_cases = format!(
124        r"
125        &'{}'= {{{}
126        }}",
127        &command_name, completions
128    );
129
130    for subcommand in p.get_subcommands() {
131        let subcommand_subcommands_cases = generate_inner(subcommand, &command_name);
132        subcommands_cases.push_str(&subcommand_subcommands_cases);
133    }
134
135    subcommands_cases
136}
137