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    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}