1//! Provides hooks for customizing the behavior of the command palette.
2
3#![deny(missing_docs)]
4
5use std::{any::TypeId, rc::Rc};
6
7use collections::HashSet;
8use derive_more::{Deref, DerefMut};
9use gpui::{Action, App, BorrowAppContext, Global, Task, WeakEntity};
10use workspace::Workspace;
11
12/// Initializes the command palette hooks.
13pub fn init(cx: &mut App) {
14 cx.set_global(GlobalCommandPaletteFilter::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 CommandInterceptItem {
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/// The result of intercepting a command palette command.
108#[derive(Default, Debug)]
109pub struct CommandInterceptResult {
110 /// The items
111 pub results: Vec<CommandInterceptItem>,
112 /// Whether or not to continue to show the normal matches
113 pub exclusive: bool,
114}
115
116/// An interceptor for the command palette.
117#[derive(Clone)]
118pub struct GlobalCommandPaletteInterceptor(
119 Rc<dyn Fn(&str, WeakEntity<Workspace>, &mut App) -> Task<CommandInterceptResult>>,
120);
121
122impl Global for GlobalCommandPaletteInterceptor {}
123
124impl GlobalCommandPaletteInterceptor {
125 /// Sets the global interceptor.
126 ///
127 /// This will override the previous interceptor, if it exists.
128 pub fn set(
129 cx: &mut App,
130 interceptor: impl Fn(&str, WeakEntity<Workspace>, &mut App) -> Task<CommandInterceptResult>
131 + 'static,
132 ) {
133 cx.set_global(Self(Rc::new(interceptor)));
134 }
135
136 /// Clears the global interceptor.
137 pub fn clear(cx: &mut App) {
138 if cx.has_global::<Self>() {
139 cx.remove_global::<Self>();
140 }
141 }
142
143 /// Intercepts the given query from the command palette.
144 pub fn intercept(
145 query: &str,
146 workspace: WeakEntity<Workspace>,
147 cx: &mut App,
148 ) -> Option<Task<CommandInterceptResult>> {
149 let interceptor = cx.try_global::<Self>()?;
150 let handler = interceptor.0.clone();
151 Some(handler(query, workspace, cx))
152 }
153}