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