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#[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
59fn 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}