pub mod providers;
pub use providers::{BufferProvider, CommandProvider, FileProvider, GotoLineProvider};
use crate::input::commands::Suggestion;
use crate::input::keybindings::Action;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum QuickOpenResult {
ExecuteAction(Action),
OpenFile {
path: String,
line: Option<usize>,
column: Option<usize>,
},
ShowBuffer(usize),
GotoLine(usize),
None,
Error(String),
}
#[derive(Debug, Clone)]
pub struct QuickOpenContext {
pub cwd: String,
pub open_buffers: Vec<BufferInfo>,
pub active_buffer_id: usize,
pub active_buffer_path: Option<String>,
pub has_selection: bool,
pub key_context: crate::input::keybindings::KeyContext,
pub custom_contexts: std::collections::HashSet<String>,
pub buffer_mode: Option<String>,
}
#[derive(Debug, Clone)]
pub struct BufferInfo {
pub id: usize,
pub path: String,
pub name: String,
pub modified: bool,
}
pub trait QuickOpenProvider: Send + Sync {
fn prefix(&self) -> &str;
fn name(&self) -> &str;
fn hint(&self) -> &str;
fn suggestions(&self, query: &str, context: &QuickOpenContext) -> Vec<Suggestion>;
fn on_select(
&self,
selected_index: Option<usize>,
query: &str,
context: &QuickOpenContext,
) -> QuickOpenResult;
fn preview(
&self,
_selected_index: usize,
_context: &QuickOpenContext,
) -> Option<(String, Option<usize>)> {
None
}
}
pub struct QuickOpenRegistry {
providers: HashMap<String, Box<dyn QuickOpenProvider>>,
prefix_order: Vec<String>,
}
impl QuickOpenRegistry {
pub fn new() -> Self {
Self {
providers: HashMap::new(),
prefix_order: Vec::new(),
}
}
pub fn register(&mut self, provider: Box<dyn QuickOpenProvider>) {
let prefix = provider.prefix().to_string();
if !self.prefix_order.contains(&prefix) {
self.prefix_order.push(prefix.clone());
}
self.providers.insert(prefix, provider);
}
pub fn get_provider_for_input<'a>(
&'a self,
input: &'a str,
) -> Option<(&'a dyn QuickOpenProvider, &'a str)> {
let mut prefixes: Vec<_> = self.providers.keys().collect();
prefixes.sort_by(|a, b| b.len().cmp(&a.len()));
for prefix in prefixes {
if prefix.is_empty() {
continue; }
if input.starts_with(prefix.as_str()) {
let query = &input[prefix.len()..];
return self.providers.get(prefix).map(|p| (p.as_ref(), query));
}
}
self.providers.get("").map(|p| (p.as_ref(), input))
}
pub fn get_default_provider(&self) -> Option<&dyn QuickOpenProvider> {
self.providers.get("").map(|p| p.as_ref())
}
pub fn get_hints(&self) -> String {
self.prefix_order
.iter()
.filter_map(|prefix| self.providers.get(prefix).map(|p| p.hint()))
.collect::<Vec<_>>()
.join(" ")
}
pub fn prefixes(&self) -> Vec<&str> {
self.providers.keys().map(|s| s.as_str()).collect()
}
}
impl Default for QuickOpenRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestProvider {
prefix: String,
}
impl QuickOpenProvider for TestProvider {
fn prefix(&self) -> &str {
&self.prefix
}
fn name(&self) -> &str {
"Test"
}
fn hint(&self) -> &str {
"Test hint"
}
fn suggestions(&self, _query: &str, _context: &QuickOpenContext) -> Vec<Suggestion> {
vec![]
}
fn on_select(
&self,
_selected_index: Option<usize>,
_query: &str,
_context: &QuickOpenContext,
) -> QuickOpenResult {
QuickOpenResult::None
}
}
#[test]
fn test_provider_routing() {
let mut registry = QuickOpenRegistry::new();
registry.register(Box::new(TestProvider {
prefix: "".to_string(),
}));
registry.register(Box::new(TestProvider {
prefix: ">".to_string(),
}));
registry.register(Box::new(TestProvider {
prefix: "#".to_string(),
}));
let (provider, query) = registry.get_provider_for_input("hello").unwrap();
assert_eq!(provider.prefix(), "");
assert_eq!(query, "hello");
let (provider, query) = registry.get_provider_for_input(">save").unwrap();
assert_eq!(provider.prefix(), ">");
assert_eq!(query, "save");
let (provider, query) = registry.get_provider_for_input("#main").unwrap();
assert_eq!(provider.prefix(), "#");
assert_eq!(query, "main");
}
}