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, App, BorrowAppContext, Global};
 10
 11/// Initializes the command palette hooks.
 12pub fn init(cx: &mut App) {
 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: &App) -> 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 App) -> &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 App, update: F)
 46    where
 47        F: FnOnce(&mut Self, &mut App),
 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<'a>(&mut self, action_types: impl IntoIterator<Item = &'a 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 IntoIterator<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.
 96#[derive(Debug)]
 97pub struct CommandInterceptResult {
 98    /// The action produced as a result of the interception.
 99    pub action: Box<dyn Action>,
100    // TODO: Document this field.
101    #[allow(missing_docs)]
102    pub string: String,
103    // TODO: Document this field.
104    #[allow(missing_docs)]
105    pub positions: Vec<usize>,
106}
107
108/// An interceptor for the command palette.
109#[derive(Default)]
110pub struct CommandPaletteInterceptor(
111    Option<Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>>,
112);
113
114#[derive(Default)]
115struct GlobalCommandPaletteInterceptor(CommandPaletteInterceptor);
116
117impl Global for GlobalCommandPaletteInterceptor {}
118
119impl CommandPaletteInterceptor {
120    /// Returns the global [`CommandPaletteInterceptor`], if one is set.
121    pub fn try_global(cx: &App) -> Option<&CommandPaletteInterceptor> {
122        cx.try_global::<GlobalCommandPaletteInterceptor>()
123            .map(|interceptor| &interceptor.0)
124    }
125
126    /// Updates the global [`CommandPaletteInterceptor`] using the given closure.
127    pub fn update_global<F, R>(cx: &mut App, update: F) -> R
128    where
129        F: FnOnce(&mut Self, &mut App) -> R,
130    {
131        cx.update_global(|this: &mut GlobalCommandPaletteInterceptor, cx| update(&mut this.0, cx))
132    }
133
134    /// Intercepts the given query from the command palette.
135    pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> {
136        if let Some(handler) = self.0.as_ref() {
137            (handler)(query, cx)
138        } else {
139            Vec::new()
140        }
141    }
142
143    /// Clears the global interceptor.
144    pub fn clear(&mut self) {
145        self.0 = None;
146    }
147
148    /// Sets the global interceptor.
149    ///
150    /// This will override the previous interceptor, if it exists.
151    pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>) {
152        self.0 = Some(handler);
153    }
154}