keymap_editor.rs

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