1use std::{
2 ops::{Not, Range},
3 sync::Arc,
4};
5
6use anyhow::{Context as _, anyhow};
7use collections::{HashMap, HashSet};
8use editor::{CompletionProvider, Editor, EditorEvent};
9use feature_flags::FeatureFlagViewExt;
10use fs::Fs;
11use fuzzy::{StringMatch, StringMatchCandidate};
12use gpui::{
13 AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
14 FocusHandle, Focusable, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy,
15 StyledText, Subscription, WeakEntity, actions, div,
16};
17use language::{Language, LanguageConfig, ToOffset as _};
18use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets};
19
20use util::ResultExt;
21
22use ui::{
23 ActiveTheme as _, App, BorrowAppContext, ContextMenu, ParentElement as _, Render, SharedString,
24 Styled as _, Tooltip, Window, prelude::*, right_click_menu,
25};
26use workspace::{Item, ModalView, SerializableItem, Workspace, register_serializable_item};
27
28use crate::{
29 SettingsUiFeatureFlag,
30 keybindings::persistence::KEYBINDING_EDITORS,
31 ui_components::table::{Table, TableInteractionState},
32};
33
34const NO_ACTION_ARGUMENTS_TEXT: SharedString = SharedString::new_static("<no arguments>");
35
36actions!(
37 zed,
38 [
39 /// Opens the keymap editor.
40 OpenKeymapEditor
41 ]
42);
43
44const KEYMAP_EDITOR_NAMESPACE: &'static str = "keymap_editor";
45actions!(
46 keymap_editor,
47 [
48 /// Edits the selected key binding.
49 EditBinding,
50 /// Creates a new key binding for the selected action.
51 CreateBinding,
52 /// Copies the action name to clipboard.
53 CopyAction,
54 /// Copies the context predicate to clipboard.
55 CopyContext
56 ]
57);
58
59pub fn init(cx: &mut App) {
60 let keymap_event_channel = KeymapEventChannel::new();
61 cx.set_global(keymap_event_channel);
62
63 cx.on_action(|_: &OpenKeymapEditor, cx| {
64 workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
65 let existing = workspace
66 .active_pane()
67 .read(cx)
68 .items()
69 .find_map(|item| item.downcast::<KeymapEditor>());
70
71 if let Some(existing) = existing {
72 workspace.activate_item(&existing, true, true, window, cx);
73 } else {
74 let keymap_editor =
75 cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
76 workspace.add_item_to_active_pane(Box::new(keymap_editor), None, true, window, cx);
77 }
78 });
79 });
80
81 cx.observe_new(|_workspace: &mut Workspace, window, cx| {
82 let Some(window) = window else { return };
83
84 let keymap_ui_actions = [std::any::TypeId::of::<OpenKeymapEditor>()];
85
86 command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
87 filter.hide_action_types(&keymap_ui_actions);
88 filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
89 });
90
91 cx.observe_flag::<SettingsUiFeatureFlag, _>(
92 window,
93 move |is_enabled, _workspace, _, cx| {
94 if is_enabled {
95 command_palette_hooks::CommandPaletteFilter::update_global(
96 cx,
97 |filter, _cx| {
98 filter.show_action_types(keymap_ui_actions.iter());
99 filter.show_namespace(KEYMAP_EDITOR_NAMESPACE);
100 },
101 );
102 } else {
103 command_palette_hooks::CommandPaletteFilter::update_global(
104 cx,
105 |filter, _cx| {
106 filter.hide_action_types(&keymap_ui_actions);
107 filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
108 },
109 );
110 }
111 },
112 )
113 .detach();
114 })
115 .detach();
116
117 register_serializable_item::<KeymapEditor>(cx);
118}
119
120pub struct KeymapEventChannel {}
121
122impl Global for KeymapEventChannel {}
123
124impl KeymapEventChannel {
125 fn new() -> Self {
126 Self {}
127 }
128
129 pub fn trigger_keymap_changed(cx: &mut App) {
130 let Some(_event_channel) = cx.try_global::<Self>() else {
131 // don't panic if no global defined. This usually happens in tests
132 return;
133 };
134 cx.update_global(|_event_channel: &mut Self, _| {
135 /* triggers observers in KeymapEditors */
136 });
137 }
138}
139
140#[derive(Default, PartialEq)]
141enum FilterState {
142 #[default]
143 All,
144 Conflicts,
145}
146
147impl FilterState {
148 fn invert(&self) -> Self {
149 match self {
150 FilterState::All => FilterState::Conflicts,
151 FilterState::Conflicts => FilterState::All,
152 }
153 }
154}
155
156struct KeymapEditor {
157 workspace: WeakEntity<Workspace>,
158 focus_handle: FocusHandle,
159 _keymap_subscription: Subscription,
160 keybindings: Vec<ProcessedKeybinding>,
161 conflicts: Vec<usize>,
162 filter_state: FilterState,
163 // corresponds 1 to 1 with keybindings
164 string_match_candidates: Arc<Vec<StringMatchCandidate>>,
165 matches: Vec<StringMatch>,
166 table_interaction_state: Entity<TableInteractionState>,
167 filter_editor: Entity<Editor>,
168 selected_index: Option<usize>,
169}
170
171impl EventEmitter<()> for KeymapEditor {}
172
173impl Focusable for KeymapEditor {
174 fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
175 return self.filter_editor.focus_handle(cx);
176 }
177}
178
179impl KeymapEditor {
180 fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
181 let focus_handle = cx.focus_handle();
182
183 let _keymap_subscription =
184 cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
185 let table_interaction_state = TableInteractionState::new(window, cx);
186
187 let filter_editor = cx.new(|cx| {
188 let mut editor = Editor::single_line(window, cx);
189 editor.set_placeholder_text("Filter action names…", cx);
190 editor
191 });
192
193 cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
194 if !matches!(e, EditorEvent::BufferEdited) {
195 return;
196 }
197
198 this.update_matches(cx);
199 })
200 .detach();
201
202 let mut this = Self {
203 workspace,
204 keybindings: vec![],
205 conflicts: Vec::default(),
206 filter_state: FilterState::default(),
207 string_match_candidates: Arc::new(vec![]),
208 matches: vec![],
209 focus_handle: focus_handle.clone(),
210 _keymap_subscription,
211 table_interaction_state,
212 filter_editor,
213 selected_index: None,
214 };
215
216 this.update_keybindings(cx);
217
218 this
219 }
220
221 fn current_query(&self, cx: &mut Context<Self>) -> String {
222 self.filter_editor.read(cx).text(cx)
223 }
224
225 fn update_matches(&self, cx: &mut Context<Self>) {
226 let query = self.current_query(cx);
227
228 cx.spawn(async move |this, cx| Self::process_query(this, query, cx).await)
229 .detach();
230 }
231
232 async fn process_query(
233 this: WeakEntity<Self>,
234 query: String,
235 cx: &mut AsyncApp,
236 ) -> anyhow::Result<()> {
237 let query = command_palette::normalize_action_query(&query);
238 let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
239 (this.string_match_candidates.clone(), this.keybindings.len())
240 })?;
241 let executor = cx.background_executor().clone();
242 let mut matches = fuzzy::match_strings(
243 &string_match_candidates,
244 &query,
245 true,
246 true,
247 keybind_count,
248 &Default::default(),
249 executor,
250 )
251 .await;
252 this.update(cx, |this, cx| {
253 match this.filter_state {
254 FilterState::Conflicts => {
255 matches.retain(|candidate| this.conflicts.contains(&candidate.candidate_id));
256 }
257 FilterState::All => {}
258 }
259
260 if query.is_empty() {
261 // apply default sort
262 // sorts by source precedence, and alphabetically by action name within each source
263 matches.sort_by_key(|match_item| {
264 let keybind = &this.keybindings[match_item.candidate_id];
265 let source = keybind.source.as_ref().map(|s| s.0);
266 use KeybindSource::*;
267 let source_precedence = match source {
268 Some(User) => 0,
269 Some(Vim) => 1,
270 Some(Base) => 2,
271 Some(Default) => 3,
272 None => 4,
273 };
274 return (source_precedence, keybind.action_name.as_ref());
275 });
276 }
277 this.selected_index.take();
278 this.scroll_to_item(0, ScrollStrategy::Top, cx);
279 this.matches = matches;
280 cx.notify();
281 })
282 }
283
284 fn process_bindings(
285 json_language: Arc<Language>,
286 rust_language: Arc<Language>,
287 cx: &mut App,
288 ) -> (Vec<ProcessedKeybinding>, Vec<StringMatchCandidate>) {
289 let key_bindings_ptr = cx.key_bindings();
290 let lock = key_bindings_ptr.borrow();
291 let key_bindings = lock.bindings();
292 let mut unmapped_action_names =
293 HashSet::from_iter(cx.all_action_names().into_iter().copied());
294 let action_documentation = cx.action_documentation();
295 let mut generator = KeymapFile::action_schema_generator();
296 let action_schema = HashMap::from_iter(
297 cx.action_schemas(&mut generator)
298 .into_iter()
299 .filter_map(|(name, schema)| schema.map(|schema| (name, schema))),
300 );
301
302 let mut processed_bindings = Vec::new();
303 let mut string_match_candidates = Vec::new();
304
305 for key_binding in key_bindings {
306 let source = key_binding.meta().map(settings::KeybindSource::from_meta);
307
308 let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
309 let ui_key_binding = Some(
310 ui::KeyBinding::new_from_gpui(key_binding.clone(), cx)
311 .vim_mode(source == Some(settings::KeybindSource::Vim)),
312 );
313
314 let context = key_binding
315 .predicate()
316 .map(|predicate| {
317 KeybindContextString::Local(predicate.to_string().into(), rust_language.clone())
318 })
319 .unwrap_or(KeybindContextString::Global);
320
321 let source = source.map(|source| (source, source.name().into()));
322
323 let action_name = key_binding.action().name();
324 unmapped_action_names.remove(&action_name);
325 let action_input = key_binding
326 .action_input()
327 .map(|input| SyntaxHighlightedText::new(input, json_language.clone()));
328 let action_docs = action_documentation.get(action_name).copied();
329
330 let index = processed_bindings.len();
331 let string_match_candidate = StringMatchCandidate::new(index, &action_name);
332 processed_bindings.push(ProcessedKeybinding {
333 keystroke_text: keystroke_text.into(),
334 ui_key_binding,
335 action_name: action_name.into(),
336 action_input,
337 action_docs,
338 action_schema: action_schema.get(action_name).cloned(),
339 context: Some(context),
340 source,
341 });
342 string_match_candidates.push(string_match_candidate);
343 }
344
345 let empty = SharedString::new_static("");
346 for action_name in unmapped_action_names.into_iter() {
347 let index = processed_bindings.len();
348 let string_match_candidate = StringMatchCandidate::new(index, &action_name);
349 processed_bindings.push(ProcessedKeybinding {
350 keystroke_text: empty.clone(),
351 ui_key_binding: None,
352 action_name: action_name.into(),
353 action_input: None,
354 action_docs: action_documentation.get(action_name).copied(),
355 action_schema: action_schema.get(action_name).cloned(),
356 context: None,
357 source: None,
358 });
359 string_match_candidates.push(string_match_candidate);
360 }
361
362 (processed_bindings, string_match_candidates)
363 }
364
365 fn update_keybindings(&mut self, cx: &mut Context<KeymapEditor>) {
366 let workspace = self.workspace.clone();
367 cx.spawn(async move |this, cx| {
368 let json_language = load_json_language(workspace.clone(), cx).await;
369 let rust_language = load_rust_language(workspace.clone(), cx).await;
370
371 let query = this.update(cx, |this, cx| {
372 let (key_bindings, string_match_candidates) =
373 Self::process_bindings(json_language, rust_language, cx);
374
375 let mut conflicts: HashMap<_, Vec<usize>> = HashMap::default();
376
377 key_bindings
378 .iter()
379 .enumerate()
380 .filter(|(_, binding)| !binding.keystroke_text.is_empty())
381 .for_each(|(index, binding)| {
382 conflicts
383 .entry(binding.get_action_mapping())
384 .or_default()
385 .push(index);
386 });
387
388 this.conflicts = conflicts
389 .into_values()
390 .filter(|indices| indices.len() > 1)
391 .flatten()
392 .collect();
393
394 if this.conflicts.is_empty() {
395 this.filter_state = FilterState::All;
396 }
397
398 this.keybindings = key_bindings;
399 this.string_match_candidates = Arc::new(string_match_candidates);
400 this.matches = this
401 .string_match_candidates
402 .iter()
403 .enumerate()
404 .map(|(ix, candidate)| StringMatch {
405 candidate_id: ix,
406 score: 0.0,
407 positions: vec![],
408 string: candidate.string.clone(),
409 })
410 .collect();
411 this.current_query(cx)
412 })?;
413 // calls cx.notify
414 Self::process_query(this, query, cx).await
415 })
416 .detach_and_log_err(cx);
417 }
418
419 fn dispatch_context(&self, _window: &Window, _cx: &Context<Self>) -> KeyContext {
420 let mut dispatch_context = KeyContext::new_with_defaults();
421 dispatch_context.add("KeymapEditor");
422 dispatch_context.add("menu");
423
424 dispatch_context
425 }
426
427 fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
428 let index = usize::min(index, self.matches.len().saturating_sub(1));
429 self.table_interaction_state.update(cx, |this, _cx| {
430 this.scroll_handle.scroll_to_item(index, strategy);
431 });
432 }
433
434 fn focus_search(
435 &mut self,
436 _: &search::FocusSearch,
437 window: &mut Window,
438 cx: &mut Context<Self>,
439 ) {
440 if !self
441 .filter_editor
442 .focus_handle(cx)
443 .contains_focused(window, cx)
444 {
445 window.focus(&self.filter_editor.focus_handle(cx));
446 } else {
447 self.filter_editor.update(cx, |editor, cx| {
448 editor.select_all(&Default::default(), window, cx);
449 });
450 }
451 self.selected_index.take();
452 }
453
454 fn selected_binding(&self) -> Option<&ProcessedKeybinding> {
455 self.selected_index
456 .and_then(|match_index| self.matches.get(match_index))
457 .map(|r#match| r#match.candidate_id)
458 .and_then(|keybind_index| self.keybindings.get(keybind_index))
459 }
460
461 fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
462 if let Some(selected) = self.selected_index {
463 let selected = selected + 1;
464 if selected >= self.matches.len() {
465 self.select_last(&Default::default(), window, cx);
466 } else {
467 self.selected_index = Some(selected);
468 self.scroll_to_item(selected, ScrollStrategy::Center, cx);
469 cx.notify();
470 }
471 } else {
472 self.select_first(&Default::default(), window, cx);
473 }
474 }
475
476 fn select_previous(
477 &mut self,
478 _: &menu::SelectPrevious,
479 window: &mut Window,
480 cx: &mut Context<Self>,
481 ) {
482 if let Some(selected) = self.selected_index {
483 if selected == 0 {
484 return;
485 }
486
487 let selected = selected - 1;
488
489 if selected >= self.matches.len() {
490 self.select_last(&Default::default(), window, cx);
491 } else {
492 self.selected_index = Some(selected);
493 self.scroll_to_item(selected, ScrollStrategy::Center, cx);
494 cx.notify();
495 }
496 } else {
497 self.select_last(&Default::default(), window, cx);
498 }
499 }
500
501 fn select_first(
502 &mut self,
503 _: &menu::SelectFirst,
504 _window: &mut Window,
505 cx: &mut Context<Self>,
506 ) {
507 if self.matches.get(0).is_some() {
508 self.selected_index = Some(0);
509 self.scroll_to_item(0, ScrollStrategy::Center, cx);
510 cx.notify();
511 }
512 }
513
514 fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
515 if self.matches.last().is_some() {
516 let index = self.matches.len() - 1;
517 self.selected_index = Some(index);
518 self.scroll_to_item(index, ScrollStrategy::Center, cx);
519 cx.notify();
520 }
521 }
522
523 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
524 self.open_edit_keybinding_modal(false, window, cx);
525 }
526
527 fn open_edit_keybinding_modal(
528 &mut self,
529 create: bool,
530 window: &mut Window,
531 cx: &mut Context<Self>,
532 ) {
533 let Some(keybind) = self.selected_binding().cloned() else {
534 return;
535 };
536 self.workspace
537 .update(cx, |workspace, cx| {
538 let fs = workspace.app_state().fs.clone();
539 let workspace_weak = cx.weak_entity();
540 workspace.toggle_modal(window, cx, |window, cx| {
541 let modal =
542 KeybindingEditorModal::new(create, keybind, workspace_weak, fs, window, cx);
543 window.focus(&modal.focus_handle(cx));
544 modal
545 });
546 })
547 .log_err();
548 }
549
550 fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
551 self.open_edit_keybinding_modal(false, window, cx);
552 }
553
554 fn create_binding(&mut self, _: &CreateBinding, window: &mut Window, cx: &mut Context<Self>) {
555 self.open_edit_keybinding_modal(true, window, cx);
556 }
557
558 fn copy_context_to_clipboard(
559 &mut self,
560 _: &CopyContext,
561 _window: &mut Window,
562 cx: &mut Context<Self>,
563 ) {
564 let context = self
565 .selected_binding()
566 .and_then(|binding| binding.context.as_ref())
567 .and_then(KeybindContextString::local_str)
568 .map(|context| context.to_string());
569 let Some(context) = context else {
570 return;
571 };
572 cx.write_to_clipboard(gpui::ClipboardItem::new_string(context.clone()));
573 }
574
575 fn copy_action_to_clipboard(
576 &mut self,
577 _: &CopyAction,
578 _window: &mut Window,
579 cx: &mut Context<Self>,
580 ) {
581 let action = self
582 .selected_binding()
583 .map(|binding| binding.action_name.to_string());
584 let Some(action) = action else {
585 return;
586 };
587 cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
588 }
589}
590
591#[derive(Clone)]
592struct ProcessedKeybinding {
593 keystroke_text: SharedString,
594 ui_key_binding: Option<ui::KeyBinding>,
595 action_name: SharedString,
596 action_input: Option<SyntaxHighlightedText>,
597 action_docs: Option<&'static str>,
598 action_schema: Option<schemars::Schema>,
599 context: Option<KeybindContextString>,
600 source: Option<(KeybindSource, SharedString)>,
601}
602
603impl ProcessedKeybinding {
604 fn get_action_mapping(&self) -> (SharedString, Option<SharedString>) {
605 (
606 self.keystroke_text.clone(),
607 self.context
608 .as_ref()
609 .and_then(|context| context.local())
610 .cloned(),
611 )
612 }
613}
614
615#[derive(Clone, Debug, IntoElement, PartialEq, Eq, Hash)]
616enum KeybindContextString {
617 Global,
618 Local(SharedString, Arc<Language>),
619}
620
621impl KeybindContextString {
622 const GLOBAL: SharedString = SharedString::new_static("<global>");
623
624 pub fn local(&self) -> Option<&SharedString> {
625 match self {
626 KeybindContextString::Global => None,
627 KeybindContextString::Local(name, _) => Some(name),
628 }
629 }
630
631 pub fn local_str(&self) -> Option<&str> {
632 match self {
633 KeybindContextString::Global => None,
634 KeybindContextString::Local(name, _) => Some(name),
635 }
636 }
637}
638
639impl RenderOnce for KeybindContextString {
640 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
641 match self {
642 KeybindContextString::Global => {
643 muted_styled_text(KeybindContextString::GLOBAL.clone(), cx).into_any_element()
644 }
645 KeybindContextString::Local(name, language) => {
646 SyntaxHighlightedText::new(name, language).into_any_element()
647 }
648 }
649 }
650}
651
652fn muted_styled_text(text: SharedString, cx: &App) -> StyledText {
653 let len = text.len();
654 StyledText::new(text).with_highlights([(
655 0..len,
656 gpui::HighlightStyle::color(cx.theme().colors().text_muted),
657 )])
658}
659
660impl Item for KeymapEditor {
661 type Event = ();
662
663 fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
664 "Keymap Editor".into()
665 }
666}
667
668impl Render for KeymapEditor {
669 fn render(&mut self, window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
670 let row_count = self.matches.len();
671 let theme = cx.theme();
672
673 v_flex()
674 .id("keymap-editor")
675 .track_focus(&self.focus_handle)
676 .key_context(self.dispatch_context(window, cx))
677 .on_action(cx.listener(Self::select_next))
678 .on_action(cx.listener(Self::select_previous))
679 .on_action(cx.listener(Self::select_first))
680 .on_action(cx.listener(Self::select_last))
681 .on_action(cx.listener(Self::focus_search))
682 .on_action(cx.listener(Self::confirm))
683 .on_action(cx.listener(Self::edit_binding))
684 .on_action(cx.listener(Self::create_binding))
685 .on_action(cx.listener(Self::copy_action_to_clipboard))
686 .on_action(cx.listener(Self::copy_context_to_clipboard))
687 .size_full()
688 .p_2()
689 .gap_1()
690 .bg(theme.colors().editor_background)
691 .child(
692 h_flex()
693 .key_context({
694 let mut context = KeyContext::new_with_defaults();
695 context.add("BufferSearchBar");
696 context
697 })
698 .h_8()
699 .pl_2()
700 .pr_1()
701 .py_1()
702 .border_1()
703 .border_color(theme.colors().border)
704 .rounded_lg()
705 .child(self.filter_editor.clone())
706 .when(!self.conflicts.is_empty(), |this| {
707 this.child(
708 IconButton::new("KeymapEditorConflictIcon", IconName::Warning)
709 .tooltip(Tooltip::text(match self.filter_state {
710 FilterState::All => "Show conflicts",
711 FilterState::Conflicts => "Hide conflicts",
712 }))
713 .selected_icon_color(Color::Error)
714 .toggle_state(matches!(self.filter_state, FilterState::Conflicts))
715 .on_click(cx.listener(|this, _, _, cx| {
716 this.filter_state = this.filter_state.invert();
717 this.update_matches(cx);
718 })),
719 )
720 }),
721 )
722 .child(
723 Table::new()
724 .interactable(&self.table_interaction_state)
725 .striped()
726 .column_widths([rems(16.), rems(16.), rems(16.), rems(32.), rems(8.)])
727 .header(["Action", "Arguments", "Keystrokes", "Context", "Source"])
728 .uniform_list(
729 "keymap-editor-table",
730 row_count,
731 cx.processor(move |this, range: Range<usize>, _window, cx| {
732 range
733 .filter_map(|index| {
734 let candidate_id = this.matches.get(index)?.candidate_id;
735 let binding = &this.keybindings[candidate_id];
736
737 let action = div()
738 .child(binding.action_name.clone())
739 .id(("keymap action", index))
740 .tooltip({
741 let action_name = binding.action_name.clone();
742 let action_docs = binding.action_docs;
743 move |_, cx| {
744 let action_tooltip = Tooltip::new(
745 command_palette::humanize_action_name(
746 &action_name,
747 ),
748 );
749 let action_tooltip = match action_docs {
750 Some(docs) => action_tooltip.meta(docs),
751 None => action_tooltip,
752 };
753 cx.new(|_| action_tooltip).into()
754 }
755 })
756 .into_any_element();
757 let keystrokes = binding.ui_key_binding.clone().map_or(
758 binding.keystroke_text.clone().into_any_element(),
759 IntoElement::into_any_element,
760 );
761 let action_input = match binding.action_input.clone() {
762 Some(input) => input.into_any_element(),
763 None => {
764 if binding.action_schema.is_some() {
765 muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
766 .into_any_element()
767 } else {
768 gpui::Empty.into_any_element()
769 }
770 }
771 };
772 let context = binding
773 .context
774 .clone()
775 .map_or(gpui::Empty.into_any_element(), |context| {
776 context.into_any_element()
777 });
778 let source = binding
779 .source
780 .clone()
781 .map(|(_source, name)| name)
782 .unwrap_or_default()
783 .into_any_element();
784 Some([action, action_input, keystrokes, context, source])
785 })
786 .collect()
787 }),
788 )
789 .map_row(
790 cx.processor(|this, (row_index, row): (usize, Div), _window, cx| {
791 let is_conflict = this
792 .matches
793 .get(row_index)
794 .map(|candidate| candidate.candidate_id)
795 .is_some_and(|id| this.conflicts.contains(&id));
796 let is_selected = this.selected_index == Some(row_index);
797
798 let row = row
799 .id(("keymap-table-row", row_index))
800 .on_click(cx.listener(
801 move |this, event: &ClickEvent, window, cx| {
802 this.selected_index = Some(row_index);
803 if event.up.click_count == 2 {
804 this.open_edit_keybinding_modal(false, window, cx);
805 }
806 },
807 ))
808 .border_2()
809 .when(is_conflict, |row| {
810 row.bg(cx.theme().status().error_background)
811 })
812 .when(is_selected, |row| {
813 row.border_color(cx.theme().colors().panel_focused_border)
814 });
815
816 right_click_menu(("keymap-table-row-menu", row_index))
817 .trigger({
818 let this = cx.weak_entity();
819 move |is_menu_open: bool, _window, cx| {
820 if is_menu_open {
821 this.update(cx, |this, cx| {
822 if this.selected_index != Some(row_index) {
823 this.selected_index = Some(row_index);
824 cx.notify();
825 }
826 })
827 .ok();
828 }
829 row
830 }
831 })
832 .menu({
833 let this = cx.weak_entity();
834 move |window, cx| build_keybind_context_menu(&this, window, cx)
835 })
836 .into_any_element()
837 }),
838 ),
839 )
840 }
841}
842
843#[derive(Debug, Clone, IntoElement)]
844struct SyntaxHighlightedText {
845 text: SharedString,
846 language: Arc<Language>,
847}
848
849impl SyntaxHighlightedText {
850 pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
851 Self {
852 text: text.into(),
853 language,
854 }
855 }
856}
857
858impl RenderOnce for SyntaxHighlightedText {
859 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
860 let text_style = window.text_style();
861 let syntax_theme = cx.theme().syntax();
862
863 let text = self.text.clone();
864
865 let highlights = self
866 .language
867 .highlight_text(&text.as_ref().into(), 0..text.len());
868 let mut runs = Vec::with_capacity(highlights.len());
869 let mut offset = 0;
870
871 for (highlight_range, highlight_id) in highlights {
872 // Add un-highlighted text before the current highlight
873 if highlight_range.start > offset {
874 runs.push(text_style.to_run(highlight_range.start - offset));
875 }
876
877 let mut run_style = text_style.clone();
878 if let Some(highlight_style) = highlight_id.style(syntax_theme) {
879 run_style = run_style.highlight(highlight_style);
880 }
881 // add the highlighted range
882 runs.push(run_style.to_run(highlight_range.len()));
883 offset = highlight_range.end;
884 }
885
886 // Add any remaining un-highlighted text
887 if offset < text.len() {
888 runs.push(text_style.to_run(text.len() - offset));
889 }
890
891 return StyledText::new(text).with_runs(runs);
892 }
893}
894
895struct KeybindingEditorModal {
896 creating: bool,
897 editing_keybind: ProcessedKeybinding,
898 keybind_editor: Entity<KeystrokeInput>,
899 context_editor: Entity<Editor>,
900 input_editor: Option<Entity<Editor>>,
901 fs: Arc<dyn Fs>,
902 error: Option<String>,
903}
904
905impl ModalView for KeybindingEditorModal {}
906
907impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
908
909impl Focusable for KeybindingEditorModal {
910 fn focus_handle(&self, cx: &App) -> FocusHandle {
911 self.keybind_editor.focus_handle(cx)
912 }
913}
914
915impl KeybindingEditorModal {
916 pub fn new(
917 create: bool,
918 editing_keybind: ProcessedKeybinding,
919 workspace: WeakEntity<Workspace>,
920 fs: Arc<dyn Fs>,
921 window: &mut Window,
922 cx: &mut App,
923 ) -> Self {
924 let keybind_editor = cx.new(|cx| KeystrokeInput::new(window, cx));
925
926 let context_editor = cx.new(|cx| {
927 let mut editor = Editor::single_line(window, cx);
928
929 if let Some(context) = editing_keybind
930 .context
931 .as_ref()
932 .and_then(KeybindContextString::local)
933 {
934 editor.set_text(context.clone(), window, cx);
935 } else {
936 editor.set_placeholder_text("Keybinding context", cx);
937 }
938
939 cx.spawn(async |editor, cx| {
940 let contexts = cx
941 .background_spawn(async { collect_contexts_from_assets() })
942 .await;
943
944 editor
945 .update(cx, |editor, _cx| {
946 editor.set_completion_provider(Some(std::rc::Rc::new(
947 KeyContextCompletionProvider { contexts },
948 )));
949 })
950 .context("Failed to load completions for keybinding context")
951 })
952 .detach_and_log_err(cx);
953
954 editor
955 });
956
957 let input_editor = editing_keybind.action_schema.clone().map(|_schema| {
958 cx.new(|cx| {
959 let mut editor = Editor::auto_height_unbounded(1, window, cx);
960 if let Some(input) = editing_keybind.action_input.clone() {
961 editor.set_text(input.text, window, cx);
962 } else {
963 // TODO: default value from schema?
964 editor.set_placeholder_text("Action input", cx);
965 }
966 cx.spawn(async |editor, cx| {
967 let json_language = load_json_language(workspace, cx).await;
968 editor
969 .update(cx, |editor, cx| {
970 if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
971 buffer.update(cx, |buffer, cx| {
972 buffer.set_language(Some(json_language), cx)
973 });
974 }
975 })
976 .context("Failed to load JSON language for editing keybinding action input")
977 })
978 .detach_and_log_err(cx);
979 editor
980 })
981 });
982
983 Self {
984 creating: create,
985 editing_keybind,
986 fs,
987 keybind_editor,
988 context_editor,
989 input_editor,
990 error: None,
991 }
992 }
993
994 fn save(&mut self, cx: &mut Context<Self>) {
995 let existing_keybind = self.editing_keybind.clone();
996 let fs = self.fs.clone();
997 let new_keystrokes = self
998 .keybind_editor
999 .read_with(cx, |editor, _| editor.keystrokes().to_vec());
1000 if new_keystrokes.is_empty() {
1001 self.error = Some("Keystrokes cannot be empty".to_string());
1002 cx.notify();
1003 return;
1004 }
1005 let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
1006 let new_context = self
1007 .context_editor
1008 .read_with(cx, |editor, cx| editor.text(cx));
1009 let new_context = new_context.is_empty().not().then_some(new_context);
1010 let new_context_err = new_context.as_deref().and_then(|context| {
1011 gpui::KeyBindingContextPredicate::parse(context)
1012 .context("Failed to parse key context")
1013 .err()
1014 });
1015 if let Some(err) = new_context_err {
1016 // TODO: store and display as separate error
1017 // TODO: also, should be validating on keystroke
1018 self.error = Some(err.to_string());
1019 cx.notify();
1020 return;
1021 }
1022
1023 let create = self.creating;
1024
1025 cx.spawn(async move |this, cx| {
1026 if let Err(err) = save_keybinding_update(
1027 create,
1028 existing_keybind,
1029 &new_keystrokes,
1030 new_context.as_deref(),
1031 &fs,
1032 tab_size,
1033 )
1034 .await
1035 {
1036 this.update(cx, |this, cx| {
1037 this.error = Some(err.to_string());
1038 cx.notify();
1039 })
1040 .log_err();
1041 } else {
1042 this.update(cx, |_this, cx| {
1043 cx.emit(DismissEvent);
1044 })
1045 .ok();
1046 }
1047 })
1048 .detach();
1049 }
1050}
1051
1052impl Render for KeybindingEditorModal {
1053 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1054 let theme = cx.theme().colors();
1055 let input_base = || {
1056 div()
1057 .w_full()
1058 .py_2()
1059 .px_3()
1060 .min_h_8()
1061 .rounded_md()
1062 .bg(theme.editor_background)
1063 .border_1()
1064 .border_color(theme.border_variant)
1065 };
1066
1067 return v_flex()
1068 .w(rems(34.))
1069 .elevation_3(cx)
1070 .child(
1071 v_flex()
1072 .p_3()
1073 .child(Label::new("Edit Keystroke"))
1074 .child(
1075 Label::new("Input the desired keystroke for the selected action.")
1076 .color(Color::Muted)
1077 .mb_2(),
1078 )
1079 .child(self.keybind_editor.clone()),
1080 )
1081 .when_some(self.input_editor.clone(), |this, editor| {
1082 this.child(
1083 v_flex()
1084 .p_3()
1085 .pt_0()
1086 .child(Label::new("Edit Input"))
1087 .child(
1088 Label::new("Input the desired input to the binding.")
1089 .color(Color::Muted)
1090 .mb_2(),
1091 )
1092 .child(input_base().child(editor)),
1093 )
1094 })
1095 .child(
1096 v_flex()
1097 .p_3()
1098 .pt_0()
1099 .child(Label::new("Edit Context"))
1100 .child(
1101 Label::new("Input the desired context for the binding.")
1102 .color(Color::Muted)
1103 .mb_2(),
1104 )
1105 .child(input_base().child(self.context_editor.clone())),
1106 )
1107 .child(
1108 h_flex()
1109 .p_2()
1110 .w_full()
1111 .gap_1()
1112 .justify_end()
1113 .border_t_1()
1114 .border_color(theme.border_variant)
1115 .child(
1116 Button::new("cancel", "Cancel")
1117 .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
1118 )
1119 .child(
1120 Button::new("save-btn", "Save").on_click(
1121 cx.listener(|this, _event, _window, cx| Self::save(this, cx)),
1122 ),
1123 ),
1124 )
1125 .when_some(self.error.clone(), |this, error| {
1126 this.child(
1127 div()
1128 .bg(theme.background)
1129 .border_color(theme.border)
1130 .border_2()
1131 .rounded_md()
1132 .child(error),
1133 )
1134 });
1135 }
1136}
1137
1138struct KeyContextCompletionProvider {
1139 contexts: Vec<SharedString>,
1140}
1141
1142impl CompletionProvider for KeyContextCompletionProvider {
1143 fn completions(
1144 &self,
1145 _excerpt_id: editor::ExcerptId,
1146 buffer: &Entity<language::Buffer>,
1147 buffer_position: language::Anchor,
1148 _trigger: editor::CompletionContext,
1149 _window: &mut Window,
1150 cx: &mut Context<Editor>,
1151 ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
1152 let buffer = buffer.read(cx);
1153 let mut count_back = 0;
1154 for char in buffer.reversed_chars_at(buffer_position) {
1155 if char.is_ascii_alphanumeric() || char == '_' {
1156 count_back += 1;
1157 } else {
1158 break;
1159 }
1160 }
1161 let start_anchor = buffer.anchor_before(
1162 buffer_position
1163 .to_offset(&buffer)
1164 .saturating_sub(count_back),
1165 );
1166 let replace_range = start_anchor..buffer_position;
1167 gpui::Task::ready(Ok(vec![project::CompletionResponse {
1168 completions: self
1169 .contexts
1170 .iter()
1171 .map(|context| project::Completion {
1172 replace_range: replace_range.clone(),
1173 label: language::CodeLabel::plain(context.to_string(), None),
1174 new_text: context.to_string(),
1175 documentation: None,
1176 source: project::CompletionSource::Custom,
1177 icon_path: None,
1178 insert_text_mode: None,
1179 confirm: None,
1180 })
1181 .collect(),
1182 is_incomplete: false,
1183 }]))
1184 }
1185
1186 fn is_completion_trigger(
1187 &self,
1188 _buffer: &Entity<language::Buffer>,
1189 _position: language::Anchor,
1190 text: &str,
1191 _trigger_in_words: bool,
1192 _menu_is_open: bool,
1193 _cx: &mut Context<Editor>,
1194 ) -> bool {
1195 text.chars().last().map_or(false, |last_char| {
1196 last_char.is_ascii_alphanumeric() || last_char == '_'
1197 })
1198 }
1199}
1200
1201async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
1202 let json_language_task = workspace
1203 .read_with(cx, |workspace, cx| {
1204 workspace
1205 .project()
1206 .read(cx)
1207 .languages()
1208 .language_for_name("JSON")
1209 })
1210 .context("Failed to load JSON language")
1211 .log_err();
1212 let json_language = match json_language_task {
1213 Some(task) => task.await.context("Failed to load JSON language").log_err(),
1214 None => None,
1215 };
1216 return json_language.unwrap_or_else(|| {
1217 Arc::new(Language::new(
1218 LanguageConfig {
1219 name: "JSON".into(),
1220 ..Default::default()
1221 },
1222 Some(tree_sitter_json::LANGUAGE.into()),
1223 ))
1224 });
1225}
1226
1227async fn load_rust_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
1228 let rust_language_task = workspace
1229 .read_with(cx, |workspace, cx| {
1230 workspace
1231 .project()
1232 .read(cx)
1233 .languages()
1234 .language_for_name("Rust")
1235 })
1236 .context("Failed to load Rust language")
1237 .log_err();
1238 let rust_language = match rust_language_task {
1239 Some(task) => task.await.context("Failed to load Rust language").log_err(),
1240 None => None,
1241 };
1242 return rust_language.unwrap_or_else(|| {
1243 Arc::new(Language::new(
1244 LanguageConfig {
1245 name: "Rust".into(),
1246 ..Default::default()
1247 },
1248 Some(tree_sitter_rust::LANGUAGE.into()),
1249 ))
1250 });
1251}
1252
1253async fn save_keybinding_update(
1254 create: bool,
1255 existing: ProcessedKeybinding,
1256 new_keystrokes: &[Keystroke],
1257 new_context: Option<&str>,
1258 fs: &Arc<dyn Fs>,
1259 tab_size: usize,
1260) -> anyhow::Result<()> {
1261 let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
1262 .await
1263 .context("Failed to load keymap file")?;
1264
1265 let existing_keystrokes = existing
1266 .ui_key_binding
1267 .as_ref()
1268 .map(|keybinding| keybinding.keystrokes.as_slice())
1269 .unwrap_or_default();
1270
1271 let existing_context = existing
1272 .context
1273 .as_ref()
1274 .and_then(KeybindContextString::local_str);
1275
1276 let input = existing
1277 .action_input
1278 .as_ref()
1279 .map(|input| input.text.as_ref());
1280
1281 let operation = if !create {
1282 settings::KeybindUpdateOperation::Replace {
1283 target: settings::KeybindUpdateTarget {
1284 context: existing_context,
1285 keystrokes: existing_keystrokes,
1286 action_name: &existing.action_name,
1287 use_key_equivalents: false,
1288 input,
1289 },
1290 target_keybind_source: existing
1291 .source
1292 .map(|(source, _name)| source)
1293 .unwrap_or(KeybindSource::User),
1294 source: settings::KeybindUpdateTarget {
1295 context: new_context,
1296 keystrokes: new_keystrokes,
1297 action_name: &existing.action_name,
1298 use_key_equivalents: false,
1299 input,
1300 },
1301 }
1302 } else {
1303 settings::KeybindUpdateOperation::Add(settings::KeybindUpdateTarget {
1304 context: new_context,
1305 keystrokes: new_keystrokes,
1306 action_name: &existing.action_name,
1307 use_key_equivalents: false,
1308 input,
1309 })
1310 };
1311 let updated_keymap_contents =
1312 settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
1313 .context("Failed to update keybinding")?;
1314 fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
1315 .await
1316 .context("Failed to write keymap file")?;
1317 Ok(())
1318}
1319
1320struct KeystrokeInput {
1321 keystrokes: Vec<Keystroke>,
1322 focus_handle: FocusHandle,
1323 intercept_subscription: Option<Subscription>,
1324 _focus_subscriptions: [Subscription; 2],
1325}
1326
1327impl KeystrokeInput {
1328 fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
1329 let focus_handle = cx.focus_handle();
1330 let _focus_subscriptions = [
1331 cx.on_focus_in(&focus_handle, window, Self::on_focus_in),
1332 cx.on_focus_out(&focus_handle, window, Self::on_focus_out),
1333 ];
1334 Self {
1335 keystrokes: Vec::new(),
1336 focus_handle,
1337 intercept_subscription: None,
1338 _focus_subscriptions,
1339 }
1340 }
1341
1342 fn on_modifiers_changed(
1343 &mut self,
1344 event: &ModifiersChangedEvent,
1345 _window: &mut Window,
1346 cx: &mut Context<Self>,
1347 ) {
1348 if let Some(last) = self.keystrokes.last_mut()
1349 && last.key.is_empty()
1350 {
1351 if !event.modifiers.modified() {
1352 self.keystrokes.pop();
1353 } else {
1354 last.modifiers = event.modifiers;
1355 }
1356 } else {
1357 self.keystrokes.push(Keystroke {
1358 modifiers: event.modifiers,
1359 key: "".to_string(),
1360 key_char: None,
1361 });
1362 }
1363 cx.stop_propagation();
1364 cx.notify();
1365 }
1366
1367 fn handle_keystroke(&mut self, keystroke: &Keystroke, cx: &mut Context<Self>) {
1368 if let Some(last) = self.keystrokes.last_mut()
1369 && last.key.is_empty()
1370 {
1371 *last = keystroke.clone();
1372 } else if Some(keystroke) != self.keystrokes.last() {
1373 self.keystrokes.push(keystroke.clone());
1374 }
1375 cx.stop_propagation();
1376 cx.notify();
1377 }
1378
1379 fn on_key_up(
1380 &mut self,
1381 event: &gpui::KeyUpEvent,
1382 _window: &mut Window,
1383 cx: &mut Context<Self>,
1384 ) {
1385 if let Some(last) = self.keystrokes.last_mut()
1386 && !last.key.is_empty()
1387 && last.modifiers == event.keystroke.modifiers
1388 {
1389 self.keystrokes.push(Keystroke {
1390 modifiers: event.keystroke.modifiers,
1391 key: "".to_string(),
1392 key_char: None,
1393 });
1394 }
1395 cx.stop_propagation();
1396 cx.notify();
1397 }
1398
1399 fn on_focus_in(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
1400 if self.intercept_subscription.is_none() {
1401 let listener = cx.listener(|this, event: &gpui::KeystrokeEvent, _window, cx| {
1402 this.handle_keystroke(&event.keystroke, cx);
1403 });
1404 self.intercept_subscription = Some(cx.intercept_keystrokes(listener))
1405 }
1406 }
1407
1408 fn on_focus_out(
1409 &mut self,
1410 _event: gpui::FocusOutEvent,
1411 _window: &mut Window,
1412 _cx: &mut Context<Self>,
1413 ) {
1414 self.intercept_subscription.take();
1415 }
1416
1417 fn keystrokes(&self) -> &[Keystroke] {
1418 if self
1419 .keystrokes
1420 .last()
1421 .map_or(false, |last| last.key.is_empty())
1422 {
1423 return &self.keystrokes[..self.keystrokes.len() - 1];
1424 }
1425 return &self.keystrokes;
1426 }
1427}
1428
1429impl Focusable for KeystrokeInput {
1430 fn focus_handle(&self, _cx: &App) -> FocusHandle {
1431 self.focus_handle.clone()
1432 }
1433}
1434
1435impl Render for KeystrokeInput {
1436 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1437 let colors = cx.theme().colors();
1438 let is_focused = self.focus_handle.is_focused(window);
1439
1440 return h_flex()
1441 .id("keybinding_input")
1442 .track_focus(&self.focus_handle)
1443 .on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
1444 .on_key_up(cx.listener(Self::on_key_up))
1445 .focus(|mut style| {
1446 style.border_color = Some(colors.border_focused);
1447 style
1448 })
1449 .py_2()
1450 .px_3()
1451 .gap_2()
1452 .min_h_8()
1453 .w_full()
1454 .flex_1()
1455 .justify_between()
1456 .rounded_md()
1457 .overflow_hidden()
1458 .bg(colors.editor_background)
1459 .border_1()
1460 .border_color(colors.border_variant)
1461 .child(
1462 h_flex()
1463 .w_full()
1464 .min_w_0()
1465 .justify_center()
1466 .flex_wrap()
1467 .gap(ui::DynamicSpacing::Base04.rems(cx))
1468 .children(self.keystrokes.iter().map(|keystroke| {
1469 h_flex().children(ui::render_keystroke(
1470 keystroke,
1471 None,
1472 Some(rems(0.875).into()),
1473 ui::PlatformStyle::platform(),
1474 false,
1475 ))
1476 })),
1477 )
1478 .child(
1479 h_flex()
1480 .gap_0p5()
1481 .flex_none()
1482 .child(
1483 IconButton::new("backspace-btn", IconName::Delete)
1484 .tooltip(Tooltip::text("Delete Keystroke"))
1485 .when(!is_focused, |this| this.icon_color(Color::Muted))
1486 .on_click(cx.listener(|this, _event, _window, cx| {
1487 this.keystrokes.pop();
1488 cx.notify();
1489 })),
1490 )
1491 .child(
1492 IconButton::new("clear-btn", IconName::Eraser)
1493 .tooltip(Tooltip::text("Clear Keystrokes"))
1494 .when(!is_focused, |this| this.icon_color(Color::Muted))
1495 .on_click(cx.listener(|this, _event, _window, cx| {
1496 this.keystrokes.clear();
1497 cx.notify();
1498 })),
1499 ),
1500 );
1501 }
1502}
1503
1504fn build_keybind_context_menu(
1505 this: &WeakEntity<KeymapEditor>,
1506 window: &mut Window,
1507 cx: &mut App,
1508) -> Entity<ContextMenu> {
1509 ContextMenu::build(window, cx, |menu, _window, cx| {
1510 let Some(this) = this.upgrade() else {
1511 return menu;
1512 };
1513 let selected_binding = this.read_with(cx, |this, _cx| this.selected_binding().cloned());
1514 let Some(selected_binding) = selected_binding else {
1515 return menu;
1516 };
1517
1518 let selected_binding_has_no_context = selected_binding
1519 .context
1520 .as_ref()
1521 .and_then(KeybindContextString::local)
1522 .is_none();
1523
1524 let selected_binding_is_unbound = selected_binding.ui_key_binding.is_none();
1525
1526 menu.action_disabled_when(selected_binding_is_unbound, "Edit", Box::new(EditBinding))
1527 .action("Create", Box::new(CreateBinding))
1528 .action("Copy action", Box::new(CopyAction))
1529 .action_disabled_when(
1530 selected_binding_has_no_context,
1531 "Copy Context",
1532 Box::new(CopyContext),
1533 )
1534 })
1535}
1536
1537fn collect_contexts_from_assets() -> Vec<SharedString> {
1538 let mut keymap_assets = vec![
1539 util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
1540 util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
1541 ];
1542 keymap_assets.extend(
1543 BaseKeymap::OPTIONS
1544 .iter()
1545 .filter_map(|(_, base_keymap)| base_keymap.asset_path())
1546 .map(util::asset_str::<SettingsAssets>),
1547 );
1548
1549 let mut contexts = HashSet::default();
1550
1551 for keymap_asset in keymap_assets {
1552 let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
1553 continue;
1554 };
1555
1556 for section in keymap.sections() {
1557 let context_expr = §ion.context;
1558 let mut queue = Vec::new();
1559 let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
1560 continue;
1561 };
1562
1563 queue.push(root_context);
1564 while let Some(context) = queue.pop() {
1565 match context {
1566 gpui::KeyBindingContextPredicate::Identifier(ident) => {
1567 contexts.insert(ident);
1568 }
1569 gpui::KeyBindingContextPredicate::Equal(ident_a, ident_b) => {
1570 contexts.insert(ident_a);
1571 contexts.insert(ident_b);
1572 }
1573 gpui::KeyBindingContextPredicate::NotEqual(ident_a, ident_b) => {
1574 contexts.insert(ident_a);
1575 contexts.insert(ident_b);
1576 }
1577 gpui::KeyBindingContextPredicate::Child(ctx_a, ctx_b) => {
1578 queue.push(*ctx_a);
1579 queue.push(*ctx_b);
1580 }
1581 gpui::KeyBindingContextPredicate::Not(ctx) => {
1582 queue.push(*ctx);
1583 }
1584 gpui::KeyBindingContextPredicate::And(ctx_a, ctx_b) => {
1585 queue.push(*ctx_a);
1586 queue.push(*ctx_b);
1587 }
1588 gpui::KeyBindingContextPredicate::Or(ctx_a, ctx_b) => {
1589 queue.push(*ctx_a);
1590 queue.push(*ctx_b);
1591 }
1592 }
1593 }
1594 }
1595 }
1596
1597 let mut contexts = contexts.into_iter().collect::<Vec<_>>();
1598 contexts.sort();
1599
1600 return contexts;
1601}
1602
1603impl SerializableItem for KeymapEditor {
1604 fn serialized_item_kind() -> &'static str {
1605 "KeymapEditor"
1606 }
1607
1608 fn cleanup(
1609 workspace_id: workspace::WorkspaceId,
1610 alive_items: Vec<workspace::ItemId>,
1611 _window: &mut Window,
1612 cx: &mut App,
1613 ) -> gpui::Task<gpui::Result<()>> {
1614 workspace::delete_unloaded_items(
1615 alive_items,
1616 workspace_id,
1617 "keybinding_editors",
1618 &KEYBINDING_EDITORS,
1619 cx,
1620 )
1621 }
1622
1623 fn deserialize(
1624 _project: Entity<project::Project>,
1625 workspace: WeakEntity<Workspace>,
1626 workspace_id: workspace::WorkspaceId,
1627 item_id: workspace::ItemId,
1628 window: &mut Window,
1629 cx: &mut App,
1630 ) -> gpui::Task<gpui::Result<Entity<Self>>> {
1631 window.spawn(cx, async move |cx| {
1632 if KEYBINDING_EDITORS
1633 .get_keybinding_editor(item_id, workspace_id)?
1634 .is_some()
1635 {
1636 cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
1637 } else {
1638 Err(anyhow!("No keybinding editor to deserialize"))
1639 }
1640 })
1641 }
1642
1643 fn serialize(
1644 &mut self,
1645 workspace: &mut Workspace,
1646 item_id: workspace::ItemId,
1647 _closing: bool,
1648 _window: &mut Window,
1649 cx: &mut ui::Context<Self>,
1650 ) -> Option<gpui::Task<gpui::Result<()>>> {
1651 let workspace_id = workspace.database_id()?;
1652 Some(cx.background_spawn(async move {
1653 KEYBINDING_EDITORS
1654 .save_keybinding_editor(item_id, workspace_id)
1655 .await
1656 }))
1657 }
1658
1659 fn should_serialize(&self, _event: &Self::Event) -> bool {
1660 false
1661 }
1662}
1663
1664mod persistence {
1665 use db::{define_connection, query, sqlez_macros::sql};
1666 use workspace::WorkspaceDb;
1667
1668 define_connection! {
1669 pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
1670 &[sql!(
1671 CREATE TABLE keybinding_editors (
1672 workspace_id INTEGER,
1673 item_id INTEGER UNIQUE,
1674
1675 PRIMARY KEY(workspace_id, item_id),
1676 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
1677 ON DELETE CASCADE
1678 ) STRICT;
1679 )];
1680 }
1681
1682 impl KeybindingEditorDb {
1683 query! {
1684 pub async fn save_keybinding_editor(
1685 item_id: workspace::ItemId,
1686 workspace_id: workspace::WorkspaceId
1687 ) -> Result<()> {
1688 INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
1689 VALUES (?, ?)
1690 }
1691 }
1692
1693 query! {
1694 pub fn get_keybinding_editor(
1695 item_id: workspace::ItemId,
1696 workspace_id: workspace::WorkspaceId
1697 ) -> Result<Option<workspace::ItemId>> {
1698 SELECT item_id
1699 FROM keybinding_editors
1700 WHERE item_id = ? AND workspace_id = ?
1701 }
1702 }
1703 }
1704}