keymap_editor.rs

   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        zed_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                        zed_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 zed_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                        zed_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        cx.spawn(async move |_, _| {
1440            remove_keybinding(to_remove, &fs, keyboard_mapper.as_ref()).await
1441        })
1442        .detach_and_notify_err(self.workspace.clone(), window, cx);
1443    }
1444
1445    fn copy_context_to_clipboard(
1446        &mut self,
1447        _: &CopyContext,
1448        _window: &mut Window,
1449        cx: &mut Context<Self>,
1450    ) {
1451        let context = self
1452            .selected_binding()
1453            .and_then(|binding| binding.context())
1454            .and_then(KeybindContextString::local_str)
1455            .map(|context| context.to_string());
1456        let Some(context) = context else {
1457            return;
1458        };
1459
1460        telemetry::event!("Keybinding Context Copied", context = context);
1461        cx.write_to_clipboard(gpui::ClipboardItem::new_string(context));
1462    }
1463
1464    fn copy_action_to_clipboard(
1465        &mut self,
1466        _: &CopyAction,
1467        _window: &mut Window,
1468        cx: &mut Context<Self>,
1469    ) {
1470        let action = self
1471            .selected_binding()
1472            .map(|binding| binding.action().name.to_string());
1473        let Some(action) = action else {
1474            return;
1475        };
1476
1477        telemetry::event!("Keybinding Action Copied", action = action);
1478        cx.write_to_clipboard(gpui::ClipboardItem::new_string(action));
1479    }
1480
1481    fn toggle_conflict_filter(
1482        &mut self,
1483        _: &ToggleConflictFilter,
1484        _: &mut Window,
1485        cx: &mut Context<Self>,
1486    ) {
1487        self.set_filter_state(self.filter_state.invert(), cx);
1488    }
1489
1490    fn toggle_no_action_bindings(
1491        &mut self,
1492        _: &ToggleNoActionBindings,
1493        _: &mut Window,
1494        cx: &mut Context<Self>,
1495    ) {
1496        self.show_no_action_bindings = !self.show_no_action_bindings;
1497        self.on_query_changed(cx);
1498    }
1499
1500    fn toggle_user_bindings_filter(&mut self, cx: &mut Context<Self>) {
1501        self.source_filters.user = !self.source_filters.user;
1502        self.on_query_changed(cx);
1503    }
1504
1505    fn toggle_zed_defaults_filter(&mut self, cx: &mut Context<Self>) {
1506        self.source_filters.zed_defaults = !self.source_filters.zed_defaults;
1507        self.on_query_changed(cx);
1508    }
1509
1510    fn toggle_vim_defaults_filter(&mut self, cx: &mut Context<Self>) {
1511        self.source_filters.vim_defaults = !self.source_filters.vim_defaults;
1512        self.on_query_changed(cx);
1513    }
1514
1515    fn set_filter_state(&mut self, filter_state: FilterState, cx: &mut Context<Self>) {
1516        if self.filter_state != filter_state {
1517            self.filter_state = filter_state;
1518            self.on_query_changed(cx);
1519        }
1520    }
1521
1522    fn toggle_keystroke_search(
1523        &mut self,
1524        _: &ToggleKeystrokeSearch,
1525        window: &mut Window,
1526        cx: &mut Context<Self>,
1527    ) {
1528        self.search_mode = self.search_mode.invert();
1529        self.on_query_changed(cx);
1530
1531        match self.search_mode {
1532            SearchMode::KeyStroke { .. } => {
1533                self.keystroke_editor.update(cx, |editor, cx| {
1534                    editor.start_recording(&StartRecording, window, cx);
1535                });
1536            }
1537            SearchMode::Normal => {
1538                self.keystroke_editor.update(cx, |editor, cx| {
1539                    editor.stop_recording(&StopRecording, window, cx);
1540                    editor.clear_keystrokes(&ClearKeystrokes, window, cx);
1541                });
1542                window.focus(&self.filter_editor.focus_handle(cx), cx);
1543            }
1544        }
1545    }
1546
1547    fn toggle_exact_keystroke_matching(
1548        &mut self,
1549        _: &ToggleExactKeystrokeMatching,
1550        _: &mut Window,
1551        cx: &mut Context<Self>,
1552    ) {
1553        let SearchMode::KeyStroke { exact_match } = &mut self.search_mode else {
1554            return;
1555        };
1556
1557        *exact_match = !(*exact_match);
1558        self.on_query_changed(cx);
1559    }
1560
1561    fn show_matching_keystrokes(
1562        &mut self,
1563        _: &ShowMatchingKeybinds,
1564        _: &mut Window,
1565        cx: &mut Context<Self>,
1566    ) {
1567        let Some(selected_binding) = self.selected_binding() else {
1568            return;
1569        };
1570
1571        let keystrokes = selected_binding
1572            .keystrokes()
1573            .map(Vec::from)
1574            .unwrap_or_default();
1575
1576        self.filter_state = FilterState::All;
1577        self.search_mode = SearchMode::KeyStroke { exact_match: true };
1578
1579        self.keystroke_editor.update(cx, |editor, cx| {
1580            editor.set_keystrokes(keystrokes, cx);
1581        });
1582    }
1583
1584    fn has_binding_for(&self, action_name: &str) -> bool {
1585        self.keybindings
1586            .iter()
1587            .filter(|kb| kb.keystrokes().is_some())
1588            .any(|kb| kb.action().name == action_name)
1589    }
1590
1591    fn render_filter_dropdown(
1592        &self,
1593        focus_handle: &FocusHandle,
1594        cx: &mut Context<KeymapEditor>,
1595    ) -> impl IntoElement {
1596        let focus_handle = focus_handle.clone();
1597        let keymap_editor = cx.entity();
1598        return PopoverMenu::new("keymap-editor-filter-menu")
1599            .menu(move |window, cx| {
1600                Some(ContextMenu::build_persistent(window, cx, {
1601                    let focus_handle = focus_handle.clone();
1602                    let keymap_editor = keymap_editor.clone();
1603                    move |mut menu, _window, cx| {
1604                        let (filter_state, source_filters, show_no_action_bindings) = keymap_editor
1605                            .read_with(cx, |editor, _| {
1606                                (
1607                                    editor.filter_state,
1608                                    editor.source_filters,
1609                                    editor.show_no_action_bindings,
1610                                )
1611                            });
1612
1613                        menu = menu
1614                            .context(focus_handle.clone())
1615                            .header("Filters")
1616                            .map(add_filter(
1617                                "Conflicts",
1618                                matches!(filter_state, FilterState::Conflicts),
1619                                Some(ToggleConflictFilter.boxed_clone()),
1620                                &focus_handle,
1621                                &keymap_editor,
1622                                None,
1623                            ))
1624                            .map(add_filter(
1625                                "No Action",
1626                                show_no_action_bindings,
1627                                Some(ToggleNoActionBindings.boxed_clone()),
1628                                &focus_handle,
1629                                &keymap_editor,
1630                                None,
1631                            ))
1632                            .separator()
1633                            .header("Categories")
1634                            .map(add_filter(
1635                                "User",
1636                                source_filters.user,
1637                                None,
1638                                &focus_handle,
1639                                &keymap_editor,
1640                                Some(|editor, cx| {
1641                                    editor.toggle_user_bindings_filter(cx);
1642                                }),
1643                            ))
1644                            .map(add_filter(
1645                                "Default",
1646                                source_filters.zed_defaults,
1647                                None,
1648                                &focus_handle,
1649                                &keymap_editor,
1650                                Some(|editor, cx| {
1651                                    editor.toggle_zed_defaults_filter(cx);
1652                                }),
1653                            ))
1654                            .map(add_filter(
1655                                "Vim",
1656                                source_filters.vim_defaults,
1657                                None,
1658                                &focus_handle,
1659                                &keymap_editor,
1660                                Some(|editor, cx| {
1661                                    editor.toggle_vim_defaults_filter(cx);
1662                                }),
1663                            ));
1664                        menu
1665                    }
1666                }))
1667            })
1668            .anchor(gpui::Corner::TopRight)
1669            .offset(gpui::Point {
1670                x: px(0.0),
1671                y: px(2.0),
1672            })
1673            .trigger_with_tooltip(
1674                IconButton::new("KeymapEditorFilterMenuButton", IconName::Sliders)
1675                    .icon_size(IconSize::Small)
1676                    .when(
1677                        self.keybinding_conflict_state.any_user_binding_conflicts(),
1678                        |this| this.indicator(Indicator::dot().color(Color::Warning)),
1679                    ),
1680                Tooltip::text("Filters"),
1681            );
1682
1683        fn add_filter(
1684            name: &'static str,
1685            toggled: bool,
1686            action: Option<Box<dyn Action>>,
1687            focus_handle: &FocusHandle,
1688            keymap_editor: &Entity<KeymapEditor>,
1689            cb: Option<fn(&mut KeymapEditor, &mut Context<KeymapEditor>)>,
1690        ) -> impl FnOnce(ContextMenu) -> ContextMenu {
1691            let focus_handle = focus_handle.clone();
1692            let keymap_editor = keymap_editor.clone();
1693            return move |menu: ContextMenu| {
1694                menu.toggleable_entry(
1695                    name,
1696                    toggled,
1697                    IconPosition::End,
1698                    action.as_ref().map(|a| a.boxed_clone()),
1699                    move |window, cx| {
1700                        window.focus(&focus_handle, cx);
1701                        if let Some(action) = &action {
1702                            window.dispatch_action(action.boxed_clone(), cx);
1703                        } else if let Some(cb) = cb {
1704                            keymap_editor.update(cx, cb);
1705                        }
1706                    },
1707                )
1708            };
1709        }
1710    }
1711}
1712
1713struct HumanizedActionNameCache {
1714    cache: HashMap<&'static str, SharedString>,
1715}
1716
1717impl HumanizedActionNameCache {
1718    fn new(cx: &App) -> Self {
1719        let cache = HashMap::from_iter(cx.all_action_names().iter().map(|&action_name| {
1720            (
1721                action_name,
1722                command_palette::humanize_action_name(action_name).into(),
1723            )
1724        }));
1725        Self { cache }
1726    }
1727
1728    fn get(&self, action_name: &'static str) -> SharedString {
1729        match self.cache.get(action_name) {
1730            Some(name) => name.clone(),
1731            None => action_name.into(),
1732        }
1733    }
1734}
1735
1736#[derive(Clone)]
1737struct KeyBinding {
1738    keystrokes: Rc<[KeybindingKeystroke]>,
1739    source: KeybindSource,
1740}
1741
1742impl KeyBinding {
1743    fn new(binding: &gpui::KeyBinding, source: KeybindSource) -> Self {
1744        Self {
1745            keystrokes: Rc::from(binding.keystrokes()),
1746            source,
1747        }
1748    }
1749}
1750
1751#[derive(Clone)]
1752struct KeybindInformation {
1753    keystroke_text: SharedString,
1754    binding: KeyBinding,
1755    context: KeybindContextString,
1756    source: KeybindSource,
1757    is_no_action: bool,
1758    is_unbound_by_unbind: bool,
1759}
1760
1761impl KeybindInformation {
1762    fn get_action_mapping(&self) -> ActionMapping {
1763        ActionMapping {
1764            keystrokes: self.binding.keystrokes.clone(),
1765            context: self.context.local().cloned(),
1766        }
1767    }
1768}
1769
1770#[derive(Clone)]
1771struct ActionInformation {
1772    name: &'static str,
1773    humanized_name: SharedString,
1774    arguments: Option<SyntaxHighlightedText>,
1775    documentation: Option<&'static str>,
1776    has_schema: bool,
1777}
1778
1779impl ActionInformation {
1780    fn new(
1781        action_name: &'static str,
1782        action_arguments: Option<SyntaxHighlightedText>,
1783        actions_with_schemas: &HashSet<&'static str>,
1784        action_documentation: &HashMap<&'static str, &'static str>,
1785        action_name_cache: &HumanizedActionNameCache,
1786    ) -> Self {
1787        Self {
1788            humanized_name: action_name_cache.get(action_name),
1789            has_schema: actions_with_schemas.contains(action_name),
1790            arguments: action_arguments,
1791            documentation: action_documentation.get(action_name).copied(),
1792            name: action_name,
1793        }
1794    }
1795}
1796
1797#[derive(Clone)]
1798enum ProcessedBinding {
1799    Mapped(KeybindInformation, ActionInformation),
1800    Unmapped(ActionInformation),
1801}
1802
1803impl ProcessedBinding {
1804    fn new_mapped(
1805        keystroke_text: impl Into<SharedString>,
1806        binding: KeyBinding,
1807        context: KeybindContextString,
1808        source: KeybindSource,
1809        is_no_action: bool,
1810        is_unbound_by_unbind: bool,
1811        action_information: ActionInformation,
1812    ) -> Self {
1813        Self::Mapped(
1814            KeybindInformation {
1815                keystroke_text: keystroke_text.into(),
1816                binding,
1817                context,
1818                source,
1819                is_no_action,
1820                is_unbound_by_unbind,
1821            },
1822            action_information,
1823        )
1824    }
1825
1826    fn is_unbound(&self) -> bool {
1827        matches!(self, Self::Unmapped(_))
1828    }
1829
1830    fn get_action_mapping(&self) -> Option<ActionMapping> {
1831        self.keybind_information()
1832            .map(|keybind| keybind.get_action_mapping())
1833    }
1834
1835    fn keystrokes(&self) -> Option<&[KeybindingKeystroke]> {
1836        self.key_binding()
1837            .map(|binding| binding.keystrokes.as_ref())
1838    }
1839
1840    fn keybind_information(&self) -> Option<&KeybindInformation> {
1841        match self {
1842            Self::Mapped(keybind_information, _) => Some(keybind_information),
1843            Self::Unmapped(_) => None,
1844        }
1845    }
1846
1847    fn keybind_source(&self) -> Option<KeybindSource> {
1848        self.keybind_information().map(|keybind| keybind.source)
1849    }
1850
1851    fn context(&self) -> Option<&KeybindContextString> {
1852        self.keybind_information().map(|keybind| &keybind.context)
1853    }
1854
1855    fn key_binding(&self) -> Option<&KeyBinding> {
1856        self.keybind_information().map(|keybind| &keybind.binding)
1857    }
1858
1859    fn is_no_action(&self) -> bool {
1860        self.keybind_information()
1861            .is_some_and(|keybind| keybind.is_no_action)
1862    }
1863
1864    fn is_unbound_by_unbind(&self) -> bool {
1865        self.keybind_information()
1866            .is_some_and(|keybind| keybind.is_unbound_by_unbind)
1867    }
1868
1869    fn keystroke_text(&self) -> Option<&SharedString> {
1870        self.keybind_information()
1871            .map(|binding| &binding.keystroke_text)
1872    }
1873
1874    fn action(&self) -> &ActionInformation {
1875        match self {
1876            Self::Mapped(_, action) | Self::Unmapped(action) => action,
1877        }
1878    }
1879
1880    fn cmp(&self, other: &Self) -> cmp::Ordering {
1881        match (self, other) {
1882            (Self::Mapped(keybind1, action1), Self::Mapped(keybind2, action2)) => {
1883                match keybind1.source.cmp(&keybind2.source) {
1884                    cmp::Ordering::Equal => action1.humanized_name.cmp(&action2.humanized_name),
1885                    ordering => ordering,
1886                }
1887            }
1888            (Self::Mapped(_, _), Self::Unmapped(_)) => cmp::Ordering::Less,
1889            (Self::Unmapped(_), Self::Mapped(_, _)) => cmp::Ordering::Greater,
1890            (Self::Unmapped(action1), Self::Unmapped(action2)) => {
1891                action1.humanized_name.cmp(&action2.humanized_name)
1892            }
1893        }
1894    }
1895}
1896
1897#[derive(Clone, Debug, IntoElement, PartialEq, Eq, Hash)]
1898enum KeybindContextString {
1899    Global,
1900    Local(SharedString, Arc<Language>),
1901}
1902
1903impl KeybindContextString {
1904    const GLOBAL: SharedString = SharedString::new_static("<global>");
1905
1906    pub fn local(&self) -> Option<&SharedString> {
1907        match self {
1908            KeybindContextString::Global => None,
1909            KeybindContextString::Local(name, _) => Some(name),
1910        }
1911    }
1912
1913    pub fn local_str(&self) -> Option<&str> {
1914        match self {
1915            KeybindContextString::Global => None,
1916            KeybindContextString::Local(name, _) => Some(name),
1917        }
1918    }
1919}
1920
1921impl RenderOnce for KeybindContextString {
1922    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
1923        match self {
1924            KeybindContextString::Global => {
1925                muted_styled_text(KeybindContextString::GLOBAL, cx).into_any_element()
1926            }
1927            KeybindContextString::Local(name, language) => {
1928                SyntaxHighlightedText::new(name, language).into_any_element()
1929            }
1930        }
1931    }
1932}
1933
1934fn muted_styled_text(text: SharedString, cx: &App) -> StyledText {
1935    let len = text.len();
1936    StyledText::new(text).with_highlights([(
1937        0..len,
1938        gpui::HighlightStyle::color(cx.theme().colors().text_muted),
1939    )])
1940}
1941
1942impl Item for KeymapEditor {
1943    type Event = ();
1944
1945    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
1946        "Keymap Editor".into()
1947    }
1948}
1949
1950impl Render for KeymapEditor {
1951    fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
1952        if let SearchMode::KeyStroke { exact_match } = self.search_mode {
1953            let button = IconButton::new("keystrokes-exact-match", IconName::CaseSensitive)
1954                .tooltip(move |_window, cx| {
1955                    Tooltip::for_action(
1956                        "Toggle Exact Match Mode",
1957                        &ToggleExactKeystrokeMatching,
1958                        cx,
1959                    )
1960                })
1961                .shape(IconButtonShape::Square)
1962                .toggle_state(exact_match)
1963                .on_click(cx.listener(|_, _, window, cx| {
1964                    window.dispatch_action(ToggleExactKeystrokeMatching.boxed_clone(), cx);
1965                }));
1966
1967            self.keystroke_editor.update(cx, |editor, _| {
1968                editor.actions_slot = Some(button.into_any_element());
1969            });
1970        } else {
1971            self.keystroke_editor.update(cx, |editor, _| {
1972                editor.actions_slot = None;
1973            });
1974        }
1975
1976        let row_count = self.matches.len();
1977        let focus_handle = &self.focus_handle;
1978        let theme = cx.theme();
1979        let search_mode = self.search_mode;
1980
1981        v_flex()
1982            .id("keymap-editor")
1983            .track_focus(focus_handle)
1984            .key_context(self.key_context())
1985            .on_action(cx.listener(Self::select_next))
1986            .on_action(cx.listener(Self::select_previous))
1987            .on_action(cx.listener(Self::select_first))
1988            .on_action(cx.listener(Self::select_last))
1989            .on_action(cx.listener(Self::focus_search))
1990            .on_action(cx.listener(Self::edit_binding))
1991            .on_action(cx.listener(Self::create_binding))
1992            .on_action(cx.listener(Self::open_create_keybinding_modal))
1993            .on_action(cx.listener(Self::delete_binding))
1994            .on_action(cx.listener(Self::copy_action_to_clipboard))
1995            .on_action(cx.listener(Self::copy_context_to_clipboard))
1996            .on_action(cx.listener(Self::toggle_conflict_filter))
1997            .on_action(cx.listener(Self::toggle_no_action_bindings))
1998            .on_action(cx.listener(Self::toggle_keystroke_search))
1999            .on_action(cx.listener(Self::toggle_exact_keystroke_matching))
2000            .on_action(cx.listener(Self::show_matching_keystrokes))
2001            .on_mouse_move(cx.listener(|this, _, _window, _cx| {
2002                this.show_hover_menus = true;
2003            }))
2004            .size_full()
2005            .p_2()
2006            .gap_1()
2007            .bg(theme.colors().editor_background)
2008            .child(
2009                v_flex()
2010                    .gap_2()
2011                    .child(
2012                        h_flex()
2013                            .gap_2()
2014                            .items_center()
2015                            .child(
2016                                h_flex()
2017                                    .key_context({
2018                                        let mut context = KeyContext::new_with_defaults();
2019                                        context.add("BufferSearchBar");
2020                                        context
2021                                    })
2022                                    .size_full()
2023                                    .h_8()
2024                                    .pl_2()
2025                                    .pr_1()
2026                                    .py_1()
2027                                    .border_1()
2028                                    .border_color(theme.colors().border)
2029                                    .rounded_md()
2030                                    .child(self.filter_editor.clone()),
2031                            )
2032                            .child(
2033                                h_flex()
2034                                    .gap_1()
2035                                    .min_w_96()
2036                                    .items_center()
2037                                    .child(
2038                                        IconButton::new(
2039                                            "KeymapEditorKeystrokeSearchButton",
2040                                            IconName::Keyboard,
2041                                        )
2042                                        .icon_size(IconSize::Small)
2043                                        .toggle_state(matches!(
2044                                            search_mode,
2045                                            SearchMode::KeyStroke { .. }
2046                                        ))
2047                                        .tooltip({
2048                                            let focus_handle = focus_handle.clone();
2049                                            move |_window, cx| {
2050                                                Tooltip::for_action_in(
2051                                                    "Search by Keystrokes",
2052                                                    &ToggleKeystrokeSearch,
2053                                                    &focus_handle,
2054                                                    cx,
2055                                                )
2056                                            }
2057                                        })
2058                                        .on_click(cx.listener(|_, _, window, cx| {
2059                                            window.dispatch_action(
2060                                                ToggleKeystrokeSearch.boxed_clone(),
2061                                                cx,
2062                                            );
2063                                        })),
2064                                    )
2065                                    .child(
2066                                        self.render_filter_dropdown(focus_handle, cx)
2067                                    )
2068                                    .child(
2069                                        Button::new("edit-in-json", "Edit in JSON")
2070                                            .style(ButtonStyle::Subtle)
2071                                            .key_binding(
2072                                                ui::KeyBinding::for_action_in(&zed_actions::OpenKeymapFile, &focus_handle, cx)
2073                                                    .map(|kb| kb.size(rems_from_px(10.))),
2074                                            )
2075                                            .on_click(|_, window, cx| {
2076                                                window.dispatch_action(
2077                                                    zed_actions::OpenKeymapFile.boxed_clone(),
2078                                                    cx,
2079                                                );
2080                                            })
2081                                    )
2082                                    .child(
2083                                        Button::new("create", "Create Keybinding")
2084                                            .style(ButtonStyle::Outlined)
2085                                            .key_binding(
2086                                                ui::KeyBinding::for_action_in(&OpenCreateKeybindingModal, &focus_handle, cx)
2087                                                    .map(|kb| kb.size(rems_from_px(10.))),
2088                                            )
2089                                            .on_click(|_, window, cx| {
2090                                                window.dispatch_action(
2091                                                    OpenCreateKeybindingModal.boxed_clone(),
2092                                                    cx,
2093                                                );
2094                                            })
2095                                    )
2096                            ),
2097                    )
2098                    .when(
2099                        matches!(self.search_mode, SearchMode::KeyStroke { .. }),
2100                        |this| {
2101                            this.child(
2102                                h_flex()
2103                                    .gap_2()
2104                                    .child(self.keystroke_editor.clone())
2105                                    .child(div().min_w_96()), // Spacer div to align with the search input
2106                            )
2107                        },
2108                    ),
2109            )
2110            .child(
2111                Table::new(COLS)
2112                    .interactable(&self.table_interaction_state)
2113                    .striped()
2114                    .empty_table_callback({
2115                        let this = cx.entity();
2116                        move |window, cx| this.read(cx).render_no_matches_hint(window, cx)
2117                    })
2118                    .width_config(ColumnWidthConfig::redistributable(
2119                        self.current_widths.clone(),
2120                    ))
2121                    .header(vec!["", "Action", "Arguments", "Keystrokes", "Context", "Source"])
2122                    .uniform_list(
2123                        "keymap-editor-table",
2124                        row_count,
2125                        cx.processor(move |this, range: Range<usize>, _window, cx| {
2126                            let context_menu_deployed = this.context_menu_deployed();
2127                            range
2128                                .filter_map(|index| {
2129                                    let candidate_id = this.matches.get(index)?.candidate_id;
2130                                    let binding = &this.keybindings[candidate_id];
2131                                    let action_name = binding.action().name;
2132                                    let conflict = this.get_conflict(index);
2133                                    let is_unbound_by_unbind = binding.is_unbound_by_unbind();
2134                                    let is_overridden = conflict.is_some_and(|conflict| {
2135                                        !conflict.is_user_keybind_conflict()
2136                                    });
2137                                    let is_dimmed = is_overridden || is_unbound_by_unbind;
2138
2139                                    let icon = this.create_row_button(
2140                                        index,
2141                                        conflict,
2142                                        is_unbound_by_unbind,
2143                                        cx,
2144                                    );
2145
2146                                    let action = div()
2147                                        .id(("keymap action", index))
2148                                        .child({
2149                                            if action_name != gpui::NoAction.name() {
2150                                                binding
2151                                                    .action()
2152                                                    .humanized_name
2153                                                    .clone()
2154                                                    .into_any_element()
2155                                            } else {
2156                                                const NULL: SharedString =
2157                                                    SharedString::new_static("<null>");
2158                                                muted_styled_text(NULL, cx)
2159                                                    .into_any_element()
2160                                            }
2161                                        })
2162                                        .when(
2163                                            !context_menu_deployed
2164                                                && this.show_hover_menus
2165                                                && !is_dimmed,
2166                                            |this| {
2167                                                this.tooltip({
2168                                                    let action_name = binding.action().name;
2169                                                    let action_docs =
2170                                                        binding.action().documentation;
2171                                                    move |_, cx| {
2172                                                        let action_tooltip =
2173                                                            Tooltip::new(action_name);
2174                                                        let action_tooltip = match action_docs {
2175                                                            Some(docs) => action_tooltip.meta(docs),
2176                                                            None => action_tooltip,
2177                                                        };
2178                                                        cx.new(|_| action_tooltip).into()
2179                                                    }
2180                                                })
2181                                            },
2182                                        )
2183                                        .into_any_element();
2184
2185                                    let keystrokes = binding.key_binding().map_or(
2186                                        binding
2187                                            .keystroke_text()
2188                                            .cloned()
2189                                            .unwrap_or_default()
2190                                            .into_any_element(),
2191                                        |binding| ui::KeyBinding::from_keystrokes(binding.keystrokes.clone(), binding.source == KeybindSource::Vim).into_any_element()
2192                                    );
2193
2194                                    let action_arguments = match binding.action().arguments.clone()
2195                                    {
2196                                        Some(arguments) => arguments.into_any_element(),
2197                                        None => {
2198                                            if binding.action().has_schema {
2199                                                muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
2200                                                    .into_any_element()
2201                                            } else {
2202                                                gpui::Empty.into_any_element()
2203                                            }
2204                                        }
2205                                    };
2206
2207                                    let context = binding.context().cloned().map_or(
2208                                        gpui::Empty.into_any_element(),
2209                                        |context| {
2210                                            let is_local = context.local().is_some();
2211
2212                                            div()
2213                                                .id(("keymap context", index))
2214                                                .child(context.clone())
2215                                                .when(
2216                                                    is_local
2217                                                        && !context_menu_deployed
2218                                                        && !is_dimmed
2219                                                        && this.show_hover_menus,
2220                                                    |this| {
2221                                                        this.tooltip(Tooltip::element({
2222                                                            move |_, _| {
2223                                                                context.clone().into_any_element()
2224                                                            }
2225                                                        }))
2226                                                    },
2227                                                )
2228                                                .into_any_element()
2229                                        },
2230                                    );
2231
2232                                    let source = binding
2233                                        .keybind_source()
2234                                        .map(|source| source.name())
2235                                        .unwrap_or_default()
2236                                        .into_any_element();
2237
2238                                    Some(vec![
2239                                        icon.into_any_element(),
2240                                        action,
2241                                        action_arguments,
2242                                        keystrokes,
2243                                        context,
2244                                        source,
2245                                    ])
2246                                })
2247                                .collect()
2248                        }),
2249                    )
2250                    .map_row(cx.processor(
2251                        |this, (row_index, row): (usize, Stateful<Div>), _window, cx| {
2252                        let conflict = this.get_conflict(row_index);
2253                            let candidate_id = this.matches.get(row_index).map(|candidate| candidate.candidate_id);
2254                            let is_unbound_by_unbind = candidate_id
2255                                .and_then(|candidate_id| this.keybindings.get(candidate_id))
2256                                .is_some_and(ProcessedBinding::is_unbound_by_unbind);
2257                            let is_selected = this.selected_index == Some(row_index);
2258
2259                            let row_id = row_group_id(row_index);
2260
2261                            div()
2262                                .id(("keymap-row-wrapper", row_index))
2263                                .child(
2264                                    row.id(row_id.clone())
2265                                        .when(!is_unbound_by_unbind, |row| {
2266                                            row.on_any_mouse_down(cx.listener(
2267                                                move |this,
2268                                                      mouse_down_event: &gpui::MouseDownEvent,
2269                                                      window,
2270                                                      cx| {
2271                                                    if mouse_down_event.button == MouseButton::Right {
2272                                                        this.select_index(
2273                                                            row_index, None, window, cx,
2274                                                        );
2275                                                        this.create_context_menu(
2276                                                            mouse_down_event.position,
2277                                                            window,
2278                                                            cx,
2279                                                        );
2280                                                    }
2281                                                },
2282                                            ))
2283                                        })
2284                                        .when(!is_unbound_by_unbind, |row| {
2285                                            row.on_click(cx.listener(
2286                                                move |this, event: &ClickEvent, window, cx| {
2287                                                    this.select_index(row_index, None, window, cx);
2288                                                    if event.click_count() == 2 {
2289                                                        this.open_edit_keybinding_modal(
2290                                                            false, window, cx,
2291                                                        );
2292                                                    }
2293                                                },
2294                                            ))
2295                                        })
2296                                        .group(row_id)
2297                                        .when(
2298                                            is_unbound_by_unbind
2299                                                || conflict.is_some_and(|conflict| {
2300                                                    !conflict.is_user_keybind_conflict()
2301                                                }),
2302                                            |row| {
2303                                                const OVERRIDDEN_OPACITY: f32 = 0.5;
2304                                                row.opacity(OVERRIDDEN_OPACITY)
2305                                            },
2306                                        )
2307                                        .when_some(
2308                                            conflict.filter(|conflict| {
2309                                                !is_unbound_by_unbind
2310                                                    && !this.context_menu_deployed() &&
2311                                                !conflict.is_user_keybind_conflict()
2312                                            }),
2313                                            |row, conflict| {
2314                                                let overriding_binding = this.keybindings.get(conflict.index);
2315                                                let context = overriding_binding.and_then(|binding| {
2316                                                    match conflict.override_source {
2317                                                        KeybindSource::User  => Some("your keymap"),
2318                                                        KeybindSource::Vim => Some("the vim keymap"),
2319                                                        KeybindSource::Base => Some("your base keymap"),
2320                                                        _ => {
2321                                                            log::error!("Unexpected override from the {} keymap", conflict.override_source.name());
2322                                                            None
2323                                                        }
2324                                                    }.map(|source| format!("This keybinding is overridden by the '{}' binding from {}.", binding.action().humanized_name, source))
2325                                                }).unwrap_or_else(|| "This binding is overridden.".to_string());
2326
2327                                                row.tooltip(Tooltip::text(context))
2328                                            },
2329                                        )
2330                                        .when(is_unbound_by_unbind, |row| {
2331                                            row.tooltip(Tooltip::text("This action is unbound"))
2332                                        }),
2333                                )
2334                                .border_2()
2335                                .when(
2336                                    conflict.is_some_and(|conflict| {
2337                                        conflict.is_user_keybind_conflict()
2338                                    }),
2339                                    |row| row.bg(cx.theme().status().error_background),
2340                                )
2341                                .when(is_selected, |row| {
2342                                    row.border_color(cx.theme().colors().panel_focused_border)
2343                                })
2344                                .into_any_element()
2345                        }),
2346                    ),
2347            )
2348            .on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
2349                // This ensures that the menu is not dismissed in cases where scroll events
2350                // with a delta of zero are emitted
2351                if !event.delta.pixel_delta(px(1.)).y.is_zero() {
2352                    this.context_menu.take();
2353                    cx.notify();
2354                }
2355            }))
2356            .children(self.context_menu.as_ref().map(|(menu, position, _)| {
2357                deferred(
2358                    anchored()
2359                        .position(*position)
2360                        .anchor(gpui::Corner::TopLeft)
2361                        .child(menu.clone()),
2362                )
2363                .with_priority(1)
2364            }))
2365    }
2366}
2367
2368fn row_group_id(row_index: usize) -> SharedString {
2369    SharedString::new(format!("keymap-table-row-{}", row_index))
2370}
2371
2372fn base_button_style(row_index: usize, icon: IconName) -> IconButton {
2373    IconButton::new(("keymap-icon", row_index), icon)
2374        .shape(IconButtonShape::Square)
2375        .size(ButtonSize::Compact)
2376}
2377
2378#[derive(Debug, Clone, IntoElement)]
2379struct SyntaxHighlightedText {
2380    text: SharedString,
2381    language: Arc<Language>,
2382}
2383
2384impl SyntaxHighlightedText {
2385    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
2386        Self {
2387            text: text.into(),
2388            language,
2389        }
2390    }
2391}
2392
2393impl RenderOnce for SyntaxHighlightedText {
2394    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
2395        let text_style = window.text_style();
2396        let syntax_theme = cx.theme().syntax();
2397
2398        let text = self.text.clone();
2399
2400        let highlights = self
2401            .language
2402            .highlight_text(&text.as_ref().into(), 0..text.len());
2403        let mut runs = Vec::with_capacity(highlights.len());
2404        let mut offset = 0;
2405
2406        for (highlight_range, highlight_id) in highlights {
2407            // Add un-highlighted text before the current highlight
2408            if highlight_range.start > offset {
2409                runs.push(text_style.to_run(highlight_range.start - offset));
2410            }
2411
2412            let mut run_style = text_style.clone();
2413            if let Some(highlight_style) = syntax_theme.get(highlight_id).cloned() {
2414                run_style = run_style.highlight(highlight_style);
2415            }
2416
2417            // add the highlighted range
2418            runs.push(run_style.to_run(highlight_range.len()));
2419            offset = highlight_range.end;
2420        }
2421
2422        // Add any remaining un-highlighted text
2423        if offset < text.len() {
2424            runs.push(text_style.to_run(text.len() - offset));
2425        }
2426
2427        StyledText::new(text).with_runs(runs)
2428    }
2429}
2430
2431#[derive(PartialEq)]
2432struct InputError {
2433    severity: Severity,
2434    content: SharedString,
2435}
2436
2437impl InputError {
2438    fn warning(message: impl Into<SharedString>) -> Self {
2439        Self {
2440            severity: Severity::Warning,
2441            content: message.into(),
2442        }
2443    }
2444
2445    fn error(message: anyhow::Error) -> Self {
2446        Self {
2447            severity: Severity::Error,
2448            content: message.to_string().into(),
2449        }
2450    }
2451}
2452
2453struct KeybindingEditorModal {
2454    creating: bool,
2455    editing_keybind: ProcessedBinding,
2456    editing_keybind_idx: usize,
2457    keybind_editor: Entity<KeystrokeInput>,
2458    context_editor: Entity<InputField>,
2459    action_editor: Option<Entity<InputField>>,
2460    action_arguments_editor: Option<Entity<ActionArgumentsEditor>>,
2461    action_name_to_static: HashMap<String, &'static str>,
2462    selected_action_name: Option<&'static str>,
2463    fs: Arc<dyn Fs>,
2464    error: Option<InputError>,
2465    keymap_editor: Entity<KeymapEditor>,
2466    workspace: WeakEntity<Workspace>,
2467    focus_state: KeybindingEditorModalFocusState,
2468}
2469
2470impl ModalView for KeybindingEditorModal {}
2471
2472impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
2473
2474impl Focusable for KeybindingEditorModal {
2475    fn focus_handle(&self, cx: &App) -> FocusHandle {
2476        if let Some(action_editor) = &self.action_editor {
2477            return action_editor.focus_handle(cx);
2478        }
2479        self.keybind_editor.focus_handle(cx)
2480    }
2481}
2482
2483impl KeybindingEditorModal {
2484    pub fn new(
2485        create: bool,
2486        editing_keybind: ProcessedBinding,
2487        editing_keybind_idx: usize,
2488        keymap_editor: Entity<KeymapEditor>,
2489        action_args_temp_dir: Option<&std::path::Path>,
2490        workspace: WeakEntity<Workspace>,
2491        fs: Arc<dyn Fs>,
2492        window: &mut Window,
2493        cx: &mut App,
2494    ) -> Self {
2495        let keybind_editor = cx
2496            .new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx));
2497
2498        let context_editor: Entity<InputField> = cx.new(|cx| {
2499            let input = InputField::new(window, cx, "Keybinding Context")
2500                .label("Edit Context")
2501                .label_size(LabelSize::Default);
2502
2503            if let Some(context) = editing_keybind
2504                .context()
2505                .and_then(KeybindContextString::local)
2506            {
2507                input.set_text(&context, window, cx);
2508            }
2509
2510            let editor_entity = input.editor();
2511            let editor_entity = editor_entity
2512                .as_any()
2513                .downcast_ref::<Entity<Editor>>()
2514                .unwrap()
2515                .clone();
2516            let workspace = workspace.clone();
2517            cx.spawn(async move |_input_handle, cx| {
2518                let contexts = cx
2519                    .background_spawn(async { collect_contexts_from_assets() })
2520                    .await;
2521
2522                let language = load_keybind_context_language(workspace, cx).await;
2523                editor_entity.update(cx, |editor, cx| {
2524                    if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
2525                        buffer.update(cx, |buffer, cx| {
2526                            buffer.set_language(Some(language), cx);
2527                        });
2528                    }
2529                    editor.set_completion_provider(Some(std::rc::Rc::new(
2530                        KeyContextCompletionProvider { contexts },
2531                    )));
2532                });
2533            })
2534            .detach();
2535
2536            input
2537        });
2538
2539        let has_action_editor = create && editing_keybind.action().name == gpui::NoAction.name();
2540
2541        let (action_editor, action_name_to_static) = if has_action_editor {
2542            let actions: Vec<&'static str> = cx.all_action_names().to_vec();
2543
2544            let humanized_names: HashMap<&'static str, SharedString> = actions
2545                .iter()
2546                .map(|&name| (name, command_palette::humanize_action_name(name).into()))
2547                .collect();
2548
2549            let action_name_to_static: HashMap<String, &'static str> = actions
2550                .iter()
2551                .map(|&name| (name.to_string(), name))
2552                .collect();
2553
2554            let editor = cx.new(|cx| {
2555                let input = InputField::new(window, cx, "Type an action name")
2556                    .label("Action")
2557                    .label_size(LabelSize::Default);
2558
2559                let editor_entity = input.editor();
2560                let editor_entity = editor_entity
2561                    .as_any()
2562                    .downcast_ref::<Entity<Editor>>()
2563                    .unwrap();
2564                editor_entity.update(cx, |editor, _cx| {
2565                    editor.set_completion_provider(Some(std::rc::Rc::new(
2566                        ActionCompletionProvider::new(actions, humanized_names),
2567                    )));
2568                });
2569
2570                input
2571            });
2572
2573            (Some(editor), action_name_to_static)
2574        } else {
2575            (None, HashMap::default())
2576        };
2577
2578        let action_has_schema = editing_keybind.action().has_schema;
2579        let action_name_for_args = editing_keybind.action().name;
2580        let action_args = editing_keybind
2581            .action()
2582            .arguments
2583            .as_ref()
2584            .map(|args| args.text.clone());
2585
2586        let action_arguments_editor = action_has_schema.then(|| {
2587            cx.new(|cx| {
2588                ActionArgumentsEditor::new(
2589                    action_name_for_args,
2590                    action_args.clone(),
2591                    action_args_temp_dir,
2592                    workspace.clone(),
2593                    window,
2594                    cx,
2595                )
2596            })
2597        });
2598
2599        let focus_state = KeybindingEditorModalFocusState::new(
2600            action_editor.as_ref().map(|e| e.focus_handle(cx)),
2601            keybind_editor.focus_handle(cx),
2602            action_arguments_editor
2603                .as_ref()
2604                .map(|args_editor| args_editor.focus_handle(cx)),
2605            context_editor.focus_handle(cx),
2606        );
2607
2608        Self {
2609            creating: create,
2610            editing_keybind,
2611            editing_keybind_idx,
2612            fs,
2613            keybind_editor,
2614            context_editor,
2615            action_editor,
2616            action_arguments_editor,
2617            action_name_to_static,
2618            selected_action_name: None,
2619            error: None,
2620            keymap_editor,
2621            workspace,
2622            focus_state,
2623        }
2624    }
2625
2626    fn add_action_arguments_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2627        let Some(action_editor) = &self.action_editor else {
2628            return;
2629        };
2630
2631        let action_name_str = action_editor.read(cx).text(cx);
2632        let current_action = self.action_name_to_static.get(&action_name_str).copied();
2633
2634        if current_action == self.selected_action_name {
2635            return;
2636        }
2637
2638        self.selected_action_name = current_action;
2639
2640        let Some(action_name) = current_action else {
2641            if self.action_arguments_editor.is_some() {
2642                self.action_arguments_editor = None;
2643                self.rebuild_focus_state(cx);
2644                cx.notify();
2645            }
2646            return;
2647        };
2648
2649        let (action_has_schema, temp_dir) = {
2650            let keymap_editor = self.keymap_editor.read(cx);
2651            let has_schema = keymap_editor.actions_with_schemas.contains(action_name);
2652            let temp_dir = keymap_editor
2653                .action_args_temp_dir
2654                .as_ref()
2655                .map(|dir| dir.path().to_path_buf());
2656            (has_schema, temp_dir)
2657        };
2658
2659        let currently_has_editor = self.action_arguments_editor.is_some();
2660
2661        if action_has_schema && !currently_has_editor {
2662            let workspace = self.workspace.clone();
2663
2664            let new_editor = cx.new(|cx| {
2665                ActionArgumentsEditor::new(
2666                    action_name,
2667                    None,
2668                    temp_dir.as_deref(),
2669                    workspace,
2670                    window,
2671                    cx,
2672                )
2673            });
2674
2675            self.action_arguments_editor = Some(new_editor);
2676            self.rebuild_focus_state(cx);
2677            cx.notify();
2678        } else if !action_has_schema && currently_has_editor {
2679            self.action_arguments_editor = None;
2680            self.rebuild_focus_state(cx);
2681            cx.notify();
2682        }
2683    }
2684
2685    fn rebuild_focus_state(&mut self, cx: &App) {
2686        self.focus_state = KeybindingEditorModalFocusState::new(
2687            self.action_editor.as_ref().map(|e| e.focus_handle(cx)),
2688            self.keybind_editor.focus_handle(cx),
2689            self.action_arguments_editor
2690                .as_ref()
2691                .map(|args_editor| args_editor.focus_handle(cx)),
2692            self.context_editor.focus_handle(cx),
2693        );
2694    }
2695
2696    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
2697        if self
2698            .error
2699            .as_ref()
2700            .is_some_and(|old_error| old_error.severity == Severity::Warning && *old_error == error)
2701        {
2702            false
2703        } else {
2704            self.error = Some(error);
2705            cx.notify();
2706            true
2707        }
2708    }
2709
2710    fn get_selected_action_name(&self, cx: &App) -> anyhow::Result<&'static str> {
2711        if let Some(selector) = self.action_editor.as_ref() {
2712            let action_name_str = selector.read(cx).text(cx);
2713
2714            if action_name_str.is_empty() {
2715                anyhow::bail!("Action name is required");
2716            }
2717
2718            self.action_name_to_static
2719                .get(&action_name_str)
2720                .copied()
2721                .ok_or_else(|| anyhow::anyhow!("Action '{}' not found", action_name_str))
2722        } else {
2723            Ok(self.editing_keybind.action().name)
2724        }
2725    }
2726
2727    fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
2728        let action_name = self.get_selected_action_name(cx)?;
2729        let action_arguments = self
2730            .action_arguments_editor
2731            .as_ref()
2732            .map(|arguments_editor| arguments_editor.read(cx).editor.read(cx).text(cx))
2733            .filter(|args| !args.is_empty());
2734
2735        let value = action_arguments
2736            .as_ref()
2737            .map(|args| {
2738                serde_json::from_str(args).context("Failed to parse action arguments as JSON")
2739            })
2740            .transpose()?;
2741
2742        cx.build_action(action_name, value)
2743            .context("Failed to validate action arguments")?;
2744        Ok(action_arguments)
2745    }
2746
2747    fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<KeybindingKeystroke>> {
2748        let new_keystrokes = self
2749            .keybind_editor
2750            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
2751        anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
2752        Ok(new_keystrokes)
2753    }
2754
2755    fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
2756        let new_context = self
2757            .context_editor
2758            .read_with(cx, |input, cx| input.text(cx));
2759        let Some(context) = new_context.is_empty().not().then_some(new_context) else {
2760            return Ok(None);
2761        };
2762        gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
2763
2764        Ok(Some(context))
2765    }
2766
2767    fn save_or_display_error(&mut self, cx: &mut Context<Self>) {
2768        self.save(cx).map_err(|err| self.set_error(err, cx)).ok();
2769    }
2770
2771    fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
2772        let existing_keybind = self.editing_keybind.clone();
2773        let fs = self.fs.clone();
2774
2775        let mut new_keystrokes = self.validate_keystrokes(cx).map_err(InputError::error)?;
2776        new_keystrokes
2777            .iter_mut()
2778            .for_each(|ks| ks.remove_key_char());
2779
2780        let new_context = self.validate_context(cx).map_err(InputError::error)?;
2781        let new_action_args = self
2782            .validate_action_arguments(cx)
2783            .map_err(InputError::error)?;
2784
2785        let action_mapping = ActionMapping {
2786            keystrokes: Rc::from(new_keystrokes.as_slice()),
2787            context: new_context.map(SharedString::from),
2788        };
2789
2790        let conflicting_indices = self
2791            .keymap_editor
2792            .read(cx)
2793            .keybinding_conflict_state
2794            .conflicting_indices_for_mapping(
2795                &action_mapping,
2796                self.creating.not().then_some(self.editing_keybind_idx),
2797            );
2798
2799        conflicting_indices.map(|KeybindConflict {
2800            first_conflict_index,
2801            remaining_conflict_amount,
2802        }|
2803        {
2804            let conflicting_action_name = self
2805                .keymap_editor
2806                .read(cx)
2807                .keybindings
2808                .get(first_conflict_index)
2809                .map(|keybind| keybind.action().name);
2810
2811            let warning_message = match conflicting_action_name {
2812                Some(name) => {
2813                     if remaining_conflict_amount > 0 {
2814                        format!(
2815                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
2816                            name, remaining_conflict_amount
2817                        )
2818                    } else {
2819                        format!("Your keybind would conflict with the \"{}\" action", name)
2820                    }
2821                }
2822                None => {
2823                    log::info!(
2824                        "Could not find action in keybindings with index {}",
2825                        first_conflict_index
2826                    );
2827                    "Your keybind would conflict with other actions".to_string()
2828                }
2829            };
2830
2831            let warning = InputError::warning(warning_message);
2832            if self.error.as_ref().is_some_and(|old_error| *old_error == warning) {
2833                Ok(())
2834           } else {
2835                Err(warning)
2836            }
2837        }).unwrap_or(Ok(()))?;
2838
2839        let create = self.creating;
2840        let keyboard_mapper = cx.keyboard_mapper().clone();
2841
2842        let action_name = self
2843            .get_selected_action_name(cx)
2844            .map_err(InputError::error)?;
2845
2846        let humanized_action_name: SharedString =
2847            command_palette::humanize_action_name(action_name).into();
2848
2849        let action_information = ActionInformation::new(
2850            action_name,
2851            None,
2852            &HashSet::default(),
2853            cx.action_documentation(),
2854            &self.keymap_editor.read(cx).humanized_action_names,
2855        );
2856
2857        let keybind_for_save = if create {
2858            ProcessedBinding::Unmapped(action_information)
2859        } else {
2860            existing_keybind
2861        };
2862
2863        cx.spawn(async move |this, cx| {
2864            match save_keybinding_update(
2865                create,
2866                keybind_for_save,
2867                &action_mapping,
2868                new_action_args.as_deref(),
2869                &fs,
2870                keyboard_mapper.as_ref(),
2871            )
2872            .await
2873            {
2874                Ok(_) => {
2875                    this.update(cx, |this, cx| {
2876                        this.keymap_editor.update(cx, |keymap, cx| {
2877                            keymap.previous_edit = Some(PreviousEdit::Keybinding {
2878                                action_mapping,
2879                                action_name,
2880                                fallback: keymap.table_interaction_state.read(cx).scroll_offset(),
2881                            });
2882                            let status_toast = StatusToast::new(
2883                                format!("Saved edits to the {} action.", humanized_action_name),
2884                                cx,
2885                                move |this, _cx| {
2886                                    this.icon(
2887                                        Icon::new(IconName::Check)
2888                                            .size(IconSize::Small)
2889                                            .color(Color::Success),
2890                                    )
2891                                    .dismiss_button(true)
2892                                    // .action("Undo", f) todo: wire the undo functionality
2893                                },
2894                            );
2895
2896                            this.workspace
2897                                .update(cx, |workspace, cx| {
2898                                    workspace.toggle_status_toast(status_toast, cx);
2899                                })
2900                                .log_err();
2901                        });
2902                        cx.emit(DismissEvent);
2903                    })
2904                    .ok();
2905                }
2906                Err(err) => {
2907                    this.update(cx, |this, cx| {
2908                        this.set_error(InputError::error(err), cx);
2909                    })
2910                    .log_err();
2911                }
2912            }
2913        })
2914        .detach();
2915
2916        Ok(())
2917    }
2918
2919    fn is_any_editor_showing_completions(&self, window: &Window, cx: &App) -> bool {
2920        let is_editor_showing_completions =
2921            |focus_handle: &FocusHandle, editor_entity: &Entity<Editor>| -> bool {
2922                focus_handle.contains_focused(window, cx)
2923                    && editor_entity.read_with(cx, |editor, _cx| {
2924                        editor
2925                            .context_menu()
2926                            .borrow()
2927                            .as_ref()
2928                            .is_some_and(|menu| menu.visible())
2929                    })
2930            };
2931
2932        self.action_editor.as_ref().is_some_and(|action_editor| {
2933            let focus_handle = action_editor.read(cx).focus_handle(cx);
2934            let editor_entity = action_editor.read(cx).editor();
2935            let editor_entity = editor_entity
2936                .as_any()
2937                .downcast_ref::<Entity<Editor>>()
2938                .unwrap();
2939            is_editor_showing_completions(&focus_handle, editor_entity)
2940        }) || {
2941            let focus_handle = self.context_editor.read(cx).focus_handle(cx);
2942            let editor_entity = self.context_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        } || self
2949            .action_arguments_editor
2950            .as_ref()
2951            .is_some_and(|args_editor| {
2952                let focus_handle = args_editor.read(cx).focus_handle(cx);
2953                let editor_entity = &args_editor.read(cx).editor;
2954                is_editor_showing_completions(&focus_handle, editor_entity)
2955            })
2956    }
2957
2958    fn key_context(&self) -> KeyContext {
2959        let mut key_context = KeyContext::new_with_defaults();
2960        key_context.add("KeybindEditorModal");
2961        key_context
2962    }
2963
2964    fn key_context_internal(&self, window: &Window, cx: &App) -> KeyContext {
2965        let mut key_context = self.key_context();
2966
2967        if self.is_any_editor_showing_completions(window, cx) {
2968            key_context.add("showing_completions");
2969        }
2970
2971        key_context
2972    }
2973
2974    fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
2975        if self.is_any_editor_showing_completions(window, cx) {
2976            return;
2977        }
2978        self.focus_state.focus_next(window, cx);
2979    }
2980
2981    fn focus_prev(
2982        &mut self,
2983        _: &menu::SelectPrevious,
2984        window: &mut Window,
2985        cx: &mut Context<Self>,
2986    ) {
2987        if self.is_any_editor_showing_completions(window, cx) {
2988            return;
2989        }
2990        self.focus_state.focus_previous(window, cx);
2991    }
2992
2993    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
2994        self.save_or_display_error(cx);
2995    }
2996
2997    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
2998        cx.emit(DismissEvent);
2999    }
3000
3001    fn get_matching_bindings_count(&self, cx: &Context<Self>) -> usize {
3002        let current_keystrokes = self.keybind_editor.read(cx).keystrokes();
3003
3004        if current_keystrokes.is_empty() {
3005            return 0;
3006        }
3007
3008        self.keymap_editor
3009            .read(cx)
3010            .keybindings
3011            .iter()
3012            .enumerate()
3013            .filter(|(idx, binding)| {
3014                // Don't count the binding we're currently editing
3015                if !self.creating && *idx == self.editing_keybind_idx {
3016                    return false;
3017                }
3018
3019                binding.keystrokes().is_some_and(|keystrokes| {
3020                    keystrokes_match_exactly(keystrokes, current_keystrokes)
3021                })
3022            })
3023            .count()
3024    }
3025
3026    fn show_matching_bindings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3027        let keystrokes = self.keybind_editor.read(cx).keystrokes().to_vec();
3028
3029        self.keymap_editor.update(cx, |keymap_editor, cx| {
3030            keymap_editor.clear_action_query(window, cx)
3031        });
3032
3033        // Dismiss the modal
3034        cx.emit(DismissEvent);
3035
3036        // Update the keymap editor to show matching keystrokes
3037        self.keymap_editor.update(cx, |editor, cx| {
3038            editor.filter_state = FilterState::All;
3039            editor.search_mode = SearchMode::KeyStroke { exact_match: true };
3040            editor.keystroke_editor.update(cx, |keystroke_editor, cx| {
3041                keystroke_editor.set_keystrokes(keystrokes, cx);
3042            });
3043        });
3044    }
3045}
3046
3047impl Render for KeybindingEditorModal {
3048    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3049        self.add_action_arguments_input(window, cx);
3050
3051        let theme = cx.theme().colors();
3052        let matching_bindings_count = self.get_matching_bindings_count(cx);
3053        let key_context = self.key_context_internal(window, cx);
3054        let showing_completions = key_context.contains("showing_completions");
3055
3056        v_flex()
3057            .w(rems(34.))
3058            .elevation_3(cx)
3059            .key_context(key_context)
3060            .on_action(cx.listener(Self::confirm))
3061            .on_action(cx.listener(Self::cancel))
3062            .when(!showing_completions, |this| {
3063                this.on_action(cx.listener(Self::focus_next))
3064                    .on_action(cx.listener(Self::focus_prev))
3065            })
3066            .child(
3067                Modal::new("keybinding_editor_modal", None)
3068                    .header(
3069                        ModalHeader::new().child(
3070                            v_flex()
3071                                .w_full()
3072                                .pb_1p5()
3073                                .mb_1()
3074                                .gap_0p5()
3075                                .border_b_1()
3076                                .border_color(theme.border_variant)
3077                                .when(!self.creating, |this| {
3078                                    this.child(Label::new(
3079                                        self.editing_keybind.action().humanized_name.clone(),
3080                                    ))
3081                                    .when_some(
3082                                        self.editing_keybind.action().documentation,
3083                                        |this, docs| {
3084                                            this.child(
3085                                                Label::new(docs)
3086                                                    .size(LabelSize::Small)
3087                                                    .color(Color::Muted),
3088                                            )
3089                                        },
3090                                    )
3091                                })
3092                                .when(self.creating, |this| {
3093                                    this.child(Label::new("Create Keybinding"))
3094                                }),
3095                        ),
3096                    )
3097                    .section(
3098                        Section::new().child(
3099                            v_flex()
3100                                .gap_2p5()
3101                                .when_some(
3102                                    self.creating
3103                                        .then_some(())
3104                                        .and_then(|_| self.action_editor.as_ref()),
3105                                    |this, selector| this.child(selector.clone()),
3106                                )
3107                                .child(
3108                                    v_flex()
3109                                        .gap_1()
3110                                        .child(Label::new("Edit Keystroke"))
3111                                        .child(self.keybind_editor.clone())
3112                                        .child(h_flex().gap_px().when(
3113                                            matching_bindings_count > 0,
3114                                            |this| {
3115                                                let label = format!(
3116                                                    "There {} {} {} with the same keystrokes.",
3117                                                    if matching_bindings_count == 1 {
3118                                                        "is"
3119                                                    } else {
3120                                                        "are"
3121                                                    },
3122                                                    matching_bindings_count,
3123                                                    if matching_bindings_count == 1 {
3124                                                        "binding"
3125                                                    } else {
3126                                                        "bindings"
3127                                                    }
3128                                                );
3129
3130                                                this.child(
3131                                                    Label::new(label)
3132                                                        .size(LabelSize::Small)
3133                                                        .color(Color::Muted),
3134                                                )
3135                                                .child(
3136                                                    Button::new("show_matching", "View")
3137                                                        .label_size(LabelSize::Small)
3138                                                        .end_icon(
3139                                                            Icon::new(IconName::ArrowUpRight)
3140                                                                .size(IconSize::Small)
3141                                                                .color(Color::Muted),
3142                                                        )
3143                                                        .on_click(cx.listener(
3144                                                            |this, _, window, cx| {
3145                                                                this.show_matching_bindings(
3146                                                                    window, cx,
3147                                                                );
3148                                                            },
3149                                                        )),
3150                                                )
3151                                            },
3152                                        )),
3153                                )
3154                                .when_some(self.action_arguments_editor.clone(), |this, editor| {
3155                                    this.child(
3156                                        v_flex()
3157                                            .gap_1()
3158                                            .child(Label::new("Edit Arguments"))
3159                                            .child(editor),
3160                                    )
3161                                })
3162                                .child(self.context_editor.clone())
3163                                .when_some(self.error.as_ref(), |this, error| {
3164                                    this.child(
3165                                        Banner::new()
3166                                            .severity(error.severity)
3167                                            .child(Label::new(error.content.clone())),
3168                                    )
3169                                }),
3170                        ),
3171                    )
3172                    .footer(
3173                        ModalFooter::new().end_slot(
3174                            h_flex()
3175                                .gap_1()
3176                                .child(
3177                                    Button::new("cancel", "Cancel")
3178                                        .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
3179                                )
3180                                .child(Button::new("save-btn", "Save").on_click(cx.listener(
3181                                    |this, _event, _window, cx| {
3182                                        this.save_or_display_error(cx);
3183                                    },
3184                                ))),
3185                        ),
3186                    ),
3187            )
3188    }
3189}
3190
3191struct KeybindingEditorModalFocusState {
3192    handles: Vec<FocusHandle>,
3193}
3194
3195impl KeybindingEditorModalFocusState {
3196    fn new(
3197        action_editor: Option<FocusHandle>,
3198        keystrokes: FocusHandle,
3199        action_arguments: Option<FocusHandle>,
3200        context: FocusHandle,
3201    ) -> Self {
3202        Self {
3203            handles: Vec::from_iter(
3204                [
3205                    action_editor,
3206                    Some(keystrokes),
3207                    action_arguments,
3208                    Some(context),
3209                ]
3210                .into_iter()
3211                .flatten(),
3212            ),
3213        }
3214    }
3215
3216    fn focused_index(&self, window: &Window, cx: &App) -> Option<i32> {
3217        self.handles
3218            .iter()
3219            .position(|handle| handle.contains_focused(window, cx))
3220            .map(|i| i as i32)
3221    }
3222
3223    fn focus_index(&self, mut index: i32, window: &mut Window, cx: &mut App) {
3224        if index < 0 {
3225            index = self.handles.len() as i32 - 1;
3226        }
3227        if index >= self.handles.len() as i32 {
3228            index = 0;
3229        }
3230        window.focus(&self.handles[index as usize], cx);
3231    }
3232
3233    fn focus_next(&self, window: &mut Window, cx: &mut App) {
3234        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3235            index + 1
3236        } else {
3237            0
3238        };
3239        self.focus_index(index_to_focus, window, cx);
3240    }
3241
3242    fn focus_previous(&self, window: &mut Window, cx: &mut App) {
3243        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3244            index - 1
3245        } else {
3246            self.handles.len() as i32 - 1
3247        };
3248        self.focus_index(index_to_focus, window, cx);
3249    }
3250}
3251
3252struct ActionArgumentsEditor {
3253    editor: Entity<Editor>,
3254    focus_handle: FocusHandle,
3255    is_loading: bool,
3256    /// See documentation in `KeymapEditor` for why a temp dir is needed.
3257    /// This field exists because the keymap editor temp dir creation may fail,
3258    /// and rather than implement a complicated retry mechanism, we simply
3259    /// fallback to trying to create a temporary directory in this editor on
3260    /// demand. Of note is that the TempDir struct will remove the directory
3261    /// when dropped.
3262    backup_temp_dir: Option<tempfile::TempDir>,
3263}
3264
3265impl Focusable for ActionArgumentsEditor {
3266    fn focus_handle(&self, _cx: &App) -> FocusHandle {
3267        self.focus_handle.clone()
3268    }
3269}
3270
3271impl ActionArgumentsEditor {
3272    fn new(
3273        action_name: &'static str,
3274        arguments: Option<SharedString>,
3275        temp_dir: Option<&std::path::Path>,
3276        workspace: WeakEntity<Workspace>,
3277        window: &mut Window,
3278        cx: &mut Context<Self>,
3279    ) -> Self {
3280        let focus_handle = cx.focus_handle();
3281        cx.on_focus_in(&focus_handle, window, |this, window, cx| {
3282            this.editor.focus_handle(cx).focus(window, cx);
3283        })
3284        .detach();
3285        let editor = cx.new(|cx| {
3286            let mut editor = Editor::auto_height_unbounded(1, window, cx);
3287            Self::set_editor_text(&mut editor, arguments.clone(), window, cx);
3288            editor.set_read_only(true);
3289            editor
3290        });
3291
3292        let temp_dir = temp_dir.map(|path| path.to_owned());
3293        cx.spawn_in(window, async move |this, cx| {
3294            let result = async {
3295                let (project, fs) = workspace.read_with(cx, |workspace, _cx| {
3296                    (
3297                        workspace.project().downgrade(),
3298                        workspace.app_state().fs.clone(),
3299                    )
3300                })?;
3301
3302                let file_name = json_schema_store::normalized_action_file_name(action_name);
3303
3304                let (buffer, backup_temp_dir) =
3305                    Self::create_temp_buffer(temp_dir, file_name.clone(), project.clone(), fs, cx)
3306                        .await
3307                        .context(concat!(
3308                            "Failed to create temporary buffer for action arguments. ",
3309                            "Auto-complete will not work"
3310                        ))?;
3311
3312                let editor = cx.new_window_entity(|window, cx| {
3313                    let multi_buffer = cx.new(|cx| editor::MultiBuffer::singleton(buffer, cx));
3314                    let mut editor = Editor::new(
3315                        EditorMode::Full {
3316                            scale_ui_elements_with_buffer_font_size: true,
3317                            show_active_line_background: false,
3318                            sizing_behavior: SizingBehavior::SizeByContent,
3319                        },
3320                        multi_buffer,
3321                        project.upgrade(),
3322                        window,
3323                        cx,
3324                    );
3325                    editor.disable_mouse_wheel_zoom();
3326                    editor.set_searchable(false);
3327                    editor.disable_scrollbars_and_minimap(window, cx);
3328                    editor.set_show_edit_predictions(Some(false), window, cx);
3329                    editor.set_show_gutter(false, cx);
3330                    Self::set_editor_text(&mut editor, arguments, window, cx);
3331                    editor
3332                })?;
3333
3334                this.update_in(cx, |this, window, cx| {
3335                    if this.editor.focus_handle(cx).is_focused(window) {
3336                        editor.focus_handle(cx).focus(window, cx);
3337                    }
3338                    this.editor = editor;
3339                    this.backup_temp_dir = backup_temp_dir;
3340                    this.is_loading = false;
3341                })?;
3342
3343                anyhow::Ok(())
3344            }
3345            .await;
3346            if result.is_err() {
3347                let json_language = load_json_language(workspace.clone(), cx).await;
3348                this.update(cx, |this, cx| {
3349                    this.editor.update(cx, |editor, cx| {
3350                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
3351                            buffer.update(cx, |buffer, cx| {
3352                                buffer.set_language(Some(json_language.clone()), cx)
3353                            });
3354                        }
3355                    })
3356                    // .context("Failed to load JSON language for editing keybinding action arguments input")
3357                })
3358                .ok();
3359                this.update(cx, |this, _cx| {
3360                    this.is_loading = false;
3361                })
3362                .ok();
3363            }
3364            result
3365        })
3366        .detach_and_log_err(cx);
3367        Self {
3368            editor,
3369            focus_handle,
3370            is_loading: true,
3371            backup_temp_dir: None,
3372        }
3373    }
3374
3375    fn set_editor_text(
3376        editor: &mut Editor,
3377        arguments: Option<SharedString>,
3378        window: &mut Window,
3379        cx: &mut Context<Editor>,
3380    ) {
3381        if let Some(arguments) = arguments {
3382            editor.set_text(arguments, window, cx);
3383        } else {
3384            // TODO: default value from schema?
3385            editor.set_placeholder_text("Action Arguments", window, cx);
3386        }
3387    }
3388
3389    async fn create_temp_buffer(
3390        temp_dir: Option<std::path::PathBuf>,
3391        file_name: String,
3392        project: WeakEntity<Project>,
3393        fs: Arc<dyn Fs>,
3394        cx: &mut AsyncApp,
3395    ) -> anyhow::Result<(Entity<language::Buffer>, Option<tempfile::TempDir>)> {
3396        let (temp_file_path, temp_dir) = {
3397            let file_name = file_name.clone();
3398            async move {
3399                let temp_dir_backup = match temp_dir.as_ref() {
3400                    Some(_) => None,
3401                    None => {
3402                        let temp_dir = paths::temp_dir();
3403                        let sub_temp_dir = tempfile::Builder::new()
3404                            .tempdir_in(temp_dir)
3405                            .context("Failed to create temporary directory")?;
3406                        Some(sub_temp_dir)
3407                    }
3408                };
3409                let dir_path = temp_dir.as_deref().unwrap_or_else(|| {
3410                    temp_dir_backup
3411                        .as_ref()
3412                        .expect("created backup tempdir")
3413                        .path()
3414                });
3415                let path = dir_path.join(file_name);
3416                fs.create_file(
3417                    &path,
3418                    fs::CreateOptions {
3419                        ignore_if_exists: true,
3420                        overwrite: true,
3421                    },
3422                )
3423                .await
3424                .context("Failed to create temporary file")?;
3425                anyhow::Ok((path, temp_dir_backup))
3426            }
3427        }
3428        .await
3429        .context("Failed to create backing file")?;
3430
3431        project
3432            .update(cx, |project, cx| {
3433                project.open_local_buffer(temp_file_path, cx)
3434            })?
3435            .await
3436            .context("Failed to create buffer")
3437            .map(|buffer| (buffer, temp_dir))
3438    }
3439}
3440
3441impl Render for ActionArgumentsEditor {
3442    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3443        let settings = theme_settings::ThemeSettings::get_global(cx);
3444        let colors = cx.theme().colors();
3445
3446        let border_color = if self.is_loading {
3447            colors.border_disabled
3448        } else if self.focus_handle.contains_focused(window, cx) {
3449            colors.border_focused
3450        } else {
3451            colors.border_variant
3452        };
3453
3454        let text_style = {
3455            TextStyleRefinement {
3456                font_size: Some(rems(0.875).into()),
3457                font_weight: Some(settings.buffer_font.weight),
3458                line_height: Some(relative(1.2)),
3459                color: self.is_loading.then_some(colors.text_disabled),
3460                ..Default::default()
3461            }
3462        };
3463
3464        self.editor
3465            .update(cx, |editor, _| editor.set_text_style_refinement(text_style));
3466
3467        h_flex()
3468            .min_h_8()
3469            .min_w_48()
3470            .px_2()
3471            .flex_grow()
3472            .rounded_md()
3473            .bg(cx.theme().colors().editor_background)
3474            .border_1()
3475            .border_color(border_color)
3476            .track_focus(&self.focus_handle)
3477            .child(self.editor.clone())
3478    }
3479}
3480
3481struct KeyContextCompletionProvider {
3482    contexts: Vec<SharedString>,
3483}
3484
3485impl CompletionProvider for KeyContextCompletionProvider {
3486    fn completions(
3487        &self,
3488        buffer: &Entity<language::Buffer>,
3489        buffer_position: language::Anchor,
3490        _trigger: editor::CompletionContext,
3491        _window: &mut Window,
3492        cx: &mut Context<Editor>,
3493    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
3494        let buffer = buffer.read(cx);
3495        let mut count_back = 0;
3496        for char in buffer.reversed_chars_at(buffer_position) {
3497            if char.is_ascii_alphanumeric() || char == '_' {
3498                count_back += 1;
3499            } else {
3500                break;
3501            }
3502        }
3503        let start_anchor =
3504            buffer.anchor_before(buffer_position.to_offset(buffer).saturating_sub(count_back));
3505        let replace_range = start_anchor..buffer_position;
3506        gpui::Task::ready(Ok(vec![project::CompletionResponse {
3507            completions: self
3508                .contexts
3509                .iter()
3510                .map(|context| project::Completion {
3511                    replace_range: replace_range.clone(),
3512                    label: language::CodeLabel::plain(context.to_string(), None),
3513                    new_text: context.to_string(),
3514                    documentation: None,
3515                    source: project::CompletionSource::Custom,
3516                    icon_path: None,
3517                    match_start: None,
3518                    snippet_deduplication_key: None,
3519                    insert_text_mode: None,
3520                    confirm: None,
3521                })
3522                .collect(),
3523            display_options: CompletionDisplayOptions::default(),
3524            is_incomplete: false,
3525        }]))
3526    }
3527
3528    fn is_completion_trigger(
3529        &self,
3530        _buffer: &Entity<language::Buffer>,
3531        _position: language::Anchor,
3532        text: &str,
3533        _trigger_in_words: bool,
3534        _cx: &mut Context<Editor>,
3535    ) -> bool {
3536        text.chars()
3537            .last()
3538            .is_some_and(|last_char| last_char.is_ascii_alphanumeric() || last_char == '_')
3539    }
3540}
3541
3542async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
3543    let json_language_task = workspace
3544        .read_with(cx, |workspace, cx| {
3545            workspace
3546                .project()
3547                .read(cx)
3548                .languages()
3549                .language_for_name("JSON")
3550        })
3551        .context("Failed to load JSON language")
3552        .log_err();
3553    let json_language = match json_language_task {
3554        Some(task) => task.await.context("Failed to load JSON language").log_err(),
3555        None => None,
3556    };
3557    json_language.unwrap_or_else(|| {
3558        Arc::new(Language::new(
3559            LanguageConfig {
3560                name: "JSON".into(),
3561                ..Default::default()
3562            },
3563            Some(tree_sitter_json::LANGUAGE.into()),
3564        ))
3565    })
3566}
3567
3568async fn load_keybind_context_language(
3569    workspace: WeakEntity<Workspace>,
3570    cx: &mut AsyncApp,
3571) -> Arc<Language> {
3572    let language_task = workspace
3573        .read_with(cx, |workspace, cx| {
3574            workspace
3575                .project()
3576                .read(cx)
3577                .languages()
3578                .language_for_name("Zed Keybind Context")
3579        })
3580        .context("Failed to load Zed Keybind Context language")
3581        .log_err();
3582    let language = match language_task {
3583        Some(task) => task
3584            .await
3585            .context("Failed to load Zed Keybind Context language")
3586            .log_err(),
3587        None => None,
3588    };
3589    language.unwrap_or_else(|| {
3590        Arc::new(Language::new(
3591            LanguageConfig {
3592                name: "Zed Keybind Context".into(),
3593                ..Default::default()
3594            },
3595            Some(tree_sitter_rust::LANGUAGE.into()),
3596        ))
3597    })
3598}
3599
3600async fn save_keybinding_update(
3601    create: bool,
3602    existing: ProcessedBinding,
3603    action_mapping: &ActionMapping,
3604    new_args: Option<&str>,
3605    fs: &Arc<dyn Fs>,
3606    keyboard_mapper: &dyn PlatformKeyboardMapper,
3607) -> anyhow::Result<()> {
3608    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3609        .await
3610        .context("Failed to load keymap file")?;
3611
3612    let tab_size = infer_json_indent_size(&keymap_contents);
3613
3614    let existing_keystrokes = existing.keystrokes().unwrap_or_default();
3615    let existing_context = existing.context().and_then(KeybindContextString::local_str);
3616    let existing_args = existing
3617        .action()
3618        .arguments
3619        .as_ref()
3620        .map(|args| args.text.as_ref());
3621
3622    let target = settings::KeybindUpdateTarget {
3623        context: existing_context,
3624        keystrokes: existing_keystrokes,
3625        action_name: existing.action().name,
3626        action_arguments: existing_args,
3627    };
3628
3629    let source = settings::KeybindUpdateTarget {
3630        context: action_mapping.context.as_ref().map(|a| &***a),
3631        keystrokes: &action_mapping.keystrokes,
3632        action_name: existing.action().name,
3633        action_arguments: new_args,
3634    };
3635
3636    let operation = if !create {
3637        settings::KeybindUpdateOperation::Replace {
3638            target,
3639            target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3640            source,
3641        }
3642    } else {
3643        settings::KeybindUpdateOperation::Add {
3644            source,
3645            from: Some(target),
3646        }
3647    };
3648
3649    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3650
3651    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3652        operation,
3653        keymap_contents,
3654        tab_size,
3655        keyboard_mapper,
3656    )
3657    .map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?;
3658    fs.write(
3659        paths::keymap_file().as_path(),
3660        updated_keymap_contents.as_bytes(),
3661    )
3662    .await
3663    .context("Failed to write keymap file")?;
3664
3665    telemetry::event!(
3666        "Keybinding Updated",
3667        new_keybinding = new_keybinding,
3668        removed_keybinding = removed_keybinding,
3669        source = source
3670    );
3671    Ok(())
3672}
3673
3674async fn remove_keybinding(
3675    existing: ProcessedBinding,
3676    fs: &Arc<dyn Fs>,
3677    keyboard_mapper: &dyn PlatformKeyboardMapper,
3678) -> anyhow::Result<()> {
3679    let Some(keystrokes) = existing.keystrokes() else {
3680        anyhow::bail!("Cannot remove a keybinding that does not exist");
3681    };
3682    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3683        .await
3684        .context("Failed to load keymap file")?;
3685    let tab_size = infer_json_indent_size(&keymap_contents);
3686
3687    let operation = settings::KeybindUpdateOperation::Remove {
3688        target: settings::KeybindUpdateTarget {
3689            context: existing.context().and_then(KeybindContextString::local_str),
3690            keystrokes,
3691            action_name: existing.action().name,
3692            action_arguments: existing
3693                .action()
3694                .arguments
3695                .as_ref()
3696                .map(|arguments| arguments.text.as_ref()),
3697        },
3698        target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3699    };
3700
3701    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3702    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3703        operation,
3704        keymap_contents,
3705        tab_size,
3706        keyboard_mapper,
3707    )
3708    .context("Failed to update keybinding")?;
3709    fs.write(
3710        paths::keymap_file().as_path(),
3711        updated_keymap_contents.as_bytes(),
3712    )
3713    .await
3714    .context("Failed to write keymap file")?;
3715
3716    telemetry::event!(
3717        "Keybinding Removed",
3718        new_keybinding = new_keybinding,
3719        removed_keybinding = removed_keybinding,
3720        source = source
3721    );
3722    Ok(())
3723}
3724
3725fn collect_contexts_from_assets() -> Vec<SharedString> {
3726    let mut keymap_assets = vec![
3727        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
3728        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
3729    ];
3730    keymap_assets.extend(
3731        BaseKeymap::OPTIONS
3732            .iter()
3733            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
3734            .map(util::asset_str::<SettingsAssets>),
3735    );
3736
3737    let mut contexts = HashSet::default();
3738
3739    for keymap_asset in keymap_assets {
3740        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
3741            continue;
3742        };
3743
3744        for section in keymap.sections() {
3745            let context_expr = &section.context;
3746            let mut queue = Vec::new();
3747            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
3748                continue;
3749            };
3750
3751            queue.push(root_context);
3752            while let Some(context) = queue.pop() {
3753                match context {
3754                    Identifier(ident) => {
3755                        contexts.insert(ident);
3756                    }
3757                    Equal(ident_a, ident_b) => {
3758                        contexts.insert(ident_a);
3759                        contexts.insert(ident_b);
3760                    }
3761                    NotEqual(ident_a, ident_b) => {
3762                        contexts.insert(ident_a);
3763                        contexts.insert(ident_b);
3764                    }
3765                    Descendant(ctx_a, ctx_b) => {
3766                        queue.push(*ctx_a);
3767                        queue.push(*ctx_b);
3768                    }
3769                    Not(ctx) => {
3770                        queue.push(*ctx);
3771                    }
3772                    And(ctx_a, ctx_b) => {
3773                        queue.push(*ctx_a);
3774                        queue.push(*ctx_b);
3775                    }
3776                    Or(ctx_a, ctx_b) => {
3777                        queue.push(*ctx_a);
3778                        queue.push(*ctx_b);
3779                    }
3780                }
3781            }
3782        }
3783    }
3784
3785    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
3786    contexts.sort();
3787
3788    contexts
3789}
3790
3791fn normalized_ctx_eq(
3792    a: &gpui::KeyBindingContextPredicate,
3793    b: &gpui::KeyBindingContextPredicate,
3794) -> bool {
3795    use gpui::KeyBindingContextPredicate::*;
3796    return match (a, b) {
3797        (Identifier(_), Identifier(_)) => a == b,
3798        (Equal(a_left, a_right), Equal(b_left, b_right)) => {
3799            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3800        }
3801        (NotEqual(a_left, a_right), NotEqual(b_left, b_right)) => {
3802            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3803        }
3804        (Descendant(a_parent, a_child), Descendant(b_parent, b_child)) => {
3805            normalized_ctx_eq(a_parent, b_parent) && normalized_ctx_eq(a_child, b_child)
3806        }
3807        (Not(a_expr), Not(b_expr)) => normalized_ctx_eq(a_expr, b_expr),
3808        // Handle double negation: !(!a) == a
3809        (Not(a_expr), b) if matches!(a_expr.as_ref(), Not(_)) => {
3810            let Not(a_inner) = a_expr.as_ref() else {
3811                unreachable!();
3812            };
3813            normalized_ctx_eq(b, a_inner)
3814        }
3815        (a, Not(b_expr)) if matches!(b_expr.as_ref(), Not(_)) => {
3816            let Not(b_inner) = b_expr.as_ref() else {
3817                unreachable!();
3818            };
3819            normalized_ctx_eq(a, b_inner)
3820        }
3821        (And(a_left, a_right), And(b_left, b_right))
3822            if matches!(a_left.as_ref(), And(_, _))
3823                || matches!(a_right.as_ref(), And(_, _))
3824                || matches!(b_left.as_ref(), And(_, _))
3825                || matches!(b_right.as_ref(), And(_, _)) =>
3826        {
3827            let mut a_operands = Vec::new();
3828            flatten_and(a, &mut a_operands);
3829            let mut b_operands = Vec::new();
3830            flatten_and(b, &mut b_operands);
3831            compare_operand_sets(&a_operands, &b_operands)
3832        }
3833        (And(a_left, a_right), And(b_left, b_right)) => {
3834            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3835                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3836        }
3837        (Or(a_left, a_right), Or(b_left, b_right))
3838            if matches!(a_left.as_ref(), Or(_, _))
3839                || matches!(a_right.as_ref(), Or(_, _))
3840                || matches!(b_left.as_ref(), Or(_, _))
3841                || matches!(b_right.as_ref(), Or(_, _)) =>
3842        {
3843            let mut a_operands = Vec::new();
3844            flatten_or(a, &mut a_operands);
3845            let mut b_operands = Vec::new();
3846            flatten_or(b, &mut b_operands);
3847            compare_operand_sets(&a_operands, &b_operands)
3848        }
3849        (Or(a_left, a_right), Or(b_left, b_right)) => {
3850            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3851                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3852        }
3853        _ => false,
3854    };
3855
3856    fn flatten_and<'a>(
3857        pred: &'a gpui::KeyBindingContextPredicate,
3858        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3859    ) {
3860        use gpui::KeyBindingContextPredicate::*;
3861        match pred {
3862            And(left, right) => {
3863                flatten_and(left, operands);
3864                flatten_and(right, operands);
3865            }
3866            _ => operands.push(pred),
3867        }
3868    }
3869
3870    fn flatten_or<'a>(
3871        pred: &'a gpui::KeyBindingContextPredicate,
3872        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3873    ) {
3874        use gpui::KeyBindingContextPredicate::*;
3875        match pred {
3876            Or(left, right) => {
3877                flatten_or(left, operands);
3878                flatten_or(right, operands);
3879            }
3880            _ => operands.push(pred),
3881        }
3882    }
3883
3884    fn compare_operand_sets(
3885        a: &[&gpui::KeyBindingContextPredicate],
3886        b: &[&gpui::KeyBindingContextPredicate],
3887    ) -> bool {
3888        if a.len() != b.len() {
3889            return false;
3890        }
3891
3892        // For each operand in a, find a matching operand in b
3893        let mut b_matched = vec![false; b.len()];
3894        for a_operand in a {
3895            let mut found = false;
3896            for (b_idx, b_operand) in b.iter().enumerate() {
3897                if !b_matched[b_idx] && normalized_ctx_eq(a_operand, b_operand) {
3898                    b_matched[b_idx] = true;
3899                    found = true;
3900                    break;
3901                }
3902            }
3903            if !found {
3904                return false;
3905            }
3906        }
3907
3908        true
3909    }
3910}
3911
3912impl SerializableItem for KeymapEditor {
3913    fn serialized_item_kind() -> &'static str {
3914        "KeymapEditor"
3915    }
3916
3917    fn cleanup(
3918        workspace_id: workspace::WorkspaceId,
3919        alive_items: Vec<workspace::ItemId>,
3920        _window: &mut Window,
3921        cx: &mut App,
3922    ) -> gpui::Task<gpui::Result<()>> {
3923        let db = KeybindingEditorDb::global(cx);
3924        workspace::delete_unloaded_items(alive_items, workspace_id, "keybinding_editors", &db, cx)
3925    }
3926
3927    fn deserialize(
3928        _project: Entity<project::Project>,
3929        workspace: WeakEntity<Workspace>,
3930        workspace_id: workspace::WorkspaceId,
3931        item_id: workspace::ItemId,
3932        window: &mut Window,
3933        cx: &mut App,
3934    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
3935        let db = KeybindingEditorDb::global(cx);
3936        window.spawn(cx, async move |cx| {
3937            if db.get_keybinding_editor(item_id, workspace_id)?.is_some() {
3938                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
3939            } else {
3940                Err(anyhow!("No keybinding editor to deserialize"))
3941            }
3942        })
3943    }
3944
3945    fn serialize(
3946        &mut self,
3947        workspace: &mut Workspace,
3948        item_id: workspace::ItemId,
3949        _closing: bool,
3950        _window: &mut Window,
3951        cx: &mut ui::Context<Self>,
3952    ) -> Option<gpui::Task<gpui::Result<()>>> {
3953        let workspace_id = workspace.database_id()?;
3954        let db = KeybindingEditorDb::global(cx);
3955        Some(cx.background_spawn(
3956            async move { db.save_keybinding_editor(item_id, workspace_id).await },
3957        ))
3958    }
3959
3960    fn should_serialize(&self, _event: &Self::Event) -> bool {
3961        false
3962    }
3963}
3964
3965mod persistence {
3966    use db::{query, sqlez::domain::Domain, sqlez_macros::sql};
3967    use workspace::WorkspaceDb;
3968
3969    pub struct KeybindingEditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection);
3970
3971    impl Domain for KeybindingEditorDb {
3972        const NAME: &str = stringify!(KeybindingEditorDb);
3973
3974        const MIGRATIONS: &[&str] = &[sql!(
3975                CREATE TABLE keybinding_editors (
3976                    workspace_id INTEGER,
3977                    item_id INTEGER UNIQUE,
3978
3979                    PRIMARY KEY(workspace_id, item_id),
3980                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
3981                    ON DELETE CASCADE
3982                ) STRICT;
3983        )];
3984    }
3985
3986    db::static_connection!(KeybindingEditorDb, [WorkspaceDb]);
3987
3988    impl KeybindingEditorDb {
3989        query! {
3990            pub async fn save_keybinding_editor(
3991                item_id: workspace::ItemId,
3992                workspace_id: workspace::WorkspaceId
3993            ) -> Result<()> {
3994                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
3995                VALUES (?, ?)
3996            }
3997        }
3998
3999        query! {
4000            pub fn get_keybinding_editor(
4001                item_id: workspace::ItemId,
4002                workspace_id: workspace::WorkspaceId
4003            ) -> Result<Option<workspace::ItemId>> {
4004                SELECT item_id
4005                FROM keybinding_editors
4006                WHERE item_id = ? AND workspace_id = ?
4007            }
4008        }
4009    }
4010}
4011
4012#[cfg(test)]
4013mod tests {
4014    use super::*;
4015
4016    #[test]
4017    fn normalized_ctx_cmp() {
4018        #[track_caller]
4019        fn cmp(a: &str, b: &str) -> bool {
4020            let a = gpui::KeyBindingContextPredicate::parse(a)
4021                .expect("Failed to parse keybinding context a");
4022            let b = gpui::KeyBindingContextPredicate::parse(b)
4023                .expect("Failed to parse keybinding context b");
4024            normalized_ctx_eq(&a, &b)
4025        }
4026
4027        // Basic equality - identical expressions
4028        assert!(cmp("a && b", "a && b"));
4029        assert!(cmp("a || b", "a || b"));
4030        assert!(cmp("a == b", "a == b"));
4031        assert!(cmp("a != b", "a != b"));
4032        assert!(cmp("a > b", "a > b"));
4033        assert!(cmp("!a", "!a"));
4034
4035        // AND operator - associative/commutative
4036        assert!(cmp("a && b", "b && a"));
4037        assert!(cmp("a && b && c", "c && b && a"));
4038        assert!(cmp("a && b && c", "b && a && c"));
4039        assert!(cmp("a && b && c && d", "d && c && b && a"));
4040
4041        // OR operator - associative/commutative
4042        assert!(cmp("a || b", "b || a"));
4043        assert!(cmp("a || b || c", "c || b || a"));
4044        assert!(cmp("a || b || c", "b || a || c"));
4045        assert!(cmp("a || b || c || d", "d || c || b || a"));
4046
4047        // Equality operator - associative/commutative
4048        assert!(cmp("a == b", "b == a"));
4049        assert!(cmp("x == y", "y == x"));
4050
4051        // Inequality operator - associative/commutative
4052        assert!(cmp("a != b", "b != a"));
4053        assert!(cmp("x != y", "y != x"));
4054
4055        // Complex nested expressions with associative operators
4056        assert!(cmp("(a && b) || c", "c || (a && b)"));
4057        assert!(cmp("(a && b) || c", "c || (b && a)"));
4058        assert!(cmp("(a || b) && c", "c && (a || b)"));
4059        assert!(cmp("(a || b) && c", "c && (b || a)"));
4060        assert!(cmp("(a && b) || (c && d)", "(c && d) || (a && b)"));
4061        assert!(cmp("(a && b) || (c && d)", "(d && c) || (b && a)"));
4062
4063        // Multiple levels of nesting
4064        assert!(cmp("((a && b) || c) && d", "d && ((a && b) || c)"));
4065        assert!(cmp("((a && b) || c) && d", "d && (c || (b && a))"));
4066        assert!(cmp("a && (b || (c && d))", "(b || (c && d)) && a"));
4067        assert!(cmp("a && (b || (c && d))", "(b || (d && c)) && a"));
4068
4069        // Negation with associative operators
4070        assert!(cmp("!a && b", "b && !a"));
4071        assert!(cmp("!a || b", "b || !a"));
4072        assert!(cmp("!(a && b) || c", "c || !(a && b)"));
4073        assert!(cmp("!(a && b) || c", "c || !(b && a)"));
4074
4075        // Descendant operator (>) - NOT associative/commutative
4076        assert!(cmp("a > b", "a > b"));
4077        assert!(!cmp("a > b", "b > a"));
4078        assert!(!cmp("a > b > c", "c > b > a"));
4079        assert!(!cmp("a > b > c", "a > c > b"));
4080
4081        // Mixed operators with descendant
4082        assert!(cmp("(a > b) && c", "c && (a > b)"));
4083        assert!(!cmp("(a > b) && c", "c && (b > a)"));
4084        assert!(cmp("(a > b) || (c > d)", "(c > d) || (a > b)"));
4085        assert!(!cmp("(a > b) || (c > d)", "(b > a) || (d > c)"));
4086
4087        // Negative cases - different operators
4088        assert!(!cmp("a && b", "a || b"));
4089        assert!(!cmp("a == b", "a != b"));
4090        assert!(!cmp("a && b", "a > b"));
4091        assert!(!cmp("a || b", "a > b"));
4092        assert!(!cmp("a == b", "a && b"));
4093        assert!(!cmp("a != b", "a || b"));
4094
4095        // Negative cases - different operands
4096        assert!(!cmp("a && b", "a && c"));
4097        assert!(!cmp("a && b", "c && d"));
4098        assert!(!cmp("a || b", "a || c"));
4099        assert!(!cmp("a || b", "c || d"));
4100        assert!(!cmp("a == b", "a == c"));
4101        assert!(!cmp("a != b", "a != c"));
4102        assert!(!cmp("a > b", "a > c"));
4103        assert!(!cmp("a > b", "c > b"));
4104
4105        // Negative cases - with negation
4106        assert!(!cmp("!a", "a"));
4107        assert!(!cmp("!a && b", "a && b"));
4108        assert!(!cmp("!(a && b)", "a && b"));
4109        assert!(!cmp("!a || b", "a || b"));
4110        assert!(!cmp("!(a || b)", "a || b"));
4111
4112        // Negative cases - complex expressions
4113        assert!(!cmp("(a && b) || c", "(a || b) && c"));
4114        assert!(!cmp("a && (b || c)", "a || (b && c)"));
4115        assert!(!cmp("(a && b) || (c && d)", "(a || b) && (c || d)"));
4116        assert!(!cmp("a > b && c", "a && b > c"));
4117
4118        // Edge cases - multiple same operands
4119        assert!(cmp("a && a", "a && a"));
4120        assert!(cmp("a || a", "a || a"));
4121        assert!(cmp("a && a && b", "b && a && a"));
4122        assert!(cmp("a || a || b", "b || a || a"));
4123
4124        // Edge cases - deeply nested
4125        assert!(cmp(
4126            "((a && b) || (c && d)) && ((e || f) && g)",
4127            "((e || f) && g) && ((c && d) || (a && b))"
4128        ));
4129        assert!(cmp(
4130            "((a && b) || (c && d)) && ((e || f) && g)",
4131            "(g && (f || e)) && ((d && c) || (b && a))"
4132        ));
4133
4134        // Edge cases - repeated patterns
4135        assert!(cmp("(a && b) || (a && b)", "(b && a) || (b && a)"));
4136        assert!(cmp("(a || b) && (a || b)", "(b || a) && (b || a)"));
4137
4138        // Negative cases - subtle differences
4139        assert!(!cmp("a && b && c", "a && b"));
4140        assert!(!cmp("a || b || c", "a || b"));
4141        assert!(!cmp("(a && b) || c", "a && (b || c)"));
4142
4143        // a > b > c is not the same as a > c, should not be equal
4144        assert!(!cmp("a > b > c", "a > c"));
4145
4146        // Double negation with complex expressions
4147        assert!(cmp("!(!(a && b))", "a && b"));
4148        assert!(cmp("!(!(a || b))", "a || b"));
4149        assert!(cmp("!(!(a > b))", "a > b"));
4150        assert!(cmp("!(!a) && b", "a && b"));
4151        assert!(cmp("!(!a) || b", "a || b"));
4152        assert!(cmp("!(!(a && b)) || c", "(a && b) || c"));
4153        assert!(cmp("!(!(a && b)) || c", "(b && a) || c"));
4154        assert!(cmp("!(!a)", "a"));
4155        assert!(cmp("a", "!(!a)"));
4156        assert!(cmp("!(!(!a))", "!a"));
4157        assert!(cmp("!(!(!(!a)))", "a"));
4158    }
4159
4160    #[test]
4161    fn binding_is_unbound_by_unbind_respects_precedence() {
4162        let binding = gpui::KeyBinding::new("tab", zed_actions::OpenKeymap, None);
4163        let unbind =
4164            gpui::KeyBinding::new("tab", gpui::Unbind(binding.action().name().into()), None);
4165
4166        let unbind_then_binding = vec![&unbind, &binding];
4167        assert!(!binding_is_unbound_by_unbind(
4168            &binding,
4169            1,
4170            &unbind_then_binding,
4171        ));
4172
4173        let binding_then_unbind = vec![&binding, &unbind];
4174        assert!(binding_is_unbound_by_unbind(
4175            &binding,
4176            0,
4177            &binding_then_unbind,
4178        ));
4179    }
4180}