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}
23
24#[derive(Deref, DerefMut, Default)]
25struct GlobalCommandPaletteFilter(CommandPaletteFilter);
26
27impl Global for GlobalCommandPaletteFilter {}
28
29impl CommandPaletteFilter {
30 /// Returns the global [`CommandPaletteFilter`], if one is set.
31 pub fn try_global(cx: &AppContext) -> Option<&CommandPaletteFilter> {
32 cx.try_global::<GlobalCommandPaletteFilter>()
33 .map(|filter| &filter.0)
34 }
35
36 /// Returns a mutable reference to the global [`CommandPaletteFilter`].
37 pub fn global_mut(cx: &mut AppContext) -> &mut Self {
38 cx.global_mut::<GlobalCommandPaletteFilter>()
39 }
40
41 /// Updates the global [`CommandPaletteFilter`] using the given closure.
42 pub fn update_global<F>(cx: &mut AppContext, update: F)
43 where
44 F: FnOnce(&mut Self, &mut AppContext),
45 {
46 if cx.has_global::<GlobalCommandPaletteFilter>() {
47 cx.update_global(|this: &mut GlobalCommandPaletteFilter, cx| update(&mut this.0, cx))
48 }
49 }
50
51 /// Returns whether the given [`Action`] is hidden by the filter.
52 pub fn is_hidden(&self, action: &dyn Action) -> bool {
53 let name = action.name();
54 let namespace = name.split("::").next().unwrap_or("malformed action name");
55
56 self.hidden_namespaces.contains(namespace)
57 || self.hidden_action_types.contains(&action.type_id())
58 }
59
60 /// Hides all actions in the given namespace.
61 pub fn hide_namespace(&mut self, namespace: &'static str) {
62 self.hidden_namespaces.insert(namespace);
63 }
64
65 /// Shows all actions in the given namespace.
66 pub fn show_namespace(&mut self, namespace: &'static str) {
67 self.hidden_namespaces.remove(namespace);
68 }
69
70 /// Hides all actions with the given types.
71 pub fn hide_action_types(&mut self, action_types: &[TypeId]) {
72 self.hidden_action_types.extend(action_types);
73 }
74
75 /// Shows all actions with the given types.
76 pub fn show_action_types<'a>(&mut self, action_types: impl Iterator<Item = &'a TypeId>) {
77 for action_type in action_types {
78 self.hidden_action_types.remove(action_type);
79 }
80 }
81}
82
83/// The result of intercepting a command palette command.
84pub struct CommandInterceptResult {
85 /// The action produced as a result of the interception.
86 pub action: Box<dyn Action>,
87 // TODO: Document this field.
88 #[allow(missing_docs)]
89 pub string: String,
90 // TODO: Document this field.
91 #[allow(missing_docs)]
92 pub positions: Vec<usize>,
93}
94
95/// An interceptor for the command palette.
96#[derive(Default)]
97pub struct CommandPaletteInterceptor(
98 Option<Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>>,
99);
100
101#[derive(Default)]
102struct GlobalCommandPaletteInterceptor(CommandPaletteInterceptor);
103
104impl Global for GlobalCommandPaletteInterceptor {}
105
106impl CommandPaletteInterceptor {
107 /// Returns the global [`CommandPaletteInterceptor`], if one is set.
108 pub fn try_global(cx: &AppContext) -> Option<&CommandPaletteInterceptor> {
109 cx.try_global::<GlobalCommandPaletteInterceptor>()
110 .map(|interceptor| &interceptor.0)
111 }
112
113 /// Updates the global [`CommandPaletteInterceptor`] using the given closure.
114 pub fn update_global<F, R>(cx: &mut AppContext, update: F) -> R
115 where
116 F: FnOnce(&mut Self, &mut AppContext) -> R,
117 {
118 cx.update_global(|this: &mut GlobalCommandPaletteInterceptor, cx| update(&mut this.0, cx))
119 }
120
121 /// Intercepts the given query from the command palette.
122 pub fn intercept(&self, query: &str, cx: &AppContext) -> Option<CommandInterceptResult> {
123 let handler = self.0.as_ref()?;
124
125 (handler)(query, cx)
126 }
127
128 /// Clears the global interceptor.
129 pub fn clear(&mut self) {
130 self.0 = None;
131 }
132
133 /// Sets the global interceptor.
134 ///
135 /// This will override the previous interceptor, if it exists.
136 pub fn set(
137 &mut self,
138 handler: Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>,
139 ) {
140 self.0 = Some(handler);
141 }
142}