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    /// The display string to show in the command palette for this result.
101    pub string: String,
102    /// The character positions in the string that match the query.
103    /// Used for highlighting matched characters in the command palette UI.
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, &App) -> Vec<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: &App) -> 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 App, update: F) -> R
127    where
128        F: FnOnce(&mut Self, &mut App) -> 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: &App) -> Vec<CommandInterceptResult> {
135        if let Some(handler) = self.0.as_ref() {
136            (handler)(query, cx)
137        } else {
138            Vec::new()
139        }
140    }
141
142    /// Clears the global interceptor.
143    pub fn clear(&mut self) {
144        self.0 = None;
145    }
146
147    /// Sets the global interceptor.
148    ///
149    /// This will override the previous interceptor, if it exists.
150    pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>) {
151        self.0 = Some(handler);
152    }
153}