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 picker: View<Picker<BaseKeymapSelectorDelegate>>,
42}
43
44impl FocusableView for BaseKeymapSelector {
45 fn focus_handle(&self, cx: &AppContext) -> 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 cx: &mut ViewContext<BaseKeymapSelector>,
57 ) -> Self {
58 let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
59 Self { picker }
60 }
61}
62
63impl Render for BaseKeymapSelector {
64 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
65 v_flex().w(rems(34.)).child(self.picker.clone())
66 }
67}
68
69pub struct BaseKeymapSelectorDelegate {
70 view: WeakView<BaseKeymapSelector>,
71 matches: Vec<StringMatch>,
72 selected_index: usize,
73 telemetry: Arc<Telemetry>,
74 fs: Arc<dyn Fs>,
75}
76
77impl BaseKeymapSelectorDelegate {
78 fn new(
79 weak_view: WeakView<BaseKeymapSelector>,
80 fs: Arc<dyn Fs>,
81 telemetry: Arc<Telemetry>,
82 cx: &mut ViewContext<BaseKeymapSelector>,
83 ) -> Self {
84 let base = BaseKeymap::get(None, cx);
85 let selected_index = BaseKeymap::OPTIONS
86 .iter()
87 .position(|(_, value)| value == base)
88 .unwrap_or(0);
89 Self {
90 view: weak_view,
91 matches: Vec::new(),
92 selected_index,
93 telemetry,
94 fs,
95 }
96 }
97}
98
99impl PickerDelegate for BaseKeymapSelectorDelegate {
100 type ListItem = ui::ListItem;
101
102 fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
103 "Select a base keymap...".into()
104 }
105
106 fn match_count(&self) -> usize {
107 self.matches.len()
108 }
109
110 fn selected_index(&self) -> usize {
111 self.selected_index
112 }
113
114 fn set_selected_index(
115 &mut self,
116 ix: usize,
117 _: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>,
118 ) {
119 self.selected_index = ix;
120 }
121
122 fn update_matches(
123 &mut self,
124 query: String,
125 cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>,
126 ) -> Task<()> {
127 let background = cx.background_executor().clone();
128 let candidates = BaseKeymap::names()
129 .enumerate()
130 .map(|(id, name)| StringMatchCandidate {
131 id,
132 char_bag: name.into(),
133 string: name.into(),
134 })
135 .collect::<Vec<_>>();
136
137 cx.spawn(|this, mut cx| async move {
138 let matches = if query.is_empty() {
139 candidates
140 .into_iter()
141 .enumerate()
142 .map(|(index, candidate)| StringMatch {
143 candidate_id: index,
144 string: candidate.string,
145 positions: Vec::new(),
146 score: 0.0,
147 })
148 .collect()
149 } else {
150 match_strings(
151 &candidates,
152 &query,
153 false,
154 100,
155 &Default::default(),
156 background,
157 )
158 .await
159 };
160
161 this.update(&mut cx, |this, _| {
162 this.delegate.matches = matches;
163 this.delegate.selected_index = this
164 .delegate
165 .selected_index
166 .min(this.delegate.matches.len().saturating_sub(1));
167 })
168 .log_err();
169 })
170 }
171
172 fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {
173 if let Some(selection) = self.matches.get(self.selected_index) {
174 let base_keymap = BaseKeymap::from_names(&selection.string);
175
176 self.telemetry
177 .report_setting_event("keymap", base_keymap.to_string());
178
179 update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting, _| {
180 *setting = Some(base_keymap)
181 });
182 }
183
184 self.view
185 .update(cx, |_, cx| {
186 cx.emit(DismissEvent);
187 })
188 .ok();
189 }
190
191 fn dismissed(&mut self, cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {
192 self.view
193 .update(cx, |_, cx| {
194 cx.emit(DismissEvent);
195 })
196 .log_err();
197 }
198
199 fn render_match(
200 &self,
201 ix: usize,
202 selected: bool,
203 _cx: &mut gpui::ViewContext<Picker<Self>>,
204 ) -> Option<Self::ListItem> {
205 let keymap_match = &self.matches[ix];
206
207 Some(
208 ListItem::new(ix)
209 .inset(true)
210 .spacing(ListItemSpacing::Sparse)
211 .selected(selected)
212 .child(HighlightedLabel::new(
213 keymap_match.string.clone(),
214 keymap_match.positions.clone(),
215 )),
216 )
217 }
218}