1use crate::command::Command;
8use std::fmt::Write;
9
10macro_rules! safe_writeln {
13 ($dst:expr) => {
14 if writeln!($dst).is_err() {
15 eprintln!("Warning: Failed to write to completion script buffer");
16 }
17 };
18 ($dst:expr, $($arg:tt)*) => {
19 if writeln!($dst, $($arg)*).is_err() {
20 eprintln!("Warning: Failed to write to completion script buffer");
21 }
22 };
23}
24
25#[derive(Debug, Clone, Copy)]
47pub enum Shell {
48 Bash,
50 Zsh,
52 Fish,
54}
55
56impl Command {
57 pub fn generate_completion(&self, shell: Shell) -> String {
91 match shell {
92 Shell::Bash => self.generate_bash_completion(),
93 Shell::Zsh => self.generate_zsh_completion(),
94 Shell::Fish => self.generate_fish_completion(),
95 }
96 }
97
98 fn generate_bash_completion(&self) -> String {
99 let mut script = String::new();
100
101 safe_writeln!(&mut script, "# Bash completion for {}", self.name());
102 safe_writeln!(&mut script, "_{}_complete() {{", self.name());
103 safe_writeln!(&mut script, " local cur prev words cword");
104 safe_writeln!(
105 &mut script,
106 " _get_comp_words_by_ref -n : cur prev words cword"
107 );
108 safe_writeln!(&mut script);
109 safe_writeln!(
110 &mut script,
111 " # Call our binary with special completion env var"
112 );
113 safe_writeln!(&mut script, " local IFS=$'\\n'");
114 safe_writeln!(&mut script, " local response");
115 safe_writeln!(
116 &mut script,
117 " response=$({}_COMPLETE=bash \"${{words[0]}}\" __complete \"${{words[@]:1:$((cword-1))}}\" \"$cur\" 2>/dev/null)",
118 self.name().to_uppercase()
119 );
120 safe_writeln!(&mut script);
121 safe_writeln!(&mut script, " if [[ -n \"$response\" ]]; then");
122 safe_writeln!(
123 &mut script,
124 " # Use printf to handle each line separately"
125 );
126 safe_writeln!(&mut script, " local lines=()");
127 safe_writeln!(&mut script, " local help_messages=()");
128 safe_writeln!(&mut script, " while IFS= read -r line; do");
129 safe_writeln!(
130 &mut script,
131 " if [[ \"$line\" == _activehelp_* ]]; then"
132 );
133 safe_writeln!(&mut script, " # Extract help message");
134 safe_writeln!(
135 &mut script,
136 " help_messages+=(\"${{line#_activehelp_ }}\")"
137 );
138 safe_writeln!(&mut script, " else");
139 safe_writeln!(&mut script, " lines+=(\"$line\")");
140 safe_writeln!(&mut script, " fi");
141 safe_writeln!(&mut script, " done <<< \"$response\"");
142 safe_writeln!(&mut script, " COMPREPLY=( \"${{lines[@]}}\" )");
143 safe_writeln!(&mut script);
144 safe_writeln!(&mut script, " # Display help messages if any");
145 safe_writeln!(
146 &mut script,
147 " if [[ ${{#help_messages[@]}} -gt 0 ]]; then"
148 );
149 safe_writeln!(&mut script, " printf '\\n'");
150 safe_writeln!(
151 &mut script,
152 " for msg in \"${{help_messages[@]}}\"; do"
153 );
154 safe_writeln!(&mut script, " printf '%s\\n' \"$msg\"");
155 safe_writeln!(&mut script, " done");
156 safe_writeln!(&mut script, " printf '\\n'");
157 safe_writeln!(&mut script, " fi");
158 safe_writeln!(&mut script, " fi");
159 safe_writeln!(&mut script, "}}");
160 safe_writeln!(&mut script);
161 safe_writeln!(
162 &mut script,
163 "complete -F _{}_complete {}",
164 self.name(),
165 self.name()
166 );
167
168 script
169 }
170
171 fn generate_zsh_completion(&self) -> String {
172 let mut script = String::new();
173
174 safe_writeln!(&mut script, "#compdef -P {}", self.name());
175 safe_writeln!(&mut script, "# Zsh completion for {}", self.name());
176 safe_writeln!(&mut script);
177 safe_writeln!(&mut script, "_{}_complete() {{", self.name());
178 safe_writeln!(&mut script, " local -a completions");
179 safe_writeln!(&mut script, " local IFS=$'\\n'");
180 safe_writeln!(&mut script);
181 safe_writeln!(
182 &mut script,
183 " # Get the actual command from the command line"
184 );
185 safe_writeln!(&mut script, " local cmd=\"${{words[1]}}\"");
186 safe_writeln!(
187 &mut script,
188 " if [[ \"$cmd\" != /* ]] && ! command -v \"$cmd\" &>/dev/null; then"
189 );
190 safe_writeln!(
191 &mut script,
192 " # If not found in PATH, try relative path"
193 );
194 safe_writeln!(&mut script, " if [[ -x \"$cmd\" ]]; then");
195 safe_writeln!(&mut script, " cmd=\"./$cmd\"");
196 safe_writeln!(&mut script, " fi");
197 safe_writeln!(&mut script, " fi");
198 safe_writeln!(&mut script);
199 safe_writeln!(&mut script, " # Build completion arguments");
200 safe_writeln!(&mut script, " local -a comp_line");
201 safe_writeln!(&mut script, " comp_line=(\"__complete\")");
202 safe_writeln!(&mut script);
203 safe_writeln!(&mut script, " # Add all words except the command name");
204 safe_writeln!(&mut script, " local i");
205 safe_writeln!(&mut script, " for (( i = 2; i < CURRENT; i++ )); do");
206 safe_writeln!(&mut script, " comp_line+=(\"${{words[$i]}}\")");
207 safe_writeln!(&mut script, " done");
208 safe_writeln!(&mut script);
209 safe_writeln!(&mut script, " # Add the current word being completed");
210 safe_writeln!(&mut script, " comp_line+=(\"${{words[CURRENT]}}\")");
211 safe_writeln!(&mut script);
212 safe_writeln!(
213 &mut script,
214 " # Call the command with completion environment variable"
215 );
216 safe_writeln!(&mut script, " local response");
217 safe_writeln!(
218 &mut script,
219 " response=$({}_COMPLETE=zsh \"$cmd\" \"${{comp_line[@]}}\" 2>/dev/null)",
220 self.name().to_uppercase()
221 );
222 safe_writeln!(&mut script);
223 safe_writeln!(&mut script, " if [[ -n \"$response\" ]]; then");
224 safe_writeln!(&mut script, " local -a values");
225 safe_writeln!(&mut script, " local -a descriptions");
226 safe_writeln!(&mut script, " local -a help_messages");
227 safe_writeln!(&mut script, " local line");
228 safe_writeln!(&mut script);
229 safe_writeln!(&mut script, " # Parse response lines");
230 safe_writeln!(&mut script, " while IFS= read -r line; do");
231 safe_writeln!(
232 &mut script,
233 " if [[ \"$line\" == _activehelp_::* ]]; then"
234 );
235 safe_writeln!(&mut script, " # ActiveHelp message");
236 safe_writeln!(
237 &mut script,
238 " help_messages+=(\"${{line#_activehelp_::}}\")"
239 );
240 safe_writeln!(&mut script, " elif [[ \"$line\" == *:* ]]; then");
241 safe_writeln!(&mut script, " # Line has description");
242 safe_writeln!(&mut script, " values+=(\"${{line%%:*}}\")");
243 safe_writeln!(
244 &mut script,
245 " descriptions+=(\"${{line#*:}}\")"
246 );
247 safe_writeln!(&mut script, " else");
248 safe_writeln!(&mut script, " # No description");
249 safe_writeln!(&mut script, " values+=(\"$line\")");
250 safe_writeln!(&mut script, " descriptions+=(\"\")");
251 safe_writeln!(&mut script, " fi");
252 safe_writeln!(&mut script, " done <<< \"$response\"");
253 safe_writeln!(&mut script);
254 safe_writeln!(&mut script, " # Display ActiveHelp messages if any");
255 safe_writeln!(
256 &mut script,
257 " if [[ ${{#help_messages[@]}} -gt 0 ]]; then"
258 );
259 safe_writeln!(&mut script, " local formatted_help=()");
260 safe_writeln!(
261 &mut script,
262 " for msg in \"${{help_messages[@]}}\"; do"
263 );
264 safe_writeln!(
265 &mut script,
266 " formatted_help+=(\"-- $msg --\")"
267 );
268 safe_writeln!(&mut script, " done");
269 safe_writeln!(
270 &mut script,
271 " compadd -x \"${{(j: :)formatted_help}}\""
272 );
273 safe_writeln!(&mut script, " fi");
274 safe_writeln!(&mut script);
275 safe_writeln!(&mut script, " # Add completions with descriptions");
276 safe_writeln!(
277 &mut script,
278 " if [[ ${{#descriptions[@]}} -gt 0 ]] && [[ -n \"${{descriptions[*]// }}\" ]]; then"
279 );
280 safe_writeln!(
281 &mut script,
282 " compadd -Q -d descriptions -a values"
283 );
284 safe_writeln!(&mut script, " else");
285 safe_writeln!(&mut script, " compadd -Q -a values");
286 safe_writeln!(&mut script, " fi");
287 safe_writeln!(&mut script, " fi");
288 safe_writeln!(&mut script, "}}");
289 safe_writeln!(&mut script);
290 safe_writeln!(
291 &mut script,
292 "compdef _{}_complete {}",
293 self.name(),
294 self.name()
295 );
296
297 script
298 }
299
300 fn generate_fish_completion(&self) -> String {
301 let mut script = String::new();
302
303 safe_writeln!(&mut script, "# Fish completion for {}", self.name());
304 safe_writeln!(&mut script, "function __{}_complete", self.name());
305 safe_writeln!(&mut script, " set -l cmd (commandline -opc)");
306 safe_writeln!(&mut script, " set -l cursor (commandline -C)");
307 safe_writeln!(&mut script, " set -l current (commandline -ct)");
308 safe_writeln!(&mut script);
309 safe_writeln!(
310 &mut script,
311 " # Call our binary with special completion env var"
312 );
313 safe_writeln!(
314 &mut script,
315 " set -l response (env {}_COMPLETE=fish $cmd[1] __complete $cmd[2..-1] $current 2>/dev/null)",
316 self.name().to_uppercase()
317 );
318 safe_writeln!(&mut script);
319 safe_writeln!(&mut script, " # Process response and handle ActiveHelp");
320 safe_writeln!(&mut script, " set -l help_messages");
321 safe_writeln!(&mut script, " for line in $response");
322 safe_writeln!(
323 &mut script,
324 " if string match -q '_activehelp_*' -- $line"
325 );
326 safe_writeln!(&mut script, " # Extract help message");
327 safe_writeln!(
328 &mut script,
329 " set -a help_messages (string replace '_activehelp_\t' '' -- $line)"
330 );
331 safe_writeln!(&mut script, " else");
332 safe_writeln!(&mut script, " echo $line");
333 safe_writeln!(&mut script, " end");
334 safe_writeln!(&mut script, " end");
335 safe_writeln!(&mut script);
336 safe_writeln!(&mut script, " # Display help messages if any");
337 safe_writeln!(&mut script, " if test (count $help_messages) -gt 0");
338 safe_writeln!(&mut script, " for msg in $help_messages");
339 safe_writeln!(&mut script, " echo \"ยป $msg\" >&2");
340 safe_writeln!(&mut script, " end");
341 safe_writeln!(&mut script, " end");
342 safe_writeln!(&mut script, "end");
343 safe_writeln!(&mut script);
344 safe_writeln!(
345 &mut script,
346 "complete -c {} -f -a '(__{}_complete)'",
347 self.name(),
348 self.name()
349 );
350
351 script
352 }
353}