use {
crate::Command,
reovim_kernel::api::v1::{CommandId, Service},
std::{collections::HashMap, fmt, sync::Arc},
};
#[derive(Debug, Clone)]
pub struct AmbiguousPrefix {
pub prefix: String,
pub candidates: Vec<String>,
}
impl fmt::Display for AmbiguousPrefix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E464: Ambiguous use of user-defined command: {}", self.prefix)
}
}
impl std::error::Error for AmbiguousPrefix {}
pub struct CommandNameIndex {
by_name: HashMap<String, (CommandId, Arc<dyn Command>)>,
}
impl CommandNameIndex {
#[must_use]
pub fn new() -> Self {
Self {
by_name: HashMap::new(),
}
}
pub fn insert(&mut self, name: String, id: CommandId, cmd: Arc<dyn Command>) {
self.by_name.insert(name, (id, cmd));
}
#[must_use]
pub fn resolve(&self, name: &str) -> Option<&CommandId> {
self.by_name.get(name).map(|(id, _)| id)
}
#[must_use]
pub fn resolve_entry(&self, name: &str) -> Option<(&CommandId, &dyn Command)> {
self.by_name.get(name).map(|(id, cmd)| (id, cmd.as_ref()))
}
pub fn resolve_prefix(
&self,
name: &str,
) -> Result<Option<(&CommandId, &dyn Command)>, AmbiguousPrefix> {
if name.is_empty() {
return Ok(None);
}
if let Some(entry) = self.by_name.get(name) {
return Ok(Some((&entry.0, entry.1.as_ref())));
}
let matches = self.search_by_prefix(name);
match matches.len() {
0 => Ok(None),
1 => Ok(Some(matches[0])),
_ => {
let mut candidates: Vec<String> = self
.by_name
.iter()
.filter(|(n, _)| n.starts_with(name))
.map(|(n, _)| n.clone())
.collect();
candidates.sort();
candidates.dedup();
Err(AmbiguousPrefix {
prefix: name.to_string(),
candidates,
})
}
}
}
#[must_use]
pub fn complete_args(&self, name: &str, partial: &str) -> Vec<String> {
self.by_name
.get(name)
.map_or_else(Vec::new, |(_, cmd)| cmd.complete(partial))
}
#[must_use]
pub fn search_by_prefix(&self, prefix: &str) -> Vec<(&CommandId, &dyn Command)> {
let mut seen = std::collections::HashSet::new();
let mut results = Vec::new();
for (name, (id, cmd)) in &self.by_name {
if name.starts_with(prefix) && seen.insert(id) {
results.push((id, cmd.as_ref()));
}
}
results
}
#[must_use]
pub fn list_all(&self) -> Vec<(&CommandId, &dyn Command)> {
let mut seen = std::collections::HashSet::new();
self.by_name
.values()
.filter(|(id, _)| seen.insert(id))
.map(|(id, cmd)| (id, cmd.as_ref()))
.collect()
}
#[must_use]
pub fn count(&self) -> usize {
let seen: std::collections::HashSet<_> = self.by_name.values().map(|(id, _)| id).collect();
seen.len()
}
}
impl Default for CommandNameIndex {
fn default() -> Self {
Self::new()
}
}
impl Service for CommandNameIndex {}
impl std::fmt::Debug for CommandNameIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandNameIndex")
.field("name_count", &self.by_name.len())
.field("unique_commands", &self.count())
.finish()
}
}
#[cfg(test)]
#[path = "name_index_tests.rs"]
mod tests;