keybindings.rs

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