Extract SlashCommand trait from assistant (#12252)

This PR extracts the `SlashCommand` trait (along with the
`SlashCommandRegistry`) from the `assistant` crate.

This will allow us to register slash commands from extensions without
having to make `extension` depend on `assistant`.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-05-24 13:03:41 -04:00 committed by GitHub
parent af3d7a60c8
commit 8040e43520
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 183 additions and 91 deletions

View file

@ -0,0 +1,20 @@
[package]
name = "assistant_slash_command"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/assistant_slash_command.rs"
[dependencies]
anyhow.workspace = true
collections.workspace = true
derive_more.workspace = true
futures.workspace = true
gpui.workspace = true
parking_lot.workspace = true

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -0,0 +1,50 @@
mod slash_command_registry;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::Result;
use futures::channel::oneshot;
use gpui::{AppContext, Task};
pub use slash_command_registry::*;
pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx);
}
pub trait SlashCommand: 'static + Send + Sync {
fn name(&self) -> String;
fn description(&self) -> String;
fn complete_argument(
&self,
query: String,
cancel: Arc<AtomicBool>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>>;
fn requires_argument(&self) -> bool;
fn run(&self, argument: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation;
}
pub struct SlashCommandInvocation {
pub output: Task<Result<String>>,
pub invalidated: oneshot::Receiver<()>,
pub cleanup: SlashCommandCleanup,
}
#[derive(Default)]
pub struct SlashCommandCleanup(Option<Box<dyn FnOnce()>>);
impl SlashCommandCleanup {
pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
Self(Some(Box::new(cleanup)))
}
}
impl Drop for SlashCommandCleanup {
fn drop(&mut self) {
if let Some(cleanup) = self.0.take() {
cleanup();
}
}
}

View file

@ -0,0 +1,64 @@
use std::sync::Arc;
use collections::HashMap;
use derive_more::{Deref, DerefMut};
use gpui::Global;
use gpui::{AppContext, ReadGlobal};
use parking_lot::RwLock;
use crate::SlashCommand;
#[derive(Default, Deref, DerefMut)]
struct GlobalSlashCommandRegistry(Arc<SlashCommandRegistry>);
impl Global for GlobalSlashCommandRegistry {}
#[derive(Default)]
struct SlashCommandRegistryState {
commands: HashMap<Arc<str>, Arc<dyn SlashCommand>>,
}
#[derive(Default)]
pub struct SlashCommandRegistry {
state: RwLock<SlashCommandRegistryState>,
}
impl SlashCommandRegistry {
/// Returns the global [`SlashCommandRegistry`].
pub fn global(cx: &AppContext) -> Arc<Self> {
GlobalSlashCommandRegistry::global(cx).0.clone()
}
/// Returns the global [`SlashCommandRegistry`].
///
/// Inserts a default [`SlashCommandRegistry`] if one does not yet exist.
pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
cx.default_global::<GlobalSlashCommandRegistry>().0.clone()
}
pub fn new() -> Arc<Self> {
Arc::new(Self {
state: RwLock::new(SlashCommandRegistryState {
commands: HashMap::default(),
}),
})
}
/// Registers the provided [`SlashCommand`].
pub fn register_command(&self, command: impl SlashCommand) {
self.state
.write()
.commands
.insert(command.name().into(), Arc::new(command));
}
/// Returns the names of registered [`SlashCommand`]s.
pub fn command_names(&self) -> Vec<Arc<str>> {
self.state.read().commands.keys().cloned().collect()
}
/// Returns the [`SlashCommand`] with the given name.
pub fn command(&self, name: &str) -> Option<Arc<dyn SlashCommand>> {
self.state.read().commands.get(name).cloned()
}
}