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