1use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
2use gpui::{
3 App, Context, DismissEvent, Entity, EventEmitter, Focusable, Render, Task, WeakEntity, Window,
4 actions,
5};
6use picker::{Picker, PickerDelegate};
7use project::Fs;
8use settings::{BaseKeymap, Settings, update_settings_file};
9use std::sync::Arc;
10use ui::{ListItem, ListItemSpacing, prelude::*};
11use util::ResultExt;
12use workspace::{ModalView, Workspace, ui::HighlightedLabel};
13
14actions!(
15 zed,
16 [
17 /// Toggles the base keymap selector modal.
18 ToggleBaseKeymapSelector
19 ]
20);
21
22pub fn init(cx: &mut App) {
23 cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
24 workspace.register_action(toggle);
25 })
26 .detach();
27}
28
29pub fn toggle(
30 workspace: &mut Workspace,
31 _: &ToggleBaseKeymapSelector,
32 window: &mut Window,
33 cx: &mut Context<Workspace>,
34) {
35 let fs = workspace.app_state().fs.clone();
36 workspace.toggle_modal(window, cx, |window, cx| {
37 BaseKeymapSelector::new(
38 BaseKeymapSelectorDelegate::new(cx.entity().downgrade(), fs, cx),
39 window,
40 cx,
41 )
42 });
43}
44
45pub struct BaseKeymapSelector {
46 picker: Entity<Picker<BaseKeymapSelectorDelegate>>,
47}
48
49impl Focusable for BaseKeymapSelector {
50 fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
51 self.picker.focus_handle(cx)
52 }
53}
54
55impl EventEmitter<DismissEvent> for BaseKeymapSelector {}
56impl ModalView for BaseKeymapSelector {}
57
58impl BaseKeymapSelector {
59 pub fn new(
60 delegate: BaseKeymapSelectorDelegate,
61 window: &mut Window,
62 cx: &mut Context<BaseKeymapSelector>,
63 ) -> Self {
64 let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
65 Self { picker }
66 }
67}
68
69impl Render for BaseKeymapSelector {
70 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
71 v_flex().w(rems(34.)).child(self.picker.clone())
72 }
73}
74
75pub struct BaseKeymapSelectorDelegate {
76 selector: WeakEntity<BaseKeymapSelector>,
77 matches: Vec<StringMatch>,
78 selected_index: usize,
79 fs: Arc<dyn Fs>,
80}
81
82impl BaseKeymapSelectorDelegate {
83 fn new(
84 selector: WeakEntity<BaseKeymapSelector>,
85 fs: Arc<dyn Fs>,
86 cx: &mut Context<BaseKeymapSelector>,
87 ) -> Self {
88 let base = BaseKeymap::get(None, cx);
89 let selected_index = BaseKeymap::OPTIONS
90 .iter()
91 .position(|(_, value)| value == base)
92 .unwrap_or(0);
93 Self {
94 selector,
95 matches: Vec::new(),
96 selected_index,
97 fs,
98 }
99 }
100}
101
102impl PickerDelegate for BaseKeymapSelectorDelegate {
103 type ListItem = ui::ListItem;
104
105 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
106 "Select a base keymap...".into()
107 }
108
109 fn match_count(&self) -> usize {
110 self.matches.len()
111 }
112
113 fn selected_index(&self) -> usize {
114 self.selected_index
115 }
116
117 fn set_selected_index(
118 &mut self,
119 ix: usize,
120 _window: &mut Window,
121 _: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
122 ) {
123 self.selected_index = ix;
124 }
125
126 fn update_matches(
127 &mut self,
128 query: String,
129 window: &mut Window,
130 cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
131 ) -> Task<()> {
132 let background = cx.background_executor().clone();
133 let candidates = BaseKeymap::names()
134 .enumerate()
135 .map(|(id, name)| StringMatchCandidate::new(id, name))
136 .collect::<Vec<_>>();
137
138 cx.spawn_in(window, async move |this, cx| {
139 let matches = if query.is_empty() {
140 candidates
141 .into_iter()
142 .enumerate()
143 .map(|(index, candidate)| StringMatch {
144 candidate_id: index,
145 string: candidate.string,
146 positions: Vec::new(),
147 score: 0.0,
148 })
149 .collect()
150 } else {
151 match_strings(
152 &candidates,
153 &query,
154 false,
155 true,
156 100,
157 &Default::default(),
158 background,
159 )
160 .await
161 };
162
163 this.update(cx, |this, _| {
164 this.delegate.matches = matches;
165 this.delegate.selected_index = this
166 .delegate
167 .selected_index
168 .min(this.delegate.matches.len().saturating_sub(1));
169 })
170 .log_err();
171 })
172 }
173
174 fn confirm(
175 &mut self,
176 _: bool,
177 _: &mut Window,
178 cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
179 ) {
180 if let Some(selection) = self.matches.get(self.selected_index) {
181 let base_keymap = BaseKeymap::from_names(&selection.string);
182
183 telemetry::event!(
184 "Settings Changed",
185 setting = "keymap",
186 value = base_keymap.to_string()
187 );
188
189 update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting, _| {
190 setting.base_keymap = Some(base_keymap)
191 });
192 }
193
194 self.selector
195 .update(cx, |_, cx| {
196 cx.emit(DismissEvent);
197 })
198 .ok();
199 }
200
201 fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>) {
202 self.selector
203 .update(cx, |_, cx| {
204 cx.emit(DismissEvent);
205 })
206 .log_err();
207 }
208
209 fn render_match(
210 &self,
211 ix: usize,
212 selected: bool,
213 _window: &mut Window,
214 _cx: &mut Context<Picker<Self>>,
215 ) -> Option<Self::ListItem> {
216 let keymap_match = &self.matches[ix];
217
218 Some(
219 ListItem::new(ix)
220 .inset(true)
221 .spacing(ListItemSpacing::Sparse)
222 .toggle_state(selected)
223 .child(HighlightedLabel::new(
224 keymap_match.string.clone(),
225 keymap_match.positions.clone(),
226 )),
227 )
228 }
229}