1use std::{
2 ops::{Not as _, Range},
3 sync::Arc,
4 time::Duration,
5};
6
7use anyhow::{Context as _, anyhow};
8use collections::{HashMap, HashSet};
9use editor::{CompletionProvider, Editor, EditorEvent};
10use fs::Fs;
11use fuzzy::{StringMatch, StringMatchCandidate};
12use gpui::{
13 Action, Animation, AnimationExt, AppContext as _, AsyncApp, Axis, ClickEvent, Context,
14 DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, IsZero,
15 KeyContext, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Point, ScrollStrategy,
16 ScrollWheelEvent, StyledText, Subscription, Task, WeakEntity, actions, anchored, deferred, div,
17};
18use language::{Language, LanguageConfig, ToOffset as _};
19use notifications::status_toast::{StatusToast, ToastIcon};
20use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets};
21
22use util::ResultExt;
23
24use ui::{
25 ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, IconButtonShape, Indicator,
26 Modal, ModalFooter, ModalHeader, ParentElement as _, Render, Section, SharedString,
27 Styled as _, Tooltip, Window, prelude::*,
28};
29use ui_input::SingleLineInput;
30use workspace::{
31 Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _,
32 register_serializable_item,
33};
34
35use crate::{
36 keybindings::persistence::KEYBINDING_EDITORS,
37 ui_components::table::{Table, TableInteractionState},
38};
39
40const NO_ACTION_ARGUMENTS_TEXT: SharedString = SharedString::new_static("<no arguments>");
41
42actions!(
43 zed,
44 [
45 /// Opens the keymap editor.
46 OpenKeymapEditor
47 ]
48);
49
50actions!(
51 keymap_editor,
52 [
53 /// Edits the selected key binding.
54 EditBinding,
55 /// Creates a new key binding for the selected action.
56 CreateBinding,
57 /// Deletes the selected key binding.
58 DeleteBinding,
59 /// Copies the action name to clipboard.
60 CopyAction,
61 /// Copies the context predicate to clipboard.
62 CopyContext,
63 /// Toggles Conflict Filtering
64 ToggleConflictFilter,
65 /// Toggle Keystroke search
66 ToggleKeystrokeSearch,
67 /// Toggles exact matching for keystroke search
68 ToggleExactKeystrokeMatching,
69 ]
70);
71
72actions!(
73 keystroke_input,
74 [
75 /// Starts recording keystrokes
76 StartRecording,
77 /// Stops recording keystrokes
78 StopRecording,
79 /// Clears the recorded keystrokes
80 ClearKeystrokes,
81 ]
82);
83
84pub fn init(cx: &mut App) {
85 let keymap_event_channel = KeymapEventChannel::new();
86 cx.set_global(keymap_event_channel);
87
88 cx.on_action(|_: &OpenKeymapEditor, cx| {
89 workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
90 workspace
91 .with_local_workspace(window, cx, |workspace, window, cx| {
92 let existing = workspace
93 .active_pane()
94 .read(cx)
95 .items()
96 .find_map(|item| item.downcast::<KeymapEditor>());
97
98 if let Some(existing) = existing {
99 workspace.activate_item(&existing, true, true, window, cx);
100 } else {
101 let keymap_editor =
102 cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
103 workspace.add_item_to_active_pane(
104 Box::new(keymap_editor),
105 None,
106 true,
107 window,
108 cx,
109 );
110 }
111 })
112 .detach();
113 })
114 });
115
116 register_serializable_item::<KeymapEditor>(cx);
117}
118
119pub struct KeymapEventChannel {}
120
121impl Global for KeymapEventChannel {}
122
123impl KeymapEventChannel {
124 fn new() -> Self {
125 Self {}
126 }
127
128 pub fn trigger_keymap_changed(cx: &mut App) {
129 let Some(_event_channel) = cx.try_global::<Self>() else {
130 // don't panic if no global defined. This usually happens in tests
131 return;
132 };
133 cx.update_global(|_event_channel: &mut Self, _| {
134 /* triggers observers in KeymapEditors */
135 });
136 }
137}
138
139#[derive(Default, PartialEq)]
140enum SearchMode {
141 #[default]
142 Normal,
143 KeyStroke {
144 exact_match: bool,
145 },
146}
147
148impl SearchMode {
149 fn invert(&self) -> Self {
150 match self {
151 SearchMode::Normal => SearchMode::KeyStroke { exact_match: false },
152 SearchMode::KeyStroke { .. } => SearchMode::Normal,
153 }
154 }
155
156 fn exact_match(&self) -> bool {
157 match self {
158 SearchMode::Normal => false,
159 SearchMode::KeyStroke { exact_match } => *exact_match,
160 }
161 }
162}
163
164#[derive(Default, PartialEq, Copy, Clone)]
165enum FilterState {
166 #[default]
167 All,
168 Conflicts,
169}
170
171impl FilterState {
172 fn invert(&self) -> Self {
173 match self {
174 FilterState::All => FilterState::Conflicts,
175 FilterState::Conflicts => FilterState::All,
176 }
177 }
178}
179
180#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
181struct ActionMapping {
182 keystrokes: Vec<Keystroke>,
183 context: Option<SharedString>,
184}
185
186#[derive(Debug)]
187struct KeybindConflict {
188 first_conflict_index: usize,
189 remaining_conflict_amount: usize,
190}
191
192impl KeybindConflict {
193 fn from_iter<'a>(mut indices: impl Iterator<Item = &'a usize>) -> Option<Self> {
194 indices.next().map(|index| Self {
195 first_conflict_index: *index,
196 remaining_conflict_amount: indices.count(),
197 })
198 }
199}
200
201#[derive(Default)]
202struct ConflictState {
203 conflicts: Vec<usize>,
204 keybind_mapping: HashMap<ActionMapping, Vec<usize>>,
205}
206
207impl ConflictState {
208 fn new(key_bindings: &[ProcessedKeybinding]) -> Self {
209 let mut action_keybind_mapping: HashMap<_, Vec<usize>> = HashMap::default();
210
211 key_bindings
212 .iter()
213 .enumerate()
214 .filter(|(_, binding)| {
215 binding.keystrokes().is_some()
216 && binding
217 .source
218 .as_ref()
219 .is_some_and(|source| matches!(source.0, KeybindSource::User))
220 })
221 .for_each(|(index, binding)| {
222 action_keybind_mapping
223 .entry(binding.get_action_mapping())
224 .or_default()
225 .push(index);
226 });
227
228 Self {
229 conflicts: action_keybind_mapping
230 .values()
231 .filter(|indices| indices.len() > 1)
232 .flatten()
233 .copied()
234 .collect(),
235 keybind_mapping: action_keybind_mapping,
236 }
237 }
238
239 fn conflicting_indices_for_mapping(
240 &self,
241 action_mapping: &ActionMapping,
242 keybind_idx: usize,
243 ) -> Option<KeybindConflict> {
244 self.keybind_mapping
245 .get(action_mapping)
246 .and_then(|indices| {
247 KeybindConflict::from_iter(indices.iter().filter(|&idx| *idx != keybind_idx))
248 })
249 }
250
251 fn will_conflict(&self, action_mapping: &ActionMapping) -> Option<KeybindConflict> {
252 self.keybind_mapping
253 .get(action_mapping)
254 .and_then(|indices| KeybindConflict::from_iter(indices.iter()))
255 }
256
257 fn has_conflict(&self, candidate_idx: &usize) -> bool {
258 self.conflicts.contains(candidate_idx)
259 }
260
261 fn any_conflicts(&self) -> bool {
262 !self.conflicts.is_empty()
263 }
264}
265
266struct KeymapEditor {
267 workspace: WeakEntity<Workspace>,
268 focus_handle: FocusHandle,
269 _keymap_subscription: Subscription,
270 keybindings: Vec<ProcessedKeybinding>,
271 keybinding_conflict_state: ConflictState,
272 filter_state: FilterState,
273 search_mode: SearchMode,
274 search_query_debounce: Option<Task<()>>,
275 // corresponds 1 to 1 with keybindings
276 string_match_candidates: Arc<Vec<StringMatchCandidate>>,
277 matches: Vec<StringMatch>,
278 table_interaction_state: Entity<TableInteractionState>,
279 filter_editor: Entity<Editor>,
280 keystroke_editor: Entity<KeystrokeInput>,
281 selected_index: Option<usize>,
282 context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
283 previous_edit: Option<PreviousEdit>,
284 humanized_action_names: HumanizedActionNameCache,
285 show_hover_menus: bool,
286}
287
288enum PreviousEdit {
289 /// When deleting, we want to maintain the same scroll position
290 ScrollBarOffset(Point<Pixels>),
291 /// When editing or creating, because the new keybinding could be in a different position in the sort order
292 /// we store metadata about the new binding (either the modified version or newly created one)
293 /// and upon reload, we search for this binding in the list of keybindings, and if we find the one that matches
294 /// this metadata, we set the selected index to it and scroll to it,
295 /// and if we don't find it, we scroll to 0 and don't set a selected index
296 Keybinding {
297 action_mapping: ActionMapping,
298 action_name: &'static str,
299 /// The scrollbar position to fallback to if we don't find the keybinding during a refresh
300 /// this can happen if there's a filter applied to the search and the keybinding modification
301 /// filters the binding from the search results
302 fallback: Point<Pixels>,
303 },
304}
305
306impl EventEmitter<()> for KeymapEditor {}
307
308impl Focusable for KeymapEditor {
309 fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
310 return self.filter_editor.focus_handle(cx);
311 }
312}
313
314impl KeymapEditor {
315 fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
316 let _keymap_subscription = cx.observe_global::<KeymapEventChannel>(Self::on_keymap_changed);
317 let table_interaction_state = TableInteractionState::new(window, cx);
318
319 let keystroke_editor = cx.new(|cx| {
320 let mut keystroke_editor = KeystrokeInput::new(None, window, cx);
321 keystroke_editor.search = true;
322 keystroke_editor
323 });
324
325 let filter_editor = cx.new(|cx| {
326 let mut editor = Editor::single_line(window, cx);
327 editor.set_placeholder_text("Filter action names…", cx);
328 editor
329 });
330
331 cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
332 if !matches!(e, EditorEvent::BufferEdited) {
333 return;
334 }
335
336 this.on_query_changed(cx);
337 })
338 .detach();
339
340 cx.subscribe(&keystroke_editor, |this, _, _, cx| {
341 if matches!(this.search_mode, SearchMode::Normal) {
342 return;
343 }
344
345 this.on_query_changed(cx);
346 })
347 .detach();
348
349 let mut this = Self {
350 workspace,
351 keybindings: vec![],
352 keybinding_conflict_state: ConflictState::default(),
353 filter_state: FilterState::default(),
354 search_mode: SearchMode::default(),
355 string_match_candidates: Arc::new(vec![]),
356 matches: vec![],
357 focus_handle: cx.focus_handle(),
358 _keymap_subscription,
359 table_interaction_state,
360 filter_editor,
361 keystroke_editor,
362 selected_index: None,
363 context_menu: None,
364 previous_edit: None,
365 search_query_debounce: None,
366 humanized_action_names: HumanizedActionNameCache::new(cx),
367 show_hover_menus: true,
368 };
369
370 this.on_keymap_changed(cx);
371
372 this
373 }
374
375 fn current_action_query(&self, cx: &App) -> String {
376 self.filter_editor.read(cx).text(cx)
377 }
378
379 fn current_keystroke_query(&self, cx: &App) -> Vec<Keystroke> {
380 match self.search_mode {
381 SearchMode::KeyStroke { .. } => self
382 .keystroke_editor
383 .read(cx)
384 .keystrokes()
385 .iter()
386 .cloned()
387 .collect(),
388 SearchMode::Normal => Default::default(),
389 }
390 }
391
392 fn filter_on_selected_binding_keystrokes(&mut self, cx: &mut Context<Self>) {
393 let Some(selected_binding) = self.selected_binding() else {
394 return;
395 };
396
397 let keystrokes = selected_binding
398 .keystrokes()
399 .map(Vec::from)
400 .unwrap_or_default();
401
402 self.filter_state = FilterState::All;
403 self.search_mode = SearchMode::KeyStroke { exact_match: true };
404
405 self.keystroke_editor.update(cx, |editor, cx| {
406 editor.set_keystrokes(keystrokes, cx);
407 });
408 }
409
410 fn on_query_changed(&mut self, cx: &mut Context<Self>) {
411 let action_query = self.current_action_query(cx);
412 let keystroke_query = self.current_keystroke_query(cx);
413 let exact_match = self.search_mode.exact_match();
414
415 let timer = cx.background_executor().timer(Duration::from_secs(1));
416 self.search_query_debounce = Some(cx.background_spawn({
417 let action_query = action_query.clone();
418 let keystroke_query = keystroke_query.clone();
419 async move {
420 timer.await;
421
422 let keystroke_query = keystroke_query
423 .into_iter()
424 .map(|keystroke| keystroke.unparse())
425 .collect::<Vec<String>>()
426 .join(" ");
427
428 telemetry::event!(
429 "Keystroke Search Completed",
430 action_query = action_query,
431 keystroke_query = keystroke_query,
432 keystroke_exact_match = exact_match
433 )
434 }
435 }));
436 cx.spawn(async move |this, cx| {
437 Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
438 this.update(cx, |this, cx| {
439 this.scroll_to_item(0, ScrollStrategy::Top, cx)
440 })
441 })
442 .detach();
443 }
444
445 async fn update_matches(
446 this: WeakEntity<Self>,
447 action_query: String,
448 keystroke_query: Vec<Keystroke>,
449 cx: &mut AsyncApp,
450 ) -> anyhow::Result<()> {
451 let action_query = command_palette::normalize_action_query(&action_query);
452 let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
453 (this.string_match_candidates.clone(), this.keybindings.len())
454 })?;
455 let executor = cx.background_executor().clone();
456 let mut matches = fuzzy::match_strings(
457 &string_match_candidates,
458 &action_query,
459 true,
460 true,
461 keybind_count,
462 &Default::default(),
463 executor,
464 )
465 .await;
466 this.update(cx, |this, cx| {
467 match this.filter_state {
468 FilterState::Conflicts => {
469 matches.retain(|candidate| {
470 this.keybinding_conflict_state
471 .has_conflict(&candidate.candidate_id)
472 });
473 }
474 FilterState::All => {}
475 }
476
477 match this.search_mode {
478 SearchMode::KeyStroke { exact_match } => {
479 matches.retain(|item| {
480 this.keybindings[item.candidate_id]
481 .keystrokes()
482 .is_some_and(|keystrokes| {
483 if exact_match {
484 keystroke_query.len() == keystrokes.len()
485 && keystroke_query.iter().zip(keystrokes).all(
486 |(query, keystroke)| {
487 query.key == keystroke.key
488 && query.modifiers == keystroke.modifiers
489 },
490 )
491 } else {
492 let key_press_query =
493 KeyPressIterator::new(keystroke_query.as_slice());
494 let mut last_match_idx = 0;
495
496 key_press_query.into_iter().all(|key| {
497 let key_presses = KeyPressIterator::new(keystrokes);
498 key_presses.into_iter().enumerate().any(
499 |(index, keystroke)| {
500 if last_match_idx > index || keystroke != key {
501 return false;
502 }
503
504 last_match_idx = index;
505 true
506 },
507 )
508 })
509 }
510 })
511 });
512 }
513 SearchMode::Normal => {}
514 }
515
516 if action_query.is_empty() {
517 // apply default sort
518 // sorts by source precedence, and alphabetically by action name within each source
519 matches.sort_by_key(|match_item| {
520 let keybind = &this.keybindings[match_item.candidate_id];
521 let source = keybind.source.as_ref().map(|s| s.0);
522 use KeybindSource::*;
523 let source_precedence = match source {
524 Some(User) => 0,
525 Some(Vim) => 1,
526 Some(Base) => 2,
527 Some(Default) => 3,
528 None => 4,
529 };
530 return (source_precedence, keybind.action_name);
531 });
532 }
533 this.selected_index.take();
534 this.matches = matches;
535
536 cx.notify();
537 })
538 }
539
540 fn has_conflict(&self, row_index: usize) -> bool {
541 self.matches
542 .get(row_index)
543 .map(|candidate| candidate.candidate_id)
544 .is_some_and(|id| self.keybinding_conflict_state.has_conflict(&id))
545 }
546
547 fn process_bindings(
548 json_language: Arc<Language>,
549 zed_keybind_context_language: Arc<Language>,
550 humanized_action_names: &HumanizedActionNameCache,
551 cx: &mut App,
552 ) -> (Vec<ProcessedKeybinding>, Vec<StringMatchCandidate>) {
553 let key_bindings_ptr = cx.key_bindings();
554 let lock = key_bindings_ptr.borrow();
555 let key_bindings = lock.bindings();
556 let mut unmapped_action_names =
557 HashSet::from_iter(cx.all_action_names().into_iter().copied());
558 let action_documentation = cx.action_documentation();
559 let mut generator = KeymapFile::action_schema_generator();
560 let action_schema = HashMap::from_iter(
561 cx.action_schemas(&mut generator)
562 .into_iter()
563 .filter_map(|(name, schema)| schema.map(|schema| (name, schema))),
564 );
565
566 let mut processed_bindings = Vec::new();
567 let mut string_match_candidates = Vec::new();
568
569 for key_binding in key_bindings {
570 let source = key_binding.meta().map(settings::KeybindSource::from_meta);
571
572 let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
573 let ui_key_binding = Some(
574 ui::KeyBinding::new_from_gpui(key_binding.clone(), cx)
575 .vim_mode(source == Some(settings::KeybindSource::Vim)),
576 );
577
578 let context = key_binding
579 .predicate()
580 .map(|predicate| {
581 KeybindContextString::Local(
582 predicate.to_string().into(),
583 zed_keybind_context_language.clone(),
584 )
585 })
586 .unwrap_or(KeybindContextString::Global);
587
588 let source = source.map(|source| (source, source.name().into()));
589
590 let action_name = key_binding.action().name();
591 unmapped_action_names.remove(&action_name);
592 let action_arguments = key_binding
593 .action_input()
594 .map(|arguments| SyntaxHighlightedText::new(arguments, json_language.clone()));
595 let action_docs = action_documentation.get(action_name).copied();
596
597 let index = processed_bindings.len();
598 let humanized_action_name = humanized_action_names.get(action_name);
599 let string_match_candidate = StringMatchCandidate::new(index, &humanized_action_name);
600 processed_bindings.push(ProcessedKeybinding {
601 keystroke_text: keystroke_text.into(),
602 ui_key_binding,
603 action_name,
604 action_arguments,
605 humanized_action_name,
606 action_docs,
607 action_schema: action_schema.get(action_name).cloned(),
608 context: Some(context),
609 source,
610 });
611 string_match_candidates.push(string_match_candidate);
612 }
613
614 let empty = SharedString::new_static("");
615 for action_name in unmapped_action_names.into_iter() {
616 let index = processed_bindings.len();
617 let humanized_action_name = humanized_action_names.get(action_name);
618 let string_match_candidate = StringMatchCandidate::new(index, &humanized_action_name);
619 processed_bindings.push(ProcessedKeybinding {
620 keystroke_text: empty.clone(),
621 ui_key_binding: None,
622 action_name,
623 action_arguments: None,
624 humanized_action_name,
625 action_docs: action_documentation.get(action_name).copied(),
626 action_schema: action_schema.get(action_name).cloned(),
627 context: None,
628 source: None,
629 });
630 string_match_candidates.push(string_match_candidate);
631 }
632
633 (processed_bindings, string_match_candidates)
634 }
635
636 fn on_keymap_changed(&mut self, cx: &mut Context<KeymapEditor>) {
637 let workspace = self.workspace.clone();
638 cx.spawn(async move |this, cx| {
639 let json_language = load_json_language(workspace.clone(), cx).await;
640 let zed_keybind_context_language =
641 load_keybind_context_language(workspace.clone(), cx).await;
642
643 let (action_query, keystroke_query) = this.update(cx, |this, cx| {
644 let (key_bindings, string_match_candidates) = Self::process_bindings(
645 json_language,
646 zed_keybind_context_language,
647 &this.humanized_action_names,
648 cx,
649 );
650
651 this.keybinding_conflict_state = ConflictState::new(&key_bindings);
652
653 this.keybindings = key_bindings;
654 this.string_match_candidates = Arc::new(string_match_candidates);
655 this.matches = this
656 .string_match_candidates
657 .iter()
658 .enumerate()
659 .map(|(ix, candidate)| StringMatch {
660 candidate_id: ix,
661 score: 0.0,
662 positions: vec![],
663 string: candidate.string.clone(),
664 })
665 .collect();
666 (
667 this.current_action_query(cx),
668 this.current_keystroke_query(cx),
669 )
670 })?;
671 // calls cx.notify
672 Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
673 this.update(cx, |this, cx| {
674 if let Some(previous_edit) = this.previous_edit.take() {
675 match previous_edit {
676 // should remove scroll from process_query
677 PreviousEdit::ScrollBarOffset(offset) => {
678 this.table_interaction_state.update(cx, |table, _| {
679 table.set_scrollbar_offset(Axis::Vertical, offset)
680 })
681 // set selected index and scroll
682 }
683 PreviousEdit::Keybinding {
684 action_mapping,
685 action_name,
686 fallback,
687 } => {
688 let scroll_position =
689 this.matches.iter().enumerate().find_map(|(index, item)| {
690 let binding = &this.keybindings[item.candidate_id];
691 if binding.get_action_mapping() == action_mapping
692 && binding.action_name == action_name
693 {
694 Some(index)
695 } else {
696 None
697 }
698 });
699
700 if let Some(scroll_position) = scroll_position {
701 this.scroll_to_item(scroll_position, ScrollStrategy::Top, cx);
702 this.selected_index = Some(scroll_position);
703 } else {
704 this.table_interaction_state.update(cx, |table, _| {
705 table.set_scrollbar_offset(Axis::Vertical, fallback)
706 });
707 }
708 cx.notify();
709 }
710 }
711 }
712 })
713 })
714 .detach_and_log_err(cx);
715 }
716
717 fn key_context(&self) -> KeyContext {
718 let mut dispatch_context = KeyContext::new_with_defaults();
719 dispatch_context.add("KeymapEditor");
720 dispatch_context.add("menu");
721
722 dispatch_context
723 }
724
725 fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
726 let index = usize::min(index, self.matches.len().saturating_sub(1));
727 self.table_interaction_state.update(cx, |this, _cx| {
728 this.scroll_handle.scroll_to_item(index, strategy);
729 });
730 }
731
732 fn focus_search(
733 &mut self,
734 _: &search::FocusSearch,
735 window: &mut Window,
736 cx: &mut Context<Self>,
737 ) {
738 if !self
739 .filter_editor
740 .focus_handle(cx)
741 .contains_focused(window, cx)
742 {
743 window.focus(&self.filter_editor.focus_handle(cx));
744 } else {
745 self.filter_editor.update(cx, |editor, cx| {
746 editor.select_all(&Default::default(), window, cx);
747 });
748 }
749 self.selected_index.take();
750 }
751
752 fn selected_keybind_index(&self) -> Option<usize> {
753 self.selected_index
754 .and_then(|match_index| self.matches.get(match_index))
755 .map(|r#match| r#match.candidate_id)
756 }
757
758 fn selected_keybind_and_index(&self) -> Option<(&ProcessedKeybinding, usize)> {
759 self.selected_keybind_index()
760 .map(|keybind_index| (&self.keybindings[keybind_index], keybind_index))
761 }
762
763 fn selected_binding(&self) -> Option<&ProcessedKeybinding> {
764 self.selected_keybind_index()
765 .and_then(|keybind_index| self.keybindings.get(keybind_index))
766 }
767
768 fn select_index(&mut self, index: usize, cx: &mut Context<Self>) {
769 if self.selected_index != Some(index) {
770 self.selected_index = Some(index);
771 cx.notify();
772 }
773 }
774
775 fn create_context_menu(
776 &mut self,
777 position: Point<Pixels>,
778 window: &mut Window,
779 cx: &mut Context<Self>,
780 ) {
781 let weak = cx.weak_entity();
782 self.context_menu = self.selected_binding().map(|selected_binding| {
783 let selected_binding_has_no_context = selected_binding
784 .context
785 .as_ref()
786 .and_then(KeybindContextString::local)
787 .is_none();
788
789 let selected_binding_is_unbound = selected_binding.keystrokes().is_none();
790
791 let context_menu = ContextMenu::build(window, cx, |menu, _window, _cx| {
792 menu.context(self.focus_handle.clone())
793 .action_disabled_when(
794 selected_binding_is_unbound,
795 "Edit",
796 Box::new(EditBinding),
797 )
798 .action("Create", Box::new(CreateBinding))
799 .action_disabled_when(
800 selected_binding_is_unbound,
801 "Delete",
802 Box::new(DeleteBinding),
803 )
804 .separator()
805 .action("Copy Action", Box::new(CopyAction))
806 .action_disabled_when(
807 selected_binding_has_no_context,
808 "Copy Context",
809 Box::new(CopyContext),
810 )
811 .separator()
812 .entry("Show Matching Keybindings", None, {
813 move |_, cx| {
814 weak.update(cx, |this, cx| {
815 this.filter_on_selected_binding_keystrokes(cx);
816 })
817 .ok();
818 }
819 })
820 });
821
822 let context_menu_handle = context_menu.focus_handle(cx);
823 window.defer(cx, move |window, _cx| window.focus(&context_menu_handle));
824 let subscription = cx.subscribe_in(
825 &context_menu,
826 window,
827 |this, _, _: &DismissEvent, window, cx| {
828 this.dismiss_context_menu(window, cx);
829 },
830 );
831 (context_menu, position, subscription)
832 });
833
834 cx.notify();
835 }
836
837 fn dismiss_context_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) {
838 self.context_menu.take();
839 window.focus(&self.focus_handle);
840 cx.notify();
841 }
842
843 fn context_menu_deployed(&self) -> bool {
844 self.context_menu.is_some()
845 }
846
847 fn render_no_matches_hint(&self, _window: &mut Window, _cx: &App) -> AnyElement {
848 let hint = match (self.filter_state, &self.search_mode) {
849 (FilterState::Conflicts, _) => {
850 if self.keybinding_conflict_state.any_conflicts() {
851 "No conflicting keybinds found that match the provided query"
852 } else {
853 "No conflicting keybinds found"
854 }
855 }
856 (FilterState::All, SearchMode::KeyStroke { .. }) => {
857 "No keybinds found matching the entered keystrokes"
858 }
859 (FilterState::All, SearchMode::Normal) => "No matches found for the provided query",
860 };
861
862 Label::new(hint).color(Color::Muted).into_any_element()
863 }
864
865 fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
866 self.show_hover_menus = false;
867 if let Some(selected) = self.selected_index {
868 let selected = selected + 1;
869 if selected >= self.matches.len() {
870 self.select_last(&Default::default(), window, cx);
871 } else {
872 self.selected_index = Some(selected);
873 self.scroll_to_item(selected, ScrollStrategy::Center, cx);
874 cx.notify();
875 }
876 } else {
877 self.select_first(&Default::default(), window, cx);
878 }
879 }
880
881 fn select_previous(
882 &mut self,
883 _: &menu::SelectPrevious,
884 window: &mut Window,
885 cx: &mut Context<Self>,
886 ) {
887 self.show_hover_menus = false;
888 if let Some(selected) = self.selected_index {
889 if selected == 0 {
890 return;
891 }
892
893 let selected = selected - 1;
894
895 if selected >= self.matches.len() {
896 self.select_last(&Default::default(), window, cx);
897 } else {
898 self.selected_index = Some(selected);
899 self.scroll_to_item(selected, ScrollStrategy::Center, cx);
900 cx.notify();
901 }
902 } else {
903 self.select_last(&Default::default(), window, cx);
904 }
905 }
906
907 fn select_first(
908 &mut self,
909 _: &menu::SelectFirst,
910 _window: &mut Window,
911 cx: &mut Context<Self>,
912 ) {
913 self.show_hover_menus = false;
914 if self.matches.get(0).is_some() {
915 self.selected_index = Some(0);
916 self.scroll_to_item(0, ScrollStrategy::Center, cx);
917 cx.notify();
918 }
919 }
920
921 fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
922 self.show_hover_menus = false;
923 if self.matches.last().is_some() {
924 let index = self.matches.len() - 1;
925 self.selected_index = Some(index);
926 self.scroll_to_item(index, ScrollStrategy::Center, cx);
927 cx.notify();
928 }
929 }
930
931 fn open_edit_keybinding_modal(
932 &mut self,
933 create: bool,
934 window: &mut Window,
935 cx: &mut Context<Self>,
936 ) {
937 self.show_hover_menus = false;
938 let Some((keybind, keybind_index)) = self.selected_keybind_and_index() else {
939 return;
940 };
941 let keybind = keybind.clone();
942 let keymap_editor = cx.entity();
943
944 let arguments = keybind
945 .action_arguments
946 .as_ref()
947 .map(|arguments| arguments.text.clone());
948 let context = keybind
949 .context
950 .as_ref()
951 .map(|context| context.local_str().unwrap_or("global"));
952 let source = keybind.source.as_ref().map(|source| source.1.clone());
953
954 telemetry::event!(
955 "Edit Keybinding Modal Opened",
956 keystroke = keybind.keystroke_text,
957 action = keybind.action_name,
958 source = source,
959 context = context,
960 arguments = arguments,
961 );
962
963 self.workspace
964 .update(cx, |workspace, cx| {
965 let fs = workspace.app_state().fs.clone();
966 let workspace_weak = cx.weak_entity();
967 workspace.toggle_modal(window, cx, |window, cx| {
968 let modal = KeybindingEditorModal::new(
969 create,
970 keybind,
971 keybind_index,
972 keymap_editor,
973 workspace_weak,
974 fs,
975 window,
976 cx,
977 );
978 window.focus(&modal.focus_handle(cx));
979 modal
980 });
981 })
982 .log_err();
983 }
984
985 fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
986 self.open_edit_keybinding_modal(false, window, cx);
987 }
988
989 fn create_binding(&mut self, _: &CreateBinding, window: &mut Window, cx: &mut Context<Self>) {
990 self.open_edit_keybinding_modal(true, window, cx);
991 }
992
993 fn delete_binding(&mut self, _: &DeleteBinding, window: &mut Window, cx: &mut Context<Self>) {
994 let Some(to_remove) = self.selected_binding().cloned() else {
995 return;
996 };
997
998 let std::result::Result::Ok(fs) = self
999 .workspace
1000 .read_with(cx, |workspace, _| workspace.app_state().fs.clone())
1001 else {
1002 return;
1003 };
1004 let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
1005 self.previous_edit = Some(PreviousEdit::ScrollBarOffset(
1006 self.table_interaction_state
1007 .read(cx)
1008 .get_scrollbar_offset(Axis::Vertical),
1009 ));
1010 cx.spawn(async move |_, _| remove_keybinding(to_remove, &fs, tab_size).await)
1011 .detach_and_notify_err(window, cx);
1012 }
1013
1014 fn copy_context_to_clipboard(
1015 &mut self,
1016 _: &CopyContext,
1017 _window: &mut Window,
1018 cx: &mut Context<Self>,
1019 ) {
1020 let context = self
1021 .selected_binding()
1022 .and_then(|binding| binding.context.as_ref())
1023 .and_then(KeybindContextString::local_str)
1024 .map(|context| context.to_string());
1025 let Some(context) = context else {
1026 return;
1027 };
1028
1029 telemetry::event!("Keybinding Context Copied", context = context.clone());
1030 cx.write_to_clipboard(gpui::ClipboardItem::new_string(context.clone()));
1031 }
1032
1033 fn copy_action_to_clipboard(
1034 &mut self,
1035 _: &CopyAction,
1036 _window: &mut Window,
1037 cx: &mut Context<Self>,
1038 ) {
1039 let action = self
1040 .selected_binding()
1041 .map(|binding| binding.action_name.to_string());
1042 let Some(action) = action else {
1043 return;
1044 };
1045
1046 telemetry::event!("Keybinding Action Copied", action = action.clone());
1047 cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
1048 }
1049
1050 fn toggle_conflict_filter(
1051 &mut self,
1052 _: &ToggleConflictFilter,
1053 _: &mut Window,
1054 cx: &mut Context<Self>,
1055 ) {
1056 self.set_filter_state(self.filter_state.invert(), cx);
1057 }
1058
1059 fn set_filter_state(&mut self, filter_state: FilterState, cx: &mut Context<Self>) {
1060 if self.filter_state != filter_state {
1061 self.filter_state = filter_state;
1062 self.on_query_changed(cx);
1063 }
1064 }
1065
1066 fn toggle_keystroke_search(
1067 &mut self,
1068 _: &ToggleKeystrokeSearch,
1069 window: &mut Window,
1070 cx: &mut Context<Self>,
1071 ) {
1072 self.search_mode = self.search_mode.invert();
1073 self.on_query_changed(cx);
1074
1075 match self.search_mode {
1076 SearchMode::KeyStroke { .. } => {
1077 window.focus(&self.keystroke_editor.read(cx).recording_focus_handle(cx));
1078 }
1079 SearchMode::Normal => {
1080 self.keystroke_editor.update(cx, |editor, cx| {
1081 editor.clear_keystrokes(&ClearKeystrokes, window, cx)
1082 });
1083 window.focus(&self.filter_editor.focus_handle(cx));
1084 }
1085 }
1086 }
1087
1088 fn toggle_exact_keystroke_matching(
1089 &mut self,
1090 _: &ToggleExactKeystrokeMatching,
1091 _: &mut Window,
1092 cx: &mut Context<Self>,
1093 ) {
1094 let SearchMode::KeyStroke { exact_match } = &mut self.search_mode else {
1095 return;
1096 };
1097
1098 *exact_match = !(*exact_match);
1099 self.on_query_changed(cx);
1100 }
1101}
1102
1103struct HumanizedActionNameCache {
1104 cache: HashMap<&'static str, SharedString>,
1105}
1106
1107impl HumanizedActionNameCache {
1108 fn new(cx: &App) -> Self {
1109 let cache = HashMap::from_iter(cx.all_action_names().into_iter().map(|&action_name| {
1110 (
1111 action_name,
1112 command_palette::humanize_action_name(action_name).into(),
1113 )
1114 }));
1115 Self { cache }
1116 }
1117
1118 fn get(&self, action_name: &'static str) -> SharedString {
1119 match self.cache.get(action_name) {
1120 Some(name) => name.clone(),
1121 None => action_name.into(),
1122 }
1123 }
1124}
1125
1126#[derive(Clone)]
1127struct ProcessedKeybinding {
1128 keystroke_text: SharedString,
1129 ui_key_binding: Option<ui::KeyBinding>,
1130 action_name: &'static str,
1131 humanized_action_name: SharedString,
1132 action_arguments: Option<SyntaxHighlightedText>,
1133 action_docs: Option<&'static str>,
1134 action_schema: Option<schemars::Schema>,
1135 context: Option<KeybindContextString>,
1136 source: Option<(KeybindSource, SharedString)>,
1137}
1138
1139impl ProcessedKeybinding {
1140 fn get_action_mapping(&self) -> ActionMapping {
1141 ActionMapping {
1142 keystrokes: self.keystrokes().map(Vec::from).unwrap_or_default(),
1143 context: self
1144 .context
1145 .as_ref()
1146 .and_then(|context| context.local())
1147 .cloned(),
1148 }
1149 }
1150
1151 fn keystrokes(&self) -> Option<&[Keystroke]> {
1152 self.ui_key_binding
1153 .as_ref()
1154 .map(|binding| binding.keystrokes.as_slice())
1155 }
1156}
1157
1158#[derive(Clone, Debug, IntoElement, PartialEq, Eq, Hash)]
1159enum KeybindContextString {
1160 Global,
1161 Local(SharedString, Arc<Language>),
1162}
1163
1164impl KeybindContextString {
1165 const GLOBAL: SharedString = SharedString::new_static("<global>");
1166
1167 pub fn local(&self) -> Option<&SharedString> {
1168 match self {
1169 KeybindContextString::Global => None,
1170 KeybindContextString::Local(name, _) => Some(name),
1171 }
1172 }
1173
1174 pub fn local_str(&self) -> Option<&str> {
1175 match self {
1176 KeybindContextString::Global => None,
1177 KeybindContextString::Local(name, _) => Some(name),
1178 }
1179 }
1180}
1181
1182impl RenderOnce for KeybindContextString {
1183 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
1184 match self {
1185 KeybindContextString::Global => {
1186 muted_styled_text(KeybindContextString::GLOBAL.clone(), cx).into_any_element()
1187 }
1188 KeybindContextString::Local(name, language) => {
1189 SyntaxHighlightedText::new(name, language).into_any_element()
1190 }
1191 }
1192 }
1193}
1194
1195fn muted_styled_text(text: SharedString, cx: &App) -> StyledText {
1196 let len = text.len();
1197 StyledText::new(text).with_highlights([(
1198 0..len,
1199 gpui::HighlightStyle::color(cx.theme().colors().text_muted),
1200 )])
1201}
1202
1203impl Item for KeymapEditor {
1204 type Event = ();
1205
1206 fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
1207 "Keymap Editor".into()
1208 }
1209}
1210
1211impl Render for KeymapEditor {
1212 fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
1213 let row_count = self.matches.len();
1214 let theme = cx.theme();
1215 let focus_handle = &self.focus_handle;
1216
1217 v_flex()
1218 .id("keymap-editor")
1219 .track_focus(focus_handle)
1220 .key_context(self.key_context())
1221 .on_action(cx.listener(Self::select_next))
1222 .on_action(cx.listener(Self::select_previous))
1223 .on_action(cx.listener(Self::select_first))
1224 .on_action(cx.listener(Self::select_last))
1225 .on_action(cx.listener(Self::focus_search))
1226 .on_action(cx.listener(Self::edit_binding))
1227 .on_action(cx.listener(Self::create_binding))
1228 .on_action(cx.listener(Self::delete_binding))
1229 .on_action(cx.listener(Self::copy_action_to_clipboard))
1230 .on_action(cx.listener(Self::copy_context_to_clipboard))
1231 .on_action(cx.listener(Self::toggle_conflict_filter))
1232 .on_action(cx.listener(Self::toggle_keystroke_search))
1233 .on_action(cx.listener(Self::toggle_exact_keystroke_matching))
1234 .on_mouse_move(cx.listener(|this, _, _window, _cx| {
1235 this.show_hover_menus = true;
1236 }))
1237 .size_full()
1238 .p_2()
1239 .gap_1()
1240 .bg(theme.colors().editor_background)
1241 .child(
1242 v_flex()
1243 .gap_2()
1244 .child(
1245 h_flex()
1246 .gap_2()
1247 .child(
1248 div()
1249 .key_context({
1250 let mut context = KeyContext::new_with_defaults();
1251 context.add("BufferSearchBar");
1252 context
1253 })
1254 .size_full()
1255 .h_8()
1256 .pl_2()
1257 .pr_1()
1258 .py_1()
1259 .border_1()
1260 .border_color(theme.colors().border)
1261 .rounded_lg()
1262 .child(self.filter_editor.clone()),
1263 )
1264 .child(
1265 IconButton::new(
1266 "KeymapEditorToggleFiltersIcon",
1267 IconName::Keyboard,
1268 )
1269 .shape(ui::IconButtonShape::Square)
1270 .tooltip({
1271 let focus_handle = focus_handle.clone();
1272
1273 move |window, cx| {
1274 Tooltip::for_action_in(
1275 "Search by Keystroke",
1276 &ToggleKeystrokeSearch,
1277 &focus_handle.clone(),
1278 window,
1279 cx,
1280 )
1281 }
1282 })
1283 .toggle_state(matches!(
1284 self.search_mode,
1285 SearchMode::KeyStroke { .. }
1286 ))
1287 .on_click(|_, window, cx| {
1288 window.dispatch_action(ToggleKeystrokeSearch.boxed_clone(), cx);
1289 }),
1290 )
1291 .child(
1292 IconButton::new("KeymapEditorConflictIcon", IconName::Warning)
1293 .shape(ui::IconButtonShape::Square)
1294 .when(self.keybinding_conflict_state.any_conflicts(), |this| {
1295 this.indicator(Indicator::dot().color(Color::Warning))
1296 })
1297 .tooltip({
1298 let filter_state = self.filter_state;
1299 let focus_handle = focus_handle.clone();
1300
1301 move |window, cx| {
1302 Tooltip::for_action_in(
1303 match filter_state {
1304 FilterState::All => "Show Conflicts",
1305 FilterState::Conflicts => "Hide Conflicts",
1306 },
1307 &ToggleConflictFilter,
1308 &focus_handle.clone(),
1309 window,
1310 cx,
1311 )
1312 }
1313 })
1314 .selected_icon_color(Color::Warning)
1315 .toggle_state(matches!(
1316 self.filter_state,
1317 FilterState::Conflicts
1318 ))
1319 .on_click(|_, window, cx| {
1320 window.dispatch_action(
1321 ToggleConflictFilter.boxed_clone(),
1322 cx,
1323 );
1324 }),
1325 ),
1326 )
1327 .when_some(
1328 match self.search_mode {
1329 SearchMode::Normal => None,
1330 SearchMode::KeyStroke { exact_match } => Some(exact_match),
1331 },
1332 |this, exact_match| {
1333 this.child(
1334 h_flex()
1335 .map(|this| {
1336 if self.keybinding_conflict_state.any_conflicts() {
1337 this.pr(rems_from_px(54.))
1338 } else {
1339 this.pr_7()
1340 }
1341 })
1342 .gap_2()
1343 .child(self.keystroke_editor.clone())
1344 .child(
1345 IconButton::new(
1346 "keystrokes-exact-match",
1347 IconName::CaseSensitive,
1348 )
1349 .tooltip({
1350 let keystroke_focus_handle =
1351 self.keystroke_editor.read(cx).focus_handle(cx);
1352
1353 move |window, cx| {
1354 Tooltip::for_action_in(
1355 "Toggle Exact Match Mode",
1356 &ToggleExactKeystrokeMatching,
1357 &keystroke_focus_handle,
1358 window,
1359 cx,
1360 )
1361 }
1362 })
1363 .shape(IconButtonShape::Square)
1364 .toggle_state(exact_match)
1365 .on_click(
1366 cx.listener(|_, _, window, cx| {
1367 window.dispatch_action(
1368 ToggleExactKeystrokeMatching.boxed_clone(),
1369 cx,
1370 );
1371 }),
1372 ),
1373 ),
1374 )
1375 },
1376 ),
1377 )
1378 .child(
1379 Table::new()
1380 .interactable(&self.table_interaction_state)
1381 .striped()
1382 .empty_table_callback({
1383 let this = cx.entity();
1384 move |window, cx| this.read(cx).render_no_matches_hint(window, cx)
1385 })
1386 .column_widths([
1387 DefiniteLength::Absolute(AbsoluteLength::Pixels(px(40.))),
1388 DefiniteLength::Fraction(0.25),
1389 DefiniteLength::Fraction(0.20),
1390 DefiniteLength::Fraction(0.14),
1391 DefiniteLength::Fraction(0.45),
1392 DefiniteLength::Fraction(0.08),
1393 ])
1394 .header(["", "Action", "Arguments", "Keystrokes", "Context", "Source"])
1395 .uniform_list(
1396 "keymap-editor-table",
1397 row_count,
1398 cx.processor(move |this, range: Range<usize>, _window, cx| {
1399 let context_menu_deployed = this.context_menu_deployed();
1400 range
1401 .filter_map(|index| {
1402 let candidate_id = this.matches.get(index)?.candidate_id;
1403 let binding = &this.keybindings[candidate_id];
1404 let action_name = binding.action_name;
1405
1406 let icon = if this.filter_state != FilterState::Conflicts
1407 && this.has_conflict(index)
1408 {
1409 base_button_style(index, IconName::Warning)
1410 .icon_color(Color::Warning)
1411 .tooltip(|window, cx| {
1412 Tooltip::with_meta(
1413 "View conflicts",
1414 Some(&ToggleConflictFilter),
1415 "Use alt+click to show all conflicts",
1416 window,
1417 cx,
1418 )
1419 })
1420 .on_click(cx.listener(
1421 move |this, click: &ClickEvent, window, cx| {
1422 if click.modifiers().alt {
1423 this.set_filter_state(
1424 FilterState::Conflicts,
1425 cx,
1426 );
1427 } else {
1428 this.select_index(index, cx);
1429 this.open_edit_keybinding_modal(
1430 false, window, cx,
1431 );
1432 cx.stop_propagation();
1433 }
1434 },
1435 ))
1436 .into_any_element()
1437 } else {
1438 base_button_style(index, IconName::Pencil)
1439 .visible_on_hover(
1440 if this.selected_index == Some(index) {
1441 "".into()
1442 } else if this.show_hover_menus {
1443 row_group_id(index)
1444 } else {
1445 "never-show".into()
1446 },
1447 )
1448 .when(
1449 this.show_hover_menus && !context_menu_deployed,
1450 |this| {
1451 this.tooltip(Tooltip::for_action_title(
1452 "Edit Keybinding",
1453 &EditBinding,
1454 ))
1455 },
1456 )
1457 .on_click(cx.listener(move |this, _, window, cx| {
1458 this.select_index(index, cx);
1459 this.open_edit_keybinding_modal(false, window, cx);
1460 cx.stop_propagation();
1461 }))
1462 .into_any_element()
1463 };
1464
1465 let action = div()
1466 .id(("keymap action", index))
1467 .child({
1468 if action_name != gpui::NoAction.name() {
1469 binding
1470 .humanized_action_name
1471 .clone()
1472 .into_any_element()
1473 } else {
1474 const NULL: SharedString =
1475 SharedString::new_static("<null>");
1476 muted_styled_text(NULL.clone(), cx)
1477 .into_any_element()
1478 }
1479 })
1480 .when(
1481 !context_menu_deployed && this.show_hover_menus,
1482 |this| {
1483 this.tooltip({
1484 let action_name = binding.action_name;
1485 let action_docs = binding.action_docs;
1486 move |_, cx| {
1487 let action_tooltip =
1488 Tooltip::new(action_name);
1489 let action_tooltip = match action_docs {
1490 Some(docs) => action_tooltip.meta(docs),
1491 None => action_tooltip,
1492 };
1493 cx.new(|_| action_tooltip).into()
1494 }
1495 })
1496 },
1497 )
1498 .into_any_element();
1499 let keystrokes = binding.ui_key_binding.clone().map_or(
1500 binding.keystroke_text.clone().into_any_element(),
1501 IntoElement::into_any_element,
1502 );
1503 let action_arguments = match binding.action_arguments.clone() {
1504 Some(arguments) => arguments.into_any_element(),
1505 None => {
1506 if binding.action_schema.is_some() {
1507 muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
1508 .into_any_element()
1509 } else {
1510 gpui::Empty.into_any_element()
1511 }
1512 }
1513 };
1514 let context = binding.context.clone().map_or(
1515 gpui::Empty.into_any_element(),
1516 |context| {
1517 let is_local = context.local().is_some();
1518
1519 div()
1520 .id(("keymap context", index))
1521 .child(context.clone())
1522 .when(
1523 is_local
1524 && !context_menu_deployed
1525 && this.show_hover_menus,
1526 |this| {
1527 this.tooltip(Tooltip::element({
1528 move |_, _| {
1529 context.clone().into_any_element()
1530 }
1531 }))
1532 },
1533 )
1534 .into_any_element()
1535 },
1536 );
1537 let source = binding
1538 .source
1539 .clone()
1540 .map(|(_source, name)| name)
1541 .unwrap_or_default()
1542 .into_any_element();
1543 Some([
1544 icon,
1545 action,
1546 action_arguments,
1547 keystrokes,
1548 context,
1549 source,
1550 ])
1551 })
1552 .collect()
1553 }),
1554 )
1555 .map_row(
1556 cx.processor(|this, (row_index, row): (usize, Div), _window, cx| {
1557 let is_conflict = this.has_conflict(row_index);
1558 let is_selected = this.selected_index == Some(row_index);
1559
1560 let row_id = row_group_id(row_index);
1561
1562 let row = row
1563 .id(row_id.clone())
1564 .on_any_mouse_down(cx.listener(
1565 move |this,
1566 mouse_down_event: &gpui::MouseDownEvent,
1567 window,
1568 cx| {
1569 match mouse_down_event.button {
1570 MouseButton::Right => {
1571 this.select_index(row_index, cx);
1572 this.create_context_menu(
1573 mouse_down_event.position,
1574 window,
1575 cx,
1576 );
1577 }
1578 _ => {}
1579 }
1580 },
1581 ))
1582 .on_click(cx.listener(
1583 move |this, event: &ClickEvent, window, cx| {
1584 this.select_index(row_index, cx);
1585 if event.up.click_count == 2 {
1586 this.open_edit_keybinding_modal(false, window, cx);
1587 }
1588 },
1589 ))
1590 .group(row_id)
1591 .border_2()
1592 .when(is_conflict, |row| {
1593 row.bg(cx.theme().status().error_background)
1594 })
1595 .when(is_selected, |row| {
1596 row.border_color(cx.theme().colors().panel_focused_border)
1597 });
1598
1599 row.into_any_element()
1600 }),
1601 ),
1602 )
1603 .on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
1604 // This ensures that the menu is not dismissed in cases where scroll events
1605 // with a delta of zero are emitted
1606 if !event.delta.pixel_delta(px(1.)).y.is_zero() {
1607 this.context_menu.take();
1608 cx.notify();
1609 }
1610 }))
1611 .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1612 deferred(
1613 anchored()
1614 .position(*position)
1615 .anchor(gpui::Corner::TopLeft)
1616 .child(menu.clone()),
1617 )
1618 .with_priority(1)
1619 }))
1620 }
1621}
1622
1623fn row_group_id(row_index: usize) -> SharedString {
1624 SharedString::new(format!("keymap-table-row-{}", row_index))
1625}
1626
1627fn base_button_style(row_index: usize, icon: IconName) -> IconButton {
1628 IconButton::new(("keymap-icon", row_index), icon)
1629 .shape(IconButtonShape::Square)
1630 .size(ButtonSize::Compact)
1631}
1632
1633#[derive(Debug, Clone, IntoElement)]
1634struct SyntaxHighlightedText {
1635 text: SharedString,
1636 language: Arc<Language>,
1637}
1638
1639impl SyntaxHighlightedText {
1640 pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
1641 Self {
1642 text: text.into(),
1643 language,
1644 }
1645 }
1646}
1647
1648impl RenderOnce for SyntaxHighlightedText {
1649 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
1650 let text_style = window.text_style();
1651 let syntax_theme = cx.theme().syntax();
1652
1653 let text = self.text.clone();
1654
1655 let highlights = self
1656 .language
1657 .highlight_text(&text.as_ref().into(), 0..text.len());
1658 let mut runs = Vec::with_capacity(highlights.len());
1659 let mut offset = 0;
1660
1661 for (highlight_range, highlight_id) in highlights {
1662 // Add un-highlighted text before the current highlight
1663 if highlight_range.start > offset {
1664 runs.push(text_style.to_run(highlight_range.start - offset));
1665 }
1666
1667 let mut run_style = text_style.clone();
1668 if let Some(highlight_style) = highlight_id.style(syntax_theme) {
1669 run_style = run_style.highlight(highlight_style);
1670 }
1671 // add the highlighted range
1672 runs.push(run_style.to_run(highlight_range.len()));
1673 offset = highlight_range.end;
1674 }
1675
1676 // Add any remaining un-highlighted text
1677 if offset < text.len() {
1678 runs.push(text_style.to_run(text.len() - offset));
1679 }
1680
1681 StyledText::new(text).with_runs(runs)
1682 }
1683}
1684
1685#[derive(PartialEq)]
1686enum InputError {
1687 Warning(SharedString),
1688 Error(SharedString),
1689}
1690
1691impl InputError {
1692 fn warning(message: impl Into<SharedString>) -> Self {
1693 Self::Warning(message.into())
1694 }
1695
1696 fn error(error: anyhow::Error) -> Self {
1697 Self::Error(error.to_string().into())
1698 }
1699
1700 fn content(&self) -> &SharedString {
1701 match self {
1702 InputError::Warning(content) | InputError::Error(content) => content,
1703 }
1704 }
1705}
1706
1707struct KeybindingEditorModal {
1708 creating: bool,
1709 editing_keybind: ProcessedKeybinding,
1710 editing_keybind_idx: usize,
1711 keybind_editor: Entity<KeystrokeInput>,
1712 context_editor: Entity<SingleLineInput>,
1713 action_arguments_editor: Option<Entity<Editor>>,
1714 fs: Arc<dyn Fs>,
1715 error: Option<InputError>,
1716 keymap_editor: Entity<KeymapEditor>,
1717 workspace: WeakEntity<Workspace>,
1718 focus_state: KeybindingEditorModalFocusState,
1719}
1720
1721impl ModalView for KeybindingEditorModal {}
1722
1723impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
1724
1725impl Focusable for KeybindingEditorModal {
1726 fn focus_handle(&self, cx: &App) -> FocusHandle {
1727 self.keybind_editor.focus_handle(cx)
1728 }
1729}
1730
1731impl KeybindingEditorModal {
1732 pub fn new(
1733 create: bool,
1734 editing_keybind: ProcessedKeybinding,
1735 editing_keybind_idx: usize,
1736 keymap_editor: Entity<KeymapEditor>,
1737 workspace: WeakEntity<Workspace>,
1738 fs: Arc<dyn Fs>,
1739 window: &mut Window,
1740 cx: &mut App,
1741 ) -> Self {
1742 let keybind_editor = cx
1743 .new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx));
1744
1745 let context_editor: Entity<SingleLineInput> = cx.new(|cx| {
1746 let input = SingleLineInput::new(window, cx, "Keybinding Context")
1747 .label("Edit Context")
1748 .label_size(LabelSize::Default);
1749
1750 if let Some(context) = editing_keybind
1751 .context
1752 .as_ref()
1753 .and_then(KeybindContextString::local)
1754 {
1755 input.editor().update(cx, |editor, cx| {
1756 editor.set_text(context.clone(), window, cx);
1757 });
1758 }
1759
1760 let editor_entity = input.editor().clone();
1761 let workspace = workspace.clone();
1762 cx.spawn(async move |_input_handle, cx| {
1763 let contexts = cx
1764 .background_spawn(async { collect_contexts_from_assets() })
1765 .await;
1766
1767 let language = load_keybind_context_language(workspace, cx).await;
1768 editor_entity
1769 .update(cx, |editor, cx| {
1770 if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
1771 buffer.update(cx, |buffer, cx| {
1772 buffer.set_language(Some(language), cx);
1773 });
1774 }
1775 editor.set_completion_provider(Some(std::rc::Rc::new(
1776 KeyContextCompletionProvider { contexts },
1777 )));
1778 })
1779 .context("Failed to load completions for keybinding context")
1780 })
1781 .detach_and_log_err(cx);
1782
1783 input
1784 });
1785
1786 let action_arguments_editor = editing_keybind.action_schema.clone().map(|_schema| {
1787 cx.new(|cx| {
1788 let mut editor = Editor::auto_height_unbounded(1, window, cx);
1789 let workspace = workspace.clone();
1790
1791 if let Some(arguments) = editing_keybind.action_arguments.clone() {
1792 editor.set_text(arguments.text, window, cx);
1793 } else {
1794 // TODO: default value from schema?
1795 editor.set_placeholder_text("Action Arguments", cx);
1796 }
1797 cx.spawn(async |editor, cx| {
1798 let json_language = load_json_language(workspace, cx).await;
1799 editor
1800 .update(cx, |editor, cx| {
1801 if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
1802 buffer.update(cx, |buffer, cx| {
1803 buffer.set_language(Some(json_language), cx)
1804 });
1805 }
1806 })
1807 .context("Failed to load JSON language for editing keybinding action arguments input")
1808 })
1809 .detach_and_log_err(cx);
1810 editor
1811 })
1812 });
1813
1814 let focus_state = KeybindingEditorModalFocusState::new(
1815 keybind_editor.read_with(cx, |keybind_editor, cx| keybind_editor.focus_handle(cx)),
1816 action_arguments_editor.as_ref().map(|args_editor| {
1817 args_editor.read_with(cx, |args_editor, cx| args_editor.focus_handle(cx))
1818 }),
1819 context_editor.read_with(cx, |context_editor, cx| context_editor.focus_handle(cx)),
1820 );
1821
1822 Self {
1823 creating: create,
1824 editing_keybind,
1825 editing_keybind_idx,
1826 fs,
1827 keybind_editor,
1828 context_editor,
1829 action_arguments_editor,
1830 error: None,
1831 keymap_editor,
1832 workspace,
1833 focus_state,
1834 }
1835 }
1836
1837 fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) {
1838 if self
1839 .error
1840 .as_ref()
1841 .is_none_or(|old_error| *old_error != error)
1842 {
1843 self.error = Some(error);
1844 cx.notify();
1845 }
1846 }
1847
1848 fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
1849 let action_arguments = self
1850 .action_arguments_editor
1851 .as_ref()
1852 .map(|editor| editor.read(cx).text(cx));
1853
1854 let value = action_arguments
1855 .as_ref()
1856 .map(|args| {
1857 serde_json::from_str(args).context("Failed to parse action arguments as JSON")
1858 })
1859 .transpose()?;
1860
1861 cx.build_action(&self.editing_keybind.action_name, value)
1862 .context("Failed to validate action arguments")?;
1863 Ok(action_arguments)
1864 }
1865
1866 fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<Keystroke>> {
1867 let new_keystrokes = self
1868 .keybind_editor
1869 .read_with(cx, |editor, _| editor.keystrokes().to_vec());
1870 anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
1871 Ok(new_keystrokes)
1872 }
1873
1874 fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
1875 let new_context = self
1876 .context_editor
1877 .read_with(cx, |input, cx| input.editor().read(cx).text(cx));
1878 let Some(context) = new_context.is_empty().not().then_some(new_context) else {
1879 return Ok(None);
1880 };
1881 gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
1882
1883 Ok(Some(context))
1884 }
1885
1886 fn save_or_display_error(&mut self, cx: &mut Context<Self>) {
1887 self.save(cx).map_err(|err| self.set_error(err, cx)).ok();
1888 }
1889
1890 fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
1891 let existing_keybind = self.editing_keybind.clone();
1892 let fs = self.fs.clone();
1893 let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
1894
1895 let new_keystrokes = self
1896 .validate_keystrokes(cx)
1897 .map_err(InputError::error)?
1898 .into_iter()
1899 .map(remove_key_char)
1900 .collect::<Vec<_>>();
1901
1902 let new_context = self.validate_context(cx).map_err(InputError::error)?;
1903 let new_action_args = self
1904 .validate_action_arguments(cx)
1905 .map_err(InputError::error)?;
1906
1907 let action_mapping = ActionMapping {
1908 keystrokes: new_keystrokes,
1909 context: new_context.map(SharedString::from),
1910 };
1911
1912 let conflicting_indices = if self.creating {
1913 self.keymap_editor
1914 .read(cx)
1915 .keybinding_conflict_state
1916 .will_conflict(&action_mapping)
1917 } else {
1918 self.keymap_editor
1919 .read(cx)
1920 .keybinding_conflict_state
1921 .conflicting_indices_for_mapping(&action_mapping, self.editing_keybind_idx)
1922 };
1923
1924 conflicting_indices.map(|KeybindConflict {
1925 first_conflict_index,
1926 remaining_conflict_amount,
1927 }|
1928 {
1929 let conflicting_action_name = self
1930 .keymap_editor
1931 .read(cx)
1932 .keybindings
1933 .get(first_conflict_index)
1934 .map(|keybind| keybind.action_name);
1935
1936 let warning_message = match conflicting_action_name {
1937 Some(name) => {
1938 if remaining_conflict_amount > 0 {
1939 format!(
1940 "Your keybind would conflict with the \"{}\" action and {} other bindings",
1941 name, remaining_conflict_amount
1942 )
1943 } else {
1944 format!("Your keybind would conflict with the \"{}\" action", name)
1945 }
1946 }
1947 None => {
1948 log::info!(
1949 "Could not find action in keybindings with index {}",
1950 first_conflict_index
1951 );
1952 "Your keybind would conflict with other actions".to_string()
1953 }
1954 };
1955
1956 let warning = InputError::warning(warning_message);
1957 if self.error.as_ref().is_some_and(|old_error| *old_error == warning) {
1958 Ok(())
1959 } else {
1960 Err(warning)
1961 }
1962 }).unwrap_or(Ok(()))?;
1963
1964 let create = self.creating;
1965
1966 let status_toast = StatusToast::new(
1967 format!(
1968 "Saved edits to the {} action.",
1969 &self.editing_keybind.humanized_action_name
1970 ),
1971 cx,
1972 move |this, _cx| {
1973 this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
1974 .dismiss_button(true)
1975 // .action("Undo", f) todo: wire the undo functionality
1976 },
1977 );
1978
1979 self.workspace
1980 .update(cx, |workspace, cx| {
1981 workspace.toggle_status_toast(status_toast, cx);
1982 })
1983 .log_err();
1984
1985 cx.spawn(async move |this, cx| {
1986 let action_name = existing_keybind.action_name;
1987
1988 if let Err(err) = save_keybinding_update(
1989 create,
1990 existing_keybind,
1991 &action_mapping,
1992 new_action_args.as_deref(),
1993 &fs,
1994 tab_size,
1995 )
1996 .await
1997 {
1998 this.update(cx, |this, cx| {
1999 this.set_error(InputError::error(err), cx);
2000 })
2001 .log_err();
2002 } else {
2003 this.update(cx, |this, cx| {
2004 this.keymap_editor.update(cx, |keymap, cx| {
2005 keymap.previous_edit = Some(PreviousEdit::Keybinding {
2006 action_mapping,
2007 action_name,
2008 fallback: keymap
2009 .table_interaction_state
2010 .read(cx)
2011 .get_scrollbar_offset(Axis::Vertical),
2012 })
2013 });
2014 cx.emit(DismissEvent);
2015 })
2016 .ok();
2017 }
2018 })
2019 .detach();
2020
2021 Ok(())
2022 }
2023
2024 fn key_context(&self) -> KeyContext {
2025 let mut key_context = KeyContext::new_with_defaults();
2026 key_context.add("KeybindEditorModal");
2027 key_context
2028 }
2029
2030 fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
2031 self.focus_state.focus_next(window, cx);
2032 }
2033
2034 fn focus_prev(
2035 &mut self,
2036 _: &menu::SelectPrevious,
2037 window: &mut Window,
2038 cx: &mut Context<Self>,
2039 ) {
2040 self.focus_state.focus_previous(window, cx);
2041 }
2042
2043 fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
2044 self.save_or_display_error(cx);
2045 }
2046
2047 fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context<Self>) {
2048 cx.emit(DismissEvent)
2049 }
2050}
2051
2052fn remove_key_char(Keystroke { modifiers, key, .. }: Keystroke) -> Keystroke {
2053 Keystroke {
2054 modifiers,
2055 key,
2056 ..Default::default()
2057 }
2058}
2059
2060impl Render for KeybindingEditorModal {
2061 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2062 let theme = cx.theme().colors();
2063
2064 v_flex()
2065 .w(rems(34.))
2066 .elevation_3(cx)
2067 .key_context(self.key_context())
2068 .on_action(cx.listener(Self::focus_next))
2069 .on_action(cx.listener(Self::focus_prev))
2070 .on_action(cx.listener(Self::confirm))
2071 .on_action(cx.listener(Self::cancel))
2072 .child(
2073 Modal::new("keybinding_editor_modal", None)
2074 .header(
2075 ModalHeader::new().child(
2076 v_flex()
2077 .pb_1p5()
2078 .mb_1()
2079 .gap_0p5()
2080 .border_b_1()
2081 .border_color(theme.border_variant)
2082 .child(Label::new(
2083 self.editing_keybind.humanized_action_name.clone(),
2084 ))
2085 .when_some(self.editing_keybind.action_docs, |this, docs| {
2086 this.child(
2087 Label::new(docs).size(LabelSize::Small).color(Color::Muted),
2088 )
2089 }),
2090 ),
2091 )
2092 .section(
2093 Section::new().child(
2094 v_flex()
2095 .gap_2()
2096 .child(
2097 v_flex()
2098 .child(Label::new("Edit Keystroke"))
2099 .gap_1()
2100 .child(self.keybind_editor.clone()),
2101 )
2102 .when_some(self.action_arguments_editor.clone(), |this, editor| {
2103 this.child(
2104 v_flex()
2105 .mt_1p5()
2106 .gap_1()
2107 .child(Label::new("Edit Arguments"))
2108 .child(
2109 div()
2110 .w_full()
2111 .py_1()
2112 .px_1p5()
2113 .rounded_lg()
2114 .bg(theme.editor_background)
2115 .border_1()
2116 .border_color(theme.border_variant)
2117 .child(editor),
2118 ),
2119 )
2120 })
2121 .child(self.context_editor.clone())
2122 .when_some(self.error.as_ref(), |this, error| {
2123 this.child(
2124 Banner::new()
2125 .map(|banner| match error {
2126 InputError::Error(_) => {
2127 banner.severity(ui::Severity::Error)
2128 }
2129 InputError::Warning(_) => {
2130 banner.severity(ui::Severity::Warning)
2131 }
2132 })
2133 // For some reason, the div overflows its container to the
2134 //right. The padding accounts for that.
2135 .child(
2136 div()
2137 .size_full()
2138 .pr_2()
2139 .child(Label::new(error.content())),
2140 ),
2141 )
2142 }),
2143 ),
2144 )
2145 .footer(
2146 ModalFooter::new().end_slot(
2147 h_flex()
2148 .gap_1()
2149 .child(
2150 Button::new("cancel", "Cancel")
2151 .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
2152 )
2153 .child(Button::new("save-btn", "Save").on_click(cx.listener(
2154 |this, _event, _window, cx| {
2155 this.save_or_display_error(cx);
2156 },
2157 ))),
2158 ),
2159 ),
2160 )
2161 }
2162}
2163
2164struct KeybindingEditorModalFocusState {
2165 handles: Vec<FocusHandle>,
2166}
2167
2168impl KeybindingEditorModalFocusState {
2169 fn new(
2170 keystrokes: FocusHandle,
2171 action_input: Option<FocusHandle>,
2172 context: FocusHandle,
2173 ) -> Self {
2174 Self {
2175 handles: Vec::from_iter(
2176 [Some(keystrokes), action_input, Some(context)]
2177 .into_iter()
2178 .flatten(),
2179 ),
2180 }
2181 }
2182
2183 fn focused_index(&self, window: &Window, cx: &App) -> Option<i32> {
2184 self.handles
2185 .iter()
2186 .position(|handle| handle.contains_focused(window, cx))
2187 .map(|i| i as i32)
2188 }
2189
2190 fn focus_index(&self, mut index: i32, window: &mut Window) {
2191 if index < 0 {
2192 index = self.handles.len() as i32 - 1;
2193 }
2194 if index >= self.handles.len() as i32 {
2195 index = 0;
2196 }
2197 window.focus(&self.handles[index as usize]);
2198 }
2199
2200 fn focus_next(&self, window: &mut Window, cx: &App) {
2201 let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
2202 index + 1
2203 } else {
2204 0
2205 };
2206 self.focus_index(index_to_focus, window);
2207 }
2208
2209 fn focus_previous(&self, window: &mut Window, cx: &App) {
2210 let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
2211 index - 1
2212 } else {
2213 self.handles.len() as i32 - 1
2214 };
2215 self.focus_index(index_to_focus, window);
2216 }
2217}
2218
2219struct KeyContextCompletionProvider {
2220 contexts: Vec<SharedString>,
2221}
2222
2223impl CompletionProvider for KeyContextCompletionProvider {
2224 fn completions(
2225 &self,
2226 _excerpt_id: editor::ExcerptId,
2227 buffer: &Entity<language::Buffer>,
2228 buffer_position: language::Anchor,
2229 _trigger: editor::CompletionContext,
2230 _window: &mut Window,
2231 cx: &mut Context<Editor>,
2232 ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
2233 let buffer = buffer.read(cx);
2234 let mut count_back = 0;
2235 for char in buffer.reversed_chars_at(buffer_position) {
2236 if char.is_ascii_alphanumeric() || char == '_' {
2237 count_back += 1;
2238 } else {
2239 break;
2240 }
2241 }
2242 let start_anchor = buffer.anchor_before(
2243 buffer_position
2244 .to_offset(&buffer)
2245 .saturating_sub(count_back),
2246 );
2247 let replace_range = start_anchor..buffer_position;
2248 gpui::Task::ready(Ok(vec![project::CompletionResponse {
2249 completions: self
2250 .contexts
2251 .iter()
2252 .map(|context| project::Completion {
2253 replace_range: replace_range.clone(),
2254 label: language::CodeLabel::plain(context.to_string(), None),
2255 new_text: context.to_string(),
2256 documentation: None,
2257 source: project::CompletionSource::Custom,
2258 icon_path: None,
2259 insert_text_mode: None,
2260 confirm: None,
2261 })
2262 .collect(),
2263 is_incomplete: false,
2264 }]))
2265 }
2266
2267 fn is_completion_trigger(
2268 &self,
2269 _buffer: &Entity<language::Buffer>,
2270 _position: language::Anchor,
2271 text: &str,
2272 _trigger_in_words: bool,
2273 _menu_is_open: bool,
2274 _cx: &mut Context<Editor>,
2275 ) -> bool {
2276 text.chars().last().map_or(false, |last_char| {
2277 last_char.is_ascii_alphanumeric() || last_char == '_'
2278 })
2279 }
2280}
2281
2282async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
2283 let json_language_task = workspace
2284 .read_with(cx, |workspace, cx| {
2285 workspace
2286 .project()
2287 .read(cx)
2288 .languages()
2289 .language_for_name("JSON")
2290 })
2291 .context("Failed to load JSON language")
2292 .log_err();
2293 let json_language = match json_language_task {
2294 Some(task) => task.await.context("Failed to load JSON language").log_err(),
2295 None => None,
2296 };
2297 return json_language.unwrap_or_else(|| {
2298 Arc::new(Language::new(
2299 LanguageConfig {
2300 name: "JSON".into(),
2301 ..Default::default()
2302 },
2303 Some(tree_sitter_json::LANGUAGE.into()),
2304 ))
2305 });
2306}
2307
2308async fn load_keybind_context_language(
2309 workspace: WeakEntity<Workspace>,
2310 cx: &mut AsyncApp,
2311) -> Arc<Language> {
2312 let language_task = workspace
2313 .read_with(cx, |workspace, cx| {
2314 workspace
2315 .project()
2316 .read(cx)
2317 .languages()
2318 .language_for_name("Zed Keybind Context")
2319 })
2320 .context("Failed to load Zed Keybind Context language")
2321 .log_err();
2322 let language = match language_task {
2323 Some(task) => task
2324 .await
2325 .context("Failed to load Zed Keybind Context language")
2326 .log_err(),
2327 None => None,
2328 };
2329 return language.unwrap_or_else(|| {
2330 Arc::new(Language::new(
2331 LanguageConfig {
2332 name: "Zed Keybind Context".into(),
2333 ..Default::default()
2334 },
2335 Some(tree_sitter_rust::LANGUAGE.into()),
2336 ))
2337 });
2338}
2339
2340async fn save_keybinding_update(
2341 create: bool,
2342 existing: ProcessedKeybinding,
2343 action_mapping: &ActionMapping,
2344 new_args: Option<&str>,
2345 fs: &Arc<dyn Fs>,
2346 tab_size: usize,
2347) -> anyhow::Result<()> {
2348 let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
2349 .await
2350 .context("Failed to load keymap file")?;
2351
2352 let existing_keystrokes = existing.keystrokes().unwrap_or_default();
2353 let existing_context = existing
2354 .context
2355 .as_ref()
2356 .and_then(KeybindContextString::local_str);
2357 let existing_args = existing
2358 .action_arguments
2359 .as_ref()
2360 .map(|args| args.text.as_ref());
2361
2362 let target = settings::KeybindUpdateTarget {
2363 context: existing_context,
2364 keystrokes: existing_keystrokes,
2365 action_name: &existing.action_name,
2366 action_arguments: existing_args,
2367 };
2368
2369 let source = settings::KeybindUpdateTarget {
2370 context: action_mapping.context.as_ref().map(|a| &***a),
2371 keystrokes: &action_mapping.keystrokes,
2372 action_name: &existing.action_name,
2373 action_arguments: new_args,
2374 };
2375
2376 let operation = if !create {
2377 settings::KeybindUpdateOperation::Replace {
2378 target,
2379 target_keybind_source: existing
2380 .source
2381 .as_ref()
2382 .map(|(source, _name)| *source)
2383 .unwrap_or(KeybindSource::User),
2384 source,
2385 }
2386 } else {
2387 settings::KeybindUpdateOperation::Add {
2388 source,
2389 from: Some(target),
2390 }
2391 };
2392
2393 let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
2394
2395 let updated_keymap_contents =
2396 settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
2397 .context("Failed to update keybinding")?;
2398 fs.write(
2399 paths::keymap_file().as_path(),
2400 updated_keymap_contents.as_bytes(),
2401 )
2402 .await
2403 .context("Failed to write keymap file")?;
2404
2405 telemetry::event!(
2406 "Keybinding Updated",
2407 new_keybinding = new_keybinding,
2408 removed_keybinding = removed_keybinding,
2409 source = source
2410 );
2411 Ok(())
2412}
2413
2414async fn remove_keybinding(
2415 existing: ProcessedKeybinding,
2416 fs: &Arc<dyn Fs>,
2417 tab_size: usize,
2418) -> anyhow::Result<()> {
2419 let Some(keystrokes) = existing.keystrokes() else {
2420 anyhow::bail!("Cannot remove a keybinding that does not exist");
2421 };
2422 let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
2423 .await
2424 .context("Failed to load keymap file")?;
2425
2426 let operation = settings::KeybindUpdateOperation::Remove {
2427 target: settings::KeybindUpdateTarget {
2428 context: existing
2429 .context
2430 .as_ref()
2431 .and_then(KeybindContextString::local_str),
2432 keystrokes,
2433 action_name: &existing.action_name,
2434 action_arguments: existing
2435 .action_arguments
2436 .as_ref()
2437 .map(|arguments| arguments.text.as_ref()),
2438 },
2439 target_keybind_source: existing
2440 .source
2441 .as_ref()
2442 .map(|(source, _name)| *source)
2443 .unwrap_or(KeybindSource::User),
2444 };
2445
2446 let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
2447 let updated_keymap_contents =
2448 settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
2449 .context("Failed to update keybinding")?;
2450 fs.write(
2451 paths::keymap_file().as_path(),
2452 updated_keymap_contents.as_bytes(),
2453 )
2454 .await
2455 .context("Failed to write keymap file")?;
2456
2457 telemetry::event!(
2458 "Keybinding Removed",
2459 new_keybinding = new_keybinding,
2460 removed_keybinding = removed_keybinding,
2461 source = source
2462 );
2463 Ok(())
2464}
2465
2466#[derive(PartialEq, Eq, Debug, Copy, Clone)]
2467enum CloseKeystrokeResult {
2468 Partial,
2469 Close,
2470 None,
2471}
2472
2473#[derive(PartialEq, Eq, Debug, Clone)]
2474enum KeyPress<'a> {
2475 Alt,
2476 Control,
2477 Function,
2478 Shift,
2479 Platform,
2480 Key(&'a String),
2481}
2482
2483struct KeystrokeInput {
2484 keystrokes: Vec<Keystroke>,
2485 placeholder_keystrokes: Option<Vec<Keystroke>>,
2486 outer_focus_handle: FocusHandle,
2487 inner_focus_handle: FocusHandle,
2488 intercept_subscription: Option<Subscription>,
2489 _focus_subscriptions: [Subscription; 2],
2490 search: bool,
2491 /// Handles tripe escape to stop recording
2492 close_keystrokes: Option<Vec<Keystroke>>,
2493 close_keystrokes_start: Option<usize>,
2494}
2495
2496impl KeystrokeInput {
2497 const KEYSTROKE_COUNT_MAX: usize = 3;
2498
2499 fn new(
2500 placeholder_keystrokes: Option<Vec<Keystroke>>,
2501 window: &mut Window,
2502 cx: &mut Context<Self>,
2503 ) -> Self {
2504 let outer_focus_handle = cx.focus_handle();
2505 let inner_focus_handle = cx.focus_handle();
2506 let _focus_subscriptions = [
2507 cx.on_focus_in(&inner_focus_handle, window, Self::on_inner_focus_in),
2508 cx.on_focus_out(&inner_focus_handle, window, Self::on_inner_focus_out),
2509 ];
2510 Self {
2511 keystrokes: Vec::new(),
2512 placeholder_keystrokes,
2513 inner_focus_handle,
2514 outer_focus_handle,
2515 intercept_subscription: None,
2516 _focus_subscriptions,
2517 search: false,
2518 close_keystrokes: None,
2519 close_keystrokes_start: None,
2520 }
2521 }
2522
2523 fn set_keystrokes(&mut self, keystrokes: Vec<Keystroke>, cx: &mut Context<Self>) {
2524 self.keystrokes = keystrokes;
2525 self.keystrokes_changed(cx);
2526 }
2527
2528 fn dummy(modifiers: Modifiers) -> Keystroke {
2529 return Keystroke {
2530 modifiers,
2531 key: "".to_string(),
2532 key_char: None,
2533 };
2534 }
2535
2536 fn keystrokes_changed(&self, cx: &mut Context<Self>) {
2537 cx.emit(());
2538 cx.notify();
2539 }
2540
2541 fn key_context() -> KeyContext {
2542 let mut key_context = KeyContext::new_with_defaults();
2543 key_context.add("KeystrokeInput");
2544 key_context
2545 }
2546
2547 fn handle_possible_close_keystroke(
2548 &mut self,
2549 keystroke: &Keystroke,
2550 window: &mut Window,
2551 cx: &mut Context<Self>,
2552 ) -> CloseKeystrokeResult {
2553 let Some(keybind_for_close_action) = window
2554 .highest_precedence_binding_for_action_in_context(&StopRecording, Self::key_context())
2555 else {
2556 log::trace!("No keybinding to stop recording keystrokes in keystroke input");
2557 self.close_keystrokes.take();
2558 return CloseKeystrokeResult::None;
2559 };
2560 let action_keystrokes = keybind_for_close_action.keystrokes();
2561
2562 if let Some(mut close_keystrokes) = self.close_keystrokes.take() {
2563 let mut index = 0;
2564
2565 while index < action_keystrokes.len() && index < close_keystrokes.len() {
2566 if !close_keystrokes[index].should_match(&action_keystrokes[index]) {
2567 break;
2568 }
2569 index += 1;
2570 }
2571 if index == close_keystrokes.len() {
2572 if index >= action_keystrokes.len() {
2573 self.close_keystrokes_start.take();
2574 return CloseKeystrokeResult::None;
2575 }
2576 if keystroke.should_match(&action_keystrokes[index]) {
2577 if action_keystrokes.len() >= 1 && index == action_keystrokes.len() - 1 {
2578 self.stop_recording(&StopRecording, window, cx);
2579 return CloseKeystrokeResult::Close;
2580 } else {
2581 close_keystrokes.push(keystroke.clone());
2582 self.close_keystrokes = Some(close_keystrokes);
2583 return CloseKeystrokeResult::Partial;
2584 }
2585 } else {
2586 self.close_keystrokes_start.take();
2587 return CloseKeystrokeResult::None;
2588 }
2589 }
2590 } else if let Some(first_action_keystroke) = action_keystrokes.first()
2591 && keystroke.should_match(first_action_keystroke)
2592 {
2593 self.close_keystrokes = Some(vec![keystroke.clone()]);
2594 return CloseKeystrokeResult::Partial;
2595 }
2596 self.close_keystrokes_start.take();
2597 return CloseKeystrokeResult::None;
2598 }
2599
2600 fn on_modifiers_changed(
2601 &mut self,
2602 event: &ModifiersChangedEvent,
2603 _window: &mut Window,
2604 cx: &mut Context<Self>,
2605 ) {
2606 let keystrokes_len = self.keystrokes.len();
2607
2608 if let Some(last) = self.keystrokes.last_mut()
2609 && last.key.is_empty()
2610 && keystrokes_len <= Self::KEYSTROKE_COUNT_MAX
2611 {
2612 if self.search {
2613 last.modifiers = last.modifiers.xor(&event.modifiers);
2614 } else if !event.modifiers.modified() {
2615 self.keystrokes.pop();
2616 } else {
2617 last.modifiers = event.modifiers;
2618 }
2619
2620 self.keystrokes_changed(cx);
2621 } else if keystrokes_len < Self::KEYSTROKE_COUNT_MAX {
2622 self.keystrokes.push(Self::dummy(event.modifiers));
2623 self.keystrokes_changed(cx);
2624 }
2625 cx.stop_propagation();
2626 }
2627
2628 fn handle_keystroke(
2629 &mut self,
2630 keystroke: &Keystroke,
2631 window: &mut Window,
2632 cx: &mut Context<Self>,
2633 ) {
2634 let close_keystroke_result = self.handle_possible_close_keystroke(keystroke, window, cx);
2635 if close_keystroke_result != CloseKeystrokeResult::Close {
2636 let key_len = self.keystrokes.len();
2637 if let Some(last) = self.keystrokes.last_mut()
2638 && last.key.is_empty()
2639 && key_len <= Self::KEYSTROKE_COUNT_MAX
2640 {
2641 if self.search {
2642 last.key = keystroke.key.clone();
2643 self.keystrokes_changed(cx);
2644 cx.stop_propagation();
2645 return;
2646 } else {
2647 self.keystrokes.pop();
2648 }
2649 }
2650 if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX {
2651 if close_keystroke_result == CloseKeystrokeResult::Partial
2652 && self.close_keystrokes_start.is_none()
2653 {
2654 self.close_keystrokes_start = Some(self.keystrokes.len());
2655 }
2656 self.keystrokes.push(keystroke.clone());
2657 if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX {
2658 self.keystrokes.push(Self::dummy(keystroke.modifiers));
2659 }
2660 } else if close_keystroke_result != CloseKeystrokeResult::Partial {
2661 self.clear_keystrokes(&ClearKeystrokes, window, cx);
2662 }
2663 }
2664 self.keystrokes_changed(cx);
2665 cx.stop_propagation();
2666 }
2667
2668 fn on_inner_focus_in(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
2669 if self.intercept_subscription.is_none() {
2670 let listener = cx.listener(|this, event: &gpui::KeystrokeEvent, window, cx| {
2671 this.handle_keystroke(&event.keystroke, window, cx);
2672 });
2673 self.intercept_subscription = Some(cx.intercept_keystrokes(listener))
2674 }
2675 }
2676
2677 fn on_inner_focus_out(
2678 &mut self,
2679 _event: gpui::FocusOutEvent,
2680 _window: &mut Window,
2681 cx: &mut Context<Self>,
2682 ) {
2683 self.intercept_subscription.take();
2684 cx.notify();
2685 }
2686
2687 fn keystrokes(&self) -> &[Keystroke] {
2688 if let Some(placeholders) = self.placeholder_keystrokes.as_ref()
2689 && self.keystrokes.is_empty()
2690 {
2691 return placeholders;
2692 }
2693 if !self.search
2694 && self
2695 .keystrokes
2696 .last()
2697 .map_or(false, |last| last.key.is_empty())
2698 {
2699 return &self.keystrokes[..self.keystrokes.len() - 1];
2700 }
2701 return &self.keystrokes;
2702 }
2703
2704 fn render_keystrokes(&self, is_recording: bool) -> impl Iterator<Item = Div> {
2705 let keystrokes = if let Some(placeholders) = self.placeholder_keystrokes.as_ref()
2706 && self.keystrokes.is_empty()
2707 {
2708 if is_recording {
2709 &[]
2710 } else {
2711 placeholders.as_slice()
2712 }
2713 } else {
2714 &self.keystrokes
2715 };
2716 keystrokes.iter().map(move |keystroke| {
2717 h_flex().children(ui::render_keystroke(
2718 keystroke,
2719 Some(Color::Default),
2720 Some(rems(0.875).into()),
2721 ui::PlatformStyle::platform(),
2722 false,
2723 ))
2724 })
2725 }
2726
2727 fn recording_focus_handle(&self, _cx: &App) -> FocusHandle {
2728 self.inner_focus_handle.clone()
2729 }
2730
2731 fn start_recording(&mut self, _: &StartRecording, window: &mut Window, cx: &mut Context<Self>) {
2732 if !self.outer_focus_handle.is_focused(window) {
2733 return;
2734 }
2735 self.clear_keystrokes(&ClearKeystrokes, window, cx);
2736 window.focus(&self.inner_focus_handle);
2737 cx.notify();
2738 }
2739
2740 fn stop_recording(&mut self, _: &StopRecording, window: &mut Window, cx: &mut Context<Self>) {
2741 if !self.inner_focus_handle.is_focused(window) {
2742 return;
2743 }
2744 window.focus(&self.outer_focus_handle);
2745 if let Some(close_keystrokes_start) = self.close_keystrokes_start.take() {
2746 self.keystrokes.drain(close_keystrokes_start..);
2747 }
2748 self.close_keystrokes.take();
2749 cx.notify();
2750 }
2751
2752 fn clear_keystrokes(
2753 &mut self,
2754 _: &ClearKeystrokes,
2755 _window: &mut Window,
2756 cx: &mut Context<Self>,
2757 ) {
2758 self.keystrokes.clear();
2759 self.keystrokes_changed(cx);
2760 }
2761}
2762
2763impl EventEmitter<()> for KeystrokeInput {}
2764
2765impl Focusable for KeystrokeInput {
2766 fn focus_handle(&self, _cx: &App) -> FocusHandle {
2767 self.outer_focus_handle.clone()
2768 }
2769}
2770
2771impl Render for KeystrokeInput {
2772 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2773 let colors = cx.theme().colors();
2774 let is_focused = self.outer_focus_handle.contains_focused(window, cx);
2775 let is_recording = self.inner_focus_handle.is_focused(window);
2776
2777 let horizontal_padding = rems_from_px(64.);
2778
2779 let recording_bg_color = colors
2780 .editor_background
2781 .blend(colors.text_accent.opacity(0.1));
2782
2783 let recording_pulse = |color: Color| {
2784 Icon::new(IconName::Circle)
2785 .size(IconSize::Small)
2786 .color(Color::Error)
2787 .with_animation(
2788 "recording-pulse",
2789 Animation::new(std::time::Duration::from_secs(2))
2790 .repeat()
2791 .with_easing(gpui::pulsating_between(0.4, 0.8)),
2792 {
2793 let color = color.color(cx);
2794 move |this, delta| this.color(Color::Custom(color.opacity(delta)))
2795 },
2796 )
2797 };
2798
2799 let recording_indicator = h_flex()
2800 .h_4()
2801 .pr_1()
2802 .gap_0p5()
2803 .border_1()
2804 .border_color(colors.border)
2805 .bg(colors
2806 .editor_background
2807 .blend(colors.text_accent.opacity(0.1)))
2808 .rounded_sm()
2809 .child(recording_pulse(Color::Error))
2810 .child(
2811 Label::new("REC")
2812 .size(LabelSize::XSmall)
2813 .weight(FontWeight::SEMIBOLD)
2814 .color(Color::Error),
2815 );
2816
2817 let search_indicator = h_flex()
2818 .h_4()
2819 .pr_1()
2820 .gap_0p5()
2821 .border_1()
2822 .border_color(colors.border)
2823 .bg(colors
2824 .editor_background
2825 .blend(colors.text_accent.opacity(0.1)))
2826 .rounded_sm()
2827 .child(recording_pulse(Color::Accent))
2828 .child(
2829 Label::new("SEARCH")
2830 .size(LabelSize::XSmall)
2831 .weight(FontWeight::SEMIBOLD)
2832 .color(Color::Accent),
2833 );
2834
2835 let record_icon = if self.search {
2836 IconName::MagnifyingGlass
2837 } else {
2838 IconName::PlayFilled
2839 };
2840
2841 h_flex()
2842 .id("keystroke-input")
2843 .track_focus(&self.outer_focus_handle)
2844 .py_2()
2845 .px_3()
2846 .gap_2()
2847 .min_h_10()
2848 .w_full()
2849 .flex_1()
2850 .justify_between()
2851 .rounded_lg()
2852 .overflow_hidden()
2853 .map(|this| {
2854 if is_recording {
2855 this.bg(recording_bg_color)
2856 } else {
2857 this.bg(colors.editor_background)
2858 }
2859 })
2860 .border_1()
2861 .border_color(colors.border_variant)
2862 .when(is_focused, |parent| {
2863 parent.border_color(colors.border_focused)
2864 })
2865 .key_context(Self::key_context())
2866 .on_action(cx.listener(Self::start_recording))
2867 .on_action(cx.listener(Self::stop_recording))
2868 .child(
2869 h_flex()
2870 .w(horizontal_padding)
2871 .gap_0p5()
2872 .justify_start()
2873 .flex_none()
2874 .when(is_recording, |this| {
2875 this.map(|this| {
2876 if self.search {
2877 this.child(search_indicator)
2878 } else {
2879 this.child(recording_indicator)
2880 }
2881 })
2882 }),
2883 )
2884 .child(
2885 h_flex()
2886 .id("keystroke-input-inner")
2887 .track_focus(&self.inner_focus_handle)
2888 .on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
2889 .size_full()
2890 .when(!self.search, |this| {
2891 this.focus(|mut style| {
2892 style.border_color = Some(colors.border_focused);
2893 style
2894 })
2895 })
2896 .w_full()
2897 .min_w_0()
2898 .justify_center()
2899 .flex_wrap()
2900 .gap(ui::DynamicSpacing::Base04.rems(cx))
2901 .children(self.render_keystrokes(is_recording)),
2902 )
2903 .child(
2904 h_flex()
2905 .w(horizontal_padding)
2906 .gap_0p5()
2907 .justify_end()
2908 .flex_none()
2909 .map(|this| {
2910 if is_recording {
2911 this.child(
2912 IconButton::new("stop-record-btn", IconName::StopFilled)
2913 .shape(ui::IconButtonShape::Square)
2914 .map(|this| {
2915 this.tooltip(Tooltip::for_action_title(
2916 if self.search {
2917 "Stop Searching"
2918 } else {
2919 "Stop Recording"
2920 },
2921 &StopRecording,
2922 ))
2923 })
2924 .icon_color(Color::Error)
2925 .on_click(cx.listener(|this, _event, window, cx| {
2926 this.stop_recording(&StopRecording, window, cx);
2927 })),
2928 )
2929 } else {
2930 this.child(
2931 IconButton::new("record-btn", record_icon)
2932 .shape(ui::IconButtonShape::Square)
2933 .map(|this| {
2934 this.tooltip(Tooltip::for_action_title(
2935 if self.search {
2936 "Start Searching"
2937 } else {
2938 "Start Recording"
2939 },
2940 &StartRecording,
2941 ))
2942 })
2943 .when(!is_focused, |this| this.icon_color(Color::Muted))
2944 .on_click(cx.listener(|this, _event, window, cx| {
2945 this.start_recording(&StartRecording, window, cx);
2946 })),
2947 )
2948 }
2949 })
2950 .child(
2951 IconButton::new("clear-btn", IconName::Delete)
2952 .shape(ui::IconButtonShape::Square)
2953 .tooltip(Tooltip::for_action_title(
2954 "Clear Keystrokes",
2955 &ClearKeystrokes,
2956 ))
2957 .when(!is_recording || !is_focused, |this| {
2958 this.icon_color(Color::Muted)
2959 })
2960 .on_click(cx.listener(|this, _event, window, cx| {
2961 this.clear_keystrokes(&ClearKeystrokes, window, cx);
2962 })),
2963 ),
2964 )
2965 }
2966}
2967
2968fn collect_contexts_from_assets() -> Vec<SharedString> {
2969 let mut keymap_assets = vec![
2970 util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
2971 util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
2972 ];
2973 keymap_assets.extend(
2974 BaseKeymap::OPTIONS
2975 .iter()
2976 .filter_map(|(_, base_keymap)| base_keymap.asset_path())
2977 .map(util::asset_str::<SettingsAssets>),
2978 );
2979
2980 let mut contexts = HashSet::default();
2981
2982 for keymap_asset in keymap_assets {
2983 let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
2984 continue;
2985 };
2986
2987 for section in keymap.sections() {
2988 let context_expr = §ion.context;
2989 let mut queue = Vec::new();
2990 let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
2991 continue;
2992 };
2993
2994 queue.push(root_context);
2995 while let Some(context) = queue.pop() {
2996 match context {
2997 gpui::KeyBindingContextPredicate::Identifier(ident) => {
2998 contexts.insert(ident);
2999 }
3000 gpui::KeyBindingContextPredicate::Equal(ident_a, ident_b) => {
3001 contexts.insert(ident_a);
3002 contexts.insert(ident_b);
3003 }
3004 gpui::KeyBindingContextPredicate::NotEqual(ident_a, ident_b) => {
3005 contexts.insert(ident_a);
3006 contexts.insert(ident_b);
3007 }
3008 gpui::KeyBindingContextPredicate::Child(ctx_a, ctx_b) => {
3009 queue.push(*ctx_a);
3010 queue.push(*ctx_b);
3011 }
3012 gpui::KeyBindingContextPredicate::Not(ctx) => {
3013 queue.push(*ctx);
3014 }
3015 gpui::KeyBindingContextPredicate::And(ctx_a, ctx_b) => {
3016 queue.push(*ctx_a);
3017 queue.push(*ctx_b);
3018 }
3019 gpui::KeyBindingContextPredicate::Or(ctx_a, ctx_b) => {
3020 queue.push(*ctx_a);
3021 queue.push(*ctx_b);
3022 }
3023 }
3024 }
3025 }
3026 }
3027
3028 let mut contexts = contexts.into_iter().collect::<Vec<_>>();
3029 contexts.sort();
3030
3031 return contexts;
3032}
3033
3034impl SerializableItem for KeymapEditor {
3035 fn serialized_item_kind() -> &'static str {
3036 "KeymapEditor"
3037 }
3038
3039 fn cleanup(
3040 workspace_id: workspace::WorkspaceId,
3041 alive_items: Vec<workspace::ItemId>,
3042 _window: &mut Window,
3043 cx: &mut App,
3044 ) -> gpui::Task<gpui::Result<()>> {
3045 workspace::delete_unloaded_items(
3046 alive_items,
3047 workspace_id,
3048 "keybinding_editors",
3049 &KEYBINDING_EDITORS,
3050 cx,
3051 )
3052 }
3053
3054 fn deserialize(
3055 _project: Entity<project::Project>,
3056 workspace: WeakEntity<Workspace>,
3057 workspace_id: workspace::WorkspaceId,
3058 item_id: workspace::ItemId,
3059 window: &mut Window,
3060 cx: &mut App,
3061 ) -> gpui::Task<gpui::Result<Entity<Self>>> {
3062 window.spawn(cx, async move |cx| {
3063 if KEYBINDING_EDITORS
3064 .get_keybinding_editor(item_id, workspace_id)?
3065 .is_some()
3066 {
3067 cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
3068 } else {
3069 Err(anyhow!("No keybinding editor to deserialize"))
3070 }
3071 })
3072 }
3073
3074 fn serialize(
3075 &mut self,
3076 workspace: &mut Workspace,
3077 item_id: workspace::ItemId,
3078 _closing: bool,
3079 _window: &mut Window,
3080 cx: &mut ui::Context<Self>,
3081 ) -> Option<gpui::Task<gpui::Result<()>>> {
3082 let workspace_id = workspace.database_id()?;
3083 Some(cx.background_spawn(async move {
3084 KEYBINDING_EDITORS
3085 .save_keybinding_editor(item_id, workspace_id)
3086 .await
3087 }))
3088 }
3089
3090 fn should_serialize(&self, _event: &Self::Event) -> bool {
3091 false
3092 }
3093}
3094
3095mod persistence {
3096 use db::{define_connection, query, sqlez_macros::sql};
3097 use workspace::WorkspaceDb;
3098
3099 define_connection! {
3100 pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
3101 &[sql!(
3102 CREATE TABLE keybinding_editors (
3103 workspace_id INTEGER,
3104 item_id INTEGER UNIQUE,
3105
3106 PRIMARY KEY(workspace_id, item_id),
3107 FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
3108 ON DELETE CASCADE
3109 ) STRICT;
3110 )];
3111 }
3112
3113 impl KeybindingEditorDb {
3114 query! {
3115 pub async fn save_keybinding_editor(
3116 item_id: workspace::ItemId,
3117 workspace_id: workspace::WorkspaceId
3118 ) -> Result<()> {
3119 INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
3120 VALUES (?, ?)
3121 }
3122 }
3123
3124 query! {
3125 pub fn get_keybinding_editor(
3126 item_id: workspace::ItemId,
3127 workspace_id: workspace::WorkspaceId
3128 ) -> Result<Option<workspace::ItemId>> {
3129 SELECT item_id
3130 FROM keybinding_editors
3131 WHERE item_id = ? AND workspace_id = ?
3132 }
3133 }
3134 }
3135}
3136
3137/// Iterator that yields KeyPress values from a slice of Keystrokes
3138struct KeyPressIterator<'a> {
3139 keystrokes: &'a [Keystroke],
3140 current_keystroke_index: usize,
3141 current_key_press_index: usize,
3142}
3143
3144impl<'a> KeyPressIterator<'a> {
3145 fn new(keystrokes: &'a [Keystroke]) -> Self {
3146 Self {
3147 keystrokes,
3148 current_keystroke_index: 0,
3149 current_key_press_index: 0,
3150 }
3151 }
3152}
3153
3154impl<'a> Iterator for KeyPressIterator<'a> {
3155 type Item = KeyPress<'a>;
3156
3157 fn next(&mut self) -> Option<Self::Item> {
3158 loop {
3159 let keystroke = self.keystrokes.get(self.current_keystroke_index)?;
3160
3161 match self.current_key_press_index {
3162 0 => {
3163 self.current_key_press_index = 1;
3164 if keystroke.modifiers.platform {
3165 return Some(KeyPress::Platform);
3166 }
3167 }
3168 1 => {
3169 self.current_key_press_index = 2;
3170 if keystroke.modifiers.alt {
3171 return Some(KeyPress::Alt);
3172 }
3173 }
3174 2 => {
3175 self.current_key_press_index = 3;
3176 if keystroke.modifiers.control {
3177 return Some(KeyPress::Control);
3178 }
3179 }
3180 3 => {
3181 self.current_key_press_index = 4;
3182 if keystroke.modifiers.shift {
3183 return Some(KeyPress::Shift);
3184 }
3185 }
3186 4 => {
3187 self.current_key_press_index = 5;
3188 if keystroke.modifiers.function {
3189 return Some(KeyPress::Function);
3190 }
3191 }
3192 _ => {
3193 self.current_keystroke_index += 1;
3194 self.current_key_press_index = 0;
3195
3196 if keystroke.key.is_empty() {
3197 continue;
3198 }
3199 return Some(KeyPress::Key(&keystroke.key));
3200 }
3201 }
3202 }
3203 }
3204}