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                                                    .key_binding(
1876                                                        ui::KeyBinding::for_action_in(&zed_actions::OpenKeymapFile, &focus_handle, cx)
1877                                                            .map(|kb| kb.size(rems_from_px(10.))),
1878                                                    )
1879                                                    .on_click(|_, window, cx| {
1880                                                        window.dispatch_action(
1881                                                            zed_actions::OpenKeymapFile.boxed_clone(),
1882                                                            cx,
1883                                                        );
1884                                                    })
1885                                            )
1886                                            .child(
1887                                                Button::new("create", "Create Keybinding")
1888                                                    .style(ButtonStyle::Outlined)
1889                                                    .key_binding(
1890                                                        ui::KeyBinding::for_action_in(&OpenCreateKeybindingModal, &focus_handle, cx)
1891                                                            .map(|kb| kb.size(rems_from_px(10.))),
1892                                                    )
1893                                                    .on_click(|_, window, cx| {
1894                                                        window.dispatch_action(
1895                                                            OpenCreateKeybindingModal.boxed_clone(),
1896                                                            cx,
1897                                                        );
1898                                                    })
1899                                            )
1900
1901                                    )
1902                            ),
1903                    )
1904                    .when(
1905                        matches!(self.search_mode, SearchMode::KeyStroke { .. }),
1906                        |this| {
1907                            this.child(
1908                                h_flex()
1909                                    .gap_2()
1910                                    .child(self.keystroke_editor.clone())
1911                                    .child(div().min_w_96()), // Spacer div to align with the search input
1912                            )
1913                        },
1914                    ),
1915            )
1916            .child(
1917                Table::new()
1918                    .interactable(&self.table_interaction_state)
1919                    .striped()
1920                    .empty_table_callback({
1921                        let this = cx.entity();
1922                        move |window, cx| this.read(cx).render_no_matches_hint(window, cx)
1923                    })
1924                    .column_widths([
1925                        DefiniteLength::Absolute(AbsoluteLength::Pixels(px(36.))),
1926                        DefiniteLength::Fraction(0.25),
1927                        DefiniteLength::Fraction(0.20),
1928                        DefiniteLength::Fraction(0.14),
1929                        DefiniteLength::Fraction(0.45),
1930                        DefiniteLength::Fraction(0.08),
1931                    ])
1932                    .resizable_columns(
1933                        [
1934                            TableResizeBehavior::None,
1935                            TableResizeBehavior::Resizable,
1936                            TableResizeBehavior::Resizable,
1937                            TableResizeBehavior::Resizable,
1938                            TableResizeBehavior::Resizable,
1939                            TableResizeBehavior::Resizable, // this column doesn't matter
1940                        ],
1941                        &self.current_widths,
1942                        cx,
1943                    )
1944                    .header(["", "Action", "Arguments", "Keystrokes", "Context", "Source"])
1945                    .uniform_list(
1946                        "keymap-editor-table",
1947                        row_count,
1948                        cx.processor(move |this, range: Range<usize>, _window, cx| {
1949                            let context_menu_deployed = this.context_menu_deployed();
1950                            range
1951                                .filter_map(|index| {
1952                                    let candidate_id = this.matches.get(index)?.candidate_id;
1953                                    let binding = &this.keybindings[candidate_id];
1954                                    let action_name = binding.action().name;
1955                                    let conflict = this.get_conflict(index);
1956                                    let is_overridden = conflict.is_some_and(|conflict| {
1957                                        !conflict.is_user_keybind_conflict()
1958                                    });
1959
1960                                    let icon = this.create_row_button(index, conflict, cx);
1961
1962                                    let action = div()
1963                                        .id(("keymap action", index))
1964                                        .child({
1965                                            if action_name != gpui::NoAction.name() {
1966                                                binding
1967                                                    .action()
1968                                                    .humanized_name
1969                                                    .clone()
1970                                                    .into_any_element()
1971                                            } else {
1972                                                const NULL: SharedString =
1973                                                    SharedString::new_static("<null>");
1974                                                muted_styled_text(NULL, cx)
1975                                                    .into_any_element()
1976                                            }
1977                                        })
1978                                        .when(
1979                                            !context_menu_deployed
1980                                                && this.show_hover_menus
1981                                                && !is_overridden,
1982                                            |this| {
1983                                                this.tooltip({
1984                                                    let action_name = binding.action().name;
1985                                                    let action_docs =
1986                                                        binding.action().documentation;
1987                                                    move |_, cx| {
1988                                                        let action_tooltip =
1989                                                            Tooltip::new(action_name);
1990                                                        let action_tooltip = match action_docs {
1991                                                            Some(docs) => action_tooltip.meta(docs),
1992                                                            None => action_tooltip,
1993                                                        };
1994                                                        cx.new(|_| action_tooltip).into()
1995                                                    }
1996                                                })
1997                                            },
1998                                        )
1999                                        .into_any_element();
2000
2001                                    let keystrokes = binding.key_binding().map_or(
2002                                        binding
2003                                            .keystroke_text()
2004                                            .cloned()
2005                                            .unwrap_or_default()
2006                                            .into_any_element(),
2007                                        |binding| ui::KeyBinding::from_keystrokes(binding.keystrokes.clone(), binding.source).into_any_element()
2008                                    );
2009
2010                                    let action_arguments = match binding.action().arguments.clone()
2011                                    {
2012                                        Some(arguments) => arguments.into_any_element(),
2013                                        None => {
2014                                            if binding.action().has_schema {
2015                                                muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
2016                                                    .into_any_element()
2017                                            } else {
2018                                                gpui::Empty.into_any_element()
2019                                            }
2020                                        }
2021                                    };
2022
2023                                    let context = binding.context().cloned().map_or(
2024                                        gpui::Empty.into_any_element(),
2025                                        |context| {
2026                                            let is_local = context.local().is_some();
2027
2028                                            div()
2029                                                .id(("keymap context", index))
2030                                                .child(context.clone())
2031                                                .when(
2032                                                    is_local
2033                                                        && !context_menu_deployed
2034                                                        && !is_overridden
2035                                                        && this.show_hover_menus,
2036                                                    |this| {
2037                                                        this.tooltip(Tooltip::element({
2038                                                            move |_, _| {
2039                                                                context.clone().into_any_element()
2040                                                            }
2041                                                        }))
2042                                                    },
2043                                                )
2044                                                .into_any_element()
2045                                        },
2046                                    );
2047
2048                                    let source = binding
2049                                        .keybind_source()
2050                                        .map(|source| source.name())
2051                                        .unwrap_or_default()
2052                                        .into_any_element();
2053
2054                                    Some([
2055                                        icon.into_any_element(),
2056                                        action,
2057                                        action_arguments,
2058                                        keystrokes,
2059                                        context,
2060                                        source,
2061                                    ])
2062                                })
2063                                .collect()
2064                        }),
2065                    )
2066                    .map_row(cx.processor(
2067                        |this, (row_index, row): (usize, Stateful<Div>), _window, cx| {
2068                        let conflict = this.get_conflict(row_index);
2069                            let is_selected = this.selected_index == Some(row_index);
2070
2071                            let row_id = row_group_id(row_index);
2072
2073                            div()
2074                                .id(("keymap-row-wrapper", row_index))
2075                                .child(
2076                                    row.id(row_id.clone())
2077                                        .on_any_mouse_down(cx.listener(
2078                                            move |this,
2079                                                  mouse_down_event: &gpui::MouseDownEvent,
2080                                                  window,
2081                                                  cx| {
2082                                                if mouse_down_event.button == MouseButton::Right {
2083                                                    this.select_index(
2084                                                        row_index, None, window, cx,
2085                                                    );
2086                                                    this.create_context_menu(
2087                                                        mouse_down_event.position,
2088                                                        window,
2089                                                        cx,
2090                                                    );
2091                                                }
2092                                            },
2093                                        ))
2094                                        .on_click(cx.listener(
2095                                            move |this, event: &ClickEvent, window, cx| {
2096                                                this.select_index(row_index, None, window, cx);
2097                                                if event.click_count() == 2 {
2098                                                    this.open_edit_keybinding_modal(
2099                                                        false, window, cx,
2100                                                    );
2101                                                }
2102                                            },
2103                                        ))
2104                                        .group(row_id)
2105                                        .when(
2106                                            conflict.is_some_and(|conflict| {
2107                                                !conflict.is_user_keybind_conflict()
2108                                            }),
2109                                            |row| {
2110                                                const OVERRIDDEN_OPACITY: f32 = 0.5;
2111                                                row.opacity(OVERRIDDEN_OPACITY)
2112                                            },
2113                                        )
2114                                        .when_some(
2115                                            conflict.filter(|conflict| {
2116                                                !this.context_menu_deployed() &&
2117                                                !conflict.is_user_keybind_conflict()
2118                                            }),
2119                                            |row, conflict| {
2120                                                let overriding_binding = this.keybindings.get(conflict.index);
2121                                                let context = overriding_binding.and_then(|binding| {
2122                                                    match conflict.override_source {
2123                                                        KeybindSource::User  => Some("your keymap"),
2124                                                        KeybindSource::Vim => Some("the vim keymap"),
2125                                                        KeybindSource::Base => Some("your base keymap"),
2126                                                        _ => {
2127                                                            log::error!("Unexpected override from the {} keymap", conflict.override_source.name());
2128                                                            None
2129                                                        }
2130                                                    }.map(|source| format!("This keybinding is overridden by the '{}' binding from {}.", binding.action().humanized_name, source))
2131                                                }).unwrap_or_else(|| "This binding is overridden.".to_string());
2132
2133                                                row.tooltip(Tooltip::text(context))},
2134                                        ),
2135                                )
2136                                .border_2()
2137                                .when(
2138                                    conflict.is_some_and(|conflict| {
2139                                        conflict.is_user_keybind_conflict()
2140                                    }),
2141                                    |row| row.bg(cx.theme().status().error_background),
2142                                )
2143                                .when(is_selected, |row| {
2144                                    row.border_color(cx.theme().colors().panel_focused_border)
2145                                })
2146                                .into_any_element()
2147                        }),
2148                    ),
2149            )
2150            .on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
2151                // This ensures that the menu is not dismissed in cases where scroll events
2152                // with a delta of zero are emitted
2153                if !event.delta.pixel_delta(px(1.)).y.is_zero() {
2154                    this.context_menu.take();
2155                    cx.notify();
2156                }
2157            }))
2158            .children(self.context_menu.as_ref().map(|(menu, position, _)| {
2159                deferred(
2160                    anchored()
2161                        .position(*position)
2162                        .anchor(gpui::Corner::TopLeft)
2163                        .child(menu.clone()),
2164                )
2165                .with_priority(1)
2166            }))
2167    }
2168}
2169
2170fn row_group_id(row_index: usize) -> SharedString {
2171    SharedString::new(format!("keymap-table-row-{}", row_index))
2172}
2173
2174fn base_button_style(row_index: usize, icon: IconName) -> IconButton {
2175    IconButton::new(("keymap-icon", row_index), icon)
2176        .shape(IconButtonShape::Square)
2177        .size(ButtonSize::Compact)
2178}
2179
2180#[derive(Debug, Clone, IntoElement)]
2181struct SyntaxHighlightedText {
2182    text: SharedString,
2183    language: Arc<Language>,
2184}
2185
2186impl SyntaxHighlightedText {
2187    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
2188        Self {
2189            text: text.into(),
2190            language,
2191        }
2192    }
2193}
2194
2195impl RenderOnce for SyntaxHighlightedText {
2196    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
2197        let text_style = window.text_style();
2198        let syntax_theme = cx.theme().syntax();
2199
2200        let text = self.text.clone();
2201
2202        let highlights = self
2203            .language
2204            .highlight_text(&text.as_ref().into(), 0..text.len());
2205        let mut runs = Vec::with_capacity(highlights.len());
2206        let mut offset = 0;
2207
2208        for (highlight_range, highlight_id) in highlights {
2209            // Add un-highlighted text before the current highlight
2210            if highlight_range.start > offset {
2211                runs.push(text_style.to_run(highlight_range.start - offset));
2212            }
2213
2214            let mut run_style = text_style.clone();
2215            if let Some(highlight_style) = highlight_id.style(syntax_theme) {
2216                run_style = run_style.highlight(highlight_style);
2217            }
2218            // add the highlighted range
2219            runs.push(run_style.to_run(highlight_range.len()));
2220            offset = highlight_range.end;
2221        }
2222
2223        // Add any remaining un-highlighted text
2224        if offset < text.len() {
2225            runs.push(text_style.to_run(text.len() - offset));
2226        }
2227
2228        StyledText::new(text).with_runs(runs)
2229    }
2230}
2231
2232#[derive(PartialEq)]
2233struct InputError {
2234    severity: Severity,
2235    content: SharedString,
2236}
2237
2238impl InputError {
2239    fn warning(message: impl Into<SharedString>) -> Self {
2240        Self {
2241            severity: Severity::Warning,
2242            content: message.into(),
2243        }
2244    }
2245
2246    fn error(message: anyhow::Error) -> Self {
2247        Self {
2248            severity: Severity::Error,
2249            content: message.to_string().into(),
2250        }
2251    }
2252}
2253
2254struct KeybindingEditorModal {
2255    creating: bool,
2256    editing_keybind: ProcessedBinding,
2257    editing_keybind_idx: usize,
2258    keybind_editor: Entity<KeystrokeInput>,
2259    context_editor: Entity<InputField>,
2260    action_editor: Option<Entity<InputField>>,
2261    action_arguments_editor: Option<Entity<ActionArgumentsEditor>>,
2262    action_name_to_static: HashMap<String, &'static str>,
2263    selected_action_name: Option<&'static str>,
2264    fs: Arc<dyn Fs>,
2265    error: Option<InputError>,
2266    keymap_editor: Entity<KeymapEditor>,
2267    workspace: WeakEntity<Workspace>,
2268    focus_state: KeybindingEditorModalFocusState,
2269}
2270
2271impl ModalView for KeybindingEditorModal {}
2272
2273impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
2274
2275impl Focusable for KeybindingEditorModal {
2276    fn focus_handle(&self, cx: &App) -> FocusHandle {
2277        if let Some(action_editor) = &self.action_editor {
2278            return action_editor.focus_handle(cx);
2279        }
2280        self.keybind_editor.focus_handle(cx)
2281    }
2282}
2283
2284impl KeybindingEditorModal {
2285    pub fn new(
2286        create: bool,
2287        editing_keybind: ProcessedBinding,
2288        editing_keybind_idx: usize,
2289        keymap_editor: Entity<KeymapEditor>,
2290        action_args_temp_dir: Option<&std::path::Path>,
2291        workspace: WeakEntity<Workspace>,
2292        fs: Arc<dyn Fs>,
2293        window: &mut Window,
2294        cx: &mut App,
2295    ) -> Self {
2296        let keybind_editor = cx
2297            .new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx));
2298
2299        let context_editor: Entity<InputField> = cx.new(|cx| {
2300            let input = InputField::new(window, cx, "Keybinding Context")
2301                .label("Edit Context")
2302                .label_size(LabelSize::Default);
2303
2304            if let Some(context) = editing_keybind
2305                .context()
2306                .and_then(KeybindContextString::local)
2307            {
2308                input.editor().update(cx, |editor, cx| {
2309                    editor.set_text(context.clone(), window, cx);
2310                });
2311            }
2312
2313            let editor_entity = input.editor().clone();
2314            let workspace = workspace.clone();
2315            cx.spawn(async move |_input_handle, cx| {
2316                let contexts = cx
2317                    .background_spawn(async { collect_contexts_from_assets() })
2318                    .await;
2319
2320                let language = load_keybind_context_language(workspace, cx).await;
2321                editor_entity.update(cx, |editor, cx| {
2322                    if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
2323                        buffer.update(cx, |buffer, cx| {
2324                            buffer.set_language(Some(language), cx);
2325                        });
2326                    }
2327                    editor.set_completion_provider(Some(std::rc::Rc::new(
2328                        KeyContextCompletionProvider { contexts },
2329                    )));
2330                });
2331            })
2332            .detach();
2333
2334            input
2335        });
2336
2337        let has_action_editor = create && editing_keybind.action().name == gpui::NoAction.name();
2338
2339        let (action_editor, action_name_to_static) = if has_action_editor {
2340            let actions: Vec<&'static str> = cx.all_action_names().to_vec();
2341
2342            let humanized_names: HashMap<&'static str, SharedString> = actions
2343                .iter()
2344                .map(|&name| (name, command_palette::humanize_action_name(name).into()))
2345                .collect();
2346
2347            let action_name_to_static: HashMap<String, &'static str> = actions
2348                .iter()
2349                .map(|&name| (name.to_string(), name))
2350                .collect();
2351
2352            let editor = cx.new(|cx| {
2353                let input = InputField::new(window, cx, "Type an action name")
2354                    .label("Action")
2355                    .label_size(LabelSize::Default);
2356
2357                input.editor().update(cx, |editor, _cx| {
2358                    editor.set_completion_provider(Some(std::rc::Rc::new(
2359                        ActionCompletionProvider::new(actions, humanized_names),
2360                    )));
2361                });
2362
2363                input
2364            });
2365
2366            (Some(editor), action_name_to_static)
2367        } else {
2368            (None, HashMap::default())
2369        };
2370
2371        let action_has_schema = editing_keybind.action().has_schema;
2372        let action_name_for_args = editing_keybind.action().name;
2373        let action_args = editing_keybind
2374            .action()
2375            .arguments
2376            .as_ref()
2377            .map(|args| args.text.clone());
2378
2379        let action_arguments_editor = action_has_schema.then(|| {
2380            cx.new(|cx| {
2381                ActionArgumentsEditor::new(
2382                    action_name_for_args,
2383                    action_args.clone(),
2384                    action_args_temp_dir,
2385                    workspace.clone(),
2386                    window,
2387                    cx,
2388                )
2389            })
2390        });
2391
2392        let focus_state = KeybindingEditorModalFocusState::new(
2393            action_editor.as_ref().map(|e| e.focus_handle(cx)),
2394            keybind_editor.focus_handle(cx),
2395            action_arguments_editor
2396                .as_ref()
2397                .map(|args_editor| args_editor.focus_handle(cx)),
2398            context_editor.focus_handle(cx),
2399        );
2400
2401        Self {
2402            creating: create,
2403            editing_keybind,
2404            editing_keybind_idx,
2405            fs,
2406            keybind_editor,
2407            context_editor,
2408            action_editor,
2409            action_arguments_editor,
2410            action_name_to_static,
2411            selected_action_name: None,
2412            error: None,
2413            keymap_editor,
2414            workspace,
2415            focus_state,
2416        }
2417    }
2418
2419    fn add_action_arguments_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2420        let Some(action_editor) = &self.action_editor else {
2421            return;
2422        };
2423
2424        let action_name_str = action_editor.read(cx).editor.read(cx).text(cx);
2425        let current_action = self.action_name_to_static.get(&action_name_str).copied();
2426
2427        if current_action == self.selected_action_name {
2428            return;
2429        }
2430
2431        self.selected_action_name = current_action;
2432
2433        let Some(action_name) = current_action else {
2434            if self.action_arguments_editor.is_some() {
2435                self.action_arguments_editor = None;
2436                self.rebuild_focus_state(cx);
2437                cx.notify();
2438            }
2439            return;
2440        };
2441
2442        let (action_has_schema, temp_dir) = {
2443            let keymap_editor = self.keymap_editor.read(cx);
2444            let has_schema = keymap_editor.actions_with_schemas.contains(action_name);
2445            let temp_dir = keymap_editor
2446                .action_args_temp_dir
2447                .as_ref()
2448                .map(|dir| dir.path().to_path_buf());
2449            (has_schema, temp_dir)
2450        };
2451
2452        let currently_has_editor = self.action_arguments_editor.is_some();
2453
2454        if action_has_schema && !currently_has_editor {
2455            let workspace = self.workspace.clone();
2456
2457            let new_editor = cx.new(|cx| {
2458                ActionArgumentsEditor::new(
2459                    action_name,
2460                    None,
2461                    temp_dir.as_deref(),
2462                    workspace,
2463                    window,
2464                    cx,
2465                )
2466            });
2467
2468            self.action_arguments_editor = Some(new_editor);
2469            self.rebuild_focus_state(cx);
2470            cx.notify();
2471        } else if !action_has_schema && currently_has_editor {
2472            self.action_arguments_editor = None;
2473            self.rebuild_focus_state(cx);
2474            cx.notify();
2475        }
2476    }
2477
2478    fn rebuild_focus_state(&mut self, cx: &App) {
2479        self.focus_state = KeybindingEditorModalFocusState::new(
2480            self.action_editor.as_ref().map(|e| e.focus_handle(cx)),
2481            self.keybind_editor.focus_handle(cx),
2482            self.action_arguments_editor
2483                .as_ref()
2484                .map(|args_editor| args_editor.focus_handle(cx)),
2485            self.context_editor.focus_handle(cx),
2486        );
2487    }
2488
2489    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
2490        if self
2491            .error
2492            .as_ref()
2493            .is_some_and(|old_error| old_error.severity == Severity::Warning && *old_error == error)
2494        {
2495            false
2496        } else {
2497            self.error = Some(error);
2498            cx.notify();
2499            true
2500        }
2501    }
2502
2503    fn get_selected_action_name(&self, cx: &App) -> anyhow::Result<&'static str> {
2504        if let Some(selector) = self.action_editor.as_ref() {
2505            let action_name_str = selector.read(cx).editor.read(cx).text(cx);
2506
2507            if action_name_str.is_empty() {
2508                anyhow::bail!("Action name is required");
2509            }
2510
2511            self.action_name_to_static
2512                .get(&action_name_str)
2513                .copied()
2514                .ok_or_else(|| anyhow::anyhow!("Action '{}' not found", action_name_str))
2515        } else {
2516            Ok(self.editing_keybind.action().name)
2517        }
2518    }
2519
2520    fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
2521        let action_name = self.get_selected_action_name(cx)?;
2522        let action_arguments = self
2523            .action_arguments_editor
2524            .as_ref()
2525            .map(|arguments_editor| arguments_editor.read(cx).editor.read(cx).text(cx))
2526            .filter(|args| !args.is_empty());
2527
2528        let value = action_arguments
2529            .as_ref()
2530            .map(|args| {
2531                serde_json::from_str(args).context("Failed to parse action arguments as JSON")
2532            })
2533            .transpose()?;
2534
2535        cx.build_action(action_name, value)
2536            .context("Failed to validate action arguments")?;
2537        Ok(action_arguments)
2538    }
2539
2540    fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<KeybindingKeystroke>> {
2541        let new_keystrokes = self
2542            .keybind_editor
2543            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
2544        anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
2545        Ok(new_keystrokes)
2546    }
2547
2548    fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
2549        let new_context = self
2550            .context_editor
2551            .read_with(cx, |input, cx| input.editor().read(cx).text(cx));
2552        let Some(context) = new_context.is_empty().not().then_some(new_context) else {
2553            return Ok(None);
2554        };
2555        gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
2556
2557        Ok(Some(context))
2558    }
2559
2560    fn save_or_display_error(&mut self, cx: &mut Context<Self>) {
2561        self.save(cx).map_err(|err| self.set_error(err, cx)).ok();
2562    }
2563
2564    fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
2565        let existing_keybind = self.editing_keybind.clone();
2566        let fs = self.fs.clone();
2567
2568        let mut new_keystrokes = self.validate_keystrokes(cx).map_err(InputError::error)?;
2569        new_keystrokes
2570            .iter_mut()
2571            .for_each(|ks| ks.remove_key_char());
2572
2573        let new_context = self.validate_context(cx).map_err(InputError::error)?;
2574        let new_action_args = self
2575            .validate_action_arguments(cx)
2576            .map_err(InputError::error)?;
2577
2578        let action_mapping = ActionMapping {
2579            keystrokes: Rc::from(new_keystrokes.as_slice()),
2580            context: new_context.map(SharedString::from),
2581        };
2582
2583        let conflicting_indices = self
2584            .keymap_editor
2585            .read(cx)
2586            .keybinding_conflict_state
2587            .conflicting_indices_for_mapping(
2588                &action_mapping,
2589                self.creating.not().then_some(self.editing_keybind_idx),
2590            );
2591
2592        conflicting_indices.map(|KeybindConflict {
2593            first_conflict_index,
2594            remaining_conflict_amount,
2595        }|
2596        {
2597            let conflicting_action_name = self
2598                .keymap_editor
2599                .read(cx)
2600                .keybindings
2601                .get(first_conflict_index)
2602                .map(|keybind| keybind.action().name);
2603
2604            let warning_message = match conflicting_action_name {
2605                Some(name) => {
2606                     if remaining_conflict_amount > 0 {
2607                        format!(
2608                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
2609                            name, remaining_conflict_amount
2610                        )
2611                    } else {
2612                        format!("Your keybind would conflict with the \"{}\" action", name)
2613                    }
2614                }
2615                None => {
2616                    log::info!(
2617                        "Could not find action in keybindings with index {}",
2618                        first_conflict_index
2619                    );
2620                    "Your keybind would conflict with other actions".to_string()
2621                }
2622            };
2623
2624            let warning = InputError::warning(warning_message);
2625            if self.error.as_ref().is_some_and(|old_error| *old_error == warning) {
2626                Ok(())
2627           } else {
2628                Err(warning)
2629            }
2630        }).unwrap_or(Ok(()))?;
2631
2632        let create = self.creating;
2633        let keyboard_mapper = cx.keyboard_mapper().clone();
2634
2635        let action_name = self
2636            .get_selected_action_name(cx)
2637            .map_err(InputError::error)?;
2638
2639        let humanized_action_name: SharedString =
2640            command_palette::humanize_action_name(action_name).into();
2641
2642        let action_information = ActionInformation::new(
2643            action_name,
2644            None,
2645            &HashSet::default(),
2646            cx.action_documentation(),
2647            &self.keymap_editor.read(cx).humanized_action_names,
2648        );
2649
2650        let keybind_for_save = if create {
2651            ProcessedBinding::Unmapped(action_information)
2652        } else {
2653            existing_keybind
2654        };
2655
2656        cx.spawn(async move |this, cx| {
2657            match save_keybinding_update(
2658                create,
2659                keybind_for_save,
2660                &action_mapping,
2661                new_action_args.as_deref(),
2662                &fs,
2663                keyboard_mapper.as_ref(),
2664            )
2665            .await
2666            {
2667                Ok(_) => {
2668                    this.update(cx, |this, cx| {
2669                        this.keymap_editor.update(cx, |keymap, cx| {
2670                            keymap.previous_edit = Some(PreviousEdit::Keybinding {
2671                                action_mapping,
2672                                action_name,
2673                                fallback: keymap.table_interaction_state.read(cx).scroll_offset(),
2674                            });
2675                            let status_toast = StatusToast::new(
2676                                format!("Saved edits to the {} action.", humanized_action_name),
2677                                cx,
2678                                move |this, _cx| {
2679                                    this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
2680                                        .dismiss_button(true)
2681                                    // .action("Undo", f) todo: wire the undo functionality
2682                                },
2683                            );
2684
2685                            this.workspace
2686                                .update(cx, |workspace, cx| {
2687                                    workspace.toggle_status_toast(status_toast, cx);
2688                                })
2689                                .log_err();
2690                        });
2691                        cx.emit(DismissEvent);
2692                    })
2693                    .ok();
2694                }
2695                Err(err) => {
2696                    this.update(cx, |this, cx| {
2697                        this.set_error(InputError::error(err), cx);
2698                    })
2699                    .log_err();
2700                }
2701            }
2702        })
2703        .detach();
2704
2705        Ok(())
2706    }
2707
2708    fn is_any_editor_showing_completions(&self, window: &Window, cx: &App) -> bool {
2709        let is_editor_showing_completions =
2710            |focus_handle: &FocusHandle, editor_entity: &Entity<Editor>| -> bool {
2711                focus_handle.contains_focused(window, cx)
2712                    && editor_entity.read_with(cx, |editor, _cx| {
2713                        editor
2714                            .context_menu()
2715                            .borrow()
2716                            .as_ref()
2717                            .is_some_and(|menu| menu.visible())
2718                    })
2719            };
2720
2721        self.action_editor.as_ref().is_some_and(|action_editor| {
2722            let focus_handle = action_editor.read(cx).focus_handle(cx);
2723            let editor_entity = action_editor.read(cx).editor();
2724            is_editor_showing_completions(&focus_handle, editor_entity)
2725        }) || {
2726            let focus_handle = self.context_editor.read(cx).focus_handle(cx);
2727            let editor_entity = self.context_editor.read(cx).editor();
2728            is_editor_showing_completions(&focus_handle, editor_entity)
2729        } || self
2730            .action_arguments_editor
2731            .as_ref()
2732            .is_some_and(|args_editor| {
2733                let focus_handle = args_editor.read(cx).focus_handle(cx);
2734                let editor_entity = &args_editor.read(cx).editor;
2735                is_editor_showing_completions(&focus_handle, editor_entity)
2736            })
2737    }
2738
2739    fn key_context(&self) -> KeyContext {
2740        let mut key_context = KeyContext::new_with_defaults();
2741        key_context.add("KeybindEditorModal");
2742        key_context
2743    }
2744
2745    fn key_context_internal(&self, window: &Window, cx: &App) -> KeyContext {
2746        let mut key_context = self.key_context();
2747
2748        if self.is_any_editor_showing_completions(window, cx) {
2749            key_context.add("showing_completions");
2750        }
2751
2752        key_context
2753    }
2754
2755    fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
2756        if self.is_any_editor_showing_completions(window, cx) {
2757            return;
2758        }
2759        self.focus_state.focus_next(window, cx);
2760    }
2761
2762    fn focus_prev(
2763        &mut self,
2764        _: &menu::SelectPrevious,
2765        window: &mut Window,
2766        cx: &mut Context<Self>,
2767    ) {
2768        if self.is_any_editor_showing_completions(window, cx) {
2769            return;
2770        }
2771        self.focus_state.focus_previous(window, cx);
2772    }
2773
2774    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
2775        self.save_or_display_error(cx);
2776    }
2777
2778    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
2779        cx.emit(DismissEvent);
2780    }
2781
2782    fn get_matching_bindings_count(&self, cx: &Context<Self>) -> usize {
2783        let current_keystrokes = self.keybind_editor.read(cx).keystrokes();
2784
2785        if current_keystrokes.is_empty() {
2786            return 0;
2787        }
2788
2789        self.keymap_editor
2790            .read(cx)
2791            .keybindings
2792            .iter()
2793            .enumerate()
2794            .filter(|(idx, binding)| {
2795                // Don't count the binding we're currently editing
2796                if !self.creating && *idx == self.editing_keybind_idx {
2797                    return false;
2798                }
2799
2800                binding.keystrokes().is_some_and(|keystrokes| {
2801                    keystrokes_match_exactly(keystrokes, current_keystrokes)
2802                })
2803            })
2804            .count()
2805    }
2806
2807    fn show_matching_bindings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2808        let keystrokes = self.keybind_editor.read(cx).keystrokes().to_vec();
2809
2810        self.keymap_editor.update(cx, |keymap_editor, cx| {
2811            keymap_editor.clear_action_query(window, cx)
2812        });
2813
2814        // Dismiss the modal
2815        cx.emit(DismissEvent);
2816
2817        // Update the keymap editor to show matching keystrokes
2818        self.keymap_editor.update(cx, |editor, cx| {
2819            editor.filter_state = FilterState::All;
2820            editor.search_mode = SearchMode::KeyStroke { exact_match: true };
2821            editor.keystroke_editor.update(cx, |keystroke_editor, cx| {
2822                keystroke_editor.set_keystrokes(keystrokes, cx);
2823            });
2824        });
2825    }
2826}
2827
2828impl Render for KeybindingEditorModal {
2829    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2830        self.add_action_arguments_input(window, cx);
2831
2832        let theme = cx.theme().colors();
2833        let matching_bindings_count = self.get_matching_bindings_count(cx);
2834        let key_context = self.key_context_internal(window, cx);
2835        let showing_completions = key_context.contains("showing_completions");
2836
2837        v_flex()
2838            .w(rems(34.))
2839            .elevation_3(cx)
2840            .key_context(key_context)
2841            .on_action(cx.listener(Self::confirm))
2842            .on_action(cx.listener(Self::cancel))
2843            .when(!showing_completions, |this| {
2844                this.on_action(cx.listener(Self::focus_next))
2845                    .on_action(cx.listener(Self::focus_prev))
2846            })
2847            .child(
2848                Modal::new("keybinding_editor_modal", None)
2849                    .header(
2850                        ModalHeader::new().child(
2851                            v_flex()
2852                                .w_full()
2853                                .pb_1p5()
2854                                .mb_1()
2855                                .gap_0p5()
2856                                .border_b_1()
2857                                .border_color(theme.border_variant)
2858                                .when(!self.creating, |this| {
2859                                    this.child(Label::new(
2860                                        self.editing_keybind.action().humanized_name.clone(),
2861                                    ))
2862                                    .when_some(
2863                                        self.editing_keybind.action().documentation,
2864                                        |this, docs| {
2865                                            this.child(
2866                                                Label::new(docs)
2867                                                    .size(LabelSize::Small)
2868                                                    .color(Color::Muted),
2869                                            )
2870                                        },
2871                                    )
2872                                })
2873                                .when(self.creating, |this| {
2874                                    this.child(Label::new("Create Keybinding"))
2875                                }),
2876                        ),
2877                    )
2878                    .section(
2879                        Section::new().child(
2880                            v_flex()
2881                                .gap_2p5()
2882                                .when_some(
2883                                    self.creating
2884                                        .then_some(())
2885                                        .and_then(|_| self.action_editor.as_ref()),
2886                                    |this, selector| this.child(selector.clone()),
2887                                )
2888                                .child(
2889                                    v_flex()
2890                                        .gap_1()
2891                                        .child(Label::new("Edit Keystroke"))
2892                                        .child(self.keybind_editor.clone())
2893                                        .child(h_flex().gap_px().when(
2894                                            matching_bindings_count > 0,
2895                                            |this| {
2896                                                let label = format!(
2897                                                    "There {} {} {} with the same keystrokes.",
2898                                                    if matching_bindings_count == 1 {
2899                                                        "is"
2900                                                    } else {
2901                                                        "are"
2902                                                    },
2903                                                    matching_bindings_count,
2904                                                    if matching_bindings_count == 1 {
2905                                                        "binding"
2906                                                    } else {
2907                                                        "bindings"
2908                                                    }
2909                                                );
2910
2911                                                this.child(
2912                                                    Label::new(label)
2913                                                        .size(LabelSize::Small)
2914                                                        .color(Color::Muted),
2915                                                )
2916                                                .child(
2917                                                    Button::new("show_matching", "View")
2918                                                        .label_size(LabelSize::Small)
2919                                                        .icon(IconName::ArrowUpRight)
2920                                                        .icon_color(Color::Muted)
2921                                                        .icon_size(IconSize::Small)
2922                                                        .on_click(cx.listener(
2923                                                            |this, _, window, cx| {
2924                                                                this.show_matching_bindings(
2925                                                                    window, cx,
2926                                                                );
2927                                                            },
2928                                                        )),
2929                                                )
2930                                            },
2931                                        )),
2932                                )
2933                                .when_some(self.action_arguments_editor.clone(), |this, editor| {
2934                                    this.child(
2935                                        v_flex()
2936                                            .gap_1()
2937                                            .child(Label::new("Edit Arguments"))
2938                                            .child(editor),
2939                                    )
2940                                })
2941                                .child(self.context_editor.clone())
2942                                .when_some(self.error.as_ref(), |this, error| {
2943                                    this.child(
2944                                        Banner::new()
2945                                            .severity(error.severity)
2946                                            .child(Label::new(error.content.clone())),
2947                                    )
2948                                }),
2949                        ),
2950                    )
2951                    .footer(
2952                        ModalFooter::new().end_slot(
2953                            h_flex()
2954                                .gap_1()
2955                                .child(
2956                                    Button::new("cancel", "Cancel")
2957                                        .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
2958                                )
2959                                .child(Button::new("save-btn", "Save").on_click(cx.listener(
2960                                    |this, _event, _window, cx| {
2961                                        this.save_or_display_error(cx);
2962                                    },
2963                                ))),
2964                        ),
2965                    ),
2966            )
2967    }
2968}
2969
2970struct KeybindingEditorModalFocusState {
2971    handles: Vec<FocusHandle>,
2972}
2973
2974impl KeybindingEditorModalFocusState {
2975    fn new(
2976        action_editor: Option<FocusHandle>,
2977        keystrokes: FocusHandle,
2978        action_arguments: Option<FocusHandle>,
2979        context: FocusHandle,
2980    ) -> Self {
2981        Self {
2982            handles: Vec::from_iter(
2983                [
2984                    action_editor,
2985                    Some(keystrokes),
2986                    action_arguments,
2987                    Some(context),
2988                ]
2989                .into_iter()
2990                .flatten(),
2991            ),
2992        }
2993    }
2994
2995    fn focused_index(&self, window: &Window, cx: &App) -> Option<i32> {
2996        self.handles
2997            .iter()
2998            .position(|handle| handle.contains_focused(window, cx))
2999            .map(|i| i as i32)
3000    }
3001
3002    fn focus_index(&self, mut index: i32, window: &mut Window, cx: &mut App) {
3003        if index < 0 {
3004            index = self.handles.len() as i32 - 1;
3005        }
3006        if index >= self.handles.len() as i32 {
3007            index = 0;
3008        }
3009        window.focus(&self.handles[index as usize], cx);
3010    }
3011
3012    fn focus_next(&self, window: &mut Window, cx: &mut App) {
3013        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3014            index + 1
3015        } else {
3016            0
3017        };
3018        self.focus_index(index_to_focus, window, cx);
3019    }
3020
3021    fn focus_previous(&self, window: &mut Window, cx: &mut App) {
3022        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3023            index - 1
3024        } else {
3025            self.handles.len() as i32 - 1
3026        };
3027        self.focus_index(index_to_focus, window, cx);
3028    }
3029}
3030
3031struct ActionArgumentsEditor {
3032    editor: Entity<Editor>,
3033    focus_handle: FocusHandle,
3034    is_loading: bool,
3035    /// See documentation in `KeymapEditor` for why a temp dir is needed.
3036    /// This field exists because the keymap editor temp dir creation may fail,
3037    /// and rather than implement a complicated retry mechanism, we simply
3038    /// fallback to trying to create a temporary directory in this editor on
3039    /// demand. Of note is that the TempDir struct will remove the directory
3040    /// when dropped.
3041    backup_temp_dir: Option<tempfile::TempDir>,
3042}
3043
3044impl Focusable for ActionArgumentsEditor {
3045    fn focus_handle(&self, _cx: &App) -> FocusHandle {
3046        self.focus_handle.clone()
3047    }
3048}
3049
3050impl ActionArgumentsEditor {
3051    fn new(
3052        action_name: &'static str,
3053        arguments: Option<SharedString>,
3054        temp_dir: Option<&std::path::Path>,
3055        workspace: WeakEntity<Workspace>,
3056        window: &mut Window,
3057        cx: &mut Context<Self>,
3058    ) -> Self {
3059        let focus_handle = cx.focus_handle();
3060        cx.on_focus_in(&focus_handle, window, |this, window, cx| {
3061            this.editor.focus_handle(cx).focus(window, cx);
3062        })
3063        .detach();
3064        let editor = cx.new(|cx| {
3065            let mut editor = Editor::auto_height_unbounded(1, window, cx);
3066            Self::set_editor_text(&mut editor, arguments.clone(), window, cx);
3067            editor.set_read_only(true);
3068            editor
3069        });
3070
3071        let temp_dir = temp_dir.map(|path| path.to_owned());
3072        cx.spawn_in(window, async move |this, cx| {
3073            let result = async {
3074                let (project, fs) = workspace.read_with(cx, |workspace, _cx| {
3075                    (
3076                        workspace.project().downgrade(),
3077                        workspace.app_state().fs.clone(),
3078                    )
3079                })?;
3080
3081                let file_name = json_schema_store::normalized_action_file_name(action_name);
3082
3083                let (buffer, backup_temp_dir) =
3084                    Self::create_temp_buffer(temp_dir, file_name.clone(), project.clone(), fs, cx)
3085                        .await
3086                        .context(concat!(
3087                            "Failed to create temporary buffer for action arguments. ",
3088                            "Auto-complete will not work"
3089                        ))?;
3090
3091                let editor = cx.new_window_entity(|window, cx| {
3092                    let multi_buffer = cx.new(|cx| editor::MultiBuffer::singleton(buffer, cx));
3093                    let mut editor = Editor::new(
3094                        EditorMode::Full {
3095                            scale_ui_elements_with_buffer_font_size: true,
3096                            show_active_line_background: false,
3097                            sizing_behavior: SizingBehavior::SizeByContent,
3098                        },
3099                        multi_buffer,
3100                        project.upgrade(),
3101                        window,
3102                        cx,
3103                    );
3104                    editor.set_searchable(false);
3105                    editor.disable_scrollbars_and_minimap(window, cx);
3106                    editor.set_show_edit_predictions(Some(false), window, cx);
3107                    editor.set_show_gutter(false, cx);
3108                    Self::set_editor_text(&mut editor, arguments, window, cx);
3109                    editor
3110                })?;
3111
3112                this.update_in(cx, |this, window, cx| {
3113                    if this.editor.focus_handle(cx).is_focused(window) {
3114                        editor.focus_handle(cx).focus(window, cx);
3115                    }
3116                    this.editor = editor;
3117                    this.backup_temp_dir = backup_temp_dir;
3118                    this.is_loading = false;
3119                })?;
3120
3121                anyhow::Ok(())
3122            }
3123            .await;
3124            if result.is_err() {
3125                let json_language = load_json_language(workspace.clone(), cx).await;
3126                this.update(cx, |this, cx| {
3127                    this.editor.update(cx, |editor, cx| {
3128                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
3129                            buffer.update(cx, |buffer, cx| {
3130                                buffer.set_language(Some(json_language.clone()), cx)
3131                            });
3132                        }
3133                    })
3134                    // .context("Failed to load JSON language for editing keybinding action arguments input")
3135                })
3136                .ok();
3137                this.update(cx, |this, _cx| {
3138                    this.is_loading = false;
3139                })
3140                .ok();
3141            }
3142            result
3143        })
3144        .detach_and_log_err(cx);
3145        Self {
3146            editor,
3147            focus_handle,
3148            is_loading: true,
3149            backup_temp_dir: None,
3150        }
3151    }
3152
3153    fn set_editor_text(
3154        editor: &mut Editor,
3155        arguments: Option<SharedString>,
3156        window: &mut Window,
3157        cx: &mut Context<Editor>,
3158    ) {
3159        if let Some(arguments) = arguments {
3160            editor.set_text(arguments, window, cx);
3161        } else {
3162            // TODO: default value from schema?
3163            editor.set_placeholder_text("Action Arguments", window, cx);
3164        }
3165    }
3166
3167    async fn create_temp_buffer(
3168        temp_dir: Option<std::path::PathBuf>,
3169        file_name: String,
3170        project: WeakEntity<Project>,
3171        fs: Arc<dyn Fs>,
3172        cx: &mut AsyncApp,
3173    ) -> anyhow::Result<(Entity<language::Buffer>, Option<tempfile::TempDir>)> {
3174        let (temp_file_path, temp_dir) = {
3175            let file_name = file_name.clone();
3176            async move {
3177                let temp_dir_backup = match temp_dir.as_ref() {
3178                    Some(_) => None,
3179                    None => {
3180                        let temp_dir = paths::temp_dir();
3181                        let sub_temp_dir = tempfile::Builder::new()
3182                            .tempdir_in(temp_dir)
3183                            .context("Failed to create temporary directory")?;
3184                        Some(sub_temp_dir)
3185                    }
3186                };
3187                let dir_path = temp_dir.as_deref().unwrap_or_else(|| {
3188                    temp_dir_backup
3189                        .as_ref()
3190                        .expect("created backup tempdir")
3191                        .path()
3192                });
3193                let path = dir_path.join(file_name);
3194                fs.create_file(
3195                    &path,
3196                    fs::CreateOptions {
3197                        ignore_if_exists: true,
3198                        overwrite: true,
3199                    },
3200                )
3201                .await
3202                .context("Failed to create temporary file")?;
3203                anyhow::Ok((path, temp_dir_backup))
3204            }
3205        }
3206        .await
3207        .context("Failed to create backing file")?;
3208
3209        project
3210            .update(cx, |project, cx| {
3211                project.open_local_buffer(temp_file_path, cx)
3212            })?
3213            .await
3214            .context("Failed to create buffer")
3215            .map(|buffer| (buffer, temp_dir))
3216    }
3217}
3218
3219impl Render for ActionArgumentsEditor {
3220    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3221        let settings = theme::ThemeSettings::get_global(cx);
3222        let colors = cx.theme().colors();
3223
3224        let border_color = if self.is_loading {
3225            colors.border_disabled
3226        } else if self.focus_handle.contains_focused(window, cx) {
3227            colors.border_focused
3228        } else {
3229            colors.border_variant
3230        };
3231
3232        let text_style = {
3233            TextStyleRefinement {
3234                font_size: Some(rems(0.875).into()),
3235                font_weight: Some(settings.buffer_font.weight),
3236                line_height: Some(relative(1.2)),
3237                color: self.is_loading.then_some(colors.text_disabled),
3238                ..Default::default()
3239            }
3240        };
3241
3242        self.editor
3243            .update(cx, |editor, _| editor.set_text_style_refinement(text_style));
3244
3245        h_flex()
3246            .min_h_8()
3247            .min_w_48()
3248            .px_2()
3249            .flex_grow()
3250            .rounded_md()
3251            .bg(cx.theme().colors().editor_background)
3252            .border_1()
3253            .border_color(border_color)
3254            .track_focus(&self.focus_handle)
3255            .child(self.editor.clone())
3256    }
3257}
3258
3259struct KeyContextCompletionProvider {
3260    contexts: Vec<SharedString>,
3261}
3262
3263impl CompletionProvider for KeyContextCompletionProvider {
3264    fn completions(
3265        &self,
3266        _excerpt_id: editor::ExcerptId,
3267        buffer: &Entity<language::Buffer>,
3268        buffer_position: language::Anchor,
3269        _trigger: editor::CompletionContext,
3270        _window: &mut Window,
3271        cx: &mut Context<Editor>,
3272    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
3273        let buffer = buffer.read(cx);
3274        let mut count_back = 0;
3275        for char in buffer.reversed_chars_at(buffer_position) {
3276            if char.is_ascii_alphanumeric() || char == '_' {
3277                count_back += 1;
3278            } else {
3279                break;
3280            }
3281        }
3282        let start_anchor =
3283            buffer.anchor_before(buffer_position.to_offset(buffer).saturating_sub(count_back));
3284        let replace_range = start_anchor..buffer_position;
3285        gpui::Task::ready(Ok(vec![project::CompletionResponse {
3286            completions: self
3287                .contexts
3288                .iter()
3289                .map(|context| project::Completion {
3290                    replace_range: replace_range.clone(),
3291                    label: language::CodeLabel::plain(context.to_string(), None),
3292                    new_text: context.to_string(),
3293                    documentation: None,
3294                    source: project::CompletionSource::Custom,
3295                    icon_path: None,
3296                    match_start: None,
3297                    snippet_deduplication_key: None,
3298                    insert_text_mode: None,
3299                    confirm: None,
3300                })
3301                .collect(),
3302            display_options: CompletionDisplayOptions::default(),
3303            is_incomplete: false,
3304        }]))
3305    }
3306
3307    fn is_completion_trigger(
3308        &self,
3309        _buffer: &Entity<language::Buffer>,
3310        _position: language::Anchor,
3311        text: &str,
3312        _trigger_in_words: bool,
3313        _cx: &mut Context<Editor>,
3314    ) -> bool {
3315        text.chars()
3316            .last()
3317            .is_some_and(|last_char| last_char.is_ascii_alphanumeric() || last_char == '_')
3318    }
3319}
3320
3321async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
3322    let json_language_task = workspace
3323        .read_with(cx, |workspace, cx| {
3324            workspace
3325                .project()
3326                .read(cx)
3327                .languages()
3328                .language_for_name("JSON")
3329        })
3330        .context("Failed to load JSON language")
3331        .log_err();
3332    let json_language = match json_language_task {
3333        Some(task) => task.await.context("Failed to load JSON language").log_err(),
3334        None => None,
3335    };
3336    json_language.unwrap_or_else(|| {
3337        Arc::new(Language::new(
3338            LanguageConfig {
3339                name: "JSON".into(),
3340                ..Default::default()
3341            },
3342            Some(tree_sitter_json::LANGUAGE.into()),
3343        ))
3344    })
3345}
3346
3347async fn load_keybind_context_language(
3348    workspace: WeakEntity<Workspace>,
3349    cx: &mut AsyncApp,
3350) -> Arc<Language> {
3351    let language_task = workspace
3352        .read_with(cx, |workspace, cx| {
3353            workspace
3354                .project()
3355                .read(cx)
3356                .languages()
3357                .language_for_name("Zed Keybind Context")
3358        })
3359        .context("Failed to load Zed Keybind Context language")
3360        .log_err();
3361    let language = match language_task {
3362        Some(task) => task
3363            .await
3364            .context("Failed to load Zed Keybind Context language")
3365            .log_err(),
3366        None => None,
3367    };
3368    language.unwrap_or_else(|| {
3369        Arc::new(Language::new(
3370            LanguageConfig {
3371                name: "Zed Keybind Context".into(),
3372                ..Default::default()
3373            },
3374            Some(tree_sitter_rust::LANGUAGE.into()),
3375        ))
3376    })
3377}
3378
3379async fn save_keybinding_update(
3380    create: bool,
3381    existing: ProcessedBinding,
3382    action_mapping: &ActionMapping,
3383    new_args: Option<&str>,
3384    fs: &Arc<dyn Fs>,
3385    keyboard_mapper: &dyn PlatformKeyboardMapper,
3386) -> anyhow::Result<()> {
3387    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3388        .await
3389        .context("Failed to load keymap file")?;
3390
3391    let tab_size = infer_json_indent_size(&keymap_contents);
3392
3393    let existing_keystrokes = existing.keystrokes().unwrap_or_default();
3394    let existing_context = existing.context().and_then(KeybindContextString::local_str);
3395    let existing_args = existing
3396        .action()
3397        .arguments
3398        .as_ref()
3399        .map(|args| args.text.as_ref());
3400
3401    let target = settings::KeybindUpdateTarget {
3402        context: existing_context,
3403        keystrokes: existing_keystrokes,
3404        action_name: existing.action().name,
3405        action_arguments: existing_args,
3406    };
3407
3408    let source = settings::KeybindUpdateTarget {
3409        context: action_mapping.context.as_ref().map(|a| &***a),
3410        keystrokes: &action_mapping.keystrokes,
3411        action_name: existing.action().name,
3412        action_arguments: new_args,
3413    };
3414
3415    let operation = if !create {
3416        settings::KeybindUpdateOperation::Replace {
3417            target,
3418            target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3419            source,
3420        }
3421    } else {
3422        settings::KeybindUpdateOperation::Add {
3423            source,
3424            from: Some(target),
3425        }
3426    };
3427
3428    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3429
3430    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3431        operation,
3432        keymap_contents,
3433        tab_size,
3434        keyboard_mapper,
3435    )
3436    .map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?;
3437    fs.write(
3438        paths::keymap_file().as_path(),
3439        updated_keymap_contents.as_bytes(),
3440    )
3441    .await
3442    .context("Failed to write keymap file")?;
3443
3444    telemetry::event!(
3445        "Keybinding Updated",
3446        new_keybinding = new_keybinding,
3447        removed_keybinding = removed_keybinding,
3448        source = source
3449    );
3450    Ok(())
3451}
3452
3453async fn remove_keybinding(
3454    existing: ProcessedBinding,
3455    fs: &Arc<dyn Fs>,
3456    keyboard_mapper: &dyn PlatformKeyboardMapper,
3457) -> anyhow::Result<()> {
3458    let Some(keystrokes) = existing.keystrokes() else {
3459        anyhow::bail!("Cannot remove a keybinding that does not exist");
3460    };
3461    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3462        .await
3463        .context("Failed to load keymap file")?;
3464    let tab_size = infer_json_indent_size(&keymap_contents);
3465
3466    let operation = settings::KeybindUpdateOperation::Remove {
3467        target: settings::KeybindUpdateTarget {
3468            context: existing.context().and_then(KeybindContextString::local_str),
3469            keystrokes,
3470            action_name: existing.action().name,
3471            action_arguments: existing
3472                .action()
3473                .arguments
3474                .as_ref()
3475                .map(|arguments| arguments.text.as_ref()),
3476        },
3477        target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3478    };
3479
3480    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3481    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3482        operation,
3483        keymap_contents,
3484        tab_size,
3485        keyboard_mapper,
3486    )
3487    .context("Failed to update keybinding")?;
3488    fs.write(
3489        paths::keymap_file().as_path(),
3490        updated_keymap_contents.as_bytes(),
3491    )
3492    .await
3493    .context("Failed to write keymap file")?;
3494
3495    telemetry::event!(
3496        "Keybinding Removed",
3497        new_keybinding = new_keybinding,
3498        removed_keybinding = removed_keybinding,
3499        source = source
3500    );
3501    Ok(())
3502}
3503
3504fn collect_contexts_from_assets() -> Vec<SharedString> {
3505    let mut keymap_assets = vec![
3506        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
3507        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
3508    ];
3509    keymap_assets.extend(
3510        BaseKeymap::OPTIONS
3511            .iter()
3512            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
3513            .map(util::asset_str::<SettingsAssets>),
3514    );
3515
3516    let mut contexts = HashSet::default();
3517
3518    for keymap_asset in keymap_assets {
3519        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
3520            continue;
3521        };
3522
3523        for section in keymap.sections() {
3524            let context_expr = &section.context;
3525            let mut queue = Vec::new();
3526            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
3527                continue;
3528            };
3529
3530            queue.push(root_context);
3531            while let Some(context) = queue.pop() {
3532                match context {
3533                    Identifier(ident) => {
3534                        contexts.insert(ident);
3535                    }
3536                    Equal(ident_a, ident_b) => {
3537                        contexts.insert(ident_a);
3538                        contexts.insert(ident_b);
3539                    }
3540                    NotEqual(ident_a, ident_b) => {
3541                        contexts.insert(ident_a);
3542                        contexts.insert(ident_b);
3543                    }
3544                    Descendant(ctx_a, ctx_b) => {
3545                        queue.push(*ctx_a);
3546                        queue.push(*ctx_b);
3547                    }
3548                    Not(ctx) => {
3549                        queue.push(*ctx);
3550                    }
3551                    And(ctx_a, ctx_b) => {
3552                        queue.push(*ctx_a);
3553                        queue.push(*ctx_b);
3554                    }
3555                    Or(ctx_a, ctx_b) => {
3556                        queue.push(*ctx_a);
3557                        queue.push(*ctx_b);
3558                    }
3559                }
3560            }
3561        }
3562    }
3563
3564    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
3565    contexts.sort();
3566
3567    contexts
3568}
3569
3570fn normalized_ctx_eq(
3571    a: &gpui::KeyBindingContextPredicate,
3572    b: &gpui::KeyBindingContextPredicate,
3573) -> bool {
3574    use gpui::KeyBindingContextPredicate::*;
3575    return match (a, b) {
3576        (Identifier(_), Identifier(_)) => a == b,
3577        (Equal(a_left, a_right), Equal(b_left, b_right)) => {
3578            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3579        }
3580        (NotEqual(a_left, a_right), NotEqual(b_left, b_right)) => {
3581            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3582        }
3583        (Descendant(a_parent, a_child), Descendant(b_parent, b_child)) => {
3584            normalized_ctx_eq(a_parent, b_parent) && normalized_ctx_eq(a_child, b_child)
3585        }
3586        (Not(a_expr), Not(b_expr)) => normalized_ctx_eq(a_expr, b_expr),
3587        // Handle double negation: !(!a) == a
3588        (Not(a_expr), b) if matches!(a_expr.as_ref(), Not(_)) => {
3589            let Not(a_inner) = a_expr.as_ref() else {
3590                unreachable!();
3591            };
3592            normalized_ctx_eq(b, a_inner)
3593        }
3594        (a, Not(b_expr)) if matches!(b_expr.as_ref(), Not(_)) => {
3595            let Not(b_inner) = b_expr.as_ref() else {
3596                unreachable!();
3597            };
3598            normalized_ctx_eq(a, b_inner)
3599        }
3600        (And(a_left, a_right), And(b_left, b_right))
3601            if matches!(a_left.as_ref(), And(_, _))
3602                || matches!(a_right.as_ref(), And(_, _))
3603                || matches!(b_left.as_ref(), And(_, _))
3604                || matches!(b_right.as_ref(), And(_, _)) =>
3605        {
3606            let mut a_operands = Vec::new();
3607            flatten_and(a, &mut a_operands);
3608            let mut b_operands = Vec::new();
3609            flatten_and(b, &mut b_operands);
3610            compare_operand_sets(&a_operands, &b_operands)
3611        }
3612        (And(a_left, a_right), And(b_left, b_right)) => {
3613            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3614                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3615        }
3616        (Or(a_left, a_right), Or(b_left, b_right))
3617            if matches!(a_left.as_ref(), Or(_, _))
3618                || matches!(a_right.as_ref(), Or(_, _))
3619                || matches!(b_left.as_ref(), Or(_, _))
3620                || matches!(b_right.as_ref(), Or(_, _)) =>
3621        {
3622            let mut a_operands = Vec::new();
3623            flatten_or(a, &mut a_operands);
3624            let mut b_operands = Vec::new();
3625            flatten_or(b, &mut b_operands);
3626            compare_operand_sets(&a_operands, &b_operands)
3627        }
3628        (Or(a_left, a_right), Or(b_left, b_right)) => {
3629            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3630                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3631        }
3632        _ => false,
3633    };
3634
3635    fn flatten_and<'a>(
3636        pred: &'a gpui::KeyBindingContextPredicate,
3637        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3638    ) {
3639        use gpui::KeyBindingContextPredicate::*;
3640        match pred {
3641            And(left, right) => {
3642                flatten_and(left, operands);
3643                flatten_and(right, operands);
3644            }
3645            _ => operands.push(pred),
3646        }
3647    }
3648
3649    fn flatten_or<'a>(
3650        pred: &'a gpui::KeyBindingContextPredicate,
3651        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3652    ) {
3653        use gpui::KeyBindingContextPredicate::*;
3654        match pred {
3655            Or(left, right) => {
3656                flatten_or(left, operands);
3657                flatten_or(right, operands);
3658            }
3659            _ => operands.push(pred),
3660        }
3661    }
3662
3663    fn compare_operand_sets(
3664        a: &[&gpui::KeyBindingContextPredicate],
3665        b: &[&gpui::KeyBindingContextPredicate],
3666    ) -> bool {
3667        if a.len() != b.len() {
3668            return false;
3669        }
3670
3671        // For each operand in a, find a matching operand in b
3672        let mut b_matched = vec![false; b.len()];
3673        for a_operand in a {
3674            let mut found = false;
3675            for (b_idx, b_operand) in b.iter().enumerate() {
3676                if !b_matched[b_idx] && normalized_ctx_eq(a_operand, b_operand) {
3677                    b_matched[b_idx] = true;
3678                    found = true;
3679                    break;
3680                }
3681            }
3682            if !found {
3683                return false;
3684            }
3685        }
3686
3687        true
3688    }
3689}
3690
3691impl SerializableItem for KeymapEditor {
3692    fn serialized_item_kind() -> &'static str {
3693        "KeymapEditor"
3694    }
3695
3696    fn cleanup(
3697        workspace_id: workspace::WorkspaceId,
3698        alive_items: Vec<workspace::ItemId>,
3699        _window: &mut Window,
3700        cx: &mut App,
3701    ) -> gpui::Task<gpui::Result<()>> {
3702        workspace::delete_unloaded_items(
3703            alive_items,
3704            workspace_id,
3705            "keybinding_editors",
3706            &KEYBINDING_EDITORS,
3707            cx,
3708        )
3709    }
3710
3711    fn deserialize(
3712        _project: Entity<project::Project>,
3713        workspace: WeakEntity<Workspace>,
3714        workspace_id: workspace::WorkspaceId,
3715        item_id: workspace::ItemId,
3716        window: &mut Window,
3717        cx: &mut App,
3718    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
3719        window.spawn(cx, async move |cx| {
3720            if KEYBINDING_EDITORS
3721                .get_keybinding_editor(item_id, workspace_id)?
3722                .is_some()
3723            {
3724                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
3725            } else {
3726                Err(anyhow!("No keybinding editor to deserialize"))
3727            }
3728        })
3729    }
3730
3731    fn serialize(
3732        &mut self,
3733        workspace: &mut Workspace,
3734        item_id: workspace::ItemId,
3735        _closing: bool,
3736        _window: &mut Window,
3737        cx: &mut ui::Context<Self>,
3738    ) -> Option<gpui::Task<gpui::Result<()>>> {
3739        let workspace_id = workspace.database_id()?;
3740        Some(cx.background_spawn(async move {
3741            KEYBINDING_EDITORS
3742                .save_keybinding_editor(item_id, workspace_id)
3743                .await
3744        }))
3745    }
3746
3747    fn should_serialize(&self, _event: &Self::Event) -> bool {
3748        false
3749    }
3750}
3751
3752mod persistence {
3753    use db::{query, sqlez::domain::Domain, sqlez_macros::sql};
3754    use workspace::WorkspaceDb;
3755
3756    pub struct KeybindingEditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection);
3757
3758    impl Domain for KeybindingEditorDb {
3759        const NAME: &str = stringify!(KeybindingEditorDb);
3760
3761        const MIGRATIONS: &[&str] = &[sql!(
3762                CREATE TABLE keybinding_editors (
3763                    workspace_id INTEGER,
3764                    item_id INTEGER UNIQUE,
3765
3766                    PRIMARY KEY(workspace_id, item_id),
3767                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
3768                    ON DELETE CASCADE
3769                ) STRICT;
3770        )];
3771    }
3772
3773    db::static_connection!(KEYBINDING_EDITORS, KeybindingEditorDb, [WorkspaceDb]);
3774
3775    impl KeybindingEditorDb {
3776        query! {
3777            pub async fn save_keybinding_editor(
3778                item_id: workspace::ItemId,
3779                workspace_id: workspace::WorkspaceId
3780            ) -> Result<()> {
3781                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
3782                VALUES (?, ?)
3783            }
3784        }
3785
3786        query! {
3787            pub fn get_keybinding_editor(
3788                item_id: workspace::ItemId,
3789                workspace_id: workspace::WorkspaceId
3790            ) -> Result<Option<workspace::ItemId>> {
3791                SELECT item_id
3792                FROM keybinding_editors
3793                WHERE item_id = ? AND workspace_id = ?
3794            }
3795        }
3796    }
3797}
3798
3799#[cfg(test)]
3800mod tests {
3801    use super::*;
3802
3803    #[test]
3804    fn normalized_ctx_cmp() {
3805        #[track_caller]
3806        fn cmp(a: &str, b: &str) -> bool {
3807            let a = gpui::KeyBindingContextPredicate::parse(a)
3808                .expect("Failed to parse keybinding context a");
3809            let b = gpui::KeyBindingContextPredicate::parse(b)
3810                .expect("Failed to parse keybinding context b");
3811            normalized_ctx_eq(&a, &b)
3812        }
3813
3814        // Basic equality - identical expressions
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 != b", "a != b"));
3819        assert!(cmp("a > b", "a > b"));
3820        assert!(cmp("!a", "!a"));
3821
3822        // AND operator - associative/commutative
3823        assert!(cmp("a && b", "b && a"));
3824        assert!(cmp("a && b && c", "c && b && a"));
3825        assert!(cmp("a && b && c", "b && a && c"));
3826        assert!(cmp("a && b && c && d", "d && c && b && a"));
3827
3828        // OR operator - associative/commutative
3829        assert!(cmp("a || b", "b || a"));
3830        assert!(cmp("a || b || c", "c || b || a"));
3831        assert!(cmp("a || b || c", "b || a || c"));
3832        assert!(cmp("a || b || c || d", "d || c || b || a"));
3833
3834        // Equality operator - associative/commutative
3835        assert!(cmp("a == b", "b == a"));
3836        assert!(cmp("x == y", "y == x"));
3837
3838        // Inequality operator - associative/commutative
3839        assert!(cmp("a != b", "b != a"));
3840        assert!(cmp("x != y", "y != x"));
3841
3842        // Complex nested expressions with associative operators
3843        assert!(cmp("(a && b) || c", "c || (a && b)"));
3844        assert!(cmp("(a && b) || c", "c || (b && a)"));
3845        assert!(cmp("(a || b) && c", "c && (a || b)"));
3846        assert!(cmp("(a || b) && c", "c && (b || a)"));
3847        assert!(cmp("(a && b) || (c && d)", "(c && d) || (a && b)"));
3848        assert!(cmp("(a && b) || (c && d)", "(d && c) || (b && a)"));
3849
3850        // Multiple levels of nesting
3851        assert!(cmp("((a && b) || c) && d", "d && ((a && b) || c)"));
3852        assert!(cmp("((a && b) || c) && d", "d && (c || (b && a))"));
3853        assert!(cmp("a && (b || (c && d))", "(b || (c && d)) && a"));
3854        assert!(cmp("a && (b || (c && d))", "(b || (d && c)) && a"));
3855
3856        // Negation with associative operators
3857        assert!(cmp("!a && b", "b && !a"));
3858        assert!(cmp("!a || b", "b || !a"));
3859        assert!(cmp("!(a && b) || c", "c || !(a && b)"));
3860        assert!(cmp("!(a && b) || c", "c || !(b && a)"));
3861
3862        // Descendant operator (>) - NOT associative/commutative
3863        assert!(cmp("a > b", "a > b"));
3864        assert!(!cmp("a > b", "b > a"));
3865        assert!(!cmp("a > b > c", "c > b > a"));
3866        assert!(!cmp("a > b > c", "a > c > b"));
3867
3868        // Mixed operators with descendant
3869        assert!(cmp("(a > b) && c", "c && (a > b)"));
3870        assert!(!cmp("(a > b) && c", "c && (b > a)"));
3871        assert!(cmp("(a > b) || (c > d)", "(c > d) || (a > b)"));
3872        assert!(!cmp("(a > b) || (c > d)", "(b > a) || (d > c)"));
3873
3874        // Negative cases - different operators
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        assert!(!cmp("a == b", "a && b"));
3880        assert!(!cmp("a != b", "a || b"));
3881
3882        // Negative cases - different operands
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", "c || d"));
3887        assert!(!cmp("a == b", "a == c"));
3888        assert!(!cmp("a != b", "a != c"));
3889        assert!(!cmp("a > b", "a > c"));
3890        assert!(!cmp("a > b", "c > b"));
3891
3892        // Negative cases - with negation
3893        assert!(!cmp("!a", "a"));
3894        assert!(!cmp("!a && b", "a && b"));
3895        assert!(!cmp("!(a && b)", "a && b"));
3896        assert!(!cmp("!a || b", "a || b"));
3897        assert!(!cmp("!(a || b)", "a || b"));
3898
3899        // Negative cases - complex expressions
3900        assert!(!cmp("(a && b) || c", "(a || b) && c"));
3901        assert!(!cmp("a && (b || c)", "a || (b && c)"));
3902        assert!(!cmp("(a && b) || (c && d)", "(a || b) && (c || d)"));
3903        assert!(!cmp("a > b && c", "a && b > c"));
3904
3905        // Edge cases - multiple same operands
3906        assert!(cmp("a && a", "a && a"));
3907        assert!(cmp("a || a", "a || a"));
3908        assert!(cmp("a && a && b", "b && a && a"));
3909        assert!(cmp("a || a || b", "b || a || a"));
3910
3911        // Edge cases - deeply nested
3912        assert!(cmp(
3913            "((a && b) || (c && d)) && ((e || f) && g)",
3914            "((e || f) && g) && ((c && d) || (a && b))"
3915        ));
3916        assert!(cmp(
3917            "((a && b) || (c && d)) && ((e || f) && g)",
3918            "(g && (f || e)) && ((d && c) || (b && a))"
3919        ));
3920
3921        // Edge cases - repeated patterns
3922        assert!(cmp("(a && b) || (a && b)", "(b && a) || (b && a)"));
3923        assert!(cmp("(a || b) && (a || b)", "(b || a) && (b || a)"));
3924
3925        // Negative cases - subtle differences
3926        assert!(!cmp("a && b && c", "a && b"));
3927        assert!(!cmp("a || b || c", "a || b"));
3928        assert!(!cmp("(a && b) || c", "a && (b || c)"));
3929
3930        // a > b > c is not the same as a > c, should not be equal
3931        assert!(!cmp("a > b > c", "a > c"));
3932
3933        // Double negation with complex expressions
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", "a && b"));
3938        assert!(cmp("!(!a) || b", "a || b"));
3939        assert!(cmp("!(!(a && b)) || c", "(a && b) || c"));
3940        assert!(cmp("!(!(a && b)) || c", "(b && a) || c"));
3941        assert!(cmp("!(!a)", "a"));
3942        assert!(cmp("a", "!(!a)"));
3943        assert!(cmp("!(!(!a))", "!a"));
3944        assert!(cmp("!(!(!(!a)))", "a"));
3945    }
3946}