command_palette_hooks.rs

  1//! Provides hooks for customizing the behavior of the command palette.
  2
  3#![deny(missing_docs)]
  4
  5use std::any::TypeId;
  6
  7use collections::HashSet;
  8use derive_more::{Deref, DerefMut};
  9use gpui::{Action, AppContext, BorrowAppContext, Global};
 10
 11/// Initializes the command palette hooks.
 12pub fn init(cx: &mut AppContext) {
 13    cx.set_global(GlobalCommandPaletteFilter::default());
 14    cx.set_global(GlobalCommandPaletteInterceptor::default());
 15}
 16
 17/// A filter for the command palette.
 18#[derive(Default)]
 19pub struct CommandPaletteFilter {
 20    hidden_namespaces: HashSet<&'static str>,
 21    hidden_action_types: HashSet<TypeId>,
 22    /// Actions that have explicitly been shown. These should be shown even if
 23    /// they are in a hidden namespace.
 24    shown_action_types: HashSet<TypeId>,
 25}
 26
 27#[derive(Deref, DerefMut, Default)]
 28struct GlobalCommandPaletteFilter(CommandPaletteFilter);
 29
 30impl Global for GlobalCommandPaletteFilter {}
 31
 32impl CommandPaletteFilter {
 33    /// Returns the global [`CommandPaletteFilter`], if one is set.
 34    pub fn try_global(cx: &AppContext) -> Option<&CommandPaletteFilter> {
 35        cx.try_global::<GlobalCommandPaletteFilter>()
 36            .map(|filter| &filter.0)
 37    }
 38
 39    /// Returns a mutable reference to the global [`CommandPaletteFilter`].
 40    pub fn global_mut(cx: &mut AppContext) -> &mut Self {
 41        cx.global_mut::<GlobalCommandPaletteFilter>()
 42    }
 43
 44    /// Updates the global [`CommandPaletteFilter`] using the given closure.
 45    pub fn update_global<F>(cx: &mut AppContext, update: F)
 46    where
 47        F: FnOnce(&mut Self, &mut AppContext),
 48    {
 49        if cx.has_global::<GlobalCommandPaletteFilter>() {
 50            cx.update_global(|this: &mut GlobalCommandPaletteFilter, cx| update(&mut this.0, cx))
 51        }
 52    }
 53
 54    /// Returns whether the given [`Action`] is hidden by the filter.
 55    pub fn is_hidden(&self, action: &dyn Action) -> bool {
 56        let name = action.name();
 57        let namespace = name.split("::").next().unwrap_or("malformed action name");
 58
 59        // If this action has specifically been shown then it should be visible.
 60        if self.shown_action_types.contains(&action.type_id()) {
 61            return false;
 62        }
 63
 64        self.hidden_namespaces.contains(namespace)
 65            || self.hidden_action_types.contains(&action.type_id())
 66    }
 67
 68    /// Hides all actions in the given namespace.
 69    pub fn hide_namespace(&mut self, namespace: &'static str) {
 70        self.hidden_namespaces.insert(namespace);
 71    }
 72
 73    /// Shows all actions in the given namespace.
 74    pub fn show_namespace(&mut self, namespace: &'static str) {
 75        self.hidden_namespaces.remove(namespace);
 76    }
 77
 78    /// Hides all actions with the given types.
 79    pub fn hide_action_types(&mut self, action_types: &[TypeId]) {
 80        for action_type in action_types {
 81            self.hidden_action_types.insert(*action_type);
 82            self.shown_action_types.remove(action_type);
 83        }
 84    }
 85
 86    /// Shows all actions with the given types.
 87    pub fn show_action_types<'a>(&mut self, action_types: impl Iterator<Item = &'a TypeId>) {
 88        for action_type in action_types {
 89            self.shown_action_types.insert(*action_type);
 90            self.hidden_action_types.remove(action_type);
 91        }
 92    }
 93}
 94
 95/// The result of intercepting a command palette command.
 96pub struct CommandInterceptResult {
 97    /// The action produced as a result of the interception.
 98    pub action: Box<dyn Action>,
 99    // TODO: Document this field.
100    #[allow(missing_docs)]
101    pub string: String,
102    // TODO: Document this field.
103    #[allow(missing_docs)]
104    pub positions: Vec<usize>,
105}
106
107/// An interceptor for the command palette.
108#[derive(Default)]
109pub struct CommandPaletteInterceptor(
110    Option<Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>>,
111);
112
113#[derive(Default)]
114struct GlobalCommandPaletteInterceptor(CommandPaletteInterceptor);
115
116impl Global for GlobalCommandPaletteInterceptor {}
117
118impl CommandPaletteInterceptor {
119    /// Returns the global [`CommandPaletteInterceptor`], if one is set.
120    pub fn try_global(cx: &AppContext) -> Option<&CommandPaletteInterceptor> {
121        cx.try_global::<GlobalCommandPaletteInterceptor>()
122            .map(|interceptor| &interceptor.0)
123    }
124
125    /// Updates the global [`CommandPaletteInterceptor`] using the given closure.
126    pub fn update_global<F, R>(cx: &mut AppContext, update: F) -> R
127    where
128        F: FnOnce(&mut Self, &mut AppContext) -> R,
129    {
130        cx.update_global(|this: &mut GlobalCommandPaletteInterceptor, cx| update(&mut this.0, cx))
131    }
132
133    /// Intercepts the given query from the command palette.
134    pub fn intercept(&self, query: &str, cx: &AppContext) -> Option<CommandInterceptResult> {
135        let handler = self.0.as_ref()?;
136
137        (handler)(query, cx)
138    }
139
140    /// Clears the global interceptor.
141    pub fn clear(&mut self) {
142        self.0 = None;
143    }
144
145    /// Sets the global interceptor.
146    ///
147    /// This will override the previous interceptor, if it exists.
148    pub fn set(
149        &mut self,
150        handler: Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>,
151    ) {
152        self.0 = Some(handler);
153    }
154}