1 use std::io::Write;
2
3 use clap::builder::StyledStr;
4 use clap::*;
5
6 use crate::generator::{utils, Generator};
7 use crate::INTERNAL_ERROR_MSG;
8
9 /// Generate elvish completion file
10 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
11 pub struct Elvish;
12
13 impl Generator for Elvish {
file_name(&self, name: &str) -> String14 fn file_name(&self, name: &str) -> String {
15 format!("{}.elv", name)
16 }
17
generate(&self, cmd: &Command, buf: &mut dyn Write)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#"
27 use builtin;
28 use str;
29
30 set 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 bin_name = bin_name,
50 subcommands_cases = subcommands_cases
51 );
52
53 w!(buf, result.as_bytes());
54 }
55 }
56
57 // Escape string inside single quotes
escape_string(string: &str) -> String58 fn escape_string(string: &str) -> String {
59 string.replace('\'', "''")
60 }
61
get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String62 fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
63 match help {
64 Some(help) => escape_string(&help.to_string()),
65 _ => data.to_string(),
66 }
67 }
68
generate_inner(p: &Command, previous_command_name: &str) -> String69 fn generate_inner(p: &Command, previous_command_name: &str) -> String {
70 debug!("generate_inner");
71
72 let command_name = if previous_command_name.is_empty() {
73 p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()
74 } else {
75 format!("{};{}", previous_command_name, &p.get_name())
76 };
77
78 let mut completions = String::new();
79 let preamble = String::from("\n cand ");
80
81 for option in p.get_opts() {
82 if let Some(shorts) = option.get_short_and_visible_aliases() {
83 let tooltip = get_tooltip(option.get_help(), shorts[0]);
84 for short in shorts {
85 completions.push_str(&preamble);
86 completions.push_str(format!("-{} '{}'", short, tooltip).as_str());
87 }
88 }
89
90 if let Some(longs) = option.get_long_and_visible_aliases() {
91 let tooltip = get_tooltip(option.get_help(), longs[0]);
92 for long in longs {
93 completions.push_str(&preamble);
94 completions.push_str(format!("--{} '{}'", long, tooltip).as_str());
95 }
96 }
97 }
98
99 for flag in utils::flags(p) {
100 if let Some(shorts) = flag.get_short_and_visible_aliases() {
101 let tooltip = get_tooltip(flag.get_help(), shorts[0]);
102 for short in shorts {
103 completions.push_str(&preamble);
104 completions.push_str(format!("-{} '{}'", short, tooltip).as_str());
105 }
106 }
107
108 if let Some(longs) = flag.get_long_and_visible_aliases() {
109 let tooltip = get_tooltip(flag.get_help(), longs[0]);
110 for long in longs {
111 completions.push_str(&preamble);
112 completions.push_str(format!("--{} '{}'", long, tooltip).as_str());
113 }
114 }
115 }
116
117 for subcommand in p.get_subcommands() {
118 let data = &subcommand.get_name();
119 let tooltip = get_tooltip(subcommand.get_about(), data);
120
121 completions.push_str(&preamble);
122 completions.push_str(format!("{} '{}'", data, tooltip).as_str());
123 }
124
125 let mut subcommands_cases = format!(
126 r"
127 &'{}'= {{{}
128 }}",
129 &command_name, completions
130 );
131
132 for subcommand in p.get_subcommands() {
133 let subcommand_subcommands_cases = generate_inner(subcommand, &command_name);
134 subcommands_cases.push_str(&subcommand_subcommands_cases);
135 }
136
137 subcommands_cases
138 }
139