clap_complete/shells/
fish.rs1use std::io::Write;
2
3use clap::{builder, Arg, Command, ValueHint};
4
5use crate::generator::{utils, Generator};
6
7#[derive(Copy, Clone, PartialEq, Eq, Debug)]
11pub struct Fish;
12
13impl Generator for Fish {
14 fn file_name(&self, name: &str) -> String {
15 format!("{name}.fish")
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 mut buffer = String::new();
24 gen_fish_inner(bin_name, &[], cmd, &mut buffer);
25 w!(buf, buffer.as_bytes());
26 }
27}
28
29fn escape_string(string: &str, escape_comma: bool) -> String {
31 let string = string.replace('\\', "\\\\").replace('\'', "\\'");
32 if escape_comma {
33 string.replace(',', "\\,")
34 } else {
35 string
36 }
37}
38
39fn escape_help(help: &builder::StyledStr) -> String {
40 escape_string(&help.to_string().replace('\n', " "), false)
41}
42
43fn gen_fish_inner(
44 root_command: &str,
45 parent_commands: &[&str],
46 cmd: &Command,
47 buffer: &mut String,
48) {
49 debug!("gen_fish_inner");
50 let mut basic_template = format!("complete -c {root_command}");
64
65 if parent_commands.is_empty() {
66 if cmd.has_subcommands() {
67 basic_template.push_str(" -n \"__fish_use_subcommand\"");
68 }
69 } else {
70 basic_template.push_str(
71 format!(
72 " -n \"{}\"",
73 parent_commands
74 .iter()
75 .map(|command| format!("__fish_seen_subcommand_from {command}"))
76 .chain(
77 cmd.get_subcommands()
78 .flat_map(Command::get_name_and_visible_aliases)
79 .map(|name| format!("not __fish_seen_subcommand_from {name}"))
80 )
81 .collect::<Vec<_>>()
82 .join("; and ")
83 )
84 .as_str(),
85 );
86 }
87
88 debug!("gen_fish_inner: parent_commands={parent_commands:?}");
89
90 for option in cmd.get_opts() {
91 let mut template = basic_template.clone();
92
93 if let Some(shorts) = option.get_short_and_visible_aliases() {
94 for short in shorts {
95 template.push_str(format!(" -s {short}").as_str());
96 }
97 }
98
99 if let Some(longs) = option.get_long_and_visible_aliases() {
100 for long in longs {
101 template.push_str(format!(" -l {}", escape_string(long, false)).as_str());
102 }
103 }
104
105 if let Some(data) = option.get_help() {
106 template.push_str(&format!(" -d '{}'", escape_help(data)));
107 }
108
109 template.push_str(value_completion(option).as_str());
110
111 buffer.push_str(template.as_str());
112 buffer.push('\n');
113 }
114
115 for flag in utils::flags(cmd) {
116 let mut template = basic_template.clone();
117
118 if let Some(shorts) = flag.get_short_and_visible_aliases() {
119 for short in shorts {
120 template.push_str(format!(" -s {short}").as_str());
121 }
122 }
123
124 if let Some(longs) = flag.get_long_and_visible_aliases() {
125 for long in longs {
126 template.push_str(format!(" -l {}", escape_string(long, false)).as_str());
127 }
128 }
129
130 if let Some(data) = flag.get_help() {
131 template.push_str(&format!(" -d '{}'", escape_help(data)));
132 }
133
134 buffer.push_str(template.as_str());
135 buffer.push('\n');
136 }
137
138 for subcommand in cmd.get_subcommands() {
139 for subcommand_name in subcommand.get_name_and_visible_aliases() {
140 let mut template = basic_template.clone();
141
142 template.push_str(" -f");
143 template.push_str(format!(" -a \"{}\"", subcommand_name).as_str());
144
145 if let Some(data) = subcommand.get_about() {
146 template.push_str(format!(" -d '{}'", escape_help(data)).as_str());
147 }
148
149 buffer.push_str(template.as_str());
150 buffer.push('\n');
151 }
152 }
153
154 for subcommand in cmd.get_subcommands() {
156 for subcommand_name in subcommand.get_name_and_visible_aliases() {
157 let mut parent_commands: Vec<_> = parent_commands.into();
158 parent_commands.push(subcommand_name);
159 gen_fish_inner(root_command, &parent_commands, subcommand, buffer);
160 }
161 }
162}
163
164fn value_completion(option: &Arg) -> String {
165 if !option.get_num_args().expect("built").takes_values() {
166 return "".to_string();
167 }
168
169 if let Some(data) = utils::possible_values(option) {
170 format!(
173 " -r -f -a \"{{{}}}\"",
174 data.iter()
175 .filter_map(|value| if value.is_hide_set() {
176 None
177 } else {
178 Some(format!(
181 "{}\t'{}'",
182 escape_string(value.get_name(), true).as_str(),
183 escape_help(value.get_help().unwrap_or_default())
184 ))
185 })
186 .collect::<Vec<_>>()
187 .join(",")
188 )
189 } else {
190 match option.get_value_hint() {
192 ValueHint::Unknown => " -r",
193 ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => " -r -F",
195 ValueHint::DirPath => " -r -f -a \"(__fish_complete_directories)\"",
196 ValueHint::CommandString | ValueHint::CommandName => {
199 " -r -f -a \"(__fish_complete_command)\""
200 }
201 ValueHint::Username => " -r -f -a \"(__fish_complete_users)\"",
202 ValueHint::Hostname => " -r -f -a \"(__fish_print_hostnames)\"",
203 _ => " -r -f",
205 }
206 .to_string()
207 }
208}