keymap_editor.rs

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