Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:29:33.141Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

keymap_editor.rs

4393 lines · 168.0 KB · rust
1use std::{
2    cell::RefCell,
3    cmp::{self},
4    ops::{Not as _, Range},
5    rc::Rc,
6    sync::Arc,
7    time::{Duration, Instant},
8};
9
10mod action_completion_provider;
11mod ui_components;
12
13use anyhow::{Context as _, anyhow};
14use collections::{HashMap, HashSet};
15use editor::{CompletionProvider, Editor, EditorEvent, EditorMode, SizingBehavior};
16use fs::Fs;
17use fuzzy::{StringMatch, StringMatchCandidate};
18use gpui::{
19    Action, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
20    FocusHandle, Focusable, Global, IsZero,
21    KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or},
22    KeyContext, KeybindingKeystroke, MouseButton, PlatformKeyboardMapper, Point, ScrollStrategy,
23    ScrollWheelEvent, Stateful, StyledText, Subscription, Task, TextStyleRefinement, WeakEntity,
24    actions, anchored, deferred, div,
25};
26use language::{Language, LanguageConfig, ToOffset as _};
27
28use notifications::status_toast::StatusToast;
29use project::{CompletionDisplayOptions, Project};
30use settings::{
31    BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAssets, infer_json_indent_size,
32};
33use ui::{
34    ActiveTheme as _, App, Banner, BorrowAppContext, ColumnWidthConfig, ContextMenu,
35    IconButtonShape, IconPosition, Indicator, Modal, ModalFooter, ModalHeader, ParentElement as _,
36    PopoverMenu, RedistributableColumnsState, Render, Section, SharedString, Styled as _, Table,
37    TableInteractionState, TableResizeBehavior, Tooltip, Window, prelude::*,
38};
39use ui_input::InputField;
40use util::ResultExt;
41use workspace::{
42    Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _,
43    register_serializable_item, with_active_or_new_workspace,
44};
45
46pub use ui_components::*;
47use zed_actions::{ChangeKeybinding, OpenKeymap};
48
49use crate::{
50    action_completion_provider::ActionCompletionProvider,
51    persistence::KeybindingEditorDb,
52    ui_components::keystroke_input::{
53        ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording,
54    },
55};
56
57const NO_ACTION_ARGUMENTS_TEXT: SharedString = SharedString::new_static("<no arguments>");
58const COLS: usize = 6;
59
60actions!(
61    keymap_editor,
62    [
63        /// Edits the selected key binding.
64        EditBinding,
65        /// Creates a new key binding for the selected action.
66        CreateBinding,
67        /// Creates a new key binding from scratch, prompting for the action.
68        OpenCreateKeybindingModal,
69        /// Deletes the selected key binding.
70        DeleteBinding,
71        /// Copies the action name to clipboard.
72        CopyAction,
73        /// Copies the context predicate to clipboard.
74        CopyContext,
75        /// Toggles Conflict Filtering
76        ToggleConflictFilter,
77        /// Toggles whether NoAction bindings are shown
78        ToggleNoActionBindings,
79        /// Toggle Keystroke search
80        ToggleKeystrokeSearch,
81        /// Toggles exact matching for keystroke search
82        ToggleExactKeystrokeMatching,
83        /// Shows matching keystrokes for the currently selected binding
84        ShowMatchingKeybinds
85    ]
86);
87
88pub fn init(cx: &mut App) {
89    let keymap_event_channel = KeymapEventChannel::new();
90    cx.set_global(keymap_event_channel);
91
92    fn open_keymap_editor(
93        filter: Option<String>,
94        workspace: &mut Workspace,
95        window: &mut Window,
96        cx: &mut Context<Workspace>,
97    ) {
98        let existing = workspace
99            .active_pane()
100            .read(cx)
101            .items()
102            .find_map(|item| item.downcast::<KeymapEditor>());
103
104        let keymap_editor = if let Some(existing) = existing {
105            workspace.activate_item(&existing, true, true, window, cx);
106            existing
107        } else {
108            let keymap_editor = cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
109            workspace.add_item_to_active_pane(
110                Box::new(keymap_editor.clone()),
111                None,
112                true,
113                window,
114                cx,
115            );
116            keymap_editor
117        };
118
119        if let Some(filter) = filter {
120            keymap_editor.update(cx, |editor, cx| {
121                editor.filter_editor.update(cx, |editor, cx| {
122                    editor.clear(window, cx);
123                    editor.insert(&filter, window, cx);
124                });
125                if !editor.has_binding_for(&filter) {
126                    open_binding_modal_after_loading(cx)
127                }
128            })
129        }
130    }
131
132    cx.on_action(|_: &OpenKeymap, cx| {
133        with_active_or_new_workspace(cx, |workspace, window, cx| {
134            open_keymap_editor(None, workspace, window, cx);
135        });
136    });
137
138    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
139        workspace.register_action(|workspace, action: &ChangeKeybinding, window, cx| {
140            open_keymap_editor(Some(action.action.clone()), workspace, window, cx);
141        });
142    })
143    .detach();
144
145    register_serializable_item::<KeymapEditor>(cx);
146}
147
148fn open_binding_modal_after_loading(cx: &mut Context<KeymapEditor>) {
149    let started_at = Instant::now();
150    let observer = Rc::new(RefCell::new(None));
151    let handle = {
152        let observer = Rc::clone(&observer);
153        cx.observe(&cx.entity(), move |editor, _, cx| {
154            let subscription = observer.borrow_mut().take();
155
156            if started_at.elapsed().as_secs() > 10 {
157                return;
158            }
159            if !editor.matches.is_empty() {
160                editor.selected_index = Some(0);
161                cx.dispatch_action(&CreateBinding);
162                return;
163            }
164
165            *observer.borrow_mut() = subscription;
166        })
167    };
168    *observer.borrow_mut() = Some(handle);
169}
170
171pub struct KeymapEventChannel {}
172
173impl Global for KeymapEventChannel {}
174
175impl KeymapEventChannel {
176    fn new() -> Self {
177        Self {}
178    }
179
180    pub fn trigger_keymap_changed(cx: &mut App) {
181        let Some(_event_channel) = cx.try_global::<Self>() else {
182            // don't panic if no global defined. This usually happens in tests
183            return;
184        };
185        cx.update_global(|_event_channel: &mut Self, _| {
186            /* triggers observers in KeymapEditors */
187        });
188    }
189}
190
191#[derive(Default, PartialEq, Copy, Clone)]
192enum SearchMode {
193    #[default]
194    Normal,
195    KeyStroke {
196        exact_match: bool,
197    },
198}
199
200impl SearchMode {
201    fn invert(&self) -> Self {
202        match self {
203            SearchMode::Normal => SearchMode::KeyStroke { exact_match: true },
204            SearchMode::KeyStroke { .. } => SearchMode::Normal,
205        }
206    }
207
208    fn exact_match(&self) -> bool {
209        match self {
210            SearchMode::Normal => false,
211            SearchMode::KeyStroke { exact_match } => *exact_match,
212        }
213    }
214}
215
216#[derive(Default, PartialEq, Copy, Clone)]
217enum FilterState {
218    #[default]
219    All,
220    Conflicts,
221}
222
223impl FilterState {
224    fn invert(&self) -> Self {
225        match self {
226            FilterState::All => FilterState::Conflicts,
227            FilterState::Conflicts => FilterState::All,
228        }
229    }
230}
231
232#[derive(Default, PartialEq, Eq, Copy, Clone)]
233struct SourceFilters {
234    user: bool,
235    zed_defaults: bool,
236    vim_defaults: bool,
237}
238
239impl SourceFilters {
240    fn allows(&self, source: Option<KeybindSource>) -> bool {
241        match source {
242            Some(KeybindSource::User) => self.user,
243            Some(KeybindSource::Vim) => self.vim_defaults,
244            Some(KeybindSource::Base | KeybindSource::Default | KeybindSource::Unknown) | None => {
245                self.zed_defaults
246            }
247        }
248    }
249}
250
251#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
252struct ActionMapping {
253    keystrokes: Rc<[KeybindingKeystroke]>,
254    context: Option<SharedString>,
255}
256
257#[derive(Debug)]
258struct KeybindConflict {
259    first_conflict_index: usize,
260    remaining_conflict_amount: usize,
261}
262
263#[derive(Clone, Copy, PartialEq)]
264struct ConflictOrigin {
265    override_source: KeybindSource,
266    overridden_source: Option<KeybindSource>,
267    index: usize,
268}
269
270impl ConflictOrigin {
271    fn new(source: KeybindSource, index: usize) -> Self {
272        Self {
273            override_source: source,
274            index,
275            overridden_source: None,
276        }
277    }
278
279    fn with_overridden_source(self, source: KeybindSource) -> Self {
280        Self {
281            overridden_source: Some(source),
282            ..self
283        }
284    }
285
286    fn get_conflict_with(&self, other: &Self) -> Option<Self> {
287        if self.override_source == KeybindSource::User
288            && other.override_source == KeybindSource::User
289        {
290            Some(
291                Self::new(KeybindSource::User, other.index)
292                    .with_overridden_source(self.override_source),
293            )
294        } else if self.override_source > other.override_source {
295            Some(other.with_overridden_source(self.override_source))
296        } else {
297            None
298        }
299    }
300
301    fn is_user_keybind_conflict(&self) -> bool {
302        self.override_source == KeybindSource::User
303            && self.overridden_source == Some(KeybindSource::User)
304    }
305}
306
307#[derive(Default)]
308struct ConflictState {
309    conflicts: Vec<Option<ConflictOrigin>>,
310    keybind_mapping: ConflictKeybindMapping,
311    has_user_conflicts: bool,
312}
313
314type ConflictKeybindMapping = HashMap<
315    Rc<[KeybindingKeystroke]>,
316    Vec<(
317        Option<gpui::KeyBindingContextPredicate>,
318        Vec<ConflictOrigin>,
319    )>,
320>;
321
322impl ConflictState {
323    fn new(key_bindings: &[ProcessedBinding]) -> Self {
324        let mut action_keybind_mapping = ConflictKeybindMapping::default();
325
326        let mut largest_index = 0;
327        for (index, binding) in key_bindings
328            .iter()
329            .enumerate()
330            .flat_map(|(index, binding)| Some(index).zip(binding.keybind_information()))
331        {
332            let mapping = binding.get_action_mapping();
333            let predicate = mapping
334                .context
335                .and_then(|ctx| gpui::KeyBindingContextPredicate::parse(&ctx).ok());
336            let entry = action_keybind_mapping
337                .entry(mapping.keystrokes.clone())
338                .or_default();
339            let origin = ConflictOrigin::new(binding.source, index);
340            if let Some((_, origins)) =
341                entry
342                    .iter_mut()
343                    .find(|(other_predicate, _)| match (&predicate, other_predicate) {
344                        (None, None) => true,
345                        (Some(a), Some(b)) => normalized_ctx_eq(a, b),
346                        _ => false,
347                    })
348            {
349                origins.push(origin);
350            } else {
351                entry.push((predicate, vec![origin]));
352            }
353            largest_index = index;
354        }
355
356        let mut conflicts = vec![None; largest_index + 1];
357        let mut has_user_conflicts = false;
358
359        for entries in action_keybind_mapping.values_mut() {
360            for (_, indices) in entries.iter_mut() {
361                indices.sort_unstable_by_key(|origin| origin.override_source);
362                let Some((fst, snd)) = indices.get(0).zip(indices.get(1)) else {
363                    continue;
364                };
365
366                for origin in indices.iter() {
367                    conflicts[origin.index] =
368                        origin.get_conflict_with(if origin == fst { snd } else { fst })
369                }
370
371                has_user_conflicts |= fst.override_source == KeybindSource::User
372                    && snd.override_source == KeybindSource::User;
373            }
374        }
375
376        Self {
377            conflicts,
378            keybind_mapping: action_keybind_mapping,
379            has_user_conflicts,
380        }
381    }
382
383    fn conflicting_indices_for_mapping(
384        &self,
385        action_mapping: &ActionMapping,
386        keybind_idx: Option<usize>,
387    ) -> Option<KeybindConflict> {
388        let ActionMapping {
389            keystrokes,
390            context,
391        } = action_mapping;
392        let predicate = context
393            .as_deref()
394            .and_then(|ctx| gpui::KeyBindingContextPredicate::parse(&ctx).ok());
395        self.keybind_mapping.get(keystrokes).and_then(|entries| {
396            entries
397                .iter()
398                .find_map(|(other_predicate, indices)| {
399                    match (&predicate, other_predicate) {
400                        (None, None) => true,
401                        (Some(pred), Some(other)) => normalized_ctx_eq(pred, other),
402                        _ => false,
403                    }
404                    .then_some(indices)
405                })
406                .and_then(|indices| {
407                    let mut indices = indices
408                        .iter()
409                        .filter(|&conflict| Some(conflict.index) != keybind_idx);
410                    indices.next().map(|origin| KeybindConflict {
411                        first_conflict_index: origin.index,
412                        remaining_conflict_amount: indices.count(),
413                    })
414                })
415        })
416    }
417
418    fn conflict_for_idx(&self, idx: usize) -> Option<ConflictOrigin> {
419        self.conflicts.get(idx).copied().flatten()
420    }
421
422    fn has_user_conflict(&self, candidate_idx: usize) -> bool {
423        self.conflict_for_idx(candidate_idx)
424            .is_some_and(|conflict| conflict.is_user_keybind_conflict())
425    }
426
427    fn any_user_binding_conflicts(&self) -> bool {
428        self.has_user_conflicts
429    }
430}
431
432struct KeymapEditor {
433    workspace: WeakEntity<Workspace>,
434    focus_handle: FocusHandle,
435    _keymap_subscription: Subscription,
436    keybindings: Vec<ProcessedBinding>,
437    keybinding_conflict_state: ConflictState,
438    filter_state: FilterState,
439    source_filters: SourceFilters,
440    show_no_action_bindings: bool,
441    search_mode: SearchMode,
442    search_query_debounce: Option<Task<()>>,
443    // corresponds 1 to 1 with keybindings
444    string_match_candidates: Arc<Vec<StringMatchCandidate>>,
445    matches: Vec<StringMatch>,
446    table_interaction_state: Entity<TableInteractionState>,
447    filter_editor: Entity<Editor>,
448    keystroke_editor: Entity<KeystrokeInput>,
449    selected_index: Option<usize>,
450    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
451    previous_edit: Option<PreviousEdit>,
452    humanized_action_names: HumanizedActionNameCache,
453    current_widths: Entity<RedistributableColumnsState>,
454    show_hover_menus: bool,
455    actions_with_schemas: HashSet<&'static str>,
456    /// In order for the JSON LSP to run in the actions arguments editor, we
457    /// require a backing file In order to avoid issues (primarily log spam)
458    /// with drop order between the buffer, file, worktree, etc, we create a
459    /// temporary directory for these backing files in the keymap editor struct
460    /// instead of here. This has the added benefit of only having to create a
461    /// worktree and directory once, although the perf improvement is negligible.
462    action_args_temp_dir_worktree: Option<Entity<project::Worktree>>,
463    action_args_temp_dir: Option<tempfile::TempDir>,
464}
465
466enum PreviousEdit {
467    /// When deleting, we want to maintain the same scroll position
468    ScrollBarOffset(Point<Pixels>),
469    /// When editing or creating, because the new keybinding could be in a different position in the sort order
470    /// we store metadata about the new binding (either the modified version or newly created one)
471    /// and upon reload, we search for this binding in the list of keybindings, and if we find the one that matches
472    /// this metadata, we set the selected index to it and scroll to it,
473    /// and if we don't find it, we scroll to 0 and don't set a selected index
474    Keybinding {
475        action_mapping: ActionMapping,
476        action_name: &'static str,
477        /// The scrollbar position to fallback to if we don't find the keybinding during a refresh
478        /// this can happen if there's a filter applied to the search and the keybinding modification
479        /// filters the binding from the search results
480        fallback: Point<Pixels>,
481    },
482}
483
484impl EventEmitter<()> for KeymapEditor {}
485
486impl Focusable for KeymapEditor {
487    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
488        if self.selected_index.is_some() {
489            self.focus_handle.clone()
490        } else {
491            self.filter_editor.focus_handle(cx)
492        }
493    }
494}
495/// Helper function to check if two keystroke sequences match exactly
496fn keystrokes_match_exactly(
497    keystrokes1: &[KeybindingKeystroke],
498    keystrokes2: &[KeybindingKeystroke],
499) -> bool {
500    keystrokes1.len() == keystrokes2.len()
501        && keystrokes1.iter().zip(keystrokes2).all(|(k1, k2)| {
502            k1.inner().key == k2.inner().key && k1.inner().modifiers == k2.inner().modifiers
503        })
504}
505
506fn disabled_binding_matches_context(
507    disabled_binding: &gpui::KeyBinding,
508    binding: &gpui::KeyBinding,
509) -> bool {
510    match (
511        disabled_binding.predicate().as_deref(),
512        binding.predicate().as_deref(),
513    ) {
514        (None, _) => true,
515        (Some(_), None) => false,
516        (Some(disabled_predicate), Some(predicate)) => disabled_predicate.is_superset(predicate),
517    }
518}
519
520fn binding_is_unbound_by_unbind(
521    binding: &gpui::KeyBinding,
522    binding_index: usize,
523    all_bindings: &[&gpui::KeyBinding],
524) -> bool {
525    all_bindings[binding_index + 1..]
526        .iter()
527        .rev()
528        .any(|disabled_binding| {
529            gpui::is_unbind(disabled_binding.action())
530                && keystrokes_match_exactly(disabled_binding.keystrokes(), binding.keystrokes())
531                && disabled_binding
532                    .action()
533                    .as_any()
534                    .downcast_ref::<gpui::Unbind>()
535                    .is_some_and(|unbind| unbind.0.as_ref() == binding.action().name())
536                && disabled_binding_matches_context(disabled_binding, binding)
537        })
538}
539
540impl KeymapEditor {
541    fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
542        let _keymap_subscription =
543            cx.observe_global_in::<KeymapEventChannel>(window, Self::on_keymap_changed);
544        let table_interaction_state = cx.new(|cx| {
545            TableInteractionState::new(cx).with_custom_scrollbar(ui::Scrollbars::for_settings::<
546                editor::EditorSettingsScrollbarProxy,
547            >())
548        });
549
550        let keystroke_editor = cx.new(|cx| {
551            let mut keystroke_editor = KeystrokeInput::new(None, window, cx);
552            keystroke_editor.set_search(true);
553            keystroke_editor
554        });
555
556        let filter_editor = cx.new(|cx| {
557            let mut editor = Editor::single_line(window, cx);
558            editor.set_placeholder_text("Filter action names…", window, cx);
559            editor
560        });
561
562        cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
563            if !matches!(e, EditorEvent::BufferEdited) {
564                return;
565            }
566
567            this.on_query_changed(cx);
568        })
569        .detach();
570
571        cx.subscribe(&keystroke_editor, |this, _, _, cx| {
572            if matches!(this.search_mode, SearchMode::Normal) {
573                return;
574            }
575
576            this.on_query_changed(cx);
577        })
578        .detach();
579
580        cx.spawn({
581            let workspace = workspace.clone();
582            async move |this, cx| {
583                let temp_dir = tempfile::tempdir_in(paths::temp_dir())?;
584                let worktree = workspace
585                    .update(cx, |ws, cx| {
586                        ws.project()
587                            .update(cx, |p, cx| p.create_worktree(temp_dir.path(), false, cx))
588                    })?
589                    .await?;
590                this.update(cx, |this, _| {
591                    this.action_args_temp_dir = Some(temp_dir);
592                    this.action_args_temp_dir_worktree = Some(worktree);
593                })
594            }
595        })
596        .detach();
597
598        let mut this = Self {
599            workspace,
600            keybindings: vec![],
601            keybinding_conflict_state: ConflictState::default(),
602            filter_state: FilterState::default(),
603            source_filters: SourceFilters {
604                user: true,
605                zed_defaults: true,
606                vim_defaults: true,
607            },
608            show_no_action_bindings: true,
609            search_mode: SearchMode::default(),
610            string_match_candidates: Arc::new(vec![]),
611            matches: vec![],
612            focus_handle: cx.focus_handle(),
613            _keymap_subscription,
614            table_interaction_state,
615            filter_editor,
616            keystroke_editor,
617            selected_index: None,
618            context_menu: None,
619            previous_edit: None,
620            search_query_debounce: None,
621            humanized_action_names: HumanizedActionNameCache::new(cx),
622            show_hover_menus: true,
623            actions_with_schemas: HashSet::default(),
624            action_args_temp_dir: None,
625            action_args_temp_dir_worktree: None,
626            current_widths: cx.new(|_cx| {
627                RedistributableColumnsState::new(
628                    COLS,
629                    vec![
630                        DefiniteLength::Absolute(AbsoluteLength::Pixels(px(36.))),
631                        DefiniteLength::Fraction(0.25),
632                        DefiniteLength::Fraction(0.20),
633                        DefiniteLength::Fraction(0.14),
634                        DefiniteLength::Fraction(0.45),
635                        DefiniteLength::Fraction(0.08),
636                    ],
637                    vec![
638                        TableResizeBehavior::None,
639                        TableResizeBehavior::Resizable,
640                        TableResizeBehavior::Resizable,
641                        TableResizeBehavior::Resizable,
642                        TableResizeBehavior::Resizable,
643                        TableResizeBehavior::Resizable,
644                    ],
645                )
646            }),
647        };
648
649        this.on_keymap_changed(window, cx);
650
651        this
652    }
653
654    fn current_action_query(&self, cx: &App) -> String {
655        self.filter_editor.read(cx).text(cx)
656    }
657
658    fn current_keystroke_query(&self, cx: &App) -> Vec<KeybindingKeystroke> {
659        match self.search_mode {
660            SearchMode::KeyStroke { .. } => self.keystroke_editor.read(cx).keystrokes().to_vec(),
661            SearchMode::Normal => Default::default(),
662        }
663    }
664
665    fn clear_action_query(&self, window: &mut Window, cx: &mut Context<Self>) {
666        self.filter_editor
667            .update(cx, |editor, cx| editor.clear(window, cx))
668    }
669
670    fn on_query_changed(&mut self, cx: &mut Context<Self>) {
671        let action_query = self.current_action_query(cx);
672        let keystroke_query = self.current_keystroke_query(cx);
673        let exact_match = self.search_mode.exact_match();
674
675        let timer = cx.background_executor().timer(Duration::from_secs(1));
676        self.search_query_debounce = Some(cx.background_spawn({
677            let action_query = action_query.clone();
678            let keystroke_query = keystroke_query.clone();
679            async move {
680                timer.await;
681
682                let keystroke_query = keystroke_query
683                    .into_iter()
684                    .map(|keystroke| keystroke.inner().unparse())
685                    .collect::<Vec<String>>()
686                    .join(" ");
687
688                telemetry::event!(
689                    "Keystroke Search Completed",
690                    action_query = action_query,
691                    keystroke_query = keystroke_query,
692                    keystroke_exact_match = exact_match
693                )
694            }
695        }));
696        cx.spawn(async move |this, cx| {
697            Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
698            this.update(cx, |this, cx| {
699                this.scroll_to_item(0, ScrollStrategy::Top, cx)
700            })
701        })
702        .detach();
703    }
704
705    async fn update_matches(
706        this: WeakEntity<Self>,
707        action_query: String,
708        keystroke_query: Vec<KeybindingKeystroke>,
709        cx: &mut AsyncApp,
710    ) -> anyhow::Result<()> {
711        let action_query = command_palette::normalize_action_query(&action_query);
712        let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
713            (this.string_match_candidates.clone(), this.keybindings.len())
714        })?;
715        let executor = cx.background_executor().clone();
716        let mut matches = fuzzy::match_strings(
717            &string_match_candidates,
718            &action_query,
719            true,
720            true,
721            keybind_count,
722            &Default::default(),
723            executor,
724        )
725        .await;
726        this.update(cx, |this, cx| {
727            matches.retain(|candidate| {
728                this.source_filters
729                    .allows(this.keybindings[candidate.candidate_id].keybind_source())
730            });
731
732            match this.filter_state {
733                FilterState::Conflicts => {
734                    matches.retain(|candidate| {
735                        this.keybinding_conflict_state
736                            .has_user_conflict(candidate.candidate_id)
737                    });
738                }
739                FilterState::All => {}
740            }
741
742            match this.search_mode {
743                SearchMode::KeyStroke { exact_match } => {
744                    matches.retain(|item| {
745                        this.keybindings[item.candidate_id]
746                            .keystrokes()
747                            .is_some_and(|keystrokes| {
748                                if exact_match {
749                                    keystrokes_match_exactly(&keystroke_query, keystrokes)
750                                } else if keystroke_query.len() > keystrokes.len() {
751                                    false
752                                } else {
753                                    for keystroke_offset in 0..keystrokes.len() {
754                                        let mut found_count = 0;
755                                        let mut query_cursor = 0;
756                                        let mut keystroke_cursor = keystroke_offset;
757                                        while query_cursor < keystroke_query.len()
758                                            && keystroke_cursor < keystrokes.len()
759                                        {
760                                            let query = &keystroke_query[query_cursor];
761                                            let keystroke = &keystrokes[keystroke_cursor];
762                                            let matches = query
763                                                .inner()
764                                                .modifiers
765                                                .is_subset_of(&keystroke.inner().modifiers)
766                                                && ((query.inner().key.is_empty()
767                                                    || query.inner().key == keystroke.inner().key)
768                                                    && query.inner().key_char.as_ref().is_none_or(
769                                                        |q_kc| q_kc == &keystroke.inner().key,
770                                                    ));
771                                            if matches {
772                                                found_count += 1;
773                                                query_cursor += 1;
774                                            }
775                                            keystroke_cursor += 1;
776                                        }
777
778                                        if found_count == keystroke_query.len() {
779                                            return true;
780                                        }
781                                    }
782                                    false
783                                }
784                            })
785                    });
786                }
787                SearchMode::Normal => {}
788            }
789
790            if !this.show_no_action_bindings {
791                matches.retain(|item| !this.keybindings[item.candidate_id].is_no_action());
792            }
793
794            if action_query.is_empty() {
795                matches.sort_by(|item1, item2| {
796                    let binding1 = &this.keybindings[item1.candidate_id];
797                    let binding2 = &this.keybindings[item2.candidate_id];
798
799                    binding1.cmp(binding2)
800                });
801            }
802            this.selected_index.take();
803            this.matches = matches;
804
805            cx.notify();
806        })
807    }
808
809    fn get_conflict(&self, row_index: usize) -> Option<ConflictOrigin> {
810        self.matches.get(row_index).and_then(|candidate| {
811            self.keybinding_conflict_state
812                .conflict_for_idx(candidate.candidate_id)
813        })
814    }
815
816    fn process_bindings(
817        json_language: Arc<Language>,
818        keybind_context_language: Arc<Language>,
819        humanized_action_names: &HumanizedActionNameCache,
820        cx: &mut App,
821    ) -> (
822        Vec<ProcessedBinding>,
823        Vec<StringMatchCandidate>,
824        HashSet<&'static str>,
825    ) {
826        let key_bindings_ptr = cx.key_bindings();
827        let lock = key_bindings_ptr.borrow();
828        let key_bindings = lock.bindings().collect::<Vec<_>>();
829        let mut unmapped_action_names = HashSet::from_iter(cx.all_action_names().iter().copied());
830        let action_documentation = cx.action_documentation();
831        let mut generator = KeymapFile::action_schema_generator();
832        let actions_with_schemas = HashSet::from_iter(
833            cx.action_schemas(&mut generator)
834                .into_iter()
835                .filter_map(|(name, schema)| schema.is_some().then_some(name)),
836        );
837
838        let mut processed_bindings = Vec::new();
839        let mut string_match_candidates = Vec::new();
840
841        for (binding_index, &key_binding) in key_bindings.iter().enumerate() {
842            if gpui::is_unbind(key_binding.action()) {
843                continue;
844            }
845
846            let source = key_binding
847                .meta()
848                .map(KeybindSource::from_meta)
849                .unwrap_or(KeybindSource::Unknown);
850
851            let keystroke_text = ui::text_for_keybinding_keystrokes(key_binding.keystrokes(), cx);
852            let is_no_action = gpui::is_no_action(key_binding.action());
853            let is_unbound_by_unbind =
854                binding_is_unbound_by_unbind(key_binding, binding_index, &key_bindings);
855            let binding = KeyBinding::new(key_binding, source);
856
857            let context = key_binding
858                .predicate()
859                .map(|predicate| {
860                    KeybindContextString::Local(
861                        predicate.to_string().into(),
862                        keybind_context_language.clone(),
863                    )
864                })
865                .unwrap_or(KeybindContextString::Global);
866
867            let action_name = key_binding.action().name();
868            unmapped_action_names.remove(&action_name);
869
870            let action_arguments = key_binding
871                .action_input()
872                .map(|arguments| SyntaxHighlightedText::new(arguments, json_language.clone()));
873            let action_information = ActionInformation::new(
874                action_name,
875                action_arguments,
876                &actions_with_schemas,
877                action_documentation,
878                humanized_action_names,
879            );
880
881            let index = processed_bindings.len();
882            let string_match_candidate =
883                StringMatchCandidate::new(index, &action_information.humanized_name);
884            processed_bindings.push(ProcessedBinding::new_mapped(
885                keystroke_text,
886                binding,
887                context,
888                source,
889                is_no_action,
890                is_unbound_by_unbind,
891                action_information,
892            ));
893            string_match_candidates.push(string_match_candidate);
894        }
895
896        for action_name in unmapped_action_names.into_iter() {
897            let index = processed_bindings.len();
898            let action_information = ActionInformation::new(
899                action_name,
900                None,
901                &actions_with_schemas,
902                action_documentation,
903                humanized_action_names,
904            );
905            let string_match_candidate =
906                StringMatchCandidate::new(index, &action_information.humanized_name);
907
908            processed_bindings.push(ProcessedBinding::Unmapped(action_information));
909            string_match_candidates.push(string_match_candidate);
910        }
911        (
912            processed_bindings,
913            string_match_candidates,
914            actions_with_schemas,
915        )
916    }
917
918    fn on_keymap_changed(&mut self, window: &mut Window, cx: &mut Context<KeymapEditor>) {
919        let workspace = self.workspace.clone();
920        cx.spawn_in(window, async move |this, cx| {
921            let json_language = load_json_language(workspace.clone(), cx).await;
922            let keybind_context_language =
923                load_keybind_context_language(workspace.clone(), cx).await;
924
925            let (action_query, keystroke_query) = this.update(cx, |this, cx| {
926                let (key_bindings, string_match_candidates, actions_with_schemas) =
927                    Self::process_bindings(
928                        json_language,
929                        keybind_context_language,
930                        &this.humanized_action_names,
931                        cx,
932                    );
933
934                this.keybinding_conflict_state = ConflictState::new(&key_bindings);
935
936                this.keybindings = key_bindings;
937                this.actions_with_schemas = actions_with_schemas;
938                this.string_match_candidates = Arc::new(string_match_candidates);
939                this.matches = this
940                    .string_match_candidates
941                    .iter()
942                    .enumerate()
943                    .map(|(ix, candidate)| StringMatch {
944                        candidate_id: ix,
945                        score: 0.0,
946                        positions: vec![],
947                        string: candidate.string.clone(),
948                    })
949                    .collect();
950                (
951                    this.current_action_query(cx),
952                    this.current_keystroke_query(cx),
953                )
954            })?;
955            // calls cx.notify
956            Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
957            this.update_in(cx, |this, window, cx| {
958                if let Some(previous_edit) = this.previous_edit.take() {
959                    match previous_edit {
960                        // should remove scroll from process_query
961                        PreviousEdit::ScrollBarOffset(offset) => {
962                            this.table_interaction_state
963                                .update(cx, |table, _| table.set_scroll_offset(offset))
964                            // set selected index and scroll
965                        }
966                        PreviousEdit::Keybinding {
967                            action_mapping,
968                            action_name,
969                            fallback,
970                        } => {
971                            let scroll_position =
972                                this.matches.iter().enumerate().find_map(|(index, item)| {
973                                    let binding = &this.keybindings[item.candidate_id];
974                                    if binding.get_action_mapping().is_some_and(|binding_mapping| {
975                                        binding_mapping == action_mapping
976                                    }) && binding.action().name == action_name
977                                    {
978                                        Some(index)
979                                    } else {
980                                        None
981                                    }
982                                });
983
984                            if let Some(scroll_position) = scroll_position {
985                                this.select_index(
986                                    scroll_position,
987                                    Some(ScrollStrategy::Top),
988                                    window,
989                                    cx,
990                                );
991                            } else {
992                                this.table_interaction_state
993                                    .update(cx, |table, _| table.set_scroll_offset(fallback));
994                            }
995                            cx.notify();
996                        }
997                    }
998                }
999            })
1000        })
1001        .detach_and_log_err(cx);
1002    }
1003
1004    fn key_context(&self) -> KeyContext {
1005        let mut dispatch_context = KeyContext::new_with_defaults();
1006        dispatch_context.add("KeymapEditor");
1007        dispatch_context.add("menu");
1008
1009        dispatch_context
1010    }
1011
1012    fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
1013        let index = usize::min(index, self.matches.len().saturating_sub(1));
1014        self.table_interaction_state.update(cx, |this, _cx| {
1015            this.scroll_handle.scroll_to_item(index, strategy);
1016        });
1017    }
1018
1019    fn focus_search(
1020        &mut self,
1021        _: &search::FocusSearch,
1022        window: &mut Window,
1023        cx: &mut Context<Self>,
1024    ) {
1025        if !self
1026            .filter_editor
1027            .focus_handle(cx)
1028            .contains_focused(window, cx)
1029        {
1030            window.focus(&self.filter_editor.focus_handle(cx), cx);
1031        } else {
1032            self.filter_editor.update(cx, |editor, cx| {
1033                editor.select_all(&Default::default(), window, cx);
1034            });
1035        }
1036        self.selected_index.take();
1037    }
1038
1039    fn selected_keybind_index(&self) -> Option<usize> {
1040        self.selected_index
1041            .and_then(|match_index| self.matches.get(match_index))
1042            .map(|r#match| r#match.candidate_id)
1043    }
1044
1045    fn selected_keybind_and_index(&self) -> Option<(&ProcessedBinding, usize)> {
1046        self.selected_keybind_index()
1047            .map(|keybind_index| (&self.keybindings[keybind_index], keybind_index))
1048    }
1049
1050    fn selected_binding(&self) -> Option<&ProcessedBinding> {
1051        self.selected_keybind_index()
1052            .and_then(|keybind_index| self.keybindings.get(keybind_index))
1053    }
1054
1055    fn select_index(
1056        &mut self,
1057        index: usize,
1058        scroll: Option<ScrollStrategy>,
1059        window: &mut Window,
1060        cx: &mut Context<Self>,
1061    ) {
1062        if self.selected_index != Some(index) {
1063            self.selected_index = Some(index);
1064            if let Some(scroll_strategy) = scroll {
1065                self.scroll_to_item(index, scroll_strategy, cx);
1066            }
1067            window.focus(&self.focus_handle, cx);
1068            cx.notify();
1069        }
1070    }
1071
1072    fn create_context_menu(
1073        &mut self,
1074        position: Point<Pixels>,
1075        window: &mut Window,
1076        cx: &mut Context<Self>,
1077    ) {
1078        self.context_menu = self.selected_binding().map(|selected_binding| {
1079            let selected_binding_has_no_context = selected_binding
1080                .context()
1081                .and_then(KeybindContextString::local)
1082                .is_none();
1083
1084            let selected_binding_is_unmapped = selected_binding.is_unbound();
1085            let selected_binding_is_suppressed = selected_binding.is_unbound_by_unbind();
1086            let selected_binding_is_non_interactable =
1087                selected_binding_is_unmapped || selected_binding_is_suppressed;
1088
1089            let context_menu = ContextMenu::build(window, cx, |menu, _window, _cx| {
1090                menu.context(self.focus_handle.clone())
1091                    .when(selected_binding_is_unmapped, |this| {
1092                        this.action("Create", Box::new(CreateBinding))
1093                    })
1094                    .action_disabled_when(
1095                        selected_binding_is_non_interactable,
1096                        "Edit",
1097                        Box::new(EditBinding),
1098                    )
1099                    .action_disabled_when(
1100                        selected_binding_is_non_interactable,
1101                        "Delete",
1102                        Box::new(DeleteBinding),
1103                    )
1104                    .separator()
1105                    .action("Copy Action", Box::new(CopyAction))
1106                    .action_disabled_when(
1107                        selected_binding_has_no_context,
1108                        "Copy Context",
1109                        Box::new(CopyContext),
1110                    )
1111                    .separator()
1112                    .action_disabled_when(
1113                        selected_binding_has_no_context,
1114                        "Show Matching Keybindings",
1115                        Box::new(ShowMatchingKeybinds),
1116                    )
1117            });
1118
1119            let context_menu_handle = context_menu.focus_handle(cx);
1120            window.defer(cx, move |window, cx| window.focus(&context_menu_handle, cx));
1121            let subscription = cx.subscribe_in(
1122                &context_menu,
1123                window,
1124                |this, _, _: &DismissEvent, window, cx| {
1125                    this.dismiss_context_menu(window, cx);
1126                },
1127            );
1128            (context_menu, position, subscription)
1129        });
1130
1131        cx.notify();
1132    }
1133
1134    fn dismiss_context_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1135        self.context_menu.take();
1136        window.focus(&self.focus_handle, cx);
1137        cx.notify();
1138    }
1139
1140    fn context_menu_deployed(&self) -> bool {
1141        self.context_menu.is_some()
1142    }
1143
1144    fn create_row_button(
1145        &self,
1146        index: usize,
1147        conflict: Option<ConflictOrigin>,
1148        is_unbound_by_unbind: bool,
1149        cx: &mut Context<Self>,
1150    ) -> IconButton {
1151        if is_unbound_by_unbind {
1152            base_button_style(index, IconName::Warning)
1153                .icon_color(Color::Warning)
1154                .disabled(true)
1155                .tooltip(Tooltip::text("This action is unbound"))
1156        } else if self.filter_state != FilterState::Conflicts
1157            && let Some(conflict) = conflict
1158        {
1159            if conflict.is_user_keybind_conflict() {
1160                base_button_style(index, IconName::Warning)
1161                    .icon_color(Color::Warning)
1162                    .tooltip(|_window, cx| {
1163                        Tooltip::with_meta(
1164                            "View conflicts",
1165                            Some(&ToggleConflictFilter),
1166                            "Use alt+click to show all conflicts",
1167                            cx,
1168                        )
1169                    })
1170                    .on_click(cx.listener(move |this, click: &ClickEvent, window, cx| {
1171                        if click.modifiers().alt {
1172                            this.set_filter_state(FilterState::Conflicts, cx);
1173                        } else {
1174                            this.select_index(index, None, window, cx);
1175                            this.open_edit_keybinding_modal(false, window, cx);
1176                            cx.stop_propagation();
1177                        }
1178                    }))
1179            } else if self.search_mode.exact_match() {
1180                base_button_style(index, IconName::Info)
1181                    .tooltip(|_window, cx| {
1182                        Tooltip::with_meta(
1183                            "Edit this binding",
1184                            Some(&ShowMatchingKeybinds),
1185                            "This binding is overridden by other bindings.",
1186                            cx,
1187                        )
1188                    })
1189                    .on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
1190                        this.select_index(index, None, window, cx);
1191                        this.open_edit_keybinding_modal(false, window, cx);
1192                        cx.stop_propagation();
1193                    }))
1194            } else {
1195                base_button_style(index, IconName::Info)
1196                    .tooltip(|_window, cx|  {
1197                        Tooltip::with_meta(
1198                            "Show matching keybinds",
1199                            Some(&ShowMatchingKeybinds),
1200                            "This binding is overridden by other bindings.\nUse alt+click to edit this binding",
1201                            cx,
1202                        )
1203                    })
1204                    .on_click(cx.listener(move |this, click: &ClickEvent, window, cx| {
1205                        if click.modifiers().alt {
1206                            this.select_index(index, None, window, cx);
1207                            this.open_edit_keybinding_modal(false, window, cx);
1208                            cx.stop_propagation();
1209                        } else {
1210                            this.show_matching_keystrokes(&Default::default(), window, cx);
1211                        }
1212                    }))
1213            }
1214        } else {
1215            base_button_style(index, IconName::Pencil)
1216                .visible_on_hover(if self.selected_index == Some(index) {
1217                    "".into()
1218                } else if self.show_hover_menus {
1219                    row_group_id(index)
1220                } else {
1221                    "never-show".into()
1222                })
1223                .when(
1224                    self.show_hover_menus && !self.context_menu_deployed(),
1225                    |this| this.tooltip(Tooltip::for_action_title("Edit Keybinding", &EditBinding)),
1226                )
1227                .on_click(cx.listener(move |this, _, window, cx| {
1228                    this.select_index(index, None, window, cx);
1229                    this.open_edit_keybinding_modal(false, window, cx);
1230                    cx.stop_propagation();
1231                }))
1232        }
1233    }
1234
1235    fn render_no_matches_hint(&self, _window: &mut Window, _cx: &App) -> AnyElement {
1236        let hint = match (self.filter_state, &self.search_mode) {
1237            (FilterState::Conflicts, _) => {
1238                if self.keybinding_conflict_state.any_user_binding_conflicts() {
1239                    "No conflicting keybinds found that match the provided query"
1240                } else {
1241                    "No conflicting keybinds found"
1242                }
1243            }
1244            (FilterState::All, SearchMode::KeyStroke { .. }) => {
1245                "No keybinds found matching the entered keystrokes"
1246            }
1247            (FilterState::All, SearchMode::Normal) => "No matches found for the provided query",
1248        };
1249
1250        Label::new(hint).color(Color::Muted).into_any_element()
1251    }
1252
1253    fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
1254        self.show_hover_menus = false;
1255        if let Some(selected) = self.selected_index {
1256            let selected = selected + 1;
1257            if selected >= self.matches.len() {
1258                self.select_last(&Default::default(), window, cx);
1259            } else {
1260                self.select_index(selected, Some(ScrollStrategy::Center), window, cx);
1261            }
1262        } else {
1263            self.select_first(&Default::default(), window, cx);
1264        }
1265    }
1266
1267    fn select_previous(
1268        &mut self,
1269        _: &menu::SelectPrevious,
1270        window: &mut Window,
1271        cx: &mut Context<Self>,
1272    ) {
1273        self.show_hover_menus = false;
1274        if let Some(selected) = self.selected_index {
1275            if selected == 0 {
1276                return;
1277            }
1278
1279            let selected = selected - 1;
1280
1281            if selected >= self.matches.len() {
1282                self.select_last(&Default::default(), window, cx);
1283            } else {
1284                self.select_index(selected, Some(ScrollStrategy::Center), window, cx);
1285            }
1286        } else {
1287            self.select_last(&Default::default(), window, cx);
1288        }
1289    }
1290
1291    fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
1292        self.show_hover_menus = false;
1293        if self.matches.get(0).is_some() {
1294            self.select_index(0, Some(ScrollStrategy::Center), window, cx);
1295        }
1296    }
1297
1298    fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
1299        self.show_hover_menus = false;
1300        if self.matches.last().is_some() {
1301            let index = self.matches.len() - 1;
1302            self.select_index(index, Some(ScrollStrategy::Center), window, cx);
1303        }
1304    }
1305
1306    fn open_edit_keybinding_modal(
1307        &mut self,
1308        create: bool,
1309        window: &mut Window,
1310        cx: &mut Context<Self>,
1311    ) {
1312        self.show_hover_menus = false;
1313        let Some((keybind, keybind_index)) = self.selected_keybind_and_index() else {
1314            return;
1315        };
1316        if !create && keybind.is_unbound_by_unbind() {
1317            return;
1318        }
1319        let keybind = keybind.clone();
1320        let keymap_editor = cx.entity();
1321
1322        let keystroke = keybind.keystroke_text().cloned().unwrap_or_default();
1323        let arguments = keybind
1324            .action()
1325            .arguments
1326            .as_ref()
1327            .map(|arguments| arguments.text.clone());
1328        let context = keybind
1329            .context()
1330            .map(|context| context.local_str().unwrap_or("global"));
1331        let action = keybind.action().name;
1332        let source = keybind.keybind_source().map(|source| source.name());
1333
1334        telemetry::event!(
1335            "Edit Keybinding Modal Opened",
1336            keystroke = keystroke,
1337            action = action,
1338            source = source,
1339            context = context,
1340            arguments = arguments,
1341        );
1342
1343        let temp_dir = self.action_args_temp_dir.as_ref().map(|dir| dir.path());
1344
1345        self.workspace
1346            .update(cx, |workspace, cx| {
1347                let fs = workspace.app_state().fs.clone();
1348                let workspace_weak = cx.weak_entity();
1349                workspace.toggle_modal(window, cx, |window, cx| {
1350                    let modal = KeybindingEditorModal::new(
1351                        create,
1352                        keybind,
1353                        keybind_index,
1354                        keymap_editor,
1355                        temp_dir,
1356                        workspace_weak,
1357                        fs,
1358                        window,
1359                        cx,
1360                    );
1361                    window.focus(&modal.focus_handle(cx), cx);
1362                    modal
1363                });
1364            })
1365            .log_err();
1366    }
1367
1368    fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
1369        self.open_edit_keybinding_modal(false, window, cx);
1370    }
1371
1372    fn create_binding(&mut self, _: &CreateBinding, window: &mut Window, cx: &mut Context<Self>) {
1373        self.open_edit_keybinding_modal(true, window, cx);
1374    }
1375
1376    fn open_create_keybinding_modal(
1377        &mut self,
1378        _: &OpenCreateKeybindingModal,
1379        window: &mut Window,
1380        cx: &mut Context<Self>,
1381    ) {
1382        let keymap_editor = cx.entity();
1383
1384        let action_information = ActionInformation::new(
1385            gpui::NoAction.name(),
1386            None,
1387            &HashSet::default(),
1388            cx.action_documentation(),
1389            &self.humanized_action_names,
1390        );
1391
1392        let dummy_binding = ProcessedBinding::Unmapped(action_information);
1393        let dummy_index = self.keybindings.len();
1394
1395        let temp_dir = self.action_args_temp_dir.as_ref().map(|dir| dir.path());
1396
1397        self.workspace
1398            .update(cx, |workspace, cx| {
1399                let fs = workspace.app_state().fs.clone();
1400                let workspace_weak = cx.weak_entity();
1401                workspace.toggle_modal(window, cx, |window, cx| {
1402                    let modal = KeybindingEditorModal::new(
1403                        true,
1404                        dummy_binding,
1405                        dummy_index,
1406                        keymap_editor,
1407                        temp_dir,
1408                        workspace_weak,
1409                        fs,
1410                        window,
1411                        cx,
1412                    );
1413
1414                    window.focus(&modal.focus_handle(cx), cx);
1415                    modal
1416                });
1417            })
1418            .log_err();
1419    }
1420
1421    fn delete_binding(&mut self, _: &DeleteBinding, window: &mut Window, cx: &mut Context<Self>) {
1422        let Some(to_remove) = self.selected_binding().cloned() else {
1423            return;
1424        };
1425        if to_remove.is_unbound_by_unbind() {
1426            return;
1427        }
1428
1429        let std::result::Result::Ok(fs) = self
1430            .workspace
1431            .read_with(cx, |workspace, _| workspace.app_state().fs.clone())
1432        else {
1433            return;
1434        };
1435        self.previous_edit = Some(PreviousEdit::ScrollBarOffset(
1436            self.table_interaction_state.read(cx).scroll_offset(),
1437        ));
1438        let keyboard_mapper = cx.keyboard_mapper().clone();
1439        let deprecated_aliases = cx.deprecated_actions_to_preferred_actions().clone();
1440        cx.spawn(async move |_, _| {
1441            remove_keybinding(
1442                to_remove,
1443                &fs,
1444                keyboard_mapper.as_ref(),
1445                &deprecated_aliases,
1446            )
1447            .await
1448        })
1449        .detach_and_notify_err(self.workspace.clone(), window, cx);
1450    }
1451
1452    fn copy_context_to_clipboard(
1453        &mut self,
1454        _: &CopyContext,
1455        _window: &mut Window,
1456        cx: &mut Context<Self>,
1457    ) {
1458        let context = self
1459            .selected_binding()
1460            .and_then(|binding| binding.context())
1461            .and_then(KeybindContextString::local_str)
1462            .map(|context| context.to_string());
1463        let Some(context) = context else {
1464            return;
1465        };
1466
1467        telemetry::event!("Keybinding Context Copied", context = context);
1468        cx.write_to_clipboard(gpui::ClipboardItem::new_string(context));
1469    }
1470
1471    fn copy_action_to_clipboard(
1472        &mut self,
1473        _: &CopyAction,
1474        _window: &mut Window,
1475        cx: &mut Context<Self>,
1476    ) {
1477        let action = self
1478            .selected_binding()
1479            .map(|binding| binding.action().name.to_string());
1480        let Some(action) = action else {
1481            return;
1482        };
1483
1484        telemetry::event!("Keybinding Action Copied", action = action);
1485        cx.write_to_clipboard(gpui::ClipboardItem::new_string(action));
1486    }
1487
1488    fn toggle_conflict_filter(
1489        &mut self,
1490        _: &ToggleConflictFilter,
1491        _: &mut Window,
1492        cx: &mut Context<Self>,
1493    ) {
1494        self.set_filter_state(self.filter_state.invert(), cx);
1495    }
1496
1497    fn toggle_no_action_bindings(
1498        &mut self,
1499        _: &ToggleNoActionBindings,
1500        _: &mut Window,
1501        cx: &mut Context<Self>,
1502    ) {
1503        self.show_no_action_bindings = !self.show_no_action_bindings;
1504        self.on_query_changed(cx);
1505    }
1506
1507    fn toggle_user_bindings_filter(&mut self, cx: &mut Context<Self>) {
1508        self.source_filters.user = !self.source_filters.user;
1509        self.on_query_changed(cx);
1510    }
1511
1512    fn toggle_zed_defaults_filter(&mut self, cx: &mut Context<Self>) {
1513        self.source_filters.zed_defaults = !self.source_filters.zed_defaults;
1514        self.on_query_changed(cx);
1515    }
1516
1517    fn toggle_vim_defaults_filter(&mut self, cx: &mut Context<Self>) {
1518        self.source_filters.vim_defaults = !self.source_filters.vim_defaults;
1519        self.on_query_changed(cx);
1520    }
1521
1522    fn set_filter_state(&mut self, filter_state: FilterState, cx: &mut Context<Self>) {
1523        if self.filter_state != filter_state {
1524            self.filter_state = filter_state;
1525            self.on_query_changed(cx);
1526        }
1527    }
1528
1529    fn toggle_keystroke_search(
1530        &mut self,
1531        _: &ToggleKeystrokeSearch,
1532        window: &mut Window,
1533        cx: &mut Context<Self>,
1534    ) {
1535        self.search_mode = self.search_mode.invert();
1536        self.on_query_changed(cx);
1537
1538        match self.search_mode {
1539            SearchMode::KeyStroke { .. } => {
1540                self.keystroke_editor.update(cx, |editor, cx| {
1541                    editor.start_recording(&StartRecording, window, cx);
1542                });
1543            }
1544            SearchMode::Normal => {
1545                self.keystroke_editor.update(cx, |editor, cx| {
1546                    editor.stop_recording(&StopRecording, window, cx);
1547                    editor.clear_keystrokes(&ClearKeystrokes, window, cx);
1548                });
1549                window.focus(&self.filter_editor.focus_handle(cx), cx);
1550            }
1551        }
1552    }
1553
1554    fn toggle_exact_keystroke_matching(
1555        &mut self,
1556        _: &ToggleExactKeystrokeMatching,
1557        _: &mut Window,
1558        cx: &mut Context<Self>,
1559    ) {
1560        let SearchMode::KeyStroke { exact_match } = &mut self.search_mode else {
1561            return;
1562        };
1563
1564        *exact_match = !(*exact_match);
1565        self.on_query_changed(cx);
1566    }
1567
1568    fn show_matching_keystrokes(
1569        &mut self,
1570        _: &ShowMatchingKeybinds,
1571        _: &mut Window,
1572        cx: &mut Context<Self>,
1573    ) {
1574        let Some(selected_binding) = self.selected_binding() else {
1575            return;
1576        };
1577
1578        let keystrokes = selected_binding
1579            .keystrokes()
1580            .map(Vec::from)
1581            .unwrap_or_default();
1582
1583        self.filter_state = FilterState::All;
1584        self.search_mode = SearchMode::KeyStroke { exact_match: true };
1585
1586        self.keystroke_editor.update(cx, |editor, cx| {
1587            editor.set_keystrokes(keystrokes, cx);
1588        });
1589    }
1590
1591    fn has_binding_for(&self, action_name: &str) -> bool {
1592        self.keybindings
1593            .iter()
1594            .filter(|kb| kb.keystrokes().is_some())
1595            .any(|kb| kb.action().name == action_name)
1596    }
1597
1598    fn render_filter_dropdown(
1599        &self,
1600        focus_handle: &FocusHandle,
1601        cx: &mut Context<KeymapEditor>,
1602    ) -> impl IntoElement {
1603        let focus_handle = focus_handle.clone();
1604        let keymap_editor = cx.entity();
1605        return PopoverMenu::new("keymap-editor-filter-menu")
1606            .menu(move |window, cx| {
1607                Some(ContextMenu::build_persistent(window, cx, {
1608                    let focus_handle = focus_handle.clone();
1609                    let keymap_editor = keymap_editor.clone();
1610                    move |mut menu, _window, cx| {
1611                        let (filter_state, source_filters, show_no_action_bindings) = keymap_editor
1612                            .read_with(cx, |editor, _| {
1613                                (
1614                                    editor.filter_state,
1615                                    editor.source_filters,
1616                                    editor.show_no_action_bindings,
1617                                )
1618                            });
1619
1620                        menu = menu
1621                            .context(focus_handle.clone())
1622                            .header("Filters")
1623                            .map(add_filter(
1624                                "Conflicts",
1625                                matches!(filter_state, FilterState::Conflicts),
1626                                Some(ToggleConflictFilter.boxed_clone()),
1627                                &focus_handle,
1628                                &keymap_editor,
1629                                None,
1630                            ))
1631                            .map(add_filter(
1632                                "No Action",
1633                                show_no_action_bindings,
1634                                Some(ToggleNoActionBindings.boxed_clone()),
1635                                &focus_handle,
1636                                &keymap_editor,
1637                                None,
1638                            ))
1639                            .separator()
1640                            .header("Categories")
1641                            .map(add_filter(
1642                                "User",
1643                                source_filters.user,
1644                                None,
1645                                &focus_handle,
1646                                &keymap_editor,
1647                                Some(|editor, cx| {
1648                                    editor.toggle_user_bindings_filter(cx);
1649                                }),
1650                            ))
1651                            .map(add_filter(
1652                                "Default",
1653                                source_filters.zed_defaults,
1654                                None,
1655                                &focus_handle,
1656                                &keymap_editor,
1657                                Some(|editor, cx| {
1658                                    editor.toggle_zed_defaults_filter(cx);
1659                                }),
1660                            ))
1661                            .map(add_filter(
1662                                "Vim",
1663                                source_filters.vim_defaults,
1664                                None,
1665                                &focus_handle,
1666                                &keymap_editor,
1667                                Some(|editor, cx| {
1668                                    editor.toggle_vim_defaults_filter(cx);
1669                                }),
1670                            ));
1671                        menu
1672                    }
1673                }))
1674            })
1675            .anchor(gpui::Anchor::TopRight)
1676            .offset(gpui::Point {
1677                x: px(0.0),
1678                y: px(2.0),
1679            })
1680            .trigger_with_tooltip(
1681                IconButton::new("KeymapEditorFilterMenuButton", IconName::Filter)
1682                    .icon_size(IconSize::Small)
1683                    .when(
1684                        self.keybinding_conflict_state.any_user_binding_conflicts(),
1685                        |this| this.indicator(Indicator::dot().color(Color::Warning)),
1686                    ),
1687                Tooltip::text("Filters"),
1688            );
1689
1690        fn add_filter(
1691            name: &'static str,
1692            toggled: bool,
1693            action: Option<Box<dyn Action>>,
1694            focus_handle: &FocusHandle,
1695            keymap_editor: &Entity<KeymapEditor>,
1696            cb: Option<fn(&mut KeymapEditor, &mut Context<KeymapEditor>)>,
1697        ) -> impl FnOnce(ContextMenu) -> ContextMenu {
1698            let focus_handle = focus_handle.clone();
1699            let keymap_editor = keymap_editor.clone();
1700            return move |menu: ContextMenu| {
1701                menu.toggleable_entry(
1702                    name,
1703                    toggled,
1704                    IconPosition::Start,
1705                    action.as_ref().map(|a| a.boxed_clone()),
1706                    move |window, cx| {
1707                        window.focus(&focus_handle, cx);
1708                        if let Some(action) = &action {
1709                            window.dispatch_action(action.boxed_clone(), cx);
1710                        } else if let Some(cb) = cb {
1711                            keymap_editor.update(cx, cb);
1712                        }
1713                    },
1714                )
1715            };
1716        }
1717    }
1718}
1719
1720struct HumanizedActionNameCache {
1721    cache: HashMap<&'static str, SharedString>,
1722}
1723
1724impl HumanizedActionNameCache {
1725    fn new(cx: &App) -> Self {
1726        let cache = HashMap::from_iter(cx.all_action_names().iter().map(|&action_name| {
1727            (
1728                action_name,
1729                command_palette::humanize_action_name(action_name).into(),
1730            )
1731        }));
1732        Self { cache }
1733    }
1734
1735    fn get(&self, action_name: &'static str) -> SharedString {
1736        match self.cache.get(action_name) {
1737            Some(name) => name.clone(),
1738            None => action_name.into(),
1739        }
1740    }
1741}
1742
1743#[derive(Clone)]
1744struct KeyBinding {
1745    keystrokes: Rc<[KeybindingKeystroke]>,
1746    source: KeybindSource,
1747}
1748
1749impl KeyBinding {
1750    fn new(binding: &gpui::KeyBinding, source: KeybindSource) -> Self {
1751        Self {
1752            keystrokes: Rc::from(binding.keystrokes()),
1753            source,
1754        }
1755    }
1756}
1757
1758#[derive(Clone)]
1759struct KeybindInformation {
1760    keystroke_text: SharedString,
1761    binding: KeyBinding,
1762    context: KeybindContextString,
1763    source: KeybindSource,
1764    is_no_action: bool,
1765    is_unbound_by_unbind: bool,
1766}
1767
1768impl KeybindInformation {
1769    fn get_action_mapping(&self) -> ActionMapping {
1770        ActionMapping {
1771            keystrokes: self.binding.keystrokes.clone(),
1772            context: self.context.local().cloned(),
1773        }
1774    }
1775}
1776
1777#[derive(Clone)]
1778struct ActionInformation {
1779    name: &'static str,
1780    humanized_name: SharedString,
1781    arguments: Option<SyntaxHighlightedText>,
1782    documentation: Option<&'static str>,
1783    has_schema: bool,
1784}
1785
1786impl ActionInformation {
1787    fn new(
1788        action_name: &'static str,
1789        action_arguments: Option<SyntaxHighlightedText>,
1790        actions_with_schemas: &HashSet<&'static str>,
1791        action_documentation: &HashMap<&'static str, &'static str>,
1792        action_name_cache: &HumanizedActionNameCache,
1793    ) -> Self {
1794        Self {
1795            humanized_name: action_name_cache.get(action_name),
1796            has_schema: actions_with_schemas.contains(action_name),
1797            arguments: action_arguments,
1798            documentation: action_documentation.get(action_name).copied(),
1799            name: action_name,
1800        }
1801    }
1802}
1803
1804#[derive(Clone)]
1805enum ProcessedBinding {
1806    Mapped(KeybindInformation, ActionInformation),
1807    Unmapped(ActionInformation),
1808}
1809
1810impl ProcessedBinding {
1811    fn new_mapped(
1812        keystroke_text: impl Into<SharedString>,
1813        binding: KeyBinding,
1814        context: KeybindContextString,
1815        source: KeybindSource,
1816        is_no_action: bool,
1817        is_unbound_by_unbind: bool,
1818        action_information: ActionInformation,
1819    ) -> Self {
1820        Self::Mapped(
1821            KeybindInformation {
1822                keystroke_text: keystroke_text.into(),
1823                binding,
1824                context,
1825                source,
1826                is_no_action,
1827                is_unbound_by_unbind,
1828            },
1829            action_information,
1830        )
1831    }
1832
1833    fn is_unbound(&self) -> bool {
1834        matches!(self, Self::Unmapped(_))
1835    }
1836
1837    fn get_action_mapping(&self) -> Option<ActionMapping> {
1838        self.keybind_information()
1839            .map(|keybind| keybind.get_action_mapping())
1840    }
1841
1842    fn keystrokes(&self) -> Option<&[KeybindingKeystroke]> {
1843        self.key_binding()
1844            .map(|binding| binding.keystrokes.as_ref())
1845    }
1846
1847    fn keybind_information(&self) -> Option<&KeybindInformation> {
1848        match self {
1849            Self::Mapped(keybind_information, _) => Some(keybind_information),
1850            Self::Unmapped(_) => None,
1851        }
1852    }
1853
1854    fn keybind_source(&self) -> Option<KeybindSource> {
1855        self.keybind_information().map(|keybind| keybind.source)
1856    }
1857
1858    fn context(&self) -> Option<&KeybindContextString> {
1859        self.keybind_information().map(|keybind| &keybind.context)
1860    }
1861
1862    fn key_binding(&self) -> Option<&KeyBinding> {
1863        self.keybind_information().map(|keybind| &keybind.binding)
1864    }
1865
1866    fn is_no_action(&self) -> bool {
1867        self.keybind_information()
1868            .is_some_and(|keybind| keybind.is_no_action)
1869    }
1870
1871    fn is_unbound_by_unbind(&self) -> bool {
1872        self.keybind_information()
1873            .is_some_and(|keybind| keybind.is_unbound_by_unbind)
1874    }
1875
1876    fn keystroke_text(&self) -> Option<&SharedString> {
1877        self.keybind_information()
1878            .map(|binding| &binding.keystroke_text)
1879    }
1880
1881    fn action(&self) -> &ActionInformation {
1882        match self {
1883            Self::Mapped(_, action) | Self::Unmapped(action) => action,
1884        }
1885    }
1886
1887    fn cmp(&self, other: &Self) -> cmp::Ordering {
1888        match (self, other) {
1889            (Self::Mapped(keybind1, action1), Self::Mapped(keybind2, action2)) => {
1890                match keybind1.source.cmp(&keybind2.source) {
1891                    cmp::Ordering::Equal => action1.humanized_name.cmp(&action2.humanized_name),
1892                    ordering => ordering,
1893                }
1894            }
1895            (Self::Mapped(_, _), Self::Unmapped(_)) => cmp::Ordering::Less,
1896            (Self::Unmapped(_), Self::Mapped(_, _)) => cmp::Ordering::Greater,
1897            (Self::Unmapped(action1), Self::Unmapped(action2)) => {
1898                action1.humanized_name.cmp(&action2.humanized_name)
1899            }
1900        }
1901    }
1902}
1903
1904#[derive(Clone, Debug, IntoElement, PartialEq, Eq, Hash)]
1905enum KeybindContextString {
1906    Global,
1907    Local(SharedString, Arc<Language>),
1908}
1909
1910impl KeybindContextString {
1911    const GLOBAL: SharedString = SharedString::new_static("<global>");
1912
1913    pub fn local(&self) -> Option<&SharedString> {
1914        match self {
1915            KeybindContextString::Global => None,
1916            KeybindContextString::Local(name, _) => Some(name),
1917        }
1918    }
1919
1920    pub fn local_str(&self) -> Option<&str> {
1921        match self {
1922            KeybindContextString::Global => None,
1923            KeybindContextString::Local(name, _) => Some(name),
1924        }
1925    }
1926}
1927
1928impl RenderOnce for KeybindContextString {
1929    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
1930        match self {
1931            KeybindContextString::Global => {
1932                muted_styled_text(KeybindContextString::GLOBAL, cx).into_any_element()
1933            }
1934            KeybindContextString::Local(name, language) => {
1935                SyntaxHighlightedText::new(name, language).into_any_element()
1936            }
1937        }
1938    }
1939}
1940
1941fn muted_styled_text(text: SharedString, cx: &App) -> StyledText {
1942    let len = text.len();
1943    StyledText::new(text).with_highlights([(
1944        0..len,
1945        gpui::HighlightStyle::color(cx.theme().colors().text_muted),
1946    )])
1947}
1948
1949impl Item for KeymapEditor {
1950    type Event = ();
1951
1952    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
1953        "Keymap Editor".into()
1954    }
1955}
1956
1957impl Render for KeymapEditor {
1958    fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
1959        if let SearchMode::KeyStroke { exact_match } = self.search_mode {
1960            let button = IconButton::new("keystrokes-exact-match", IconName::CaseSensitive)
1961                .tooltip(move |_window, cx| {
1962                    Tooltip::for_action(
1963                        "Toggle Exact Match Mode",
1964                        &ToggleExactKeystrokeMatching,
1965                        cx,
1966                    )
1967                })
1968                .shape(IconButtonShape::Square)
1969                .toggle_state(exact_match)
1970                .on_click(cx.listener(|_, _, window, cx| {
1971                    window.dispatch_action(ToggleExactKeystrokeMatching.boxed_clone(), cx);
1972                }));
1973
1974            self.keystroke_editor.update(cx, |editor, _| {
1975                editor.actions_slot = Some(button.into_any_element());
1976            });
1977        } else {
1978            self.keystroke_editor.update(cx, |editor, _| {
1979                editor.actions_slot = None;
1980            });
1981        }
1982
1983        let row_count = self.matches.len();
1984        let focus_handle = &self.focus_handle;
1985        let theme = cx.theme();
1986        let search_mode = self.search_mode;
1987
1988        v_flex()
1989            .id("keymap-editor")
1990            .track_focus(focus_handle)
1991            .key_context(self.key_context())
1992            .on_action(cx.listener(Self::select_next))
1993            .on_action(cx.listener(Self::select_previous))
1994            .on_action(cx.listener(Self::select_first))
1995            .on_action(cx.listener(Self::select_last))
1996            .on_action(cx.listener(Self::focus_search))
1997            .on_action(cx.listener(Self::edit_binding))
1998            .on_action(cx.listener(Self::create_binding))
1999            .on_action(cx.listener(Self::open_create_keybinding_modal))
2000            .on_action(cx.listener(Self::delete_binding))
2001            .on_action(cx.listener(Self::copy_action_to_clipboard))
2002            .on_action(cx.listener(Self::copy_context_to_clipboard))
2003            .on_action(cx.listener(Self::toggle_conflict_filter))
2004            .on_action(cx.listener(Self::toggle_no_action_bindings))
2005            .on_action(cx.listener(Self::toggle_keystroke_search))
2006            .on_action(cx.listener(Self::toggle_exact_keystroke_matching))
2007            .on_action(cx.listener(Self::show_matching_keystrokes))
2008            .on_mouse_move(cx.listener(|this, _, _window, _cx| {
2009                this.show_hover_menus = true;
2010            }))
2011            .size_full()
2012            .p_2()
2013            .gap_1()
2014            .bg(theme.colors().editor_background)
2015            .child(
2016                v_flex()
2017                    .gap_2()
2018                    .child(
2019                        h_flex()
2020                            .gap_2()
2021                            .child(
2022                                h_flex()
2023                                    .key_context({
2024                                        let mut context = KeyContext::new_with_defaults();
2025                                        context.add("BufferSearchBar");
2026                                        context
2027                                    })
2028                                    .flex_1()
2029                                    .min_w_0()
2030                                    .h_8()
2031                                    .px_2()
2032                                    .border_1()
2033                                    .border_color(theme.colors().border)
2034                                    .rounded_md()
2035                                    .child(self.filter_editor.clone()),
2036                            )
2037                            .child(
2038                                h_flex()
2039                                    .gap_1()
2040                                    .flex_none()
2041                                    // Make sure this min-width value aligns with the spacer
2042                                    // div in the keystroke search input
2043                                    .min_w_80()
2044                                    .child(
2045                                        IconButton::new(
2046                                            "KeymapEditorKeystrokeSearchButton",
2047                                            IconName::Keyboard,
2048                                        )
2049                                        .icon_size(IconSize::Small)
2050                                        .toggle_state(matches!(
2051                                            search_mode,
2052                                            SearchMode::KeyStroke { .. }
2053                                        ))
2054                                        .tooltip({
2055                                            let focus_handle = focus_handle.clone();
2056                                            move |_window, cx| {
2057                                                Tooltip::for_action_in(
2058                                                    "Search by Keystrokes",
2059                                                    &ToggleKeystrokeSearch,
2060                                                    &focus_handle,
2061                                                    cx,
2062                                                )
2063                                            }
2064                                        })
2065                                        .on_click(cx.listener(|_, _, window, cx| {
2066                                            window.dispatch_action(
2067                                                ToggleKeystrokeSearch.boxed_clone(),
2068                                                cx,
2069                                            );
2070                                        })),
2071                                    )
2072                                    .child(
2073                                        self.render_filter_dropdown(focus_handle, cx)
2074                                    )
2075                                    .child(
2076                                        Button::new("edit-in-json", "Edit in JSON")
2077                                            .key_binding(
2078                                                ui::KeyBinding::for_action_in(&zed_actions::OpenKeymapFile, &focus_handle, cx)
2079                                                    .map(|kb| kb.size(rems_from_px(10.))),
2080                                            )
2081                                            .on_click(|_, window, cx| {
2082                                                window.dispatch_action(
2083                                                    zed_actions::OpenKeymapFile.boxed_clone(),
2084                                                    cx,
2085                                                );
2086                                            })
2087                                    )
2088                                    .child(
2089                                        Button::new("create", "Create Keybinding")
2090                                            .style(ButtonStyle::Outlined)
2091                                            .key_binding(
2092                                                ui::KeyBinding::for_action_in(&OpenCreateKeybindingModal, &focus_handle, cx)
2093                                                    .map(|kb| kb.size(rems_from_px(10.))),
2094                                            )
2095                                            .on_click(|_, window, cx| {
2096                                                window.dispatch_action(
2097                                                    OpenCreateKeybindingModal.boxed_clone(),
2098                                                    cx,
2099                                                );
2100                                            })
2101                                    )
2102                            ),
2103                    )
2104                    .when(
2105                        matches!(self.search_mode, SearchMode::KeyStroke { .. }),
2106                        |this| {
2107                            this.child(
2108                                h_flex()
2109                                    .gap_2()
2110                                    .child(self.keystroke_editor.clone())
2111                                    .child(div().min_w_80()), // Spacer div to align with the search input
2112                            )
2113                        },
2114                    ),
2115            )
2116            .child(
2117                Table::new(COLS)
2118                    .interactable(&self.table_interaction_state)
2119                    .striped()
2120                    .empty_table_callback({
2121                        let this = cx.entity();
2122                        move |window, cx| this.read(cx).render_no_matches_hint(window, cx)
2123                    })
2124                    .width_config(ColumnWidthConfig::redistributable(
2125                        self.current_widths.clone(),
2126                    ))
2127                    .header(vec!["", "Action", "Arguments", "Keystrokes", "Context", "Source"])
2128                    .uniform_list(
2129                        "keymap-editor-table",
2130                        row_count,
2131                        cx.processor(move |this, range: Range<usize>, _window, cx| {
2132                            let context_menu_deployed = this.context_menu_deployed();
2133                            range
2134                                .filter_map(|index| {
2135                                    let candidate_id = this.matches.get(index)?.candidate_id;
2136                                    let binding = &this.keybindings[candidate_id];
2137                                    let action_name = binding.action().name;
2138                                    let conflict = this.get_conflict(index);
2139                                    let is_unbound_by_unbind = binding.is_unbound_by_unbind();
2140                                    let is_overridden = conflict.is_some_and(|conflict| {
2141                                        !conflict.is_user_keybind_conflict()
2142                                    });
2143                                    let is_dimmed = is_overridden || is_unbound_by_unbind;
2144
2145                                    let icon = this.create_row_button(
2146                                        index,
2147                                        conflict,
2148                                        is_unbound_by_unbind,
2149                                        cx,
2150                                    );
2151
2152                                    let action = div()
2153                                        .id(("keymap action", index))
2154                                        .child({
2155                                            if action_name != gpui::NoAction.name() {
2156                                                binding
2157                                                    .action()
2158                                                    .humanized_name
2159                                                    .clone()
2160                                                    .into_any_element()
2161                                            } else {
2162                                                const NULL: SharedString =
2163                                                    SharedString::new_static("<null>");
2164                                                muted_styled_text(NULL, cx)
2165                                                    .into_any_element()
2166                                            }
2167                                        })
2168                                        .when(
2169                                            !context_menu_deployed
2170                                                && this.show_hover_menus
2171                                                && !is_dimmed,
2172                                            |this| {
2173                                                this.tooltip({
2174                                                    let action_name = binding.action().name;
2175                                                    let action_docs =
2176                                                        binding.action().documentation;
2177                                                    move |_, cx| {
2178                                                        let action_tooltip =
2179                                                            Tooltip::new(action_name);
2180                                                        let action_tooltip = match action_docs {
2181                                                            Some(docs) => action_tooltip.meta(docs),
2182                                                            None => action_tooltip,
2183                                                        };
2184                                                        cx.new(|_| action_tooltip).into()
2185                                                    }
2186                                                })
2187                                            },
2188                                        )
2189                                        .into_any_element();
2190
2191                                    let keystrokes = binding.key_binding().map_or(
2192                                        binding
2193                                            .keystroke_text()
2194                                            .cloned()
2195                                            .unwrap_or_default()
2196                                            .into_any_element(),
2197                                        |binding| ui::KeyBinding::from_keystrokes(binding.keystrokes.clone(), binding.source == KeybindSource::Vim).into_any_element()
2198                                    );
2199
2200                                    let action_arguments = match binding.action().arguments.clone()
2201                                    {
2202                                        Some(arguments) => arguments.into_any_element(),
2203                                        None => {
2204                                            if binding.action().has_schema {
2205                                                muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
2206                                                    .into_any_element()
2207                                            } else {
2208                                                gpui::Empty.into_any_element()
2209                                            }
2210                                        }
2211                                    };
2212
2213                                    let context = binding.context().cloned().map_or(
2214                                        gpui::Empty.into_any_element(),
2215                                        |context| {
2216                                            let is_local = context.local().is_some();
2217
2218                                            div()
2219                                                .id(("keymap context", index))
2220                                                .child(context.clone())
2221                                                .when(
2222                                                    is_local
2223                                                        && !context_menu_deployed
2224                                                        && !is_dimmed
2225                                                        && this.show_hover_menus,
2226                                                    |this| {
2227                                                        this.tooltip(Tooltip::element({
2228                                                            move |_, _| {
2229                                                                context.clone().into_any_element()
2230                                                            }
2231                                                        }))
2232                                                    },
2233                                                )
2234                                                .into_any_element()
2235                                        },
2236                                    );
2237
2238                                    let source = binding
2239                                        .keybind_source()
2240                                        .map(|source| source.name())
2241                                        .unwrap_or_default()
2242                                        .into_any_element();
2243
2244                                    Some(vec![
2245                                        icon.into_any_element(),
2246                                        action,
2247                                        action_arguments,
2248                                        keystrokes,
2249                                        context,
2250                                        source,
2251                                    ])
2252                                })
2253                                .collect()
2254                        }),
2255                    )
2256                    .map_row(cx.processor(
2257                        |this, (row_index, row): (usize, Stateful<Div>), _window, cx| {
2258                        let conflict = this.get_conflict(row_index);
2259                            let candidate_id = this.matches.get(row_index).map(|candidate| candidate.candidate_id);
2260                            let is_unbound_by_unbind = candidate_id
2261                                .and_then(|candidate_id| this.keybindings.get(candidate_id))
2262                                .is_some_and(ProcessedBinding::is_unbound_by_unbind);
2263                            let is_selected = this.selected_index == Some(row_index);
2264
2265                            let row_id = row_group_id(row_index);
2266
2267                            div()
2268                                .id(("keymap-row-wrapper", row_index))
2269                                .child(
2270                                    row.id(row_id.clone())
2271                                        .when(!is_unbound_by_unbind, |row| {
2272                                            row.on_any_mouse_down(cx.listener(
2273                                                move |this,
2274                                                      mouse_down_event: &gpui::MouseDownEvent,
2275                                                      window,
2276                                                      cx| {
2277                                                    if mouse_down_event.button == MouseButton::Right {
2278                                                        this.select_index(
2279                                                            row_index, None, window, cx,
2280                                                        );
2281                                                        this.create_context_menu(
2282                                                            mouse_down_event.position,
2283                                                            window,
2284                                                            cx,
2285                                                        );
2286                                                    }
2287                                                },
2288                                            ))
2289                                        })
2290                                        .when(!is_unbound_by_unbind, |row| {
2291                                            row.on_click(cx.listener(
2292                                                move |this, event: &ClickEvent, window, cx| {
2293                                                    this.select_index(row_index, None, window, cx);
2294                                                    if event.click_count() == 2 {
2295                                                        this.open_edit_keybinding_modal(
2296                                                            false, window, cx,
2297                                                        );
2298                                                    }
2299                                                },
2300                                            ))
2301                                        })
2302                                        .group(row_id)
2303                                        .when(
2304                                            is_unbound_by_unbind
2305                                                || conflict.is_some_and(|conflict| {
2306                                                    !conflict.is_user_keybind_conflict()
2307                                                }),
2308                                            |row| {
2309                                                const OVERRIDDEN_OPACITY: f32 = 0.5;
2310                                                row.opacity(OVERRIDDEN_OPACITY)
2311                                            },
2312                                        )
2313                                        .when_some(
2314                                            conflict.filter(|conflict| {
2315                                                !is_unbound_by_unbind
2316                                                    && !this.context_menu_deployed() &&
2317                                                !conflict.is_user_keybind_conflict()
2318                                            }),
2319                                            |row, conflict| {
2320                                                let overriding_binding = this.keybindings.get(conflict.index);
2321                                                let context = overriding_binding.and_then(|binding| {
2322                                                    match conflict.override_source {
2323                                                        KeybindSource::User  => Some("your keymap"),
2324                                                        KeybindSource::Vim => Some("the vim keymap"),
2325                                                        KeybindSource::Base => Some("your base keymap"),
2326                                                        _ => {
2327                                                            log::error!("Unexpected override from the {} keymap", conflict.override_source.name());
2328                                                            None
2329                                                        }
2330                                                    }.map(|source| format!("This keybinding is overridden by the '{}' binding from {}.", binding.action().humanized_name, source))
2331                                                }).unwrap_or_else(|| "This binding is overridden.".to_string());
2332
2333                                                row.tooltip(Tooltip::text(context))
2334                                            },
2335                                        )
2336                                        .when(is_unbound_by_unbind, |row| {
2337                                            row.tooltip(Tooltip::text("This action is unbound"))
2338                                        }),
2339                                )
2340                                .border_2()
2341                                .when(
2342                                    conflict.is_some_and(|conflict| {
2343                                        conflict.is_user_keybind_conflict()
2344                                    }),
2345                                    |row| row.bg(cx.theme().status().error_background),
2346                                )
2347                                .when(is_selected, |row| {
2348                                    row.border_color(cx.theme().colors().panel_focused_border)
2349                                })
2350                                .into_any_element()
2351                        }),
2352                    ),
2353            )
2354            .on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
2355                // This ensures that the menu is not dismissed in cases where scroll events
2356                // with a delta of zero are emitted
2357                if !event.delta.pixel_delta(px(1.)).y.is_zero() {
2358                    this.context_menu.take();
2359                    cx.notify();
2360                }
2361            }))
2362            .children(self.context_menu.as_ref().map(|(menu, position, _)| {
2363                deferred(
2364                    anchored()
2365                        .position(*position)
2366                        .anchor(gpui::Anchor::TopLeft)
2367                        .child(menu.clone()),
2368                )
2369                .with_priority(1)
2370            }))
2371    }
2372}
2373
2374fn row_group_id(row_index: usize) -> SharedString {
2375    SharedString::new(format!("keymap-table-row-{}", row_index))
2376}
2377
2378fn base_button_style(row_index: usize, icon: IconName) -> IconButton {
2379    IconButton::new(("keymap-icon", row_index), icon)
2380        .shape(IconButtonShape::Square)
2381        .size(ButtonSize::Compact)
2382}
2383
2384#[derive(Debug, Clone, IntoElement)]
2385struct SyntaxHighlightedText {
2386    text: SharedString,
2387    language: Arc<Language>,
2388}
2389
2390impl SyntaxHighlightedText {
2391    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
2392        Self {
2393            text: text.into(),
2394            language,
2395        }
2396    }
2397}
2398
2399impl RenderOnce for SyntaxHighlightedText {
2400    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
2401        let text_style = window.text_style();
2402        let syntax_theme = cx.theme().syntax();
2403
2404        let text = self.text.clone();
2405
2406        let highlights = self
2407            .language
2408            .highlight_text(&text.as_ref().into(), 0..text.len());
2409        let mut runs = Vec::with_capacity(highlights.len());
2410        let mut offset = 0;
2411
2412        for (highlight_range, highlight_id) in highlights {
2413            // Add un-highlighted text before the current highlight
2414            if highlight_range.start > offset {
2415                runs.push(text_style.to_run(highlight_range.start - offset));
2416            }
2417
2418            let mut run_style = text_style.clone();
2419            if let Some(highlight_style) = syntax_theme.get(highlight_id).cloned() {
2420                run_style = run_style.highlight(highlight_style);
2421            }
2422
2423            // add the highlighted range
2424            runs.push(run_style.to_run(highlight_range.len()));
2425            offset = highlight_range.end;
2426        }
2427
2428        // Add any remaining un-highlighted text
2429        if offset < text.len() {
2430            runs.push(text_style.to_run(text.len() - offset));
2431        }
2432
2433        StyledText::new(text).with_runs(runs)
2434    }
2435}
2436
2437#[derive(PartialEq)]
2438struct InputError {
2439    severity: Severity,
2440    content: SharedString,
2441}
2442
2443impl InputError {
2444    fn warning(message: impl Into<SharedString>) -> Self {
2445        Self {
2446            severity: Severity::Warning,
2447            content: message.into(),
2448        }
2449    }
2450
2451    fn error(message: anyhow::Error) -> Self {
2452        Self {
2453            severity: Severity::Error,
2454            content: message.to_string().into(),
2455        }
2456    }
2457}
2458
2459struct KeybindingEditorModal {
2460    creating: bool,
2461    editing_keybind: ProcessedBinding,
2462    editing_keybind_idx: usize,
2463    keybind_editor: Entity<KeystrokeInput>,
2464    context_editor: Entity<InputField>,
2465    action_editor: Option<Entity<InputField>>,
2466    action_arguments_editor: Option<Entity<ActionArgumentsEditor>>,
2467    action_name_to_static: HashMap<String, &'static str>,
2468    selected_action_name: Option<&'static str>,
2469    fs: Arc<dyn Fs>,
2470    error: Option<InputError>,
2471    keymap_editor: Entity<KeymapEditor>,
2472    workspace: WeakEntity<Workspace>,
2473    focus_state: KeybindingEditorModalFocusState,
2474}
2475
2476impl ModalView for KeybindingEditorModal {}
2477
2478impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
2479
2480impl Focusable for KeybindingEditorModal {
2481    fn focus_handle(&self, cx: &App) -> FocusHandle {
2482        if let Some(action_editor) = &self.action_editor {
2483            return action_editor.focus_handle(cx);
2484        }
2485        self.keybind_editor.focus_handle(cx)
2486    }
2487}
2488
2489impl KeybindingEditorModal {
2490    pub fn new(
2491        create: bool,
2492        editing_keybind: ProcessedBinding,
2493        editing_keybind_idx: usize,
2494        keymap_editor: Entity<KeymapEditor>,
2495        action_args_temp_dir: Option<&std::path::Path>,
2496        workspace: WeakEntity<Workspace>,
2497        fs: Arc<dyn Fs>,
2498        window: &mut Window,
2499        cx: &mut App,
2500    ) -> Self {
2501        let keybind_editor = cx
2502            .new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx));
2503
2504        let context_editor: Entity<InputField> = cx.new(|cx| {
2505            let input = InputField::new(window, cx, "Keybinding Context")
2506                .label("Edit Context")
2507                .label_size(LabelSize::Default);
2508
2509            if let Some(context) = editing_keybind
2510                .context()
2511                .and_then(KeybindContextString::local)
2512            {
2513                input.set_text(&context, window, cx);
2514            }
2515
2516            let editor_entity = input.editor();
2517            let editor_entity = editor_entity
2518                .as_any()
2519                .downcast_ref::<Entity<Editor>>()
2520                .unwrap()
2521                .clone();
2522            let workspace = workspace.clone();
2523            cx.spawn(async move |_input_handle, cx| {
2524                let contexts = cx
2525                    .background_spawn(async { collect_contexts_from_assets() })
2526                    .await;
2527
2528                let language = load_keybind_context_language(workspace, cx).await;
2529                editor_entity.update(cx, |editor, cx| {
2530                    if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
2531                        buffer.update(cx, |buffer, cx| {
2532                            buffer.set_language(Some(language), cx);
2533                        });
2534                    }
2535                    editor.set_completion_provider(Some(std::rc::Rc::new(
2536                        KeyContextCompletionProvider { contexts },
2537                    )));
2538                });
2539            })
2540            .detach();
2541
2542            input
2543        });
2544
2545        let has_action_editor = create && editing_keybind.action().name == gpui::NoAction.name();
2546
2547        let (action_editor, action_name_to_static) = if has_action_editor {
2548            let actions: Vec<&'static str> = cx.all_action_names().to_vec();
2549
2550            let humanized_names: HashMap<&'static str, SharedString> = actions
2551                .iter()
2552                .map(|&name| (name, command_palette::humanize_action_name(name).into()))
2553                .collect();
2554
2555            let action_name_to_static: HashMap<String, &'static str> = actions
2556                .iter()
2557                .map(|&name| (name.to_string(), name))
2558                .collect();
2559
2560            let editor = cx.new(|cx| {
2561                let input = InputField::new(window, cx, "Type an action name")
2562                    .label("Action")
2563                    .label_size(LabelSize::Default);
2564
2565                let editor_entity = input.editor();
2566                let editor_entity = editor_entity
2567                    .as_any()
2568                    .downcast_ref::<Entity<Editor>>()
2569                    .unwrap();
2570                editor_entity.update(cx, |editor, _cx| {
2571                    editor.set_completion_provider(Some(std::rc::Rc::new(
2572                        ActionCompletionProvider::new(actions, humanized_names),
2573                    )));
2574                });
2575
2576                input
2577            });
2578
2579            (Some(editor), action_name_to_static)
2580        } else {
2581            (None, HashMap::default())
2582        };
2583
2584        let action_has_schema = editing_keybind.action().has_schema;
2585        let action_name_for_args = editing_keybind.action().name;
2586        let action_args = editing_keybind
2587            .action()
2588            .arguments
2589            .as_ref()
2590            .map(|args| args.text.clone());
2591
2592        let action_arguments_editor = action_has_schema.then(|| {
2593            cx.new(|cx| {
2594                ActionArgumentsEditor::new(
2595                    action_name_for_args,
2596                    action_args.clone(),
2597                    action_args_temp_dir,
2598                    workspace.clone(),
2599                    window,
2600                    cx,
2601                )
2602            })
2603        });
2604
2605        let focus_state = KeybindingEditorModalFocusState::new(
2606            action_editor.as_ref().map(|e| e.focus_handle(cx)),
2607            keybind_editor.focus_handle(cx),
2608            action_arguments_editor
2609                .as_ref()
2610                .map(|args_editor| args_editor.focus_handle(cx)),
2611            context_editor.focus_handle(cx),
2612        );
2613
2614        Self {
2615            creating: create,
2616            editing_keybind,
2617            editing_keybind_idx,
2618            fs,
2619            keybind_editor,
2620            context_editor,
2621            action_editor,
2622            action_arguments_editor,
2623            action_name_to_static,
2624            selected_action_name: None,
2625            error: None,
2626            keymap_editor,
2627            workspace,
2628            focus_state,
2629        }
2630    }
2631
2632    fn add_action_arguments_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2633        let Some(action_editor) = &self.action_editor else {
2634            return;
2635        };
2636
2637        let action_name_str = action_editor.read(cx).text(cx);
2638        let current_action = self.action_name_to_static.get(&action_name_str).copied();
2639
2640        if current_action == self.selected_action_name {
2641            return;
2642        }
2643
2644        self.selected_action_name = current_action;
2645
2646        let Some(action_name) = current_action else {
2647            if self.action_arguments_editor.is_some() {
2648                self.action_arguments_editor = None;
2649                self.rebuild_focus_state(cx);
2650                cx.notify();
2651            }
2652            return;
2653        };
2654
2655        let (action_has_schema, temp_dir) = {
2656            let keymap_editor = self.keymap_editor.read(cx);
2657            let has_schema = keymap_editor.actions_with_schemas.contains(action_name);
2658            let temp_dir = keymap_editor
2659                .action_args_temp_dir
2660                .as_ref()
2661                .map(|dir| dir.path().to_path_buf());
2662            (has_schema, temp_dir)
2663        };
2664
2665        let currently_has_editor = self.action_arguments_editor.is_some();
2666
2667        if action_has_schema && !currently_has_editor {
2668            let workspace = self.workspace.clone();
2669
2670            let new_editor = cx.new(|cx| {
2671                ActionArgumentsEditor::new(
2672                    action_name,
2673                    None,
2674                    temp_dir.as_deref(),
2675                    workspace,
2676                    window,
2677                    cx,
2678                )
2679            });
2680
2681            self.action_arguments_editor = Some(new_editor);
2682            self.rebuild_focus_state(cx);
2683            cx.notify();
2684        } else if !action_has_schema && currently_has_editor {
2685            self.action_arguments_editor = None;
2686            self.rebuild_focus_state(cx);
2687            cx.notify();
2688        }
2689    }
2690
2691    fn rebuild_focus_state(&mut self, cx: &App) {
2692        self.focus_state = KeybindingEditorModalFocusState::new(
2693            self.action_editor.as_ref().map(|e| e.focus_handle(cx)),
2694            self.keybind_editor.focus_handle(cx),
2695            self.action_arguments_editor
2696                .as_ref()
2697                .map(|args_editor| args_editor.focus_handle(cx)),
2698            self.context_editor.focus_handle(cx),
2699        );
2700    }
2701
2702    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
2703        if self
2704            .error
2705            .as_ref()
2706            .is_some_and(|old_error| old_error.severity == Severity::Warning && *old_error == error)
2707        {
2708            false
2709        } else {
2710            self.error = Some(error);
2711            cx.notify();
2712            true
2713        }
2714    }
2715
2716    fn get_selected_action_name(&self, cx: &App) -> anyhow::Result<&'static str> {
2717        if let Some(selector) = self.action_editor.as_ref() {
2718            let action_name_str = selector.read(cx).text(cx);
2719
2720            if action_name_str.is_empty() {
2721                anyhow::bail!("Action name is required");
2722            }
2723
2724            self.action_name_to_static
2725                .get(&action_name_str)
2726                .copied()
2727                .ok_or_else(|| anyhow::anyhow!("Action '{}' not found", action_name_str))
2728        } else {
2729            Ok(self.editing_keybind.action().name)
2730        }
2731    }
2732
2733    fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
2734        let action_name = self.get_selected_action_name(cx)?;
2735        let action_arguments = self
2736            .action_arguments_editor
2737            .as_ref()
2738            .map(|arguments_editor| arguments_editor.read(cx).editor.read(cx).text(cx))
2739            .filter(|args| !args.is_empty());
2740
2741        let value = action_arguments
2742            .as_ref()
2743            .map(|args| {
2744                serde_json::from_str(args).context("Failed to parse action arguments as JSON")
2745            })
2746            .transpose()?;
2747
2748        cx.build_action(action_name, value)
2749            .context("Failed to validate action arguments")?;
2750        Ok(action_arguments)
2751    }
2752
2753    fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<KeybindingKeystroke>> {
2754        let new_keystrokes = self
2755            .keybind_editor
2756            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
2757        anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
2758        Ok(new_keystrokes)
2759    }
2760
2761    fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
2762        let new_context = self
2763            .context_editor
2764            .read_with(cx, |input, cx| input.text(cx));
2765        let Some(context) = new_context.is_empty().not().then_some(new_context) else {
2766            return Ok(None);
2767        };
2768        gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
2769
2770        Ok(Some(context))
2771    }
2772
2773    fn save_or_display_error(&mut self, cx: &mut Context<Self>) {
2774        self.save(cx).map_err(|err| self.set_error(err, cx)).ok();
2775    }
2776
2777    fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
2778        let existing_keybind = self.editing_keybind.clone();
2779        let fs = self.fs.clone();
2780
2781        let mut new_keystrokes = self.validate_keystrokes(cx).map_err(InputError::error)?;
2782        new_keystrokes
2783            .iter_mut()
2784            .for_each(|ks| ks.remove_key_char());
2785
2786        let new_context = self.validate_context(cx).map_err(InputError::error)?;
2787        let new_action_args = self
2788            .validate_action_arguments(cx)
2789            .map_err(InputError::error)?;
2790
2791        let action_mapping = ActionMapping {
2792            keystrokes: Rc::from(new_keystrokes.as_slice()),
2793            context: new_context.map(SharedString::from),
2794        };
2795
2796        let conflicting_indices = self
2797            .keymap_editor
2798            .read(cx)
2799            .keybinding_conflict_state
2800            .conflicting_indices_for_mapping(
2801                &action_mapping,
2802                self.creating.not().then_some(self.editing_keybind_idx),
2803            );
2804
2805        conflicting_indices.map(|KeybindConflict {
2806            first_conflict_index,
2807            remaining_conflict_amount,
2808        }|
2809        {
2810            let conflicting_action_name = self
2811                .keymap_editor
2812                .read(cx)
2813                .keybindings
2814                .get(first_conflict_index)
2815                .map(|keybind| keybind.action().name);
2816
2817            let warning_message = match conflicting_action_name {
2818                Some(name) => {
2819                     if remaining_conflict_amount > 0 {
2820                        format!(
2821                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
2822                            name, remaining_conflict_amount
2823                        )
2824                    } else {
2825                        format!("Your keybind would conflict with the \"{}\" action", name)
2826                    }
2827                }
2828                None => {
2829                    log::info!(
2830                        "Could not find action in keybindings with index {}",
2831                        first_conflict_index
2832                    );
2833                    "Your keybind would conflict with other actions".to_string()
2834                }
2835            };
2836
2837            let warning = InputError::warning(warning_message);
2838            if self.error.as_ref().is_some_and(|old_error| *old_error == warning) {
2839                Ok(())
2840           } else {
2841                Err(warning)
2842            }
2843        }).unwrap_or(Ok(()))?;
2844
2845        let create = self.creating;
2846        let keyboard_mapper = cx.keyboard_mapper().clone();
2847        let deprecated_aliases = cx.deprecated_actions_to_preferred_actions().clone();
2848
2849        let action_name = self
2850            .get_selected_action_name(cx)
2851            .map_err(InputError::error)?;
2852
2853        let humanized_action_name: SharedString =
2854            command_palette::humanize_action_name(action_name).into();
2855
2856        let action_information = ActionInformation::new(
2857            action_name,
2858            None,
2859            &HashSet::default(),
2860            cx.action_documentation(),
2861            &self.keymap_editor.read(cx).humanized_action_names,
2862        );
2863
2864        let keybind_for_save = if create {
2865            ProcessedBinding::Unmapped(action_information)
2866        } else {
2867            existing_keybind
2868        };
2869
2870        cx.spawn(async move |this, cx| {
2871            match save_keybinding_update(
2872                create,
2873                keybind_for_save,
2874                &action_mapping,
2875                new_action_args.as_deref(),
2876                &fs,
2877                keyboard_mapper.as_ref(),
2878                &deprecated_aliases,
2879            )
2880            .await
2881            {
2882                Ok(_) => {
2883                    this.update(cx, |this, cx| {
2884                        this.keymap_editor.update(cx, |keymap, cx| {
2885                            keymap.previous_edit = Some(PreviousEdit::Keybinding {
2886                                action_mapping,
2887                                action_name,
2888                                fallback: keymap.table_interaction_state.read(cx).scroll_offset(),
2889                            });
2890                            let status_toast = StatusToast::new(
2891                                format!("Saved edits to the {} action.", humanized_action_name),
2892                                cx,
2893                                move |this, _cx| {
2894                                    this.icon(
2895                                        Icon::new(IconName::Check)
2896                                            .size(IconSize::Small)
2897                                            .color(Color::Success),
2898                                    )
2899                                    .dismiss_button(true)
2900                                    // .action("Undo", f) todo: wire the undo functionality
2901                                },
2902                            );
2903
2904                            this.workspace
2905                                .update(cx, |workspace, cx| {
2906                                    workspace.toggle_status_toast(status_toast, cx);
2907                                })
2908                                .log_err();
2909                        });
2910                        cx.emit(DismissEvent);
2911                    })
2912                    .ok();
2913                }
2914                Err(err) => {
2915                    this.update(cx, |this, cx| {
2916                        this.set_error(InputError::error(err), cx);
2917                    })
2918                    .log_err();
2919                }
2920            }
2921        })
2922        .detach();
2923
2924        Ok(())
2925    }
2926
2927    fn is_any_editor_showing_completions(&self, window: &Window, cx: &App) -> bool {
2928        let is_editor_showing_completions =
2929            |focus_handle: &FocusHandle, editor_entity: &Entity<Editor>| -> bool {
2930                focus_handle.contains_focused(window, cx)
2931                    && editor_entity.read_with(cx, |editor, _cx| {
2932                        editor
2933                            .context_menu()
2934                            .borrow()
2935                            .as_ref()
2936                            .is_some_and(|menu| menu.visible())
2937                    })
2938            };
2939
2940        self.action_editor.as_ref().is_some_and(|action_editor| {
2941            let focus_handle = action_editor.read(cx).focus_handle(cx);
2942            let editor_entity = action_editor.read(cx).editor();
2943            let editor_entity = editor_entity
2944                .as_any()
2945                .downcast_ref::<Entity<Editor>>()
2946                .unwrap();
2947            is_editor_showing_completions(&focus_handle, editor_entity)
2948        }) || {
2949            let focus_handle = self.context_editor.read(cx).focus_handle(cx);
2950            let editor_entity = self.context_editor.read(cx).editor();
2951            let editor_entity = editor_entity
2952                .as_any()
2953                .downcast_ref::<Entity<Editor>>()
2954                .unwrap();
2955            is_editor_showing_completions(&focus_handle, editor_entity)
2956        } || self
2957            .action_arguments_editor
2958            .as_ref()
2959            .is_some_and(|args_editor| {
2960                let focus_handle = args_editor.read(cx).focus_handle(cx);
2961                let editor_entity = &args_editor.read(cx).editor;
2962                is_editor_showing_completions(&focus_handle, editor_entity)
2963            })
2964    }
2965
2966    fn key_context(&self) -> KeyContext {
2967        let mut key_context = KeyContext::new_with_defaults();
2968        key_context.add("KeybindEditorModal");
2969        key_context
2970    }
2971
2972    fn key_context_internal(&self, window: &Window, cx: &App) -> KeyContext {
2973        let mut key_context = self.key_context();
2974
2975        if self.is_any_editor_showing_completions(window, cx) {
2976            key_context.add("showing_completions");
2977        }
2978
2979        key_context
2980    }
2981
2982    fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
2983        if self.is_any_editor_showing_completions(window, cx) {
2984            return;
2985        }
2986        self.focus_state.focus_next(window, cx);
2987    }
2988
2989    fn focus_prev(
2990        &mut self,
2991        _: &menu::SelectPrevious,
2992        window: &mut Window,
2993        cx: &mut Context<Self>,
2994    ) {
2995        if self.is_any_editor_showing_completions(window, cx) {
2996            return;
2997        }
2998        self.focus_state.focus_previous(window, cx);
2999    }
3000
3001    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
3002        self.save_or_display_error(cx);
3003    }
3004
3005    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
3006        cx.emit(DismissEvent);
3007    }
3008
3009    fn get_matching_bindings_count(&self, cx: &Context<Self>) -> usize {
3010        let current_keystrokes = self.keybind_editor.read(cx).keystrokes();
3011
3012        if current_keystrokes.is_empty() {
3013            return 0;
3014        }
3015
3016        self.keymap_editor
3017            .read(cx)
3018            .keybindings
3019            .iter()
3020            .enumerate()
3021            .filter(|(idx, binding)| {
3022                // Don't count the binding we're currently editing
3023                if !self.creating && *idx == self.editing_keybind_idx {
3024                    return false;
3025                }
3026
3027                binding.keystrokes().is_some_and(|keystrokes| {
3028                    keystrokes_match_exactly(keystrokes, current_keystrokes)
3029                })
3030            })
3031            .count()
3032    }
3033
3034    fn show_matching_bindings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3035        let keystrokes = self.keybind_editor.read(cx).keystrokes().to_vec();
3036
3037        self.keymap_editor.update(cx, |keymap_editor, cx| {
3038            keymap_editor.clear_action_query(window, cx)
3039        });
3040
3041        // Dismiss the modal
3042        cx.emit(DismissEvent);
3043
3044        // Update the keymap editor to show matching keystrokes
3045        self.keymap_editor.update(cx, |editor, cx| {
3046            editor.filter_state = FilterState::All;
3047            editor.search_mode = SearchMode::KeyStroke { exact_match: true };
3048            editor.keystroke_editor.update(cx, |keystroke_editor, cx| {
3049                keystroke_editor.set_keystrokes(keystrokes, cx);
3050            });
3051        });
3052    }
3053}
3054
3055impl Render for KeybindingEditorModal {
3056    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3057        self.add_action_arguments_input(window, cx);
3058
3059        let theme = cx.theme().colors();
3060        let matching_bindings_count = self.get_matching_bindings_count(cx);
3061        let key_context = self.key_context_internal(window, cx);
3062        let showing_completions = key_context.contains("showing_completions");
3063
3064        v_flex()
3065            .w(rems(34.))
3066            .elevation_3(cx)
3067            .key_context(key_context)
3068            .on_action(cx.listener(Self::confirm))
3069            .on_action(cx.listener(Self::cancel))
3070            .when(!showing_completions, |this| {
3071                this.on_action(cx.listener(Self::focus_next))
3072                    .on_action(cx.listener(Self::focus_prev))
3073            })
3074            .child(
3075                Modal::new("keybinding_editor_modal", None)
3076                    .header(
3077                        ModalHeader::new().child(
3078                            v_flex()
3079                                .w_full()
3080                                .pb_1p5()
3081                                .mb_1()
3082                                .gap_0p5()
3083                                .border_b_1()
3084                                .border_color(theme.border_variant)
3085                                .when(!self.creating, |this| {
3086                                    this.child(Label::new(
3087                                        self.editing_keybind.action().humanized_name.clone(),
3088                                    ))
3089                                    .when_some(
3090                                        self.editing_keybind.action().documentation,
3091                                        |this, docs| {
3092                                            this.child(
3093                                                Label::new(docs)
3094                                                    .size(LabelSize::Small)
3095                                                    .color(Color::Muted),
3096                                            )
3097                                        },
3098                                    )
3099                                })
3100                                .when(self.creating, |this| {
3101                                    this.child(Label::new("Create Keybinding"))
3102                                }),
3103                        ),
3104                    )
3105                    .section(
3106                        Section::new().child(
3107                            v_flex()
3108                                .gap_2p5()
3109                                .when_some(
3110                                    self.creating
3111                                        .then_some(())
3112                                        .and_then(|_| self.action_editor.as_ref()),
3113                                    |this, selector| this.child(selector.clone()),
3114                                )
3115                                .child(
3116                                    v_flex()
3117                                        .gap_1()
3118                                        .child(Label::new("Edit Keystroke"))
3119                                        .child(self.keybind_editor.clone())
3120                                        .child(h_flex().gap_px().when(
3121                                            matching_bindings_count > 0,
3122                                            |this| {
3123                                                let label = format!(
3124                                                    "There {} {} {} with the same keystrokes.",
3125                                                    if matching_bindings_count == 1 {
3126                                                        "is"
3127                                                    } else {
3128                                                        "are"
3129                                                    },
3130                                                    matching_bindings_count,
3131                                                    if matching_bindings_count == 1 {
3132                                                        "binding"
3133                                                    } else {
3134                                                        "bindings"
3135                                                    }
3136                                                );
3137
3138                                                this.child(
3139                                                    Label::new(label)
3140                                                        .size(LabelSize::Small)
3141                                                        .color(Color::Muted),
3142                                                )
3143                                                .child(
3144                                                    Button::new("show_matching", "View")
3145                                                        .label_size(LabelSize::Small)
3146                                                        .end_icon(
3147                                                            Icon::new(IconName::ArrowUpRight)
3148                                                                .size(IconSize::Small)
3149                                                                .color(Color::Muted),
3150                                                        )
3151                                                        .on_click(cx.listener(
3152                                                            |this, _, window, cx| {
3153                                                                this.show_matching_bindings(
3154                                                                    window, cx,
3155                                                                );
3156                                                            },
3157                                                        )),
3158                                                )
3159                                            },
3160                                        )),
3161                                )
3162                                .when_some(self.action_arguments_editor.clone(), |this, editor| {
3163                                    this.child(
3164                                        v_flex()
3165                                            .gap_1()
3166                                            .child(Label::new("Edit Arguments"))
3167                                            .child(editor),
3168                                    )
3169                                })
3170                                .child(self.context_editor.clone())
3171                                .when_some(self.error.as_ref(), |this, error| {
3172                                    this.child(
3173                                        Banner::new()
3174                                            .severity(error.severity)
3175                                            .child(Label::new(error.content.clone())),
3176                                    )
3177                                }),
3178                        ),
3179                    )
3180                    .footer(
3181                        ModalFooter::new().end_slot(
3182                            h_flex()
3183                                .gap_1()
3184                                .child(
3185                                    Button::new("cancel", "Cancel")
3186                                        .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
3187                                )
3188                                .child(Button::new("save-btn", "Save").on_click(cx.listener(
3189                                    |this, _event, _window, cx| {
3190                                        this.save_or_display_error(cx);
3191                                    },
3192                                ))),
3193                        ),
3194                    ),
3195            )
3196    }
3197}
3198
3199struct KeybindingEditorModalFocusState {
3200    handles: Vec<FocusHandle>,
3201}
3202
3203impl KeybindingEditorModalFocusState {
3204    fn new(
3205        action_editor: Option<FocusHandle>,
3206        keystrokes: FocusHandle,
3207        action_arguments: Option<FocusHandle>,
3208        context: FocusHandle,
3209    ) -> Self {
3210        Self {
3211            handles: Vec::from_iter(
3212                [
3213                    action_editor,
3214                    Some(keystrokes),
3215                    action_arguments,
3216                    Some(context),
3217                ]
3218                .into_iter()
3219                .flatten(),
3220            ),
3221        }
3222    }
3223
3224    fn focused_index(&self, window: &Window, cx: &App) -> Option<i32> {
3225        self.handles
3226            .iter()
3227            .position(|handle| handle.contains_focused(window, cx))
3228            .map(|i| i as i32)
3229    }
3230
3231    fn focus_index(&self, mut index: i32, window: &mut Window, cx: &mut App) {
3232        if index < 0 {
3233            index = self.handles.len() as i32 - 1;
3234        }
3235        if index >= self.handles.len() as i32 {
3236            index = 0;
3237        }
3238        window.focus(&self.handles[index as usize], cx);
3239    }
3240
3241    fn focus_next(&self, window: &mut Window, cx: &mut App) {
3242        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3243            index + 1
3244        } else {
3245            0
3246        };
3247        self.focus_index(index_to_focus, window, cx);
3248    }
3249
3250    fn focus_previous(&self, window: &mut Window, cx: &mut App) {
3251        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3252            index - 1
3253        } else {
3254            self.handles.len() as i32 - 1
3255        };
3256        self.focus_index(index_to_focus, window, cx);
3257    }
3258}
3259
3260struct ActionArgumentsEditor {
3261    editor: Entity<Editor>,
3262    focus_handle: FocusHandle,
3263    is_loading: bool,
3264    /// See documentation in `KeymapEditor` for why a temp dir is needed.
3265    /// This field exists because the keymap editor temp dir creation may fail,
3266    /// and rather than implement a complicated retry mechanism, we simply
3267    /// fallback to trying to create a temporary directory in this editor on
3268    /// demand. Of note is that the TempDir struct will remove the directory
3269    /// when dropped.
3270    backup_temp_dir: Option<tempfile::TempDir>,
3271}
3272
3273impl Focusable for ActionArgumentsEditor {
3274    fn focus_handle(&self, _cx: &App) -> FocusHandle {
3275        self.focus_handle.clone()
3276    }
3277}
3278
3279impl ActionArgumentsEditor {
3280    fn new(
3281        action_name: &'static str,
3282        arguments: Option<SharedString>,
3283        temp_dir: Option<&std::path::Path>,
3284        workspace: WeakEntity<Workspace>,
3285        window: &mut Window,
3286        cx: &mut Context<Self>,
3287    ) -> Self {
3288        let focus_handle = cx.focus_handle();
3289        cx.on_focus_in(&focus_handle, window, |this, window, cx| {
3290            this.editor.focus_handle(cx).focus(window, cx);
3291        })
3292        .detach();
3293        let editor = cx.new(|cx| {
3294            let mut editor = Editor::auto_height_unbounded(1, window, cx);
3295            Self::set_editor_text(&mut editor, arguments.clone(), window, cx);
3296            editor.set_read_only(true);
3297            editor
3298        });
3299
3300        let temp_dir = temp_dir.map(|path| path.to_owned());
3301        cx.spawn_in(window, async move |this, cx| {
3302            let result = async {
3303                let (project, fs) = workspace.read_with(cx, |workspace, _cx| {
3304                    (
3305                        workspace.project().downgrade(),
3306                        workspace.app_state().fs.clone(),
3307                    )
3308                })?;
3309
3310                let file_name = json_schema_store::normalized_action_file_name(action_name);
3311
3312                let (buffer, backup_temp_dir) =
3313                    Self::create_temp_buffer(temp_dir, file_name.clone(), project.clone(), fs, cx)
3314                        .await
3315                        .context(concat!(
3316                            "Failed to create temporary buffer for action arguments. ",
3317                            "Auto-complete will not work"
3318                        ))?;
3319
3320                let editor = cx.new_window_entity(|window, cx| {
3321                    let multi_buffer = cx.new(|cx| editor::MultiBuffer::singleton(buffer, cx));
3322                    let mut editor = Editor::new(
3323                        EditorMode::Full {
3324                            scale_ui_elements_with_buffer_font_size: true,
3325                            show_active_line_background: false,
3326                            sizing_behavior: SizingBehavior::SizeByContent,
3327                        },
3328                        multi_buffer,
3329                        project.upgrade(),
3330                        window,
3331                        cx,
3332                    );
3333                    editor.disable_mouse_wheel_zoom();
3334                    editor.set_searchable(false);
3335                    editor.disable_scrollbars_and_minimap(window, cx);
3336                    editor.set_show_edit_predictions(Some(false), window, cx);
3337                    editor.set_show_gutter(false, cx);
3338                    Self::set_editor_text(&mut editor, arguments, window, cx);
3339                    editor
3340                })?;
3341
3342                this.update_in(cx, |this, window, cx| {
3343                    if this.editor.focus_handle(cx).is_focused(window) {
3344                        editor.focus_handle(cx).focus(window, cx);
3345                    }
3346                    this.editor = editor;
3347                    this.backup_temp_dir = backup_temp_dir;
3348                    this.is_loading = false;
3349                })?;
3350
3351                anyhow::Ok(())
3352            }
3353            .await;
3354            if result.is_err() {
3355                let json_language = load_json_language(workspace.clone(), cx).await;
3356                this.update(cx, |this, cx| {
3357                    this.editor.update(cx, |editor, cx| {
3358                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
3359                            buffer.update(cx, |buffer, cx| {
3360                                buffer.set_language(Some(json_language.clone()), cx)
3361                            });
3362                        }
3363                    })
3364                    // .context("Failed to load JSON language for editing keybinding action arguments input")
3365                })
3366                .ok();
3367                this.update(cx, |this, _cx| {
3368                    this.is_loading = false;
3369                })
3370                .ok();
3371            }
3372            result
3373        })
3374        .detach_and_log_err(cx);
3375        Self {
3376            editor,
3377            focus_handle,
3378            is_loading: true,
3379            backup_temp_dir: None,
3380        }
3381    }
3382
3383    fn set_editor_text(
3384        editor: &mut Editor,
3385        arguments: Option<SharedString>,
3386        window: &mut Window,
3387        cx: &mut Context<Editor>,
3388    ) {
3389        if let Some(arguments) = arguments {
3390            editor.set_text(arguments, window, cx);
3391        } else {
3392            // TODO: default value from schema?
3393            editor.set_placeholder_text("Action Arguments", window, cx);
3394        }
3395    }
3396
3397    async fn create_temp_buffer(
3398        temp_dir: Option<std::path::PathBuf>,
3399        file_name: String,
3400        project: WeakEntity<Project>,
3401        fs: Arc<dyn Fs>,
3402        cx: &mut AsyncApp,
3403    ) -> anyhow::Result<(Entity<language::Buffer>, Option<tempfile::TempDir>)> {
3404        let (temp_file_path, temp_dir) = {
3405            let file_name = file_name.clone();
3406            async move {
3407                let temp_dir_backup = match temp_dir.as_ref() {
3408                    Some(_) => None,
3409                    None => {
3410                        let temp_dir = paths::temp_dir();
3411                        let sub_temp_dir = tempfile::Builder::new()
3412                            .tempdir_in(temp_dir)
3413                            .context("Failed to create temporary directory")?;
3414                        Some(sub_temp_dir)
3415                    }
3416                };
3417                let dir_path = temp_dir.as_deref().unwrap_or_else(|| {
3418                    temp_dir_backup
3419                        .as_ref()
3420                        .expect("created backup tempdir")
3421                        .path()
3422                });
3423                let path = dir_path.join(file_name);
3424                fs.create_file(
3425                    &path,
3426                    fs::CreateOptions {
3427                        ignore_if_exists: true,
3428                        overwrite: true,
3429                    },
3430                )
3431                .await
3432                .context("Failed to create temporary file")?;
3433                anyhow::Ok((path, temp_dir_backup))
3434            }
3435        }
3436        .await
3437        .context("Failed to create backing file")?;
3438
3439        project
3440            .update(cx, |project, cx| {
3441                project.open_local_buffer(temp_file_path, cx)
3442            })?
3443            .await
3444            .context("Failed to create buffer")
3445            .map(|buffer| (buffer, temp_dir))
3446    }
3447}
3448
3449impl Render for ActionArgumentsEditor {
3450    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3451        let settings = theme_settings::ThemeSettings::get_global(cx);
3452        let colors = cx.theme().colors();
3453
3454        let border_color = if self.is_loading {
3455            colors.border_disabled
3456        } else if self.focus_handle.contains_focused(window, cx) {
3457            colors.border_focused
3458        } else {
3459            colors.border_variant
3460        };
3461
3462        let text_style = {
3463            TextStyleRefinement {
3464                font_size: Some(rems(0.875).into()),
3465                font_weight: Some(settings.buffer_font.weight),
3466                line_height: Some(relative(1.2)),
3467                color: self.is_loading.then_some(colors.text_disabled),
3468                ..Default::default()
3469            }
3470        };
3471
3472        self.editor
3473            .update(cx, |editor, _| editor.set_text_style_refinement(text_style));
3474
3475        h_flex()
3476            .min_h_8()
3477            .min_w_48()
3478            .px_2()
3479            .flex_grow_1()
3480            .rounded_md()
3481            .bg(cx.theme().colors().editor_background)
3482            .border_1()
3483            .border_color(border_color)
3484            .track_focus(&self.focus_handle)
3485            .child(self.editor.clone())
3486    }
3487}
3488
3489struct KeyContextCompletionProvider {
3490    contexts: Vec<SharedString>,
3491}
3492
3493impl CompletionProvider for KeyContextCompletionProvider {
3494    fn completions(
3495        &self,
3496        buffer: &Entity<language::Buffer>,
3497        buffer_position: language::Anchor,
3498        _trigger: editor::CompletionContext,
3499        _window: &mut Window,
3500        cx: &mut Context<Editor>,
3501    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
3502        let buffer = buffer.read(cx);
3503        let mut count_back = 0;
3504        for char in buffer.reversed_chars_at(buffer_position) {
3505            if char.is_ascii_alphanumeric() || char == '_' {
3506                count_back += 1;
3507            } else {
3508                break;
3509            }
3510        }
3511        let start_anchor =
3512            buffer.anchor_before(buffer_position.to_offset(buffer).saturating_sub(count_back));
3513        let replace_range = start_anchor..buffer_position;
3514        gpui::Task::ready(Ok(vec![project::CompletionResponse {
3515            completions: self
3516                .contexts
3517                .iter()
3518                .map(|context| project::Completion {
3519                    replace_range: replace_range.clone(),
3520                    label: language::CodeLabel::plain(context.to_string(), None),
3521                    new_text: context.to_string(),
3522                    documentation: None,
3523                    source: project::CompletionSource::Custom,
3524                    icon_path: None,
3525                    icon_color: None,
3526                    match_start: None,
3527                    snippet_deduplication_key: None,
3528                    insert_text_mode: None,
3529                    confirm: None,
3530                    group: None,
3531                })
3532                .collect(),
3533            display_options: CompletionDisplayOptions::default(),
3534            is_incomplete: false,
3535        }]))
3536    }
3537
3538    fn is_completion_trigger(
3539        &self,
3540        _buffer: &Entity<language::Buffer>,
3541        _position: language::Anchor,
3542        text: &str,
3543        _trigger_in_words: bool,
3544        _cx: &mut Context<Editor>,
3545    ) -> bool {
3546        text.chars()
3547            .last()
3548            .is_some_and(|last_char| last_char.is_ascii_alphanumeric() || last_char == '_')
3549    }
3550}
3551
3552async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
3553    let json_language_task = workspace
3554        .read_with(cx, |workspace, cx| {
3555            workspace
3556                .project()
3557                .read(cx)
3558                .languages()
3559                .language_for_name("JSON")
3560        })
3561        .context("Failed to load JSON language")
3562        .log_err();
3563    let json_language = match json_language_task {
3564        Some(task) => task.await.context("Failed to load JSON language").log_err(),
3565        None => None,
3566    };
3567    json_language.unwrap_or_else(|| {
3568        Arc::new(Language::new(
3569            LanguageConfig {
3570                name: "JSON".into(),
3571                ..Default::default()
3572            },
3573            Some(tree_sitter_json::LANGUAGE.into()),
3574        ))
3575    })
3576}
3577
3578async fn load_keybind_context_language(
3579    workspace: WeakEntity<Workspace>,
3580    cx: &mut AsyncApp,
3581) -> Arc<Language> {
3582    let language_task = workspace
3583        .read_with(cx, |workspace, cx| {
3584            workspace
3585                .project()
3586                .read(cx)
3587                .languages()
3588                .language_for_name("Keybind Context")
3589        })
3590        .context("Failed to load Keybind Context language")
3591        .log_err();
3592    let language = match language_task {
3593        Some(task) => task
3594            .await
3595            .context("Failed to load Keybind Context language")
3596            .log_err(),
3597        None => None,
3598    };
3599    language.unwrap_or_else(|| {
3600        Arc::new(Language::new(
3601            LanguageConfig {
3602                name: "Keybind Context".into(),
3603                ..Default::default()
3604            },
3605            Some(tree_sitter_rust::LANGUAGE.into()),
3606        ))
3607    })
3608}
3609
3610async fn save_keybinding_update(
3611    create: bool,
3612    existing: ProcessedBinding,
3613    action_mapping: &ActionMapping,
3614    new_args: Option<&str>,
3615    fs: &Arc<dyn Fs>,
3616    keyboard_mapper: &dyn PlatformKeyboardMapper,
3617    deprecated_aliases: &HashMap<&'static str, &'static str>,
3618) -> anyhow::Result<()> {
3619    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3620        .await
3621        .context("Failed to load keymap file")?;
3622
3623    let tab_size = infer_json_indent_size(&keymap_contents);
3624
3625    let existing_keystrokes = existing.keystrokes().unwrap_or_default();
3626    let existing_context = existing.context().and_then(KeybindContextString::local_str);
3627    let existing_args = existing
3628        .action()
3629        .arguments
3630        .as_ref()
3631        .map(|args| args.text.as_ref());
3632
3633    let target = settings::KeybindUpdateTarget {
3634        context: existing_context,
3635        keystrokes: existing_keystrokes,
3636        action_name: existing.action().name,
3637        action_arguments: existing_args,
3638    };
3639
3640    let source = settings::KeybindUpdateTarget {
3641        context: action_mapping.context.as_deref(),
3642        keystrokes: &action_mapping.keystrokes,
3643        action_name: existing.action().name,
3644        action_arguments: new_args,
3645    };
3646
3647    let operation = if !create {
3648        settings::KeybindUpdateOperation::Replace {
3649            target,
3650            target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3651            source,
3652        }
3653    } else {
3654        settings::KeybindUpdateOperation::Add {
3655            source,
3656            from: Some(target),
3657        }
3658    };
3659
3660    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3661
3662    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3663        operation,
3664        keymap_contents,
3665        tab_size,
3666        keyboard_mapper,
3667        deprecated_aliases,
3668    )
3669    .map_err(|err| err.context("Could not save updated keybinding"))?;
3670    fs.write(
3671        paths::keymap_file().as_path(),
3672        updated_keymap_contents.as_bytes(),
3673    )
3674    .await
3675    .context("Failed to write keymap file")?;
3676
3677    telemetry::event!(
3678        "Keybinding Updated",
3679        new_keybinding = new_keybinding,
3680        removed_keybinding = removed_keybinding,
3681        source = source
3682    );
3683    Ok(())
3684}
3685
3686async fn remove_keybinding(
3687    existing: ProcessedBinding,
3688    fs: &Arc<dyn Fs>,
3689    keyboard_mapper: &dyn PlatformKeyboardMapper,
3690    deprecated_aliases: &HashMap<&'static str, &'static str>,
3691) -> anyhow::Result<()> {
3692    let Some(keystrokes) = existing.keystrokes() else {
3693        anyhow::bail!("Cannot remove a keybinding that does not exist");
3694    };
3695    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3696        .await
3697        .context("Failed to load keymap file")?;
3698    let tab_size = infer_json_indent_size(&keymap_contents);
3699
3700    let operation = settings::KeybindUpdateOperation::Remove {
3701        target: settings::KeybindUpdateTarget {
3702            context: existing.context().and_then(KeybindContextString::local_str),
3703            keystrokes,
3704            action_name: existing.action().name,
3705            action_arguments: existing
3706                .action()
3707                .arguments
3708                .as_ref()
3709                .map(|arguments| arguments.text.as_ref()),
3710        },
3711        target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3712    };
3713
3714    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3715    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3716        operation,
3717        keymap_contents,
3718        tab_size,
3719        keyboard_mapper,
3720        deprecated_aliases,
3721    )
3722    .context("Failed to update keybinding")?;
3723    fs.write(
3724        paths::keymap_file().as_path(),
3725        updated_keymap_contents.as_bytes(),
3726    )
3727    .await
3728    .context("Failed to write keymap file")?;
3729
3730    telemetry::event!(
3731        "Keybinding Removed",
3732        new_keybinding = new_keybinding,
3733        removed_keybinding = removed_keybinding,
3734        source = source
3735    );
3736    Ok(())
3737}
3738
3739fn collect_contexts_from_assets() -> Vec<SharedString> {
3740    let mut keymap_assets = vec![
3741        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
3742        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
3743    ];
3744    keymap_assets.extend(
3745        BaseKeymap::OPTIONS
3746            .iter()
3747            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
3748            .map(util::asset_str::<SettingsAssets>),
3749    );
3750
3751    let mut contexts = HashSet::default();
3752
3753    for keymap_asset in keymap_assets {
3754        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
3755            continue;
3756        };
3757
3758        for section in keymap.sections() {
3759            let context_expr = &section.context;
3760            let mut queue = Vec::new();
3761            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
3762                continue;
3763            };
3764
3765            queue.push(root_context);
3766            while let Some(context) = queue.pop() {
3767                match context {
3768                    Identifier(ident) => {
3769                        contexts.insert(ident);
3770                    }
3771                    Equal(ident_a, ident_b) => {
3772                        contexts.insert(ident_a);
3773                        contexts.insert(ident_b);
3774                    }
3775                    NotEqual(ident_a, ident_b) => {
3776                        contexts.insert(ident_a);
3777                        contexts.insert(ident_b);
3778                    }
3779                    Descendant(ctx_a, ctx_b) => {
3780                        queue.push(*ctx_a);
3781                        queue.push(*ctx_b);
3782                    }
3783                    Not(ctx) => {
3784                        queue.push(*ctx);
3785                    }
3786                    And(ctx_a, ctx_b) => {
3787                        queue.push(*ctx_a);
3788                        queue.push(*ctx_b);
3789                    }
3790                    Or(ctx_a, ctx_b) => {
3791                        queue.push(*ctx_a);
3792                        queue.push(*ctx_b);
3793                    }
3794                }
3795            }
3796        }
3797    }
3798
3799    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
3800    contexts.sort();
3801
3802    contexts
3803}
3804
3805fn normalized_ctx_eq(
3806    a: &gpui::KeyBindingContextPredicate,
3807    b: &gpui::KeyBindingContextPredicate,
3808) -> bool {
3809    use gpui::KeyBindingContextPredicate::*;
3810    return match (a, b) {
3811        (Identifier(_), Identifier(_)) => a == b,
3812        (Equal(a_left, a_right), Equal(b_left, b_right)) => {
3813            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3814        }
3815        (NotEqual(a_left, a_right), NotEqual(b_left, b_right)) => {
3816            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3817        }
3818        (Descendant(a_parent, a_child), Descendant(b_parent, b_child)) => {
3819            normalized_ctx_eq(a_parent, b_parent) && normalized_ctx_eq(a_child, b_child)
3820        }
3821        (Not(a_expr), Not(b_expr)) => normalized_ctx_eq(a_expr, b_expr),
3822        // Handle double negation: !(!a) == a
3823        (Not(a_expr), b) if matches!(a_expr.as_ref(), Not(_)) => {
3824            let Not(a_inner) = a_expr.as_ref() else {
3825                unreachable!();
3826            };
3827            normalized_ctx_eq(b, a_inner)
3828        }
3829        (a, Not(b_expr)) if matches!(b_expr.as_ref(), Not(_)) => {
3830            let Not(b_inner) = b_expr.as_ref() else {
3831                unreachable!();
3832            };
3833            normalized_ctx_eq(a, b_inner)
3834        }
3835        (And(a_left, a_right), And(b_left, b_right))
3836            if matches!(a_left.as_ref(), And(_, _))
3837                || matches!(a_right.as_ref(), And(_, _))
3838                || matches!(b_left.as_ref(), And(_, _))
3839                || matches!(b_right.as_ref(), And(_, _)) =>
3840        {
3841            let mut a_operands = Vec::new();
3842            flatten_and(a, &mut a_operands);
3843            let mut b_operands = Vec::new();
3844            flatten_and(b, &mut b_operands);
3845            compare_operand_sets(&a_operands, &b_operands)
3846        }
3847        (And(a_left, a_right), And(b_left, b_right)) => {
3848            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3849                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3850        }
3851        (Or(a_left, a_right), Or(b_left, b_right))
3852            if matches!(a_left.as_ref(), Or(_, _))
3853                || matches!(a_right.as_ref(), Or(_, _))
3854                || matches!(b_left.as_ref(), Or(_, _))
3855                || matches!(b_right.as_ref(), Or(_, _)) =>
3856        {
3857            let mut a_operands = Vec::new();
3858            flatten_or(a, &mut a_operands);
3859            let mut b_operands = Vec::new();
3860            flatten_or(b, &mut b_operands);
3861            compare_operand_sets(&a_operands, &b_operands)
3862        }
3863        (Or(a_left, a_right), Or(b_left, b_right)) => {
3864            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3865                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3866        }
3867        _ => false,
3868    };
3869
3870    fn flatten_and<'a>(
3871        pred: &'a gpui::KeyBindingContextPredicate,
3872        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3873    ) {
3874        use gpui::KeyBindingContextPredicate::*;
3875        match pred {
3876            And(left, right) => {
3877                flatten_and(left, operands);
3878                flatten_and(right, operands);
3879            }
3880            _ => operands.push(pred),
3881        }
3882    }
3883
3884    fn flatten_or<'a>(
3885        pred: &'a gpui::KeyBindingContextPredicate,
3886        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3887    ) {
3888        use gpui::KeyBindingContextPredicate::*;
3889        match pred {
3890            Or(left, right) => {
3891                flatten_or(left, operands);
3892                flatten_or(right, operands);
3893            }
3894            _ => operands.push(pred),
3895        }
3896    }
3897
3898    fn compare_operand_sets(
3899        a: &[&gpui::KeyBindingContextPredicate],
3900        b: &[&gpui::KeyBindingContextPredicate],
3901    ) -> bool {
3902        if a.len() != b.len() {
3903            return false;
3904        }
3905
3906        // For each operand in a, find a matching operand in b
3907        let mut b_matched = vec![false; b.len()];
3908        for a_operand in a {
3909            let mut found = false;
3910            for (b_idx, b_operand) in b.iter().enumerate() {
3911                if !b_matched[b_idx] && normalized_ctx_eq(a_operand, b_operand) {
3912                    b_matched[b_idx] = true;
3913                    found = true;
3914                    break;
3915                }
3916            }
3917            if !found {
3918                return false;
3919            }
3920        }
3921
3922        true
3923    }
3924}
3925
3926impl SerializableItem for KeymapEditor {
3927    fn serialized_item_kind() -> &'static str {
3928        "KeymapEditor"
3929    }
3930
3931    fn cleanup(
3932        workspace_id: workspace::WorkspaceId,
3933        alive_items: Vec<workspace::ItemId>,
3934        _window: &mut Window,
3935        cx: &mut App,
3936    ) -> gpui::Task<gpui::Result<()>> {
3937        let db = KeybindingEditorDb::global(cx);
3938        workspace::delete_unloaded_items(alive_items, workspace_id, "keybinding_editors", &db, cx)
3939    }
3940
3941    fn deserialize(
3942        _project: Entity<project::Project>,
3943        workspace: WeakEntity<Workspace>,
3944        workspace_id: workspace::WorkspaceId,
3945        item_id: workspace::ItemId,
3946        window: &mut Window,
3947        cx: &mut App,
3948    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
3949        let db = KeybindingEditorDb::global(cx);
3950        window.spawn(cx, async move |cx| {
3951            if db.get_keybinding_editor(item_id, workspace_id)?.is_some() {
3952                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
3953            } else {
3954                Err(anyhow!("No keybinding editor to deserialize"))
3955            }
3956        })
3957    }
3958
3959    fn serialize(
3960        &mut self,
3961        workspace: &mut Workspace,
3962        item_id: workspace::ItemId,
3963        _closing: bool,
3964        _window: &mut Window,
3965        cx: &mut ui::Context<Self>,
3966    ) -> Option<gpui::Task<gpui::Result<()>>> {
3967        let workspace_id = workspace.database_id()?;
3968        let db = KeybindingEditorDb::global(cx);
3969        Some(cx.background_spawn(
3970            async move { db.save_keybinding_editor(item_id, workspace_id).await },
3971        ))
3972    }
3973
3974    fn should_serialize(&self, _event: &Self::Event) -> bool {
3975        false
3976    }
3977}
3978
3979mod persistence {
3980    use db::{query, sqlez::domain::Domain, sqlez_macros::sql};
3981    use workspace::WorkspaceDb;
3982
3983    pub struct KeybindingEditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection);
3984
3985    impl Domain for KeybindingEditorDb {
3986        const NAME: &str = stringify!(KeybindingEditorDb);
3987
3988        const MIGRATIONS: &[&str] = &[sql!(
3989                CREATE TABLE keybinding_editors (
3990                    workspace_id INTEGER,
3991                    item_id INTEGER UNIQUE,
3992
3993                    PRIMARY KEY(workspace_id, item_id),
3994                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
3995                    ON DELETE CASCADE
3996                ) STRICT;
3997        )];
3998    }
3999
4000    db::static_connection!(KeybindingEditorDb, [WorkspaceDb]);
4001
4002    impl KeybindingEditorDb {
4003        query! {
4004            pub async fn save_keybinding_editor(
4005                item_id: workspace::ItemId,
4006                workspace_id: workspace::WorkspaceId
4007            ) -> Result<()> {
4008                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
4009                VALUES (?, ?)
4010            }
4011        }
4012
4013        query! {
4014            pub fn get_keybinding_editor(
4015                item_id: workspace::ItemId,
4016                workspace_id: workspace::WorkspaceId
4017            ) -> Result<Option<workspace::ItemId>> {
4018                SELECT item_id
4019                FROM keybinding_editors
4020                WHERE item_id = ? AND workspace_id = ?
4021            }
4022        }
4023    }
4024}
4025
4026#[cfg(test)]
4027mod tests {
4028    use super::*;
4029    use fs::FakeFs;
4030    use gpui::{TestAppContext, VisualTestContext};
4031    use project::Project;
4032    use serde_json::json;
4033    use settings::KeymapFileLoadResult;
4034    use workspace::{AppState, MultiWorkspace};
4035
4036    async fn reload_keymap_from_file(fs: &Arc<FakeFs>, cx: &mut TestAppContext) {
4037        let content = fs.load(paths::keymap_file().as_path()).await.unwrap();
4038        cx.update(|cx| {
4039            let mut key_bindings = match KeymapFile::load(&content, cx) {
4040                KeymapFileLoadResult::Success { key_bindings } => key_bindings,
4041                KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
4042                    panic!("keymap failed to load: {error_message:?}")
4043                }
4044                KeymapFileLoadResult::JsonParseFailure { error } => {
4045                    panic!("keymap json parse failure: {error}")
4046                }
4047            };
4048            cx.clear_key_bindings();
4049            for key_binding in &mut key_bindings {
4050                key_binding.set_meta(KeybindSource::User.meta());
4051            }
4052            cx.bind_keys(key_bindings);
4053            KeymapEventChannel::trigger_keymap_changed(cx);
4054        });
4055    }
4056
4057    async fn setup_keymap_editor(
4058        cx: &mut TestAppContext,
4059        keymap_content: &str,
4060    ) -> (Arc<FakeFs>, Entity<KeymapEditor>, VisualTestContext) {
4061        cx.update(|cx| {
4062            let _state = AppState::test(cx);
4063            editor::init(cx);
4064            cx.set_global(KeymapEventChannel::new());
4065        });
4066
4067        let fs = FakeFs::new(cx.executor());
4068        fs.insert_tree(
4069            paths::config_dir(),
4070            json!({ "keymap.json": keymap_content }),
4071        )
4072        .await;
4073
4074        reload_keymap_from_file(&fs, cx).await;
4075
4076        let project = Project::test(fs.clone(), [], cx).await;
4077        let window_handle =
4078            cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
4079        let workspace = window_handle
4080            .read_with(cx, |multi_workspace, _| multi_workspace.workspace().clone())
4081            .unwrap();
4082        let mut cx = VisualTestContext::from_window(window_handle.into(), cx);
4083        let keymap_editor = cx
4084            .update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace.downgrade(), window, cx)));
4085        cx.run_until_parked();
4086        (fs, keymap_editor, cx)
4087    }
4088
4089    fn visible_rows_for_action(editor: &KeymapEditor, action_name: &str) -> Vec<usize> {
4090        editor
4091            .matches
4092            .iter()
4093            .enumerate()
4094            .filter(|(_, string_match)| {
4095                let binding = &editor.keybindings[string_match.candidate_id];
4096                binding.action().name == action_name && binding.keystrokes().is_some()
4097            })
4098            .map(|(index, _)| index)
4099            .collect()
4100    }
4101
4102    #[gpui::test]
4103    async fn test_delete_one_of_two_identical_user_bindings(cx: &mut TestAppContext) {
4104        let keymap_content = r#"[
4105    {
4106        "bindings": {
4107            "alt-cmd-shift-c": "omega::OpenKeymap"
4108        }
4109    },
4110    {
4111        "bindings": {
4112            "alt-cmd-shift-c": "omega::OpenKeymap"
4113        }
4114    }
4115]"#;
4116        let (fs, keymap_editor, mut cx) = setup_keymap_editor(cx, keymap_content).await;
4117        let cx = &mut cx;
4118
4119        let rows = keymap_editor.read_with(cx, |editor, _| {
4120            visible_rows_for_action(editor, "omega::OpenKeymap")
4121        });
4122        assert_eq!(
4123            rows.len(),
4124            2,
4125            "expected the two duplicate bindings to show as two rows"
4126        );
4127
4128        keymap_editor.update_in(cx, |editor, window, cx| {
4129            editor.selected_index = Some(rows[1]);
4130            editor.delete_binding(&DeleteBinding, window, cx);
4131        });
4132        cx.run_until_parked();
4133
4134        let content = fs.load(paths::keymap_file().as_path()).await.unwrap();
4135        assert_eq!(
4136            content.matches("alt-cmd-shift-c").count(),
4137            1,
4138            "expected exactly one binding remaining in the keymap file, got:\n{content}"
4139        );
4140
4141        // Simulate the keymap file watcher reacting to the change.
4142        reload_keymap_from_file(&fs, cx).await;
4143        cx.run_until_parked();
4144
4145        let rows = keymap_editor.read_with(cx, |editor, _| {
4146            visible_rows_for_action(editor, "omega::OpenKeymap")
4147        });
4148        assert_eq!(rows.len(), 1, "expected one row remaining after deletion");
4149    }
4150
4151    // Regression test: one of the two entries in the keymap file uses a
4152    // deprecated alias of the action (`editor::CopyRelativePath` instead of
4153    // `workspace::CopyRelativePath`). Both rows display identically in the
4154    // keymap editor (aliases resolve to the canonical action on load), but
4155    // deletion targets the canonical action name, so `KeymapFile::update_keybinding`
4156    // used to never find the alias entry, making it impossible to delete.
4157    #[gpui::test]
4158    async fn test_delete_binding_with_deprecated_action_alias(cx: &mut TestAppContext) {
4159        let keymap_content = r#"[
4160    {
4161        "bindings": {
4162            "alt-cmd-shift-c": "editor::CopyRelativePath"
4163        }
4164    },
4165    {
4166        "bindings": {
4167            "alt-cmd-shift-c": "workspace::CopyRelativePath"
4168        }
4169    }
4170]"#;
4171        let (fs, keymap_editor, mut cx) = setup_keymap_editor(cx, keymap_content).await;
4172        let cx = &mut cx;
4173
4174        let rows = keymap_editor.read_with(cx, |editor, _| {
4175            visible_rows_for_action(editor, "workspace::CopyRelativePath")
4176        });
4177        assert_eq!(
4178            rows.len(),
4179            2,
4180            "both the alias and the canonical entry should show as (identical) rows"
4181        );
4182
4183        // Delete the first row. Both rows report the canonical action name, so
4184        // `find_binding` matches the canonical file entry and removes it.
4185        keymap_editor.update_in(cx, |editor, window, cx| {
4186            editor.selected_index = Some(rows[0]);
4187            editor.delete_binding(&DeleteBinding, window, cx);
4188        });
4189        cx.run_until_parked();
4190
4191        let content = fs.load(paths::keymap_file().as_path()).await.unwrap();
4192        assert_eq!(
4193            content.matches("alt-cmd-shift-c").count(),
4194            1,
4195            "first deletion should remove one of the two entries, got:\n{content}"
4196        );
4197
4198        // Simulate the keymap file watcher reacting to the change.
4199        reload_keymap_from_file(&fs, cx).await;
4200        cx.run_until_parked();
4201
4202        let rows = keymap_editor.read_with(cx, |editor, _| {
4203            visible_rows_for_action(editor, "workspace::CopyRelativePath")
4204        });
4205        assert_eq!(
4206            rows.len(),
4207            1,
4208            "one row should remain after the first deletion"
4209        );
4210
4211        // Delete the remaining row (the alias entry). `find_binding` must
4212        // resolve the deprecated alias in the file to the canonical action
4213        // name to find and remove it.
4214        keymap_editor.update_in(cx, |editor, window, cx| {
4215            editor.selected_index = Some(rows[0]);
4216            editor.delete_binding(&DeleteBinding, window, cx);
4217        });
4218        cx.run_until_parked();
4219
4220        let content = fs.load(paths::keymap_file().as_path()).await.unwrap();
4221        assert_eq!(
4222            content.matches("alt-cmd-shift-c").count(),
4223            0,
4224            "second deletion should remove the remaining (alias) entry, got:\n{content}"
4225        );
4226    }
4227
4228    #[test]
4229    fn normalized_ctx_cmp() {
4230        #[track_caller]
4231        fn cmp(a: &str, b: &str) -> bool {
4232            let a = gpui::KeyBindingContextPredicate::parse(a)
4233                .expect("Failed to parse keybinding context a");
4234            let b = gpui::KeyBindingContextPredicate::parse(b)
4235                .expect("Failed to parse keybinding context b");
4236            normalized_ctx_eq(&a, &b)
4237        }
4238
4239        // Basic equality - identical expressions
4240        assert!(cmp("a && b", "a && b"));
4241        assert!(cmp("a || b", "a || b"));
4242        assert!(cmp("a == b", "a == b"));
4243        assert!(cmp("a != b", "a != b"));
4244        assert!(cmp("a > b", "a > b"));
4245        assert!(cmp("!a", "!a"));
4246
4247        // AND operator - associative/commutative
4248        assert!(cmp("a && b", "b && a"));
4249        assert!(cmp("a && b && c", "c && b && a"));
4250        assert!(cmp("a && b && c", "b && a && c"));
4251        assert!(cmp("a && b && c && d", "d && c && b && a"));
4252
4253        // OR operator - associative/commutative
4254        assert!(cmp("a || b", "b || a"));
4255        assert!(cmp("a || b || c", "c || b || a"));
4256        assert!(cmp("a || b || c", "b || a || c"));
4257        assert!(cmp("a || b || c || d", "d || c || b || a"));
4258
4259        // Equality operator - associative/commutative
4260        assert!(cmp("a == b", "b == a"));
4261        assert!(cmp("x == y", "y == x"));
4262
4263        // Inequality operator - associative/commutative
4264        assert!(cmp("a != b", "b != a"));
4265        assert!(cmp("x != y", "y != x"));
4266
4267        // Complex nested expressions with associative operators
4268        assert!(cmp("(a && b) || c", "c || (a && b)"));
4269        assert!(cmp("(a && b) || c", "c || (b && a)"));
4270        assert!(cmp("(a || b) && c", "c && (a || b)"));
4271        assert!(cmp("(a || b) && c", "c && (b || a)"));
4272        assert!(cmp("(a && b) || (c && d)", "(c && d) || (a && b)"));
4273        assert!(cmp("(a && b) || (c && d)", "(d && c) || (b && a)"));
4274
4275        // Multiple levels of nesting
4276        assert!(cmp("((a && b) || c) && d", "d && ((a && b) || c)"));
4277        assert!(cmp("((a && b) || c) && d", "d && (c || (b && a))"));
4278        assert!(cmp("a && (b || (c && d))", "(b || (c && d)) && a"));
4279        assert!(cmp("a && (b || (c && d))", "(b || (d && c)) && a"));
4280
4281        // Negation with associative operators
4282        assert!(cmp("!a && b", "b && !a"));
4283        assert!(cmp("!a || b", "b || !a"));
4284        assert!(cmp("!(a && b) || c", "c || !(a && b)"));
4285        assert!(cmp("!(a && b) || c", "c || !(b && a)"));
4286
4287        // Descendant operator (>) - NOT associative/commutative
4288        assert!(cmp("a > b", "a > b"));
4289        assert!(!cmp("a > b", "b > a"));
4290        assert!(!cmp("a > b > c", "c > b > a"));
4291        assert!(!cmp("a > b > c", "a > c > b"));
4292
4293        // Mixed operators with descendant
4294        assert!(cmp("(a > b) && c", "c && (a > b)"));
4295        assert!(!cmp("(a > b) && c", "c && (b > a)"));
4296        assert!(cmp("(a > b) || (c > d)", "(c > d) || (a > b)"));
4297        assert!(!cmp("(a > b) || (c > d)", "(b > a) || (d > c)"));
4298
4299        // Negative cases - different operators
4300        assert!(!cmp("a && b", "a || b"));
4301        assert!(!cmp("a == b", "a != b"));
4302        assert!(!cmp("a && b", "a > b"));
4303        assert!(!cmp("a || b", "a > b"));
4304        assert!(!cmp("a == b", "a && b"));
4305        assert!(!cmp("a != b", "a || b"));
4306
4307        // Negative cases - different operands
4308        assert!(!cmp("a && b", "a && c"));
4309        assert!(!cmp("a && b", "c && d"));
4310        assert!(!cmp("a || b", "a || c"));
4311        assert!(!cmp("a || b", "c || d"));
4312        assert!(!cmp("a == b", "a == c"));
4313        assert!(!cmp("a != b", "a != c"));
4314        assert!(!cmp("a > b", "a > c"));
4315        assert!(!cmp("a > b", "c > b"));
4316
4317        // Negative cases - with negation
4318        assert!(!cmp("!a", "a"));
4319        assert!(!cmp("!a && b", "a && b"));
4320        assert!(!cmp("!(a && b)", "a && b"));
4321        assert!(!cmp("!a || b", "a || b"));
4322        assert!(!cmp("!(a || b)", "a || b"));
4323
4324        // Negative cases - complex expressions
4325        assert!(!cmp("(a && b) || c", "(a || b) && c"));
4326        assert!(!cmp("a && (b || c)", "a || (b && c)"));
4327        assert!(!cmp("(a && b) || (c && d)", "(a || b) && (c || d)"));
4328        assert!(!cmp("a > b && c", "a && b > c"));
4329
4330        // Edge cases - multiple same operands
4331        assert!(cmp("a && a", "a && a"));
4332        assert!(cmp("a || a", "a || a"));
4333        assert!(cmp("a && a && b", "b && a && a"));
4334        assert!(cmp("a || a || b", "b || a || a"));
4335
4336        // Edge cases - deeply nested
4337        assert!(cmp(
4338            "((a && b) || (c && d)) && ((e || f) && g)",
4339            "((e || f) && g) && ((c && d) || (a && b))"
4340        ));
4341        assert!(cmp(
4342            "((a && b) || (c && d)) && ((e || f) && g)",
4343            "(g && (f || e)) && ((d && c) || (b && a))"
4344        ));
4345
4346        // Edge cases - repeated patterns
4347        assert!(cmp("(a && b) || (a && b)", "(b && a) || (b && a)"));
4348        assert!(cmp("(a || b) && (a || b)", "(b || a) && (b || a)"));
4349
4350        // Negative cases - subtle differences
4351        assert!(!cmp("a && b && c", "a && b"));
4352        assert!(!cmp("a || b || c", "a || b"));
4353        assert!(!cmp("(a && b) || c", "a && (b || c)"));
4354
4355        // a > b > c is not the same as a > c, should not be equal
4356        assert!(!cmp("a > b > c", "a > c"));
4357
4358        // Double negation with complex expressions
4359        assert!(cmp("!(!(a && b))", "a && b"));
4360        assert!(cmp("!(!(a || b))", "a || b"));
4361        assert!(cmp("!(!(a > b))", "a > b"));
4362        assert!(cmp("!(!a) && b", "a && b"));
4363        assert!(cmp("!(!a) || b", "a || b"));
4364        assert!(cmp("!(!(a && b)) || c", "(a && b) || c"));
4365        assert!(cmp("!(!(a && b)) || c", "(b && a) || c"));
4366        assert!(cmp("!(!a)", "a"));
4367        assert!(cmp("a", "!(!a)"));
4368        assert!(cmp("!(!(!a))", "!a"));
4369        assert!(cmp("!(!(!(!a)))", "a"));
4370    }
4371
4372    #[test]
4373    fn binding_is_unbound_by_unbind_respects_precedence() {
4374        let binding = gpui::KeyBinding::new("tab", zed_actions::OpenKeymap, None);
4375        let unbind =
4376            gpui::KeyBinding::new("tab", gpui::Unbind(binding.action().name().into()), None);
4377
4378        let unbind_then_binding = vec![&unbind, &binding];
4379        assert!(!binding_is_unbound_by_unbind(
4380            &binding,
4381            1,
4382            &unbind_then_binding,
4383        ));
4384
4385        let binding_then_unbind = vec![&binding, &unbind];
4386        assert!(binding_is_unbound_by_unbind(
4387            &binding,
4388            0,
4389            &binding_then_unbind,
4390        ));
4391    }
4392}
4393
Served at tenant.openagents/omega Member data and write actions are omitted.