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