base_keymap_picker.rs

  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}