base_keymap_picker.rs

  1use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
  2use gpui::{
  3    actions,
  4    elements::{ChildView, Element as _, Label},
  5    AnyViewHandle, Entity, MutableAppContext, View, ViewContext, ViewHandle,
  6};
  7use picker::{Picker, PickerDelegate};
  8use settings::{settings_file::SettingsFile, BaseKeymap, Settings};
  9use workspace::Workspace;
 10
 11pub struct BaseKeymapSelector {
 12    matches: Vec<StringMatch>,
 13    picker: ViewHandle<Picker<Self>>,
 14    selected_index: usize,
 15}
 16
 17actions!(welcome, [ToggleBaseKeymapSelector]);
 18
 19pub fn init(cx: &mut MutableAppContext) {
 20    Picker::<BaseKeymapSelector>::init(cx);
 21    cx.add_action({
 22        move |workspace, _: &ToggleBaseKeymapSelector, cx| BaseKeymapSelector::toggle(workspace, cx)
 23    });
 24}
 25
 26pub enum Event {
 27    Dismissed,
 28}
 29
 30impl BaseKeymapSelector {
 31    fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
 32        workspace.toggle_modal(cx, |_, cx| {
 33            let this = cx.add_view(|cx| Self::new(cx));
 34            cx.subscribe(&this, Self::on_event).detach();
 35            this
 36        });
 37    }
 38
 39    fn new(cx: &mut ViewContext<Self>) -> Self {
 40        let base = cx.global::<Settings>().base_keymap;
 41        let selected_index = BaseKeymap::OPTIONS
 42            .iter()
 43            .position(|(_, value)| *value == base)
 44            .unwrap_or(0);
 45
 46        let this = cx.weak_handle();
 47        Self {
 48            picker: cx.add_view(|cx| Picker::new("Select a base keymap", this, cx)),
 49            matches: Vec::new(),
 50            selected_index,
 51        }
 52    }
 53
 54    fn on_event(
 55        workspace: &mut Workspace,
 56        _: ViewHandle<BaseKeymapSelector>,
 57        event: &Event,
 58        cx: &mut ViewContext<Workspace>,
 59    ) {
 60        match event {
 61            Event::Dismissed => {
 62                workspace.dismiss_modal(cx);
 63            }
 64        }
 65    }
 66}
 67
 68impl Entity for BaseKeymapSelector {
 69    type Event = Event;
 70}
 71
 72impl View for BaseKeymapSelector {
 73    fn ui_name() -> &'static str {
 74        "BaseKeymapSelector"
 75    }
 76
 77    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
 78        ChildView::new(self.picker.clone(), cx).boxed()
 79    }
 80
 81    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
 82        if cx.is_self_focused() {
 83            cx.focus(&self.picker);
 84        }
 85    }
 86}
 87
 88impl PickerDelegate for BaseKeymapSelector {
 89    fn match_count(&self) -> usize {
 90        self.matches.len()
 91    }
 92
 93    fn selected_index(&self) -> usize {
 94        self.selected_index
 95    }
 96
 97    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
 98        self.selected_index = ix;
 99    }
100
101    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
102        let background = cx.background().clone();
103        let candidates = BaseKeymap::names()
104            .enumerate()
105            .map(|(id, name)| StringMatchCandidate {
106                id,
107                char_bag: name.into(),
108                string: name.into(),
109            })
110            .collect::<Vec<_>>();
111
112        cx.spawn(|this, mut cx| async move {
113            let matches = if query.is_empty() {
114                candidates
115                    .into_iter()
116                    .enumerate()
117                    .map(|(index, candidate)| StringMatch {
118                        candidate_id: index,
119                        string: candidate.string,
120                        positions: Vec::new(),
121                        score: 0.0,
122                    })
123                    .collect()
124            } else {
125                match_strings(
126                    &candidates,
127                    &query,
128                    false,
129                    100,
130                    &Default::default(),
131                    background,
132                )
133                .await
134            };
135
136            this.update(&mut cx, |this, cx| {
137                this.matches = matches;
138                this.selected_index = this
139                    .selected_index
140                    .min(this.matches.len().saturating_sub(1));
141                cx.notify();
142            });
143        })
144    }
145
146    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
147        if let Some(selection) = self.matches.get(self.selected_index) {
148            let base_keymap = BaseKeymap::from_names(&selection.string);
149            SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap));
150        }
151        cx.emit(Event::Dismissed);
152    }
153
154    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
155        cx.emit(Event::Dismissed)
156    }
157
158    fn render_match(
159        &self,
160        ix: usize,
161        mouse_state: &mut gpui::MouseState,
162        selected: bool,
163        cx: &gpui::AppContext,
164    ) -> gpui::ElementBox {
165        let theme = &cx.global::<Settings>().theme;
166        let keymap_match = &self.matches[ix];
167        let style = theme.picker.item.style_for(mouse_state, selected);
168
169        Label::new(keymap_match.string.clone(), style.label.clone())
170            .with_highlights(keymap_match.positions.clone())
171            .contained()
172            .with_style(style.container)
173            .boxed()
174    }
175}