#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LazyTrigger {
FileType(String),
Event(String),
Command(String),
}
#[derive(Debug, Clone)]
struct LazyPlugin {
name: String,
triggers: Vec<LazyTrigger>,
entry_src: String,
activated: bool,
}
#[derive(Debug, Clone, Default)]
pub struct PluginHost {
plugins: Vec<LazyPlugin>,
}
impl PluginHost {
pub fn register(&mut self, name: impl Into<String>, triggers: Vec<LazyTrigger>, entry_src: impl Into<String>) {
self.plugins.push(LazyPlugin {
name: name.into(),
triggers,
entry_src: entry_src.into(),
activated: false,
});
}
#[must_use]
pub fn pending(&self) -> usize {
self.plugins.iter().filter(|p| !p.activated).count()
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.plugins.iter().map(|p| p.name.as_str())
}
fn take_matching(&mut self, want: &LazyTrigger) -> Vec<String> {
let mut out = Vec::new();
for p in &mut self.plugins {
if !p.activated && p.triggers.iter().any(|t| t == want) {
p.activated = true;
out.push(p.entry_src.clone());
}
}
out
}
pub fn pending_for_command(&mut self, command: &str) -> Vec<String> {
self.take_matching(&LazyTrigger::Command(command.to_string()))
}
pub fn pending_for_filetype(&mut self, filetype: &str) -> Vec<String> {
self.take_matching(&LazyTrigger::FileType(filetype.to_string()))
}
pub fn pending_for_event(&mut self, event: &str) -> Vec<String> {
self.take_matching(&LazyTrigger::Event(event.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn host() -> PluginHost {
let mut h = PluginHost::default();
h.register(
"user-trouble",
vec![LazyTrigger::Command("Trouble".into())],
r#"(defkeybind :mode "normal" :key "<leader>xx" :action "trouble.toggle")"#,
);
h.register(
"user-markdown",
vec![LazyTrigger::FileType("markdown".into())],
r#"(defoption :name "md" :value "on")"#,
);
h
}
#[test]
fn command_trigger_returns_entry_once() {
let mut h = host();
assert_eq!(h.pending(), 2);
let first = h.pending_for_command("Trouble");
assert_eq!(first.len(), 1);
assert!(first[0].contains("trouble.toggle"));
assert_eq!(h.pending(), 1, "activated plugin is no longer pending");
assert!(h.pending_for_command("Trouble").is_empty());
}
#[test]
fn filetype_trigger_isolated_from_command() {
let mut h = host();
assert!(h.pending_for_command("Other").is_empty());
let md = h.pending_for_filetype("markdown");
assert_eq!(md.len(), 1);
assert!(md[0].contains(":name \"md\""));
assert_eq!(h.pending(), 1);
}
#[test]
fn non_matching_trigger_activates_nothing() {
let mut h = host();
assert!(h.pending_for_event("BufWritePre").is_empty());
assert_eq!(h.pending(), 2);
}
}