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