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) bordered: bool,
24 pub(crate) header: Option<Rc<CommandSlot>>,
25 pub(crate) footer: Option<Rc<CommandSlot>>,
26}
27
28impl Default for CommandOptions {
29 fn default() -> Self {
30 Self {
31 style: StyleRefinement::default(),
32 placeholder: None,
33 empty: None,
34 max_h: rems(18.75).into(),
35 bordered: true,
36 header: None,
37 footer: None,
38 }
39 }
40}
41
42#[derive(IntoElement)]
58pub struct Command {
59 state: Entity<CommandState>,
60 entries: Vec<CommandEntry>,
61 searchable: bool,
62 filterable: bool,
63 on_query: Option<Rc<OnQuery>>,
64 on_select: Option<Rc<OnIndex>>,
65 on_confirm: Option<Rc<OnIndex>>,
66 on_cancel: Option<Rc<OnCancel>>,
67 options: CommandOptions,
68}
69
70impl Command {
71 pub fn new(state: &Entity<CommandState>) -> Self {
73 Self {
74 state: state.clone(),
75 entries: Vec::new(),
76 searchable: true,
77 filterable: true,
78 on_query: None,
79 on_select: None,
80 on_confirm: None,
81 on_cancel: None,
82 options: CommandOptions::default(),
83 }
84 }
85
86 pub fn item(mut self, item: CommandItem) -> Self {
88 self.entries.push(CommandEntry::Item(item));
89 self
90 }
91
92 pub fn items(mut self, items: impl IntoIterator<Item = CommandItem>) -> Self {
94 self.entries
95 .extend(items.into_iter().map(CommandEntry::Item));
96 self
97 }
98
99 pub fn group(mut self, group: CommandGroup) -> Self {
101 self.entries.push(CommandEntry::Group(group));
102 self
103 }
104
105 pub fn separator(mut self) -> Self {
107 self.entries.push(CommandEntry::Separator);
108 self
109 }
110
111 pub fn searchable(mut self, searchable: bool) -> Self {
113 self.searchable = searchable;
114 self
115 }
116
117 pub fn filterable(mut self, filterable: bool) -> Self {
124 self.filterable = filterable;
125 self
126 }
127
128 pub fn on_query<F>(mut self, callback: F) -> Self
131 where
132 F: Fn(&str, &mut Window, &mut App) + 'static,
133 {
134 self.on_query = Some(Rc::new(callback));
135 self
136 }
137
138 pub fn on_select<F>(mut self, callback: F) -> Self
146 where
147 F: Fn(IndexPath, &mut Window, &mut App) + 'static,
148 {
149 self.on_select = Some(Rc::new(callback));
150 self
151 }
152
153 pub fn on_confirm<F>(mut self, callback: F) -> Self
158 where
159 F: Fn(IndexPath, &mut Window, &mut App) + 'static,
160 {
161 self.on_confirm = Some(Rc::new(callback));
162 self
163 }
164
165 pub fn on_cancel<F>(mut self, callback: F) -> Self
169 where
170 F: Fn(&mut Window, &mut App) + 'static,
171 {
172 self.on_cancel = Some(Rc::new(callback));
173 self
174 }
175
176 pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
178 self.options.placeholder = Some(placeholder.into());
179 self
180 }
181
182 pub fn empty<F, E>(mut self, f: F) -> Self
184 where
185 F: Fn(&CommandState, &mut Window, &mut App) -> E + 'static,
186 E: IntoElement,
187 {
188 self.options.empty = Some(Rc::new(move |state, window, cx| {
189 f(state, window, cx).into_any_element()
190 }));
191 self
192 }
193
194 pub fn max_h(mut self, height: impl Into<DefiniteLength>) -> Self {
196 self.options.max_h = height.into();
197 self
198 }
199
200 pub fn bordered(mut self, bordered: bool) -> Self {
205 self.options.bordered = bordered;
206 self
207 }
208
209 pub fn header<F, E>(mut self, f: F) -> Self
211 where
212 F: Fn(&CommandState, &mut Window, &mut App) -> E + 'static,
213 E: IntoElement,
214 {
215 self.options.header = Some(Rc::new(move |state, window, cx| {
216 f(state, window, cx).into_any_element()
217 }));
218 self
219 }
220
221 pub fn footer<F, E>(mut self, f: F) -> Self
223 where
224 F: Fn(&CommandState, &mut Window, &mut App) -> E + 'static,
225 E: IntoElement,
226 {
227 self.options.footer = Some(Rc::new(move |state, window, cx| {
228 f(state, window, cx).into_any_element()
229 }));
230 self
231 }
232}
233
234impl Styled for Command {
235 fn style(&mut self) -> &mut StyleRefinement {
236 &mut self.options.style
237 }
238}
239
240impl RenderOnce for Command {
241 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
242 let options = self.options;
243 let model = CommandModel {
244 entries: self.entries,
245 searchable: self.searchable,
246 filterable: self.filterable,
247 on_query: self.on_query,
248 on_select: self.on_select,
249 on_confirm: self.on_confirm,
250 on_cancel: self.on_cancel,
251 };
252 self.state.update(cx, |state, cx| {
253 state.options = options;
254 state.install_model(model, cx);
255 });
256
257 self.state
258 }
259}