1use super::base_keymap_setting::BaseKeymap;
2use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
3use gpui::{
4 actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, Task, View,
5 ViewContext, VisualContext, WeakView,
6};
7use picker::{Picker, PickerDelegate};
8use project::Fs;
9use settings::{update_settings_file, Settings};
10use std::sync::Arc;
11use ui::{prelude::*, ListItem, ListItemSpacing};
12use util::ResultExt;
13use workspace::{ui::HighlightedLabel, ModalView, Workspace};
14
15actions!(welcome, [ToggleBaseKeymapSelector]);
16
17pub fn init(cx: &mut AppContext) {
18 cx.observe_new_views(|workspace: &mut Workspace, _cx| {
19 workspace.register_action(toggle);
20 })
21 .detach();
22}
23
24pub fn toggle(
25 workspace: &mut Workspace,
26 _: &ToggleBaseKeymapSelector,
27 cx: &mut ViewContext<Workspace>,
28) {
29 let fs = workspace.app_state().fs.clone();
30 workspace.toggle_modal(cx, |cx| {
31 BaseKeymapSelector::new(
32 BaseKeymapSelectorDelegate::new(cx.view().downgrade(), fs, cx),
33 cx,
34 )
35 });
36}
37
38pub struct BaseKeymapSelector {
39 focus_handle: gpui::FocusHandle,
40 picker: View<Picker<BaseKeymapSelectorDelegate>>,
41}
42
43impl FocusableView for BaseKeymapSelector {
44 fn focus_handle(&self, _cx: &AppContext) -> gpui::FocusHandle {
45 self.focus_handle.clone()
46 }
47}
48
49impl EventEmitter<DismissEvent> for BaseKeymapSelector {}
50impl ModalView for BaseKeymapSelector {}
51
52impl BaseKeymapSelector {
53 pub fn new(
54 delegate: BaseKeymapSelectorDelegate,
55 cx: &mut ViewContext<BaseKeymapSelector>,
56 ) -> Self {
57 let picker = cx.new_view(|cx| Picker::new(delegate, cx));
58 let focus_handle = cx.focus_handle();
59 Self {
60 focus_handle,
61 picker,
62 }
63 }
64}
65
66impl Render for BaseKeymapSelector {
67 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
68 self.picker.clone()
69 }
70}
71
72pub struct BaseKeymapSelectorDelegate {
73 view: WeakView<BaseKeymapSelector>,
74 matches: Vec<StringMatch>,
75 selected_index: usize,
76 fs: Arc<dyn Fs>,
77}
78
79impl BaseKeymapSelectorDelegate {
80 fn new(
81 weak_view: WeakView<BaseKeymapSelector>,
82 fs: Arc<dyn Fs>,
83 cx: &mut ViewContext<BaseKeymapSelector>,
84 ) -> Self {
85 let base = BaseKeymap::get(None, cx);
86 let selected_index = BaseKeymap::OPTIONS
87 .iter()
88 .position(|(_, value)| value == base)
89 .unwrap_or(0);
90 Self {
91 view: weak_view,
92 matches: Vec::new(),
93 selected_index,
94 fs,
95 }
96 }
97}
98
99impl PickerDelegate for BaseKeymapSelectorDelegate {
100 type ListItem = ui::ListItem;
101
102 fn placeholder_text(&self) -> 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 update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| {
176 *setting = Some(base_keymap)
177 });
178 }
179
180 self.view
181 .update(cx, |_, cx| {
182 cx.emit(DismissEvent);
183 })
184 .ok();
185 }
186
187 fn dismissed(&mut self, _cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {}
188
189 fn render_match(
190 &self,
191 ix: usize,
192 selected: bool,
193 _cx: &mut gpui::ViewContext<Picker<Self>>,
194 ) -> Option<Self::ListItem> {
195 let keymap_match = &self.matches[ix];
196
197 Some(
198 ListItem::new(ix)
199 .inset(true)
200 .spacing(ListItemSpacing::Sparse)
201 .selected(selected)
202 .child(HighlightedLabel::new(
203 keymap_match.string.clone(),
204 keymap_match.positions.clone(),
205 )),
206 )
207 }
208}