1use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
2use gpui::{
3 actions,
4 elements::{ChildView, Element as _, Label},
5 AnyViewHandle, AppContext, Entity, 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 AppContext) {
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, 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}