1use std::rc::Rc;
2
3use gpui::{
4 AnyElement, App, DefiniteLength, Entity, IntoElement, RenderOnce, SharedString,
5 StyleRefinement, Styled, Window, rems,
6};
7
8use crate::IndexPath;
9use crate::command::{
10 item::{CommandEntry, CommandGroup, CommandItem},
11 state::{CommandModel, CommandState, OnCancel, OnIndex, OnQuery},
12};
13
14pub(crate) type CommandSlot = dyn Fn(&CommandState, &mut Window, &mut App) -> AnyElement;
15
16#[derive(Clone)]
18pub(crate) struct CommandOptions {
19 pub(crate) style: StyleRefinement,
20 pub(crate) placeholder: Option<SharedString>,
21 pub(crate) empty: Option<Rc<CommandSlot>>,
22 pub(crate) max_h: DefiniteLength,
23 pub(crate) scroll_bounce: Option<bool>,
24 pub(crate) bordered: bool,
25 pub(crate) header: Option<Rc<CommandSlot>>,
26 pub(crate) footer: Option<Rc<CommandSlot>>,
27}
28
29impl Default for CommandOptions {
30 fn default() -> Self {
31 Self {
32 style: StyleRefinement::default(),
33 placeholder: None,
34 empty: None,
35 max_h: rems(18.75).into(),
36 scroll_bounce: None,
37 bordered: true,
38 header: None,
39 footer: None,
40 }
41 }
42}
43
44#[derive(IntoElement)]
60pub struct Command {
61 state: Entity<CommandState>,
62 entries: Vec<CommandEntry>,
63 searchable: bool,
64 filterable: bool,
65 on_query: Option<Rc<OnQuery>>,
66 on_select: Option<Rc<OnIndex>>,
67 on_confirm: Option<Rc<OnIndex>>,
68 on_cancel: Option<Rc<OnCancel>>,
69 options: CommandOptions,
70}
71
72impl Command {
73 pub fn new(state: &Entity<CommandState>) -> Self {
75 Self {
76 state: state.clone(),
77 entries: Vec::new(),
78 searchable: true,
79 filterable: true,
80 on_query: None,
81 on_select: None,
82 on_confirm: None,
83 on_cancel: None,
84 options: CommandOptions::default(),
85 }
86 }
87
88 pub fn item(mut self, item: CommandItem) -> Self {
90 self.entries.push(CommandEntry::Item(item));
91 self
92 }
93
94 pub fn items(mut self, items: impl IntoIterator<Item = CommandItem>) -> Self {
96 self.entries
97 .extend(items.into_iter().map(CommandEntry::Item));
98 self
99 }
100
101 pub fn group(mut self, group: CommandGroup) -> Self {
103 self.entries.push(CommandEntry::Group(group));
104 self
105 }
106
107 pub fn separator(mut self) -> Self {
109 self.entries.push(CommandEntry::Separator);
110 self
111 }
112
113 pub fn searchable(mut self, searchable: bool) -> Self {
115 self.searchable = searchable;
116 self
117 }
118
119 pub fn filterable(mut self, filterable: bool) -> Self {
126 self.filterable = filterable;
127 self
128 }
129
130 pub fn on_query<F>(mut self, callback: F) -> Self
133 where
134 F: Fn(&str, &mut Window, &mut App) + 'static,
135 {
136 self.on_query = Some(Rc::new(callback));
137 self
138 }
139
140 pub fn on_select<F>(mut self, callback: F) -> Self
148 where
149 F: Fn(IndexPath, &mut Window, &mut App) + 'static,
150 {
151 self.on_select = Some(Rc::new(callback));
152 self
153 }
154
155 pub fn on_confirm<F>(mut self, callback: F) -> Self
160 where
161 F: Fn(IndexPath, &mut Window, &mut App) + 'static,
162 {
163 self.on_confirm = Some(Rc::new(callback));
164 self
165 }
166
167 pub fn on_cancel<F>(mut self, callback: F) -> Self
171 where
172 F: Fn(&mut Window, &mut App) + 'static,
173 {
174 self.on_cancel = Some(Rc::new(callback));
175 self
176 }
177
178 pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
180 self.options.placeholder = Some(placeholder.into());
181 self
182 }
183
184 pub fn empty<F, E>(mut self, f: F) -> Self
186 where
187 F: Fn(&CommandState, &mut Window, &mut App) -> E + 'static,
188 E: IntoElement,
189 {
190 self.options.empty = Some(Rc::new(move |state, window, cx| {
191 f(state, window, cx).into_any_element()
192 }));
193 self
194 }
195
196 pub fn max_h(mut self, height: impl Into<DefiniteLength>) -> Self {
198 self.options.max_h = height.into();
199 self
200 }
201
202 pub fn scroll_bounce(mut self, enabled: bool) -> Self {
205 self.options.scroll_bounce = Some(enabled);
206 self
207 }
208
209 pub fn bordered(mut self, bordered: bool) -> Self {
214 self.options.bordered = bordered;
215 self
216 }
217
218 pub fn header<F, E>(mut self, f: F) -> Self
220 where
221 F: Fn(&CommandState, &mut Window, &mut App) -> E + 'static,
222 E: IntoElement,
223 {
224 self.options.header = Some(Rc::new(move |state, window, cx| {
225 f(state, window, cx).into_any_element()
226 }));
227 self
228 }
229
230 pub fn footer<F, E>(mut self, f: F) -> Self
232 where
233 F: Fn(&CommandState, &mut Window, &mut App) -> E + 'static,
234 E: IntoElement,
235 {
236 self.options.footer = Some(Rc::new(move |state, window, cx| {
237 f(state, window, cx).into_any_element()
238 }));
239 self
240 }
241}
242
243impl Styled for Command {
244 fn style(&mut self) -> &mut StyleRefinement {
245 &mut self.options.style
246 }
247}
248
249impl RenderOnce for Command {
250 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
251 let options = self.options;
252 let model = CommandModel {
253 entries: self.entries,
254 searchable: self.searchable,
255 filterable: self.filterable,
256 on_query: self.on_query,
257 on_select: self.on_select,
258 on_confirm: self.on_confirm,
259 on_cancel: self.on_cancel,
260 };
261 self.state.update(cx, |state, cx| {
262 state.set_options(options);
263 state.install_model(model, cx);
264 });
265
266 self.state
267 }
268}