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}