use crate::Shell;
use std::io::Result as IoResult;
use std::io::Write;
#[allow(unused)]
use crate::core::Command;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Completion {
pub value: String,
pub description: String,
pub group: Option<&'static str>,
pub always_match: bool,
}
impl Completion {
pub fn new(value: impl ToString, description: impl ToString) -> Self {
Completion {
value: value.to_string(),
description: description.to_string(),
group: None,
always_match: false,
}
}
pub fn set_value<F: FnOnce(&str) -> String>(&mut self, val: F) {
self.value = val(&self.value);
}
pub fn value<F: FnOnce(&str) -> String>(mut self, val: F) -> Self {
self.set_value(val);
self
}
pub fn always_match(mut self) -> Self {
self.always_match = true;
self
}
pub fn group(mut self, group: &'static str) -> Self {
self.group = Some(group);
self
}
}
#[derive(Debug)]
pub struct Ready {
arg: String,
comps: Vec<Completion>,
}
impl Ready {
#[doc(hidden)]
pub fn inner(&self) -> (&[Completion], &str) {
(&self.comps, &self.arg)
}
#[doc(hidden)]
pub fn into_inner(self) -> (Vec<Completion>, String) {
(self.comps, self.arg)
}
pub fn print(&self, shell: Shell, w: &mut impl Write) -> IoResult<()> {
let comps = self.comps.iter().filter(|comp| {
if shell == Shell::Bash {
return comp.value.starts_with(&self.arg); }
if comp.always_match {
return true;
}
naive_fuzzy(&comp.value, &self.arg)
});
match shell {
Shell::Bash => {
for comp in comps {
writeln!(w, "{}", comp.value)?; }
}
Shell::Fish => {
for comp in comps {
let desc = match (comp.description.as_str(), comp.group) {
("", None) => "",
("", Some(g)) => g,
(desc, _) => desc,
};
writeln!(w, "{}\t{}", comp.value, desc)?
}
}
Shell::Zsh => {
let mut groups: Vec<(&str, Vec<&Completion>)> = vec![];
for comp in comps {
let group = comp.group.unwrap_or("option");
let (_, group_vec) =
if let Some(group_vec) = groups.iter_mut().find(|(s, _)| *s == group) {
group_vec
} else {
groups.push((group, vec![]));
groups.last_mut().unwrap()
};
group_vec.push(comp);
}
groups.sort_by_key(|(k, v)| (v.len(), *k));
for (group, comps) in groups.into_iter() {
writeln!(w, "{}", group)?;
for comp in comps.into_iter() {
if comp.description.is_empty() {
writeln!(w, "\t{}\t{}", comp.value, comp.value)?
} else {
writeln!(
w,
"\t{}\t{} -- {}",
comp.value, comp.value, comp.description
)?
}
}
}
writeln!(w, "END")?;
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct Unready {
arg: String,
#[doc(hidden)]
pub preexist: Vec<Completion>,
#[doc(hidden)]
pub prefix: String,
}
impl Unready {
pub(crate) fn new(prefix: String, arg: String) -> Self {
Unready {
prefix,
arg,
preexist: vec![],
}
}
pub(crate) fn preexist(mut self, preexist: impl Iterator<Item = Completion>) -> Self {
self.preexist.extend(preexist);
self
}
pub fn to_ready(self, comps: Vec<Completion>) -> Ready {
log::info!("to_ready: {:?} with {:?}", self, comps);
let mut final_comps = self.preexist;
final_comps.extend(comps);
if !self.prefix.is_empty() {
for comp in final_comps.iter_mut() {
comp.set_value(|v| format!("{}{v}", self.prefix));
}
}
Ready {
arg: self.arg,
comps: final_comps,
}
}
}
#[derive(Debug)]
pub enum CompletionGroup<ID> {
Ready(Ready),
Unready {
id: ID,
value: String,
unready: Unready,
},
}
impl<ID> CompletionGroup<ID> {
pub(crate) fn new_ready(comps: Vec<Completion>, arg: String) -> Self {
CompletionGroup::Ready(Ready { comps, arg })
}
}
fn naive_fuzzy(mut value: &str, pattern: &str) -> bool {
if pattern.len() > value.len() {
return false;
}
for ch in pattern.chars() {
let pos = value.chars().position(|t| t == ch);
let Some(pos) = pos else {
return false;
};
value = &value[pos + 1..];
}
true
}