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