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