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