use super::*;
pub(super) fn emit_fish(data: &CompletionData) -> String {
let mut s = String::new();
s.push_str("# fdl fish completion (generated)\n");
s.push_str("# fdl completions fish | source\n");
s.push_str("complete -c fdl -f\n\n");
s.push_str("function __fdl_find_envs\n");
s.push_str(" set -l _dir $PWD\n");
s.push_str(" while true\n");
s.push_str(" if test -f \"$_dir/fdl.yml\"\n");
s.push_str(" for _f in \"$_dir\"/fdl.*.yml\n");
s.push_str(" test -f \"$_f\"; or continue\n");
s.push_str(" set -l _n (string replace -r '^.*/fdl\\.' '' -- $_f)\n");
s.push_str(" set _n (string replace -r '\\.yml$' '' -- $_n)\n");
s.push_str(" if test -n \"$_n\"; and not string match -q '*.*' -- \"$_n\"\n");
s.push_str(" echo \"@$_n\"\n");
s.push_str(" end\n");
s.push_str(" end\n");
s.push_str(" return\n");
s.push_str(" end\n");
s.push_str(" test \"$_dir\" = '/'; and return\n");
s.push_str(" set _dir (dirname \"$_dir\")\n");
s.push_str(" end\n");
s.push_str("end\n\n");
s.push_str("function __fdl_active_command\n");
s.push_str(" set -l toks (commandline -opc)\n");
s.push_str(" set -l idx 2\n");
s.push_str(" if test (count $toks) -ge 2\n");
s.push_str(" set -l envs (__fdl_find_envs)\n");
s.push_str(" if contains -- $toks[2] $envs\n");
s.push_str(" set idx 3\n");
s.push_str(" end\n");
s.push_str(" end\n");
s.push_str(" if test (count $toks) -ge $idx\n");
s.push_str(" echo $toks[$idx]\n");
s.push_str(" end\n");
s.push_str("end\n\n");
s.push_str("function __fdl_active_subcommand\n");
s.push_str(" set -l toks (commandline -opc)\n");
s.push_str(" set -l idx 3\n");
s.push_str(" if test (count $toks) -ge 2\n");
s.push_str(" set -l envs (__fdl_find_envs)\n");
s.push_str(" if contains -- $toks[2] $envs\n");
s.push_str(" set idx 4\n");
s.push_str(" end\n");
s.push_str(" end\n");
s.push_str(" if test (count $toks) -ge $idx\n");
s.push_str(" echo $toks[$idx]\n");
s.push_str(" end\n");
s.push_str("end\n\n");
s.push_str("function __fdl_at_command_position\n");
s.push_str(" test -z (__fdl_active_command)\n");
s.push_str("end\n\n");
s.push_str("# Top-level\n");
s.push_str(
"complete -c fdl -n '__fish_use_subcommand' -a '(__fdl_find_envs)' \
-d 'env overlay'\n",
);
for word in &data.top_level {
s.push_str(&format!(
"complete -c fdl -n '__fdl_at_command_position' -a '{word}'\n"
));
}
for flag in TOP_FLAGS {
if let Some(long) = flag.strip_prefix("--") {
s.push_str(&format!(
"complete -c fdl -n '__fish_use_subcommand' -l '{long}'\n"
));
} else if let Some(short) = flag.strip_prefix('-') {
s.push_str(&format!(
"complete -c fdl -n '__fish_use_subcommand' -s '{short}'\n"
));
}
}
s.push('\n');
for cmd in &data.commands {
s.push_str(&format!("# {name}\n", name = cmd.name));
let cond = format!("contains -- {} (__fdl_active_command)", cmd.name);
for (name, desc) in &cmd.presets {
let safe = desc
.as_deref()
.unwrap_or("preset")
.replace('\'', "\\'");
s.push_str(&format!(
"complete -c fdl -n '{cond}' -a '{name}' -d '{safe}'\n"
));
}
for sub in &cmd.sub_commands {
s.push_str(&format!(
"complete -c fdl -n '{cond}' -a '{sub}' -d 'command'\n"
));
}
for opt in &cmd.options {
let long = &opt.long;
let mut line = format!("complete -c fdl -n '{cond}' -l '{long}'");
if let Some(short_flag) = &opt.short {
line.push_str(&format!(" -s '{short_flag}'"));
}
match &opt.value {
ValueKind::None => {}
ValueKind::Choices(cs) => {
line.push_str(" -r -f -a '");
line.push_str(&cs.join(" "));
line.push('\'');
}
ValueKind::Path => {
line.push_str(" -r -F");
}
ValueKind::Completer(c) => {
line.push_str(&format!(" -r -f -a '({c})'"));
}
ValueKind::Any => {
line.push_str(" -r");
}
}
if let Some(desc) = &opt.description {
let safe = desc.replace('\'', "\\'");
line.push_str(&format!(" -d '{safe}'"));
}
line.push('\n');
s.push_str(&line);
}
for tc in &cmd.tree_commands {
let safe = tc
.description
.as_deref()
.unwrap_or("command")
.replace('\'', "\\'");
s.push_str(&format!(
"complete -c fdl -n '{cond}' -a '{}' -d '{safe}'\n",
tc.name
));
}
for tc in &cmd.tree_commands {
let sub_cond = format!(
"contains -- {} (__fdl_active_command); \
and contains -- {} (__fdl_active_subcommand)",
cmd.name, tc.name
);
for opt in &tc.options {
emit_fish_option_line(&mut s, &sub_cond, opt);
}
}
s.push('\n');
}
for b in data.builtins.iter().filter(|b| b.path.len() == 1) {
let name = &b.path[0];
let has_subs = !b.sub_commands.is_empty();
let has_opts = !b.options.is_empty();
if !has_subs && !has_opts {
continue;
}
s.push_str(&format!("# {}\n", b.joined_path()));
let parent_cond = format!("contains -- {name} (__fdl_active_command)");
if has_subs {
for sub in &b.sub_commands {
s.push_str(&format!(
"complete -c fdl -n '{parent_cond}' -a '{sub}'\n"
));
}
}
if has_opts {
for opt in &b.options {
emit_fish_option_line(&mut s, &parent_cond, opt);
}
}
}
for b in data.builtins.iter().filter(|b| b.path.len() == 2) {
if b.options.is_empty() {
continue;
}
let parent = &b.path[0];
let child = &b.path[1];
s.push_str(&format!("# {} {}\n", parent, child));
let cond = format!(
"contains -- {parent} (__fdl_active_command); \
and contains -- {child} (__fdl_active_subcommand)"
);
for opt in &b.options {
emit_fish_option_line(&mut s, &cond, opt);
}
}
s
}
fn emit_fish_option_line(s: &mut String, cond: &str, opt: &OptionCompletion) {
let long = &opt.long;
let mut line = format!("complete -c fdl -n '{cond}' -l '{long}'");
if let Some(short_flag) = &opt.short {
line.push_str(&format!(" -s '{short_flag}'"));
}
match &opt.value {
ValueKind::None => {}
ValueKind::Choices(cs) => {
line.push_str(" -r -f -a '");
line.push_str(&cs.join(" "));
line.push('\'');
}
ValueKind::Path => {
line.push_str(" -r -F");
}
ValueKind::Completer(c) => {
line.push_str(&format!(" -r -f -a '({c})'"));
}
ValueKind::Any => {
line.push_str(" -r");
}
}
if let Some(desc) = &opt.description {
let safe = desc.replace('\'', "\\'");
line.push_str(&format!(" -d '{safe}'"));
}
line.push('\n');
s.push_str(&line);
}