base_keymap_picker.rs

  1use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
  2use gpui::{
  3    App, Context, DismissEvent, Entity, EventEmitter, Focusable, Render, Task, WeakEntity, Window,
  4    actions,
  5};
  6use picker::{Picker, PickerDelegate};
  7use project::Fs;
  8use settings::{BaseKeymap, Settings, update_settings_file};
  9use std::sync::Arc;
 10use ui::{ListItem, ListItemSpacing, prelude::*};
 11use util::ResultExt;
 12use workspace::{ModalView, Workspace, ui::HighlightedLabel};
 13
 14actions!(
 15    zed,
 16    [
 17        /// Toggles the base keymap selector modal.
 18        ToggleBaseKeymapSelector
 19    ]
 20);
 21
 22pub fn init(cx: &mut App) {
 23    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
 24        workspace.register_action(toggle);
 25    })
 26    .detach();
 27}
 28
 29pub fn toggle(
 30    workspace: &mut Workspace,
 31    _: &ToggleBaseKeymapSelector,
 32    window: &mut Window,
 33    cx: &mut Context<Workspace>,
 34) {
 35    let fs = workspace.app_state().fs.clone();
 36    workspace.toggle_modal(window, cx, |window, cx| {
 37        BaseKeymapSelector::new(
 38            BaseKeymapSelectorDelegate::new(cx.entity().downgrade(), fs, cx),
 39            window,
 40            cx,
 41        )
 42    });
 43}
 44
 45pub struct BaseKeymapSelector {
 46    picker: Entity<Picker<BaseKeymapSelectorDelegate>>,
 47}
 48
 49impl Focusable for BaseKeymapSelector {
 50    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 51        self.picker.focus_handle(cx)
 52    }
 53}
 54
 55impl EventEmitter<DismissEvent> for BaseKeymapSelector {}
 56impl ModalView for BaseKeymapSelector {}
 57
 58impl BaseKeymapSelector {
 59    pub fn new(
 60        delegate: BaseKeymapSelectorDelegate,
 61        window: &mut Window,
 62        cx: &mut Context<BaseKeymapSelector>,
 63    ) -> Self {
 64        let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
 65        Self { picker }
 66    }
 67}
 68
 69impl Render for BaseKeymapSelector {
 70    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 71        v_flex().w(rems(34.)).child(self.picker.clone())
 72    }
 73}
 74
 75pub struct BaseKeymapSelectorDelegate {
 76    selector: WeakEntity<BaseKeymapSelector>,
 77    matches: Vec<StringMatch>,
 78    selected_index: usize,
 79    fs: Arc<dyn Fs>,
 80}
 81
 82impl BaseKeymapSelectorDelegate {
 83    fn new(
 84        selector: WeakEntity<BaseKeymapSelector>,
 85        fs: Arc<dyn Fs>,
 86        cx: &mut Context<BaseKeymapSelector>,
 87    ) -> Self {
 88        let base = BaseKeymap::get(None, cx);
 89        let selected_index = BaseKeymap::OPTIONS
 90            .iter()
 91            .position(|(_, value)| value == base)
 92            .unwrap_or(0);
 93        Self {
 94            selector,
 95            matches: Vec::new(),
 96            selected_index,
 97            fs,
 98        }
 99    }
100}
101
102impl PickerDelegate for BaseKeymapSelectorDelegate {
103    type ListItem = ui::ListItem;
104
105    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
106        "Select a base keymap...".into()
107    }
108
109    fn match_count(&self) -> usize {
110        self.matches.len()
111    }
112
113    fn selected_index(&self) -> usize {
114        self.selected_index
115    }
116
117    fn set_selected_index(
118        &mut self,
119        ix: usize,
120        _window: &mut Window,
121        _: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
122    ) {
123        self.selected_index = ix;
124    }
125
126    fn update_matches(
127        &mut self,
128        query: String,
129        window: &mut Window,
130        cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
131    ) -> Task<()> {
132        let background = cx.background_executor().clone();
133        let candidates = BaseKeymap::names()
134            .enumerate()
135            .map(|(id, name)| StringMatchCandidate::new(id, name))
136            .collect::<Vec<_>>();
137
138        cx.spawn_in(window, async move |this, cx| {
139            let matches = if query.is_empty() {
140                candidates
141                    .into_iter()
142                    .enumerate()
143                    .map(|(index, candidate)| StringMatch {
144                        candidate_id: index,
145                        string: candidate.string,
146                        positions: Vec::new(),
147                        score: 0.0,
148                    })
149                    .collect()
150            } else {
151                match_strings(
152                    &candidates,
153                    &query,
154                    false,
155                    true,
156                    100,
157                    &Default::default(),
158                    background,
159                )
160                .await
161            };
162
163            this.update(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(
175        &mut self,
176        _: bool,
177        _: &mut Window,
178        cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>,
179    ) {
180        if let Some(selection) = self.matches.get(self.selected_index) {
181            let base_keymap = BaseKeymap::from_names(&selection.string);
182
183            telemetry::event!(
184                "Settings Changed",
185                setting = "keymap",
186                value = base_keymap.to_string()
187            );
188
189            update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting, _| {
190                setting.base_keymap = Some(base_keymap)
191            });
192        }
193
194        self.selector
195            .update(cx, |_, cx| {
196                cx.emit(DismissEvent);
197            })
198            .ok();
199    }
200
201    fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<BaseKeymapSelectorDelegate>>) {
202        self.selector
203            .update(cx, |_, cx| {
204                cx.emit(DismissEvent);
205            })
206            .log_err();
207    }
208
209    fn render_match(
210        &self,
211        ix: usize,
212        selected: bool,
213        _window: &mut Window,
214        _cx: &mut Context<Picker<Self>>,
215    ) -> Option<Self::ListItem> {
216        let keymap_match = &self.matches[ix];
217
218        Some(
219            ListItem::new(ix)
220                .inset(true)
221                .spacing(ListItemSpacing::Sparse)
222                .toggle_state(selected)
223                .child(HighlightedLabel::new(
224                    keymap_match.string.clone(),
225                    keymap_match.positions.clone(),
226                )),
227        )
228    }
229}