use crate::{complete_gen::ShowComp, Error, Meta, Parser, State};
struct Shell<'a>(&'a str);
impl std::fmt::Display for Shell<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
f.write_char('\'')?;
for c in self.0.chars() {
if c == '\'' {
f.write_str("'\\''")
} else {
f.write_char(c)
}?
}
f.write_char('\'')?;
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum ShellComp {
File {
mask: Option<&'static str>,
},
Dir {
mask: Option<&'static str>,
},
Raw {
bash: &'static str,
zsh: &'static str,
fish: &'static str,
elvish: &'static str,
},
Nothing,
}
#[cfg(feature = "autocomplete")]
pub struct ParseCompShell<P> {
pub(crate) inner: P,
pub(crate) op: crate::complete_shell::ShellComp,
}
#[cfg(feature = "autocomplete")]
impl<P, T> Parser<T> for ParseCompShell<P>
where
P: Parser<T> + Sized,
{
fn eval(&self, args: &mut State) -> Result<T, Error> {
let mut comp_items = Vec::new();
args.swap_comps_with(&mut comp_items);
let res = self.inner.eval(args);
args.swap_comps_with(&mut comp_items);
let depth = args.depth();
if let Some(comp) = args.comp_mut() {
for ci in comp_items {
if ci.is_metavar().is_some() {
comp.push_shell(self.op, depth);
} else {
comp.push_comp(ci);
}
}
}
res
}
fn meta(&self) -> Meta {
self.inner.meta()
}
}
pub(crate) fn render_zsh(
items: &[ShowComp],
ops: &[ShellComp],
full_lit: &str,
) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let mut res = String::new();
if items.is_empty() && ops.is_empty() {
return Ok(format!("compadd -- {}\n", full_lit));
}
for op in ops {
match op {
ShellComp::File { mask: None } => writeln!(res, "_files"),
ShellComp::File { mask: Some(mask) } => writeln!(res, "_files -g {}", Shell(mask)),
ShellComp::Dir { mask: None } => writeln!(res, "_files -/"),
ShellComp::Dir { mask: Some(mask) } => writeln!(res, "_files -/ -g {}", Shell(mask)),
ShellComp::Raw { zsh, .. } => writeln!(res, "{}", Shell(zsh)),
ShellComp::Nothing => Ok(()),
}?;
}
if items.len() == 1 {
if items[0].subst.is_empty() {
writeln!(res, "compadd -- {}", Shell(items[0].pretty.as_str()))?;
writeln!(res, "compadd ''")?;
return Ok(res);
} else {
return Ok(format!("compadd -- {}\n", Shell(items[0].subst.as_str())));
}
}
writeln!(res, "local -a descr")?;
for item in items {
writeln!(res, "descr=({})", Shell(&item.to_string()))?;
if let Some(group) = &item.extra.group {
writeln!(
res,
"compadd -l -d descr -V {} -X {} -- {}",
Shell(group),
Shell(group),
Shell(&item.subst),
)?;
} else {
writeln!(
res,
"compadd -l -V nosort -d descr -- {}",
Shell(&item.subst)
)?;
}
}
Ok(res)
}
pub(crate) fn render_bash(
items: &[ShowComp],
ops: &[ShellComp],
full_lit: &str,
) -> Result<String, std::fmt::Error> {
fn bashmask(i: &str) -> &str {
i.strip_prefix("*.").unwrap_or(i)
}
use std::fmt::Write;
let mut res = String::new();
if items.is_empty() && ops.is_empty() {
return Ok(format!("COMPREPLY+=({})\n", Shell(full_lit)));
}
for op in ops {
match op {
ShellComp::File { mask: None } => write!(res, "_filedir"),
ShellComp::File { mask: Some(mask) } => {
writeln!(res, "_filedir '{}'", Shell(bashmask(mask)))
}
ShellComp::Dir { mask: None } => write!(res, "_filedir -d"),
ShellComp::Dir { mask: Some(mask) } => {
writeln!(res, "_filedir -d '{}'", Shell(bashmask(mask)))
}
ShellComp::Raw { bash, .. } => writeln!(res, "{}", Shell(bash)),
ShellComp::Nothing => Ok(()),
}?;
}
if items.len() == 1 {
if items[0].subst.is_empty() {
writeln!(res, "COMPREPLY+=( {} '')", Shell(&items[0].pretty))?;
} else {
writeln!(res, "COMPREPLY+=( {} )\n", Shell(&items[0].subst))?;
}
return Ok(res);
}
let mut prev = "";
for item in items.iter() {
if let Some(group) = &item.extra.group {
if prev != group {
prev = group;
writeln!(res, "COMPREPLY+=({})", Shell(group))?;
}
}
writeln!(res, "COMPREPLY+=({})", Shell(&item.to_string()))?;
}
Ok(res)
}
pub(crate) fn render_test(
items: &[ShowComp],
ops: &[ShellComp],
lit: &str,
) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
if items.is_empty() && ops.is_empty() {
return Ok(format!("{}\n", lit));
}
if items.len() == 1 && ops.is_empty() && !items[0].subst.is_empty() {
return Ok(items[0].subst.clone());
}
let mut res = String::new();
for op in items {
writeln!(
res,
"{}\t{}\t{}\t{}",
op.subst,
op.pretty,
op.extra.group.as_deref().unwrap_or(""),
op.extra.help.as_deref().unwrap_or("")
)?;
}
writeln!(res)?;
for op in ops {
writeln!(res, "{:?}", op)?;
}
Ok(res)
}
pub(crate) fn render_fish(
items: &[ShowComp],
ops: &[ShellComp],
full_lit: &str,
app: &str,
) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let mut res = String::new();
if items.is_empty() && ops.is_empty() {
return Ok(format!("complete -c {} --arguments={}", app, full_lit));
}
let shared = if ops.is_empty() { "-f " } else { "" };
for item in items.iter().rev().filter(|i| !i.subst.is_empty()) {
write!(res, "complete -c {} {}", app, shared)?;
if let Some(long) = item.subst.strip_prefix("--") {
write!(res, "--long-option {} ", long)?;
} else if let Some(short) = item.subst.strip_prefix('-') {
write!(res, "--short-option {} ", short)?;
} else {
write!(res, "-a {} ", item.subst)?;
}
if let Some(help) = item.extra.help.as_deref() {
write!(res, "-d {:?}", help)?;
}
writeln!(res)?;
}
Ok(res)
}
pub(crate) fn render_simple(items: &[ShowComp]) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let mut res = String::new();
if items.len() == 1 {
writeln!(res, "{}", items[0].subst)?;
} else {
for item in items {
if let Some(descr) = item.extra.help.as_deref() {
writeln!(
res,
"{}\t{}",
item.subst,
descr.split('\n').next().unwrap_or("")
)
} else {
writeln!(res, "{}", item.subst)
}?;
}
}
Ok(res)
}