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::new(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_stack().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) -> 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 {
126                id,
127                char_bag: name.into(),
128                string: name.into(),
129            })
130            .collect::<Vec<_>>();
131
132        cx.spawn(|this, mut cx| async move {
133            let matches = if query.is_empty() {
134                candidates
135                    .into_iter()
136                    .enumerate()
137                    .map(|(index, candidate)| StringMatch {
138                        candidate_id: index,
139                        string: candidate.string,
140                        positions: Vec::new(),
141                        score: 0.0,
142                    })
143                    .collect()
144            } else {
145                match_strings(
146                    &candidates,
147                    &query,
148                    false,
149                    100,
150                    &Default::default(),
151                    background,
152                )
153                .await
154            };
155
156            this.update(&mut cx, |this, _| {
157                this.delegate.matches = matches;
158                this.delegate.selected_index = this
159                    .delegate
160                    .selected_index
161                    .min(this.delegate.matches.len().saturating_sub(1));
162            })
163            .log_err();
164        })
165    }
166
167    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {
168        if let Some(selection) = self.matches.get(self.selected_index) {
169            let base_keymap = BaseKeymap::from_names(&selection.string);
170            update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| {
171                *setting = Some(base_keymap)
172            });
173        }
174
175        self.view
176            .update(cx, |_, cx| {
177                cx.emit(DismissEvent);
178            })
179            .ok();
180    }
181
182    fn dismissed(&mut self, cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {
183        self.view
184            .update(cx, |_, cx| {
185                cx.emit(DismissEvent);
186            })
187            .log_err();
188    }
189
190    fn render_match(
191        &self,
192        ix: usize,
193        selected: bool,
194        _cx: &mut gpui::ViewContext<Picker<Self>>,
195    ) -> Option<Self::ListItem> {
196        let keymap_match = &self.matches[ix];
197
198        Some(
199            ListItem::new(ix)
200                .inset(true)
201                .spacing(ListItemSpacing::Sparse)
202                .selected(selected)
203                .child(HighlightedLabel::new(
204                    keymap_match.string.clone(),
205                    keymap_match.positions.clone(),
206                )),
207        )
208    }
209}