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