1use std::sync::Arc;
2
3use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
4use gpui::{
5 actions,
6 elements::{Element as _, Label},
7 AppContext, Task, ViewContext,
8};
9use picker::{Picker, PickerDelegate, PickerEvent};
10use settings::{settings_file::SettingsFile, BaseKeymap, Settings};
11use util::ResultExt;
12use workspace::Workspace;
13
14actions!(welcome, [ToggleBaseKeymapSelector]);
15
16pub fn init(cx: &mut AppContext) {
17 cx.add_action(toggle);
18 BaseKeymapSelector::init(cx);
19}
20
21pub fn toggle(
22 workspace: &mut Workspace,
23 _: &ToggleBaseKeymapSelector,
24 cx: &mut ViewContext<Workspace>,
25) {
26 workspace.toggle_modal(cx, |_, cx| {
27 cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(cx), cx))
28 });
29}
30
31pub type BaseKeymapSelector = Picker<BaseKeymapSelectorDelegate>;
32
33pub struct BaseKeymapSelectorDelegate {
34 matches: Vec<StringMatch>,
35 selected_index: usize,
36}
37
38impl BaseKeymapSelectorDelegate {
39 fn new(cx: &mut ViewContext<BaseKeymapSelector>) -> 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 Self {
46 matches: Vec::new(),
47 selected_index,
48 }
49 }
50}
51
52impl PickerDelegate for BaseKeymapSelectorDelegate {
53 fn placeholder_text(&self) -> Arc<str> {
54 "Select a base keymap...".into()
55 }
56
57 fn match_count(&self) -> usize {
58 self.matches.len()
59 }
60
61 fn selected_index(&self) -> usize {
62 self.selected_index
63 }
64
65 fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<BaseKeymapSelector>) {
66 self.selected_index = ix;
67 }
68
69 fn update_matches(
70 &mut self,
71 query: String,
72 cx: &mut ViewContext<BaseKeymapSelector>,
73 ) -> Task<()> {
74 let background = cx.background().clone();
75 let candidates = BaseKeymap::names()
76 .enumerate()
77 .map(|(id, name)| StringMatchCandidate {
78 id,
79 char_bag: name.into(),
80 string: name.into(),
81 })
82 .collect::<Vec<_>>();
83
84 cx.spawn(|this, mut cx| async move {
85 let matches = if query.is_empty() {
86 candidates
87 .into_iter()
88 .enumerate()
89 .map(|(index, candidate)| StringMatch {
90 candidate_id: index,
91 string: candidate.string,
92 positions: Vec::new(),
93 score: 0.0,
94 })
95 .collect()
96 } else {
97 match_strings(
98 &candidates,
99 &query,
100 false,
101 100,
102 &Default::default(),
103 background,
104 )
105 .await
106 };
107
108 this.update(&mut cx, |this, _| {
109 let delegate = this.delegate_mut();
110 delegate.matches = matches;
111 delegate.selected_index = delegate
112 .selected_index
113 .min(delegate.matches.len().saturating_sub(1));
114 })
115 .log_err();
116 })
117 }
118
119 fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) {
120 if let Some(selection) = self.matches.get(self.selected_index) {
121 let base_keymap = BaseKeymap::from_names(&selection.string);
122 SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap));
123 }
124 cx.emit(PickerEvent::Dismiss);
125 }
126
127 fn dismissed(&mut self, _cx: &mut ViewContext<BaseKeymapSelector>) {}
128
129 fn render_match(
130 &self,
131 ix: usize,
132 mouse_state: &mut gpui::MouseState,
133 selected: bool,
134 cx: &gpui::AppContext,
135 ) -> gpui::AnyElement<Picker<Self>> {
136 let theme = &cx.global::<Settings>().theme;
137 let keymap_match = &self.matches[ix];
138 let style = theme.picker.item.style_for(mouse_state, selected);
139
140 Label::new(keymap_match.string.clone(), style.label.clone())
141 .with_highlights(keymap_match.positions.clone())
142 .contained()
143 .with_style(style.container)
144 .into_any()
145 }
146}