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 true,
151 100,
152 &Default::default(),
153 background,
154 )
155 .await
156 };
157
158 this.update(cx, |this, _| {
159 this.delegate.matches = matches;
160 this.delegate.selected_index = this
161 .delegate
162 .selected_index
163 .min(this.delegate.matches.len().saturating_sub(1));
164 })
165 .log_err();
166 })
167 }
168
169 fn confirm(
170 &mut self,
171 _: bool,
172 _: &mut Window,
173 cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
174 ) {
175 if let Some(selection) = self.matches.get(self.selected_index) {
176 let base_keymap = BaseKeymap::from_names(&selection.string);
177
178 telemetry::event!(
179 "Settings Changed",
180 setting = "keymap",
181 value = base_keymap.to_string()
182 );
183
184 update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting, _| {
185 *setting = Some(base_keymap)
186 });
187 }
188
189 self.selector
190 .update(cx, |_, cx| {
191 cx.emit(DismissEvent);
192 })
193 .ok();
194 }
195
196 fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>) {
197 self.selector
198 .update(cx, |_, cx| {
199 cx.emit(DismissEvent);
200 })
201 .log_err();
202 }
203
204 fn render_match(
205 &self,
206 ix: usize,
207 selected: bool,
208 _window: &mut Window,
209 _cx: &mut Context<Picker<Self>>,
210 ) -> Option<Self::ListItem> {
211 let keymap_match = &self.matches[ix];
212
213 Some(
214 ListItem::new(ix)
215 .inset(true)
216 .spacing(ListItemSpacing::Sparse)
217 .toggle_state(selected)
218 .child(HighlightedLabel::new(
219 keymap_match.string.clone(),
220 keymap_match.positions.clone(),
221 )),
222 )
223 }
224}