use std::collections::BTreeMap;
use crate::spec;
pub const MAX_COMPLETION_VALUES: usize = 100;
pub type CompleteFn = fn(&CompletionRequest<'_>) -> Completion;
#[derive(Debug, Clone, Copy)]
pub struct CompletionRequest<'a> {
pub(crate) argument: &'a str,
pub(crate) value: &'a str,
pub(crate) context: &'a BTreeMap<String, String>,
}
impl<'a> CompletionRequest<'a> {
#[must_use]
pub fn argument(&self) -> &'a str {
self.argument
}
#[must_use]
pub fn value(&self) -> &'a str {
self.value
}
#[must_use]
pub fn context(&self) -> &'a BTreeMap<String, String> {
self.context
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Completion {
values: Vec<String>,
total: Option<i64>,
has_more: Option<bool>,
}
impl Completion {
pub fn new<I>(values: I) -> Self
where
I: IntoIterator,
I::Item: Into<String>,
{
Self {
values: values.into_iter().map(Into::into).collect(),
total: None,
has_more: None,
}
}
#[must_use]
pub fn filter_prefix(candidates: &[&str], prefix: &str) -> Self {
Self::new(
candidates
.iter()
.filter(|c| {
c.get(..prefix.len())
.is_some_and(|head| head.eq_ignore_ascii_case(prefix))
})
.copied(),
)
}
#[must_use]
pub fn with_total(mut self, total: u64) -> Self {
self.total = Some(i64::try_from(total).unwrap_or(i64::MAX));
self
}
#[must_use]
pub fn with_more(mut self, has_more: bool) -> Self {
self.has_more = Some(has_more);
self
}
pub(crate) fn into_spec(mut self) -> spec::CompleteResultCompletion {
if self.values.len() > MAX_COMPLETION_VALUES {
let total = self.values.len();
self.values.truncate(MAX_COMPLETION_VALUES);
self.total = Some(
self.total
.unwrap_or_else(|| i64::try_from(total).unwrap_or(i64::MAX)),
);
self.has_more = Some(true);
}
spec::CompleteResultCompletion {
has_more: self.has_more,
total: self.total,
values: self.values,
}
}
}
impl<S: Into<String>> FromIterator<S> for Completion {
fn from_iter<I: IntoIterator<Item = S>>(iter: I) -> Self {
Self::new(iter)
}
}
#[derive(Debug, Clone)]
pub(crate) struct Completer {
pub(crate) argument: String,
pub(crate) complete: CompleteFn,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefix_filter_ignores_case() {
let c = Completion::filter_prefix(&["Rust", "Ruby", "Python"], "ru");
assert_eq!(c.into_spec().values, ["Rust", "Ruby"]);
}
#[test]
fn long_results_are_truncated() {
let c = Completion::new((0..150).map(|i| i.to_string())).into_spec();
assert_eq!(c.values.len(), 100);
assert_eq!(c.total, Some(150));
assert_eq!(c.has_more, Some(true));
}
}