1use std::sync::Arc;
 2
 3use collections::{BTreeSet, HashMap};
 4use derive_more::{Deref, DerefMut};
 5use gpui::Global;
 6use gpui::{App, ReadGlobal};
 7use parking_lot::RwLock;
 8
 9use crate::SlashCommand;
10
11#[derive(Default, Deref, DerefMut)]
12struct GlobalSlashCommandRegistry(Arc<SlashCommandRegistry>);
13
14impl Global for GlobalSlashCommandRegistry {}
15
16#[derive(Default)]
17struct SlashCommandRegistryState {
18    commands: HashMap<Arc<str>, Arc<dyn SlashCommand>>,
19    featured_commands: BTreeSet<Arc<str>>,
20}
21
22#[derive(Default)]
23pub struct SlashCommandRegistry {
24    state: RwLock<SlashCommandRegistryState>,
25}
26
27impl SlashCommandRegistry {
28    /// Returns the global [`SlashCommandRegistry`].
29    pub fn global(cx: &App) -> Arc<Self> {
30        GlobalSlashCommandRegistry::global(cx).0.clone()
31    }
32
33    /// Returns the global [`SlashCommandRegistry`].
34    ///
35    /// Inserts a default [`SlashCommandRegistry`] if one does not yet exist.
36    pub fn default_global(cx: &mut App) -> Arc<Self> {
37        cx.default_global::<GlobalSlashCommandRegistry>().0.clone()
38    }
39
40    pub fn new() -> Arc<Self> {
41        Arc::new(Self {
42            state: RwLock::new(SlashCommandRegistryState {
43                commands: HashMap::default(),
44                featured_commands: BTreeSet::default(),
45            }),
46        })
47    }
48
49    /// Registers the provided [`SlashCommand`].
50    pub fn register_command(&self, command: impl SlashCommand, is_featured: bool) {
51        let mut state = self.state.write();
52        let command_name: Arc<str> = command.name().into();
53        if is_featured {
54            state.featured_commands.insert(command_name.clone());
55        }
56        state.commands.insert(command_name, Arc::new(command));
57    }
58
59    /// Unregisters the provided [`SlashCommand`].
60    pub fn unregister_command(&self, command: impl SlashCommand) {
61        self.unregister_command_by_name(command.name().as_str())
62    }
63
64    /// Unregisters the command with the given name.
65    pub fn unregister_command_by_name(&self, command_name: &str) {
66        let mut state = self.state.write();
67        state.featured_commands.remove(command_name);
68        state.commands.remove(command_name);
69    }
70
71    /// Returns the names of registered [`SlashCommand`]s.
72    pub fn command_names(&self) -> Vec<Arc<str>> {
73        self.state.read().commands.keys().cloned().collect()
74    }
75
76    /// Returns the names of registered, featured [`SlashCommand`]s.
77    pub fn featured_command_names(&self) -> Vec<Arc<str>> {
78        self.state
79            .read()
80            .featured_commands
81            .iter()
82            .cloned()
83            .collect()
84    }
85
86    /// Returns the [`SlashCommand`] with the given name.
87    pub fn command(&self, name: &str) -> Option<Arc<dyn SlashCommand>> {
88        self.state.read().commands.get(name).cloned()
89    }
90}