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(from:'\'', to:"''")
58}
59
60fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
61 match help {
62 Some(help: &StyledStr) => 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