use std::{collections::HashMap, sync::Arc};
use {
reovim_driver_command::{
CommandContext, CommandHandler, CommandInfo, CommandPriority, CommandQueryService,
CommandResult,
},
reovim_driver_session::{
Session as DriverSession, SessionRuntime,
api::{CommandExecutor, CommandHandle},
},
reovim_driver_vfs::VfsDriver,
reovim_kernel::{
api::v1::{CommandId, KernelContext, ModuleId, Service},
profile_scope,
},
};
#[derive(Clone)]
struct CommandEntry {
handler: Arc<dyn CommandHandler>,
owner: Option<ModuleId>,
priority: CommandPriority,
}
#[derive(Default, Clone)]
pub struct CommandRegistry {
entries: HashMap<CommandId, CommandEntry>,
}
impl CommandRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, handler: Arc<dyn CommandHandler>) {
let id = handler.id();
let new_priority = handler.priority();
if self
.entries
.get(&id)
.is_some_and(|existing| new_priority < existing.priority)
{
return;
}
self.entries.insert(
id,
CommandEntry {
handler,
owner: None,
priority: new_priority,
},
);
}
pub fn register_for_module(&mut self, handler: Arc<dyn CommandHandler>, owner: ModuleId) {
let id = handler.id();
let new_priority = handler.priority();
if self
.entries
.get(&id)
.is_some_and(|existing| new_priority < existing.priority)
{
return;
}
self.entries.insert(
id,
CommandEntry {
handler,
owner: Some(owner),
priority: new_priority,
},
);
}
pub fn unregister_for_module(&mut self, module: &ModuleId) -> usize {
let before = self.entries.len();
self.entries
.retain(|_, entry| entry.owner.as_ref() != Some(module));
before - self.entries.len()
}
#[must_use]
pub fn get(&self, id: &CommandId) -> Option<&Arc<dyn CommandHandler>> {
self.entries.get(id).map(|entry| &entry.handler)
}
#[must_use]
pub fn contains(&self, id: &CommandId) -> bool {
self.entries.contains_key(id)
}
#[must_use]
#[allow(clippy::too_many_arguments)] pub fn execute_for_client(
&self,
client_id: usize,
id: &CommandId,
driver_session: &mut DriverSession,
client: reovim_driver_session::ClientContext<'_>,
kernel: &KernelContext,
vfs: &Arc<dyn VfsDriver>,
args: &CommandContext,
shared_extensions: Option<&mut reovim_driver_session::ExtensionMap>,
) -> Option<(
CommandResult,
reovim_driver_session::api::StateChanges,
Vec<reovim_driver_command_types::RuntimeSignal>,
)> {
use reovim_driver_session::{ClientId as DriverClientId, api::ChangeTracker};
profile_scope!("command_execute_for_client", "server::command");
self.entries.get(id).map(|entry| {
let mut ctx = args.clone();
if let Some(buffer_id) = *client.active_buffer {
ctx.set_buffer_id(buffer_id);
}
ctx.set_vfs(Arc::clone(vfs));
let driver_client_id = DriverClientId::new(client_id);
let mut runtime =
SessionRuntime::with_owner(driver_client_id, driver_session, client, kernel, self);
if let Some(ext) = shared_extensions {
runtime = runtime.with_shared_extensions(ext);
}
let result = entry.handler.execute(&mut runtime, &ctx);
let changes = runtime.take_changes();
let signals = runtime.take_signals();
(result, changes, signals)
})
}
pub fn ids(&self) -> impl Iterator<Item = &CommandId> {
self.entries.keys()
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn build_name_index(&self) -> reovim_driver_command::CommandNameIndex {
let mut index = reovim_driver_command::CommandNameIndex::new();
for entry in self.entries.values() {
let id = entry.handler.id();
let handler = Arc::clone(&entry.handler);
let cmd: Arc<dyn reovim_driver_command::Command> = handler;
for &name in cmd.names() {
index.insert(name.to_string(), id.clone(), Arc::clone(&cmd));
}
}
index
}
#[must_use]
pub fn all_command_infos(&self) -> Vec<CommandInfo> {
self.entries
.values()
.map(|entry| CommandInfo::from_command(&*entry.handler))
.collect()
}
}
impl std::fmt::Debug for CommandRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandRegistry")
.field("count", &self.entries.len())
.field("commands", &self.entries.keys().collect::<Vec<_>>())
.finish()
}
}
pub struct CommandQuerySnapshot {
commands: Vec<CommandInfo>,
}
impl Service for CommandQuerySnapshot {}
impl CommandQuerySnapshot {
#[must_use]
pub fn from_registry(registry: &CommandRegistry) -> Self {
Self {
commands: registry.all_command_infos(),
}
}
}
impl CommandQueryService for CommandQuerySnapshot {
fn search_by_prefix(&self, prefix: &str) -> Vec<CommandInfo> {
self.commands
.iter()
.filter(|info| info.names.iter().any(|n| n.starts_with(prefix)))
.cloned()
.collect()
}
fn find_by_name(&self, name: &str) -> Option<CommandInfo> {
self.commands
.iter()
.find(|info| info.names.iter().any(|n| n == name))
.cloned()
}
fn list_user_commands(&self) -> Vec<CommandInfo> {
self.commands
.iter()
.filter(|info| !info.names.is_empty())
.cloned()
.collect()
}
fn list_all(&self) -> Vec<CommandInfo> {
self.commands.clone()
}
fn count(&self) -> usize {
self.commands.len()
}
}
struct HandlerBridge(Arc<dyn CommandHandler>);
impl CommandHandle for HandlerBridge {
fn execute(&self, runtime: &mut SessionRuntime<'_>, ctx: &CommandContext) -> CommandResult {
self.0.execute(runtime, ctx)
}
}
impl CommandExecutor for CommandRegistry {
fn get_handle(&self, id: &CommandId) -> Option<Arc<dyn CommandHandle>> {
self.entries.get(id).map(|entry| {
Arc::new(HandlerBridge(Arc::clone(&entry.handler))) as Arc<dyn CommandHandle>
})
}
}
#[cfg(test)]
#[path = "command_tests.rs"]
mod tests;