base_keymap_picker.rs

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