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, Workspace};
 14
 15actions!(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 {}
 50
 51impl BaseKeymapSelector {
 52    pub fn new(
 53        delegate: BaseKeymapSelectorDelegate,
 54        cx: &mut ViewContext<BaseKeymapSelector>,
 55    ) -> Self {
 56        let picker = cx.build_view(|cx| Picker::new(delegate, cx));
 57        let focus_handle = cx.focus_handle();
 58        Self {
 59            focus_handle,
 60            picker,
 61        }
 62    }
 63}
 64
 65impl Render for BaseKeymapSelector {
 66    type Element = View<Picker<BaseKeymapSelectorDelegate>>;
 67
 68    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
 69        self.picker.clone()
 70    }
 71}
 72
 73pub struct BaseKeymapSelectorDelegate {
 74    view: WeakView<BaseKeymapSelector>,
 75    matches: Vec<StringMatch>,
 76    selected_index: usize,
 77    fs: Arc<dyn Fs>,
 78}
 79
 80impl BaseKeymapSelectorDelegate {
 81    fn new(
 82        weak_view: WeakView<BaseKeymapSelector>,
 83        fs: Arc<dyn Fs>,
 84        cx: &mut ViewContext<BaseKeymapSelector>,
 85    ) -> Self {
 86        let base = BaseKeymap::get(None, cx);
 87        let selected_index = BaseKeymap::OPTIONS
 88            .iter()
 89            .position(|(_, value)| value == base)
 90            .unwrap_or(0);
 91        Self {
 92            view: weak_view,
 93            matches: Vec::new(),
 94            selected_index,
 95            fs,
 96        }
 97    }
 98}
 99
100impl PickerDelegate for BaseKeymapSelectorDelegate {
101    type ListItem = ui::ListItem;
102
103    fn placeholder_text(&self) -> Arc<str> {
104        "Select a base keymap...".into()
105    }
106
107    fn match_count(&self) -> usize {
108        self.matches.len()
109    }
110
111    fn selected_index(&self) -> usize {
112        self.selected_index
113    }
114
115    fn set_selected_index(
116        &mut self,
117        ix: usize,
118        _: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>,
119    ) {
120        self.selected_index = ix;
121    }
122
123    fn update_matches(
124        &mut self,
125        query: String,
126        cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>,
127    ) -> Task<()> {
128        let background = cx.background_executor().clone();
129        let candidates = BaseKeymap::names()
130            .enumerate()
131            .map(|(id, name)| StringMatchCandidate {
132                id,
133                char_bag: name.into(),
134                string: name.into(),
135            })
136            .collect::<Vec<_>>();
137
138        cx.spawn(|this, mut cx| async move {
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                    100,
156                    &Default::default(),
157                    background,
158                )
159                .await
160            };
161
162            this.update(&mut cx, |this, _| {
163                this.delegate.matches = matches;
164                this.delegate.selected_index = this
165                    .delegate
166                    .selected_index
167                    .min(this.delegate.matches.len().saturating_sub(1));
168            })
169            .log_err();
170        })
171    }
172
173    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {
174        if let Some(selection) = self.matches.get(self.selected_index) {
175            let base_keymap = BaseKeymap::from_names(&selection.string);
176            update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting| {
177                *setting = Some(base_keymap)
178            });
179        }
180
181        self.view
182            .update(cx, |_, cx| {
183                cx.emit(DismissEvent);
184            })
185            .ok();
186    }
187
188    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<BaseKeymapSelectorDelegate>>) {}
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                .selected(selected)
201                .inset(true)
202                .child(HighlightedLabel::new(
203                    keymap_match.string.clone(),
204                    keymap_match.positions.clone(),
205                )),
206        )
207    }
208}