base_keymap_picker.rs

  1use super::base_keymap_setting::BaseKeymap;
  2use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
  3use gpui::{
  4    actions, AppContext, DismissEvent, EventEmitter, FocusableView, IntoElement, Render, Task,
  5    View, ViewContext, VisualContext, WeakView,
  6};
  7use picker::{simple_picker_match, Picker, PickerDelegate};
  8use project::Fs;
  9use settings::{update_settings_file, Settings};
 10use std::sync::Arc;
 11use util::ResultExt;
 12use workspace::{ui::HighlightedLabel, Workspace};
 13
 14actions!(ToggleBaseKeymapSelector);
 15
 16pub fn init(cx: &mut AppContext) {
 17    cx.observe_new_views(|workspace: &mut Workspace, _cx| {
 18        workspace.register_action(toggle);
 19    })
 20    .detach();
 21}
 22
 23pub fn toggle(
 24    workspace: &mut Workspace,
 25    _: &ToggleBaseKeymapSelector,
 26    cx: &mut ViewContext<Workspace>,
 27) {
 28    let fs = workspace.app_state().fs.clone();
 29    workspace.toggle_modal(cx, |cx| {
 30        BaseKeymapSelector::new(
 31            BaseKeymapSelectorDelegate::new(cx.view().downgrade(), fs, cx),
 32            cx,
 33        )
 34    });
 35}
 36
 37pub struct BaseKeymapSelector {
 38    focus_handle: gpui::FocusHandle,
 39    picker: View<Picker<BaseKeymapSelectorDelegate>>,
 40}
 41
 42impl FocusableView for BaseKeymapSelector {
 43    fn focus_handle(&self, _cx: &AppContext) -> gpui::FocusHandle {
 44        self.focus_handle.clone()
 45    }
 46}
 47
 48impl EventEmitter<DismissEvent> for BaseKeymapSelector {}
 49
 50impl BaseKeymapSelector {
 51    pub fn new(
 52        delegate: BaseKeymapSelectorDelegate,
 53        cx: &mut ViewContext<BaseKeymapSelector>,
 54    ) -> Self {
 55        let picker = cx.build_view(|cx| Picker::new(delegate, cx));
 56        let focus_handle = cx.focus_handle();
 57        Self {
 58            focus_handle,
 59            picker,
 60        }
 61    }
 62}
 63
 64impl Render for BaseKeymapSelector {
 65    type Element = View<Picker<BaseKeymapSelectorDelegate>>;
 66
 67    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
 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    fn placeholder_text(&self) -> Arc<str> {
101        "Select a base keymap...".into()
102    }
103
104    fn match_count(&self) -> usize {
105        self.matches.len()
106    }
107
108    fn selected_index(&self) -> usize {
109        self.selected_index
110    }
111
112    fn set_selected_index(
113        &mut self,
114        ix: usize,
115        _: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>,
116    ) {
117        self.selected_index = ix;
118    }
119
120    fn update_matches(
121        &mut self,
122        query: String,
123        cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>,
124    ) -> Task<()> {
125        let background = cx.background_executor().clone();
126        let candidates = BaseKeymap::names()
127            .enumerate()
128            .map(|(id, name)| StringMatchCandidate {
129                id,
130                char_bag: name.into(),
131                string: name.into(),
132            })
133            .collect::<Vec<_>>();
134
135        cx.spawn(|this, mut cx| async move {
136            let matches = if query.is_empty() {
137                candidates
138                    .into_iter()
139                    .enumerate()
140                    .map(|(index, candidate)| StringMatch {
141                        candidate_id: index,
142                        string: candidate.string,
143                        positions: Vec::new(),
144                        score: 0.0,
145                    })
146                    .collect()
147            } else {
148                match_strings(
149                    &candidates,
150                    &query,
151                    false,
152                    100,
153                    &Default::default(),
154                    background,
155                )
156                .await
157            };
158
159            this.update(&mut cx, |this, _| {
160                this.delegate.matches = matches;
161                this.delegate.selected_index = this
162                    .delegate
163                    .selected_index
164                    .min(this.delegate.matches.len().saturating_sub(1));
165            })
166            .log_err();
167        })
168    }
169
170    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {
171        if let Some(selection) = self.matches.get(self.selected_index) {
172            let base_keymap = BaseKeymap::from_names(&selection.string);
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::Dismiss);
181            })
182            .ok();
183    }
184
185    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {}
186
187    fn render_match(
188        &self,
189        ix: usize,
190        selected: bool,
191        cx: &mut gpui::ViewContext<Picker<Self>>,
192    ) -> gpui::AnyElement {
193        let keymap_match = &self.matches[ix];
194
195        simple_picker_match(selected, cx, |_cx| {
196            HighlightedLabel::new(keymap_match.string.clone(), keymap_match.positions.clone())
197                .into_any_element()
198        })
199    }
200}