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
2322                    .update(cx, |editor, cx| {
2323                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
2324                            buffer.update(cx, |buffer, cx| {
2325                                buffer.set_language(Some(language), cx);
2326                            });
2327                        }
2328                        editor.set_completion_provider(Some(std::rc::Rc::new(
2329                            KeyContextCompletionProvider { contexts },
2330                        )));
2331                    })
2332                    .context("Failed to load completions for keybinding context")
2333            })
2334            .detach_and_log_err(cx);
2335
2336            input
2337        });
2338
2339        let has_action_editor = create && editing_keybind.action().name == gpui::NoAction.name();
2340
2341        let (action_editor, action_name_to_static) = if has_action_editor {
2342            let actions: Vec<&'static str> = cx.all_action_names().to_vec();
2343
2344            let humanized_names: HashMap<&'static str, SharedString> = actions
2345                .iter()
2346                .map(|&name| (name, command_palette::humanize_action_name(name).into()))
2347                .collect();
2348
2349            let action_name_to_static: HashMap<String, &'static str> = actions
2350                .iter()
2351                .map(|&name| (name.to_string(), name))
2352                .collect();
2353
2354            let editor = cx.new(|cx| {
2355                let input = InputField::new(window, cx, "Type an action name")
2356                    .label("Action")
2357                    .label_size(LabelSize::Default);
2358
2359                input.editor().update(cx, |editor, _cx| {
2360                    editor.set_completion_provider(Some(std::rc::Rc::new(
2361                        ActionCompletionProvider::new(actions, humanized_names),
2362                    )));
2363                });
2364
2365                input
2366            });
2367
2368            (Some(editor), action_name_to_static)
2369        } else {
2370            (None, HashMap::default())
2371        };
2372
2373        let action_has_schema = editing_keybind.action().has_schema;
2374        let action_name_for_args = editing_keybind.action().name;
2375        let action_args = editing_keybind
2376            .action()
2377            .arguments
2378            .as_ref()
2379            .map(|args| args.text.clone());
2380
2381        let action_arguments_editor = action_has_schema.then(|| {
2382            cx.new(|cx| {
2383                ActionArgumentsEditor::new(
2384                    action_name_for_args,
2385                    action_args.clone(),
2386                    action_args_temp_dir,
2387                    workspace.clone(),
2388                    window,
2389                    cx,
2390                )
2391            })
2392        });
2393
2394        let focus_state = KeybindingEditorModalFocusState::new(
2395            action_editor.as_ref().map(|e| e.focus_handle(cx)),
2396            keybind_editor.focus_handle(cx),
2397            action_arguments_editor
2398                .as_ref()
2399                .map(|args_editor| args_editor.focus_handle(cx)),
2400            context_editor.focus_handle(cx),
2401        );
2402
2403        Self {
2404            creating: create,
2405            editing_keybind,
2406            editing_keybind_idx,
2407            fs,
2408            keybind_editor,
2409            context_editor,
2410            action_editor,
2411            action_arguments_editor,
2412            action_name_to_static,
2413            selected_action_name: None,
2414            error: None,
2415            keymap_editor,
2416            workspace,
2417            focus_state,
2418        }
2419    }
2420
2421    fn add_action_arguments_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2422        let Some(action_editor) = &self.action_editor else {
2423            return;
2424        };
2425
2426        let action_name_str = action_editor.read(cx).editor.read(cx).text(cx);
2427        let current_action = self.action_name_to_static.get(&action_name_str).copied();
2428
2429        if current_action == self.selected_action_name {
2430            return;
2431        }
2432
2433        self.selected_action_name = current_action;
2434
2435        let Some(action_name) = current_action else {
2436            if self.action_arguments_editor.is_some() {
2437                self.action_arguments_editor = None;
2438                self.rebuild_focus_state(cx);
2439                cx.notify();
2440            }
2441            return;
2442        };
2443
2444        let (action_has_schema, temp_dir) = {
2445            let keymap_editor = self.keymap_editor.read(cx);
2446            let has_schema = keymap_editor.actions_with_schemas.contains(action_name);
2447            let temp_dir = keymap_editor
2448                .action_args_temp_dir
2449                .as_ref()
2450                .map(|dir| dir.path().to_path_buf());
2451            (has_schema, temp_dir)
2452        };
2453
2454        let currently_has_editor = self.action_arguments_editor.is_some();
2455
2456        if action_has_schema && !currently_has_editor {
2457            let workspace = self.workspace.clone();
2458
2459            let new_editor = cx.new(|cx| {
2460                ActionArgumentsEditor::new(
2461                    action_name,
2462                    None,
2463                    temp_dir.as_deref(),
2464                    workspace,
2465                    window,
2466                    cx,
2467                )
2468            });
2469
2470            self.action_arguments_editor = Some(new_editor);
2471            self.rebuild_focus_state(cx);
2472            cx.notify();
2473        } else if !action_has_schema && currently_has_editor {
2474            self.action_arguments_editor = None;
2475            self.rebuild_focus_state(cx);
2476            cx.notify();
2477        }
2478    }
2479
2480    fn rebuild_focus_state(&mut self, cx: &App) {
2481        self.focus_state = KeybindingEditorModalFocusState::new(
2482            self.action_editor.as_ref().map(|e| e.focus_handle(cx)),
2483            self.keybind_editor.focus_handle(cx),
2484            self.action_arguments_editor
2485                .as_ref()
2486                .map(|args_editor| args_editor.focus_handle(cx)),
2487            self.context_editor.focus_handle(cx),
2488        );
2489    }
2490
2491    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
2492        if self
2493            .error
2494            .as_ref()
2495            .is_some_and(|old_error| old_error.severity == Severity::Warning && *old_error == error)
2496        {
2497            false
2498        } else {
2499            self.error = Some(error);
2500            cx.notify();
2501            true
2502        }
2503    }
2504
2505    fn get_selected_action_name(&self, cx: &App) -> anyhow::Result<&'static str> {
2506        if let Some(selector) = self.action_editor.as_ref() {
2507            let action_name_str = selector.read(cx).editor.read(cx).text(cx);
2508
2509            if action_name_str.is_empty() {
2510                anyhow::bail!("Action name is required");
2511            }
2512
2513            self.action_name_to_static
2514                .get(&action_name_str)
2515                .copied()
2516                .ok_or_else(|| anyhow::anyhow!("Action '{}' not found", action_name_str))
2517        } else {
2518            Ok(self.editing_keybind.action().name)
2519        }
2520    }
2521
2522    fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
2523        let action_name = self.get_selected_action_name(cx)?;
2524        let action_arguments = self
2525            .action_arguments_editor
2526            .as_ref()
2527            .map(|arguments_editor| arguments_editor.read(cx).editor.read(cx).text(cx))
2528            .filter(|args| !args.is_empty());
2529
2530        let value = action_arguments
2531            .as_ref()
2532            .map(|args| {
2533                serde_json::from_str(args).context("Failed to parse action arguments as JSON")
2534            })
2535            .transpose()?;
2536
2537        cx.build_action(action_name, value)
2538            .context("Failed to validate action arguments")?;
2539        Ok(action_arguments)
2540    }
2541
2542    fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<KeybindingKeystroke>> {
2543        let new_keystrokes = self
2544            .keybind_editor
2545            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
2546        anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
2547        Ok(new_keystrokes)
2548    }
2549
2550    fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
2551        let new_context = self
2552            .context_editor
2553            .read_with(cx, |input, cx| input.editor().read(cx).text(cx));
2554        let Some(context) = new_context.is_empty().not().then_some(new_context) else {
2555            return Ok(None);
2556        };
2557        gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
2558
2559        Ok(Some(context))
2560    }
2561
2562    fn save_or_display_error(&mut self, cx: &mut Context<Self>) {
2563        self.save(cx).map_err(|err| self.set_error(err, cx)).ok();
2564    }
2565
2566    fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
2567        let existing_keybind = self.editing_keybind.clone();
2568        let fs = self.fs.clone();
2569
2570        let mut new_keystrokes = self.validate_keystrokes(cx).map_err(InputError::error)?;
2571        new_keystrokes
2572            .iter_mut()
2573            .for_each(|ks| ks.remove_key_char());
2574
2575        let new_context = self.validate_context(cx).map_err(InputError::error)?;
2576        let new_action_args = self
2577            .validate_action_arguments(cx)
2578            .map_err(InputError::error)?;
2579
2580        let action_mapping = ActionMapping {
2581            keystrokes: Rc::from(new_keystrokes.as_slice()),
2582            context: new_context.map(SharedString::from),
2583        };
2584
2585        let conflicting_indices = self
2586            .keymap_editor
2587            .read(cx)
2588            .keybinding_conflict_state
2589            .conflicting_indices_for_mapping(
2590                &action_mapping,
2591                self.creating.not().then_some(self.editing_keybind_idx),
2592            );
2593
2594        conflicting_indices.map(|KeybindConflict {
2595            first_conflict_index,
2596            remaining_conflict_amount,
2597        }|
2598        {
2599            let conflicting_action_name = self
2600                .keymap_editor
2601                .read(cx)
2602                .keybindings
2603                .get(first_conflict_index)
2604                .map(|keybind| keybind.action().name);
2605
2606            let warning_message = match conflicting_action_name {
2607                Some(name) => {
2608                     if remaining_conflict_amount > 0 {
2609                        format!(
2610                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
2611                            name, remaining_conflict_amount
2612                        )
2613                    } else {
2614                        format!("Your keybind would conflict with the \"{}\" action", name)
2615                    }
2616                }
2617                None => {
2618                    log::info!(
2619                        "Could not find action in keybindings with index {}",
2620                        first_conflict_index
2621                    );
2622                    "Your keybind would conflict with other actions".to_string()
2623                }
2624            };
2625
2626            let warning = InputError::warning(warning_message);
2627            if self.error.as_ref().is_some_and(|old_error| *old_error == warning) {
2628                Ok(())
2629           } else {
2630                Err(warning)
2631            }
2632        }).unwrap_or(Ok(()))?;
2633
2634        let create = self.creating;
2635        let keyboard_mapper = cx.keyboard_mapper().clone();
2636
2637        let action_name = self
2638            .get_selected_action_name(cx)
2639            .map_err(InputError::error)?;
2640
2641        let humanized_action_name: SharedString =
2642            command_palette::humanize_action_name(action_name).into();
2643
2644        let action_information = ActionInformation::new(
2645            action_name,
2646            None,
2647            &HashSet::default(),
2648            cx.action_documentation(),
2649            &self.keymap_editor.read(cx).humanized_action_names,
2650        );
2651
2652        let keybind_for_save = if create {
2653            ProcessedBinding::Unmapped(action_information)
2654        } else {
2655            existing_keybind
2656        };
2657
2658        cx.spawn(async move |this, cx| {
2659            match save_keybinding_update(
2660                create,
2661                keybind_for_save,
2662                &action_mapping,
2663                new_action_args.as_deref(),
2664                &fs,
2665                keyboard_mapper.as_ref(),
2666            )
2667            .await
2668            {
2669                Ok(_) => {
2670                    this.update(cx, |this, cx| {
2671                        this.keymap_editor.update(cx, |keymap, cx| {
2672                            keymap.previous_edit = Some(PreviousEdit::Keybinding {
2673                                action_mapping,
2674                                action_name,
2675                                fallback: keymap.table_interaction_state.read(cx).scroll_offset(),
2676                            });
2677                            let status_toast = StatusToast::new(
2678                                format!("Saved edits to the {} action.", humanized_action_name),
2679                                cx,
2680                                move |this, _cx| {
2681                                    this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
2682                                        .dismiss_button(true)
2683                                    // .action("Undo", f) todo: wire the undo functionality
2684                                },
2685                            );
2686
2687                            this.workspace
2688                                .update(cx, |workspace, cx| {
2689                                    workspace.toggle_status_toast(status_toast, cx);
2690                                })
2691                                .log_err();
2692                        });
2693                        cx.emit(DismissEvent);
2694                    })
2695                    .ok();
2696                }
2697                Err(err) => {
2698                    this.update(cx, |this, cx| {
2699                        this.set_error(InputError::error(err), cx);
2700                    })
2701                    .log_err();
2702                }
2703            }
2704        })
2705        .detach();
2706
2707        Ok(())
2708    }
2709
2710    fn is_any_editor_showing_completions(&self, window: &Window, cx: &App) -> bool {
2711        let is_editor_showing_completions =
2712            |focus_handle: &FocusHandle, editor_entity: &Entity<Editor>| -> bool {
2713                focus_handle.contains_focused(window, cx)
2714                    && editor_entity.read_with(cx, |editor, _cx| {
2715                        editor
2716                            .context_menu()
2717                            .borrow()
2718                            .as_ref()
2719                            .is_some_and(|menu| menu.visible())
2720                    })
2721            };
2722
2723        self.action_editor.as_ref().is_some_and(|action_editor| {
2724            let focus_handle = action_editor.read(cx).focus_handle(cx);
2725            let editor_entity = action_editor.read(cx).editor();
2726            is_editor_showing_completions(&focus_handle, editor_entity)
2727        }) || {
2728            let focus_handle = self.context_editor.read(cx).focus_handle(cx);
2729            let editor_entity = self.context_editor.read(cx).editor();
2730            is_editor_showing_completions(&focus_handle, editor_entity)
2731        } || self
2732            .action_arguments_editor
2733            .as_ref()
2734            .is_some_and(|args_editor| {
2735                let focus_handle = args_editor.read(cx).focus_handle(cx);
2736                let editor_entity = &args_editor.read(cx).editor;
2737                is_editor_showing_completions(&focus_handle, editor_entity)
2738            })
2739    }
2740
2741    fn key_context(&self) -> KeyContext {
2742        let mut key_context = KeyContext::new_with_defaults();
2743        key_context.add("KeybindEditorModal");
2744        key_context
2745    }
2746
2747    fn key_context_internal(&self, window: &Window, cx: &App) -> KeyContext {
2748        let mut key_context = self.key_context();
2749
2750        if self.is_any_editor_showing_completions(window, cx) {
2751            key_context.add("showing_completions");
2752        }
2753
2754        key_context
2755    }
2756
2757    fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
2758        if self.is_any_editor_showing_completions(window, cx) {
2759            return;
2760        }
2761        self.focus_state.focus_next(window, cx);
2762    }
2763
2764    fn focus_prev(
2765        &mut self,
2766        _: &menu::SelectPrevious,
2767        window: &mut Window,
2768        cx: &mut Context<Self>,
2769    ) {
2770        if self.is_any_editor_showing_completions(window, cx) {
2771            return;
2772        }
2773        self.focus_state.focus_previous(window, cx);
2774    }
2775
2776    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
2777        self.save_or_display_error(cx);
2778    }
2779
2780    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
2781        cx.emit(DismissEvent);
2782    }
2783
2784    fn get_matching_bindings_count(&self, cx: &Context<Self>) -> usize {
2785        let current_keystrokes = self.keybind_editor.read(cx).keystrokes();
2786
2787        if current_keystrokes.is_empty() {
2788            return 0;
2789        }
2790
2791        self.keymap_editor
2792            .read(cx)
2793            .keybindings
2794            .iter()
2795            .enumerate()
2796            .filter(|(idx, binding)| {
2797                // Don't count the binding we're currently editing
2798                if !self.creating && *idx == self.editing_keybind_idx {
2799                    return false;
2800                }
2801
2802                binding.keystrokes().is_some_and(|keystrokes| {
2803                    keystrokes_match_exactly(keystrokes, current_keystrokes)
2804                })
2805            })
2806            .count()
2807    }
2808
2809    fn show_matching_bindings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2810        let keystrokes = self.keybind_editor.read(cx).keystrokes().to_vec();
2811
2812        self.keymap_editor.update(cx, |keymap_editor, cx| {
2813            keymap_editor.clear_action_query(window, cx)
2814        });
2815
2816        // Dismiss the modal
2817        cx.emit(DismissEvent);
2818
2819        // Update the keymap editor to show matching keystrokes
2820        self.keymap_editor.update(cx, |editor, cx| {
2821            editor.filter_state = FilterState::All;
2822            editor.search_mode = SearchMode::KeyStroke { exact_match: true };
2823            editor.keystroke_editor.update(cx, |keystroke_editor, cx| {
2824                keystroke_editor.set_keystrokes(keystrokes, cx);
2825            });
2826        });
2827    }
2828}
2829
2830impl Render for KeybindingEditorModal {
2831    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2832        self.add_action_arguments_input(window, cx);
2833
2834        let theme = cx.theme().colors();
2835        let matching_bindings_count = self.get_matching_bindings_count(cx);
2836        let key_context = self.key_context_internal(window, cx);
2837        let showing_completions = key_context.contains("showing_completions");
2838
2839        v_flex()
2840            .w(rems(34.))
2841            .elevation_3(cx)
2842            .key_context(key_context)
2843            .on_action(cx.listener(Self::confirm))
2844            .on_action(cx.listener(Self::cancel))
2845            .when(!showing_completions, |this| {
2846                this.on_action(cx.listener(Self::focus_next))
2847                    .on_action(cx.listener(Self::focus_prev))
2848            })
2849            .child(
2850                Modal::new("keybinding_editor_modal", None)
2851                    .header(
2852                        ModalHeader::new().child(
2853                            v_flex()
2854                                .w_full()
2855                                .pb_1p5()
2856                                .mb_1()
2857                                .gap_0p5()
2858                                .border_b_1()
2859                                .border_color(theme.border_variant)
2860                                .when(!self.creating, |this| {
2861                                    this.child(Label::new(
2862                                        self.editing_keybind.action().humanized_name.clone(),
2863                                    ))
2864                                    .when_some(
2865                                        self.editing_keybind.action().documentation,
2866                                        |this, docs| {
2867                                            this.child(
2868                                                Label::new(docs)
2869                                                    .size(LabelSize::Small)
2870                                                    .color(Color::Muted),
2871                                            )
2872                                        },
2873                                    )
2874                                })
2875                                .when(self.creating, |this| {
2876                                    this.child(Label::new("Create Keybinding"))
2877                                }),
2878                        ),
2879                    )
2880                    .section(
2881                        Section::new().child(
2882                            v_flex()
2883                                .gap_2p5()
2884                                .when_some(
2885                                    self.creating
2886                                        .then_some(())
2887                                        .and_then(|_| self.action_editor.as_ref()),
2888                                    |this, selector| this.child(selector.clone()),
2889                                )
2890                                .child(
2891                                    v_flex()
2892                                        .gap_1()
2893                                        .child(Label::new("Edit Keystroke"))
2894                                        .child(self.keybind_editor.clone())
2895                                        .child(h_flex().gap_px().when(
2896                                            matching_bindings_count > 0,
2897                                            |this| {
2898                                                let label = format!(
2899                                                    "There {} {} {} with the same keystrokes.",
2900                                                    if matching_bindings_count == 1 {
2901                                                        "is"
2902                                                    } else {
2903                                                        "are"
2904                                                    },
2905                                                    matching_bindings_count,
2906                                                    if matching_bindings_count == 1 {
2907                                                        "binding"
2908                                                    } else {
2909                                                        "bindings"
2910                                                    }
2911                                                );
2912
2913                                                this.child(
2914                                                    Label::new(label)
2915                                                        .size(LabelSize::Small)
2916                                                        .color(Color::Muted),
2917                                                )
2918                                                .child(
2919                                                    Button::new("show_matching", "View")
2920                                                        .label_size(LabelSize::Small)
2921                                                        .icon(IconName::ArrowUpRight)
2922                                                        .icon_color(Color::Muted)
2923                                                        .icon_size(IconSize::Small)
2924                                                        .on_click(cx.listener(
2925                                                            |this, _, window, cx| {
2926                                                                this.show_matching_bindings(
2927                                                                    window, cx,
2928                                                                );
2929                                                            },
2930                                                        )),
2931                                                )
2932                                            },
2933                                        )),
2934                                )
2935                                .when_some(self.action_arguments_editor.clone(), |this, editor| {
2936                                    this.child(
2937                                        v_flex()
2938                                            .gap_1()
2939                                            .child(Label::new("Edit Arguments"))
2940                                            .child(editor),
2941                                    )
2942                                })
2943                                .child(self.context_editor.clone())
2944                                .when_some(self.error.as_ref(), |this, error| {
2945                                    this.child(
2946                                        Banner::new()
2947                                            .severity(error.severity)
2948                                            .child(Label::new(error.content.clone())),
2949                                    )
2950                                }),
2951                        ),
2952                    )
2953                    .footer(
2954                        ModalFooter::new().end_slot(
2955                            h_flex()
2956                                .gap_1()
2957                                .child(
2958                                    Button::new("cancel", "Cancel")
2959                                        .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
2960                                )
2961                                .child(Button::new("save-btn", "Save").on_click(cx.listener(
2962                                    |this, _event, _window, cx| {
2963                                        this.save_or_display_error(cx);
2964                                    },
2965                                ))),
2966                        ),
2967                    ),
2968            )
2969    }
2970}
2971
2972struct KeybindingEditorModalFocusState {
2973    handles: Vec<FocusHandle>,
2974}
2975
2976impl KeybindingEditorModalFocusState {
2977    fn new(
2978        action_editor: Option<FocusHandle>,
2979        keystrokes: FocusHandle,
2980        action_arguments: Option<FocusHandle>,
2981        context: FocusHandle,
2982    ) -> Self {
2983        Self {
2984            handles: Vec::from_iter(
2985                [
2986                    action_editor,
2987                    Some(keystrokes),
2988                    action_arguments,
2989                    Some(context),
2990                ]
2991                .into_iter()
2992                .flatten(),
2993            ),
2994        }
2995    }
2996
2997    fn focused_index(&self, window: &Window, cx: &App) -> Option<i32> {
2998        self.handles
2999            .iter()
3000            .position(|handle| handle.contains_focused(window, cx))
3001            .map(|i| i as i32)
3002    }
3003
3004    fn focus_index(&self, mut index: i32, window: &mut Window, cx: &mut App) {
3005        if index < 0 {
3006            index = self.handles.len() as i32 - 1;
3007        }
3008        if index >= self.handles.len() as i32 {
3009            index = 0;
3010        }
3011        window.focus(&self.handles[index as usize], cx);
3012    }
3013
3014    fn focus_next(&self, window: &mut Window, cx: &mut App) {
3015        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3016            index + 1
3017        } else {
3018            0
3019        };
3020        self.focus_index(index_to_focus, window, cx);
3021    }
3022
3023    fn focus_previous(&self, window: &mut Window, cx: &mut App) {
3024        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3025            index - 1
3026        } else {
3027            self.handles.len() as i32 - 1
3028        };
3029        self.focus_index(index_to_focus, window, cx);
3030    }
3031}
3032
3033struct ActionArgumentsEditor {
3034    editor: Entity<Editor>,
3035    focus_handle: FocusHandle,
3036    is_loading: bool,
3037    /// See documentation in `KeymapEditor` for why a temp dir is needed.
3038    /// This field exists because the keymap editor temp dir creation may fail,
3039    /// and rather than implement a complicated retry mechanism, we simply
3040    /// fallback to trying to create a temporary directory in this editor on
3041    /// demand. Of note is that the TempDir struct will remove the directory
3042    /// when dropped.
3043    backup_temp_dir: Option<tempfile::TempDir>,
3044}
3045
3046impl Focusable for ActionArgumentsEditor {
3047    fn focus_handle(&self, _cx: &App) -> FocusHandle {
3048        self.focus_handle.clone()
3049    }
3050}
3051
3052impl ActionArgumentsEditor {
3053    fn new(
3054        action_name: &'static str,
3055        arguments: Option<SharedString>,
3056        temp_dir: Option<&std::path::Path>,
3057        workspace: WeakEntity<Workspace>,
3058        window: &mut Window,
3059        cx: &mut Context<Self>,
3060    ) -> Self {
3061        let focus_handle = cx.focus_handle();
3062        cx.on_focus_in(&focus_handle, window, |this, window, cx| {
3063            this.editor.focus_handle(cx).focus(window, cx);
3064        })
3065        .detach();
3066        let editor = cx.new(|cx| {
3067            let mut editor = Editor::auto_height_unbounded(1, window, cx);
3068            Self::set_editor_text(&mut editor, arguments.clone(), window, cx);
3069            editor.set_read_only(true);
3070            editor
3071        });
3072
3073        let temp_dir = temp_dir.map(|path| path.to_owned());
3074        cx.spawn_in(window, async move |this, cx| {
3075            let result = async {
3076                let (project, fs) = workspace.read_with(cx, |workspace, _cx| {
3077                    (
3078                        workspace.project().downgrade(),
3079                        workspace.app_state().fs.clone(),
3080                    )
3081                })?;
3082
3083                let file_name = json_schema_store::normalized_action_file_name(action_name);
3084
3085                let (buffer, backup_temp_dir) =
3086                    Self::create_temp_buffer(temp_dir, file_name.clone(), project.clone(), fs, cx)
3087                        .await
3088                        .context(concat!(
3089                            "Failed to create temporary buffer for action arguments. ",
3090                            "Auto-complete will not work"
3091                        ))?;
3092
3093                let editor = cx.new_window_entity(|window, cx| {
3094                    let multi_buffer = cx.new(|cx| editor::MultiBuffer::singleton(buffer, cx));
3095                    let mut editor = Editor::new(
3096                        EditorMode::Full {
3097                            scale_ui_elements_with_buffer_font_size: true,
3098                            show_active_line_background: false,
3099                            sizing_behavior: SizingBehavior::SizeByContent,
3100                        },
3101                        multi_buffer,
3102                        project.upgrade(),
3103                        window,
3104                        cx,
3105                    );
3106                    editor.set_searchable(false);
3107                    editor.disable_scrollbars_and_minimap(window, cx);
3108                    editor.set_show_edit_predictions(Some(false), window, cx);
3109                    editor.set_show_gutter(false, cx);
3110                    Self::set_editor_text(&mut editor, arguments, window, cx);
3111                    editor
3112                })?;
3113
3114                this.update_in(cx, |this, window, cx| {
3115                    if this.editor.focus_handle(cx).is_focused(window) {
3116                        editor.focus_handle(cx).focus(window, cx);
3117                    }
3118                    this.editor = editor;
3119                    this.backup_temp_dir = backup_temp_dir;
3120                    this.is_loading = false;
3121                })?;
3122
3123                anyhow::Ok(())
3124            }
3125            .await;
3126            if result.is_err() {
3127                let json_language = load_json_language(workspace.clone(), cx).await;
3128                this.update(cx, |this, cx| {
3129                    this.editor.update(cx, |editor, cx| {
3130                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
3131                            buffer.update(cx, |buffer, cx| {
3132                                buffer.set_language(Some(json_language.clone()), cx)
3133                            });
3134                        }
3135                    })
3136                    // .context("Failed to load JSON language for editing keybinding action arguments input")
3137                })
3138                .ok();
3139                this.update(cx, |this, _cx| {
3140                    this.is_loading = false;
3141                })
3142                .ok();
3143            }
3144            result
3145        })
3146        .detach_and_log_err(cx);
3147        Self {
3148            editor,
3149            focus_handle,
3150            is_loading: true,
3151            backup_temp_dir: None,
3152        }
3153    }
3154
3155    fn set_editor_text(
3156        editor: &mut Editor,
3157        arguments: Option<SharedString>,
3158        window: &mut Window,
3159        cx: &mut Context<Editor>,
3160    ) {
3161        if let Some(arguments) = arguments {
3162            editor.set_text(arguments, window, cx);
3163        } else {
3164            // TODO: default value from schema?
3165            editor.set_placeholder_text("Action Arguments", window, cx);
3166        }
3167    }
3168
3169    async fn create_temp_buffer(
3170        temp_dir: Option<std::path::PathBuf>,
3171        file_name: String,
3172        project: WeakEntity<Project>,
3173        fs: Arc<dyn Fs>,
3174        cx: &mut AsyncApp,
3175    ) -> anyhow::Result<(Entity<language::Buffer>, Option<tempfile::TempDir>)> {
3176        let (temp_file_path, temp_dir) = {
3177            let file_name = file_name.clone();
3178            async move {
3179                let temp_dir_backup = match temp_dir.as_ref() {
3180                    Some(_) => None,
3181                    None => {
3182                        let temp_dir = paths::temp_dir();
3183                        let sub_temp_dir = tempfile::Builder::new()
3184                            .tempdir_in(temp_dir)
3185                            .context("Failed to create temporary directory")?;
3186                        Some(sub_temp_dir)
3187                    }
3188                };
3189                let dir_path = temp_dir.as_deref().unwrap_or_else(|| {
3190                    temp_dir_backup
3191                        .as_ref()
3192                        .expect("created backup tempdir")
3193                        .path()
3194                });
3195                let path = dir_path.join(file_name);
3196                fs.create_file(
3197                    &path,
3198                    fs::CreateOptions {
3199                        ignore_if_exists: true,
3200                        overwrite: true,
3201                    },
3202                )
3203                .await
3204                .context("Failed to create temporary file")?;
3205                anyhow::Ok((path, temp_dir_backup))
3206            }
3207        }
3208        .await
3209        .context("Failed to create backing file")?;
3210
3211        project
3212            .update(cx, |project, cx| {
3213                project.open_local_buffer(temp_file_path, cx)
3214            })?
3215            .await
3216            .context("Failed to create buffer")
3217            .map(|buffer| (buffer, temp_dir))
3218    }
3219}
3220
3221impl Render for ActionArgumentsEditor {
3222    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3223        let settings = theme::ThemeSettings::get_global(cx);
3224        let colors = cx.theme().colors();
3225
3226        let border_color = if self.is_loading {
3227            colors.border_disabled
3228        } else if self.focus_handle.contains_focused(window, cx) {
3229            colors.border_focused
3230        } else {
3231            colors.border_variant
3232        };
3233
3234        let text_style = {
3235            TextStyleRefinement {
3236                font_size: Some(rems(0.875).into()),
3237                font_weight: Some(settings.buffer_font.weight),
3238                line_height: Some(relative(1.2)),
3239                color: self.is_loading.then_some(colors.text_disabled),
3240                ..Default::default()
3241            }
3242        };
3243
3244        self.editor
3245            .update(cx, |editor, _| editor.set_text_style_refinement(text_style));
3246
3247        h_flex()
3248            .min_h_8()
3249            .min_w_48()
3250            .px_2()
3251            .flex_grow()
3252            .rounded_md()
3253            .bg(cx.theme().colors().editor_background)
3254            .border_1()
3255            .border_color(border_color)
3256            .track_focus(&self.focus_handle)
3257            .child(self.editor.clone())
3258    }
3259}
3260
3261struct KeyContextCompletionProvider {
3262    contexts: Vec<SharedString>,
3263}
3264
3265impl CompletionProvider for KeyContextCompletionProvider {
3266    fn completions(
3267        &self,
3268        _excerpt_id: editor::ExcerptId,
3269        buffer: &Entity<language::Buffer>,
3270        buffer_position: language::Anchor,
3271        _trigger: editor::CompletionContext,
3272        _window: &mut Window,
3273        cx: &mut Context<Editor>,
3274    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
3275        let buffer = buffer.read(cx);
3276        let mut count_back = 0;
3277        for char in buffer.reversed_chars_at(buffer_position) {
3278            if char.is_ascii_alphanumeric() || char == '_' {
3279                count_back += 1;
3280            } else {
3281                break;
3282            }
3283        }
3284        let start_anchor =
3285            buffer.anchor_before(buffer_position.to_offset(buffer).saturating_sub(count_back));
3286        let replace_range = start_anchor..buffer_position;
3287        gpui::Task::ready(Ok(vec![project::CompletionResponse {
3288            completions: self
3289                .contexts
3290                .iter()
3291                .map(|context| project::Completion {
3292                    replace_range: replace_range.clone(),
3293                    label: language::CodeLabel::plain(context.to_string(), None),
3294                    new_text: context.to_string(),
3295                    documentation: None,
3296                    source: project::CompletionSource::Custom,
3297                    icon_path: None,
3298                    match_start: None,
3299                    snippet_deduplication_key: None,
3300                    insert_text_mode: None,
3301                    confirm: None,
3302                })
3303                .collect(),
3304            display_options: CompletionDisplayOptions::default(),
3305            is_incomplete: false,
3306        }]))
3307    }
3308
3309    fn is_completion_trigger(
3310        &self,
3311        _buffer: &Entity<language::Buffer>,
3312        _position: language::Anchor,
3313        text: &str,
3314        _trigger_in_words: bool,
3315        _cx: &mut Context<Editor>,
3316    ) -> bool {
3317        text.chars()
3318            .last()
3319            .is_some_and(|last_char| last_char.is_ascii_alphanumeric() || last_char == '_')
3320    }
3321}
3322
3323async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
3324    let json_language_task = workspace
3325        .read_with(cx, |workspace, cx| {
3326            workspace
3327                .project()
3328                .read(cx)
3329                .languages()
3330                .language_for_name("JSON")
3331        })
3332        .context("Failed to load JSON language")
3333        .log_err();
3334    let json_language = match json_language_task {
3335        Some(task) => task.await.context("Failed to load JSON language").log_err(),
3336        None => None,
3337    };
3338    json_language.unwrap_or_else(|| {
3339        Arc::new(Language::new(
3340            LanguageConfig {
3341                name: "JSON".into(),
3342                ..Default::default()
3343            },
3344            Some(tree_sitter_json::LANGUAGE.into()),
3345        ))
3346    })
3347}
3348
3349async fn load_keybind_context_language(
3350    workspace: WeakEntity<Workspace>,
3351    cx: &mut AsyncApp,
3352) -> Arc<Language> {
3353    let language_task = workspace
3354        .read_with(cx, |workspace, cx| {
3355            workspace
3356                .project()
3357                .read(cx)
3358                .languages()
3359                .language_for_name("Zed Keybind Context")
3360        })
3361        .context("Failed to load Zed Keybind Context language")
3362        .log_err();
3363    let language = match language_task {
3364        Some(task) => task
3365            .await
3366            .context("Failed to load Zed Keybind Context language")
3367            .log_err(),
3368        None => None,
3369    };
3370    language.unwrap_or_else(|| {
3371        Arc::new(Language::new(
3372            LanguageConfig {
3373                name: "Zed Keybind Context".into(),
3374                ..Default::default()
3375            },
3376            Some(tree_sitter_rust::LANGUAGE.into()),
3377        ))
3378    })
3379}
3380
3381async fn save_keybinding_update(
3382    create: bool,
3383    existing: ProcessedBinding,
3384    action_mapping: &ActionMapping,
3385    new_args: Option<&str>,
3386    fs: &Arc<dyn Fs>,
3387    keyboard_mapper: &dyn PlatformKeyboardMapper,
3388) -> anyhow::Result<()> {
3389    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3390        .await
3391        .context("Failed to load keymap file")?;
3392
3393    let tab_size = infer_json_indent_size(&keymap_contents);
3394
3395    let existing_keystrokes = existing.keystrokes().unwrap_or_default();
3396    let existing_context = existing.context().and_then(KeybindContextString::local_str);
3397    let existing_args = existing
3398        .action()
3399        .arguments
3400        .as_ref()
3401        .map(|args| args.text.as_ref());
3402
3403    let target = settings::KeybindUpdateTarget {
3404        context: existing_context,
3405        keystrokes: existing_keystrokes,
3406        action_name: existing.action().name,
3407        action_arguments: existing_args,
3408    };
3409
3410    let source = settings::KeybindUpdateTarget {
3411        context: action_mapping.context.as_ref().map(|a| &***a),
3412        keystrokes: &action_mapping.keystrokes,
3413        action_name: existing.action().name,
3414        action_arguments: new_args,
3415    };
3416
3417    let operation = if !create {
3418        settings::KeybindUpdateOperation::Replace {
3419            target,
3420            target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3421            source,
3422        }
3423    } else {
3424        settings::KeybindUpdateOperation::Add {
3425            source,
3426            from: Some(target),
3427        }
3428    };
3429
3430    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3431
3432    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3433        operation,
3434        keymap_contents,
3435        tab_size,
3436        keyboard_mapper,
3437    )
3438    .map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?;
3439    fs.write(
3440        paths::keymap_file().as_path(),
3441        updated_keymap_contents.as_bytes(),
3442    )
3443    .await
3444    .context("Failed to write keymap file")?;
3445
3446    telemetry::event!(
3447        "Keybinding Updated",
3448        new_keybinding = new_keybinding,
3449        removed_keybinding = removed_keybinding,
3450        source = source
3451    );
3452    Ok(())
3453}
3454
3455async fn remove_keybinding(
3456    existing: ProcessedBinding,
3457    fs: &Arc<dyn Fs>,
3458    keyboard_mapper: &dyn PlatformKeyboardMapper,
3459) -> anyhow::Result<()> {
3460    let Some(keystrokes) = existing.keystrokes() else {
3461        anyhow::bail!("Cannot remove a keybinding that does not exist");
3462    };
3463    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3464        .await
3465        .context("Failed to load keymap file")?;
3466    let tab_size = infer_json_indent_size(&keymap_contents);
3467
3468    let operation = settings::KeybindUpdateOperation::Remove {
3469        target: settings::KeybindUpdateTarget {
3470            context: existing.context().and_then(KeybindContextString::local_str),
3471            keystrokes,
3472            action_name: existing.action().name,
3473            action_arguments: existing
3474                .action()
3475                .arguments
3476                .as_ref()
3477                .map(|arguments| arguments.text.as_ref()),
3478        },
3479        target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3480    };
3481
3482    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3483    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3484        operation,
3485        keymap_contents,
3486        tab_size,
3487        keyboard_mapper,
3488    )
3489    .context("Failed to update keybinding")?;
3490    fs.write(
3491        paths::keymap_file().as_path(),
3492        updated_keymap_contents.as_bytes(),
3493    )
3494    .await
3495    .context("Failed to write keymap file")?;
3496
3497    telemetry::event!(
3498        "Keybinding Removed",
3499        new_keybinding = new_keybinding,
3500        removed_keybinding = removed_keybinding,
3501        source = source
3502    );
3503    Ok(())
3504}
3505
3506fn collect_contexts_from_assets() -> Vec<SharedString> {
3507    let mut keymap_assets = vec![
3508        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
3509        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
3510    ];
3511    keymap_assets.extend(
3512        BaseKeymap::OPTIONS
3513            .iter()
3514            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
3515            .map(util::asset_str::<SettingsAssets>),
3516    );
3517
3518    let mut contexts = HashSet::default();
3519
3520    for keymap_asset in keymap_assets {
3521        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
3522            continue;
3523        };
3524
3525        for section in keymap.sections() {
3526            let context_expr = &section.context;
3527            let mut queue = Vec::new();
3528            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
3529                continue;
3530            };
3531
3532            queue.push(root_context);
3533            while let Some(context) = queue.pop() {
3534                match context {
3535                    Identifier(ident) => {
3536                        contexts.insert(ident);
3537                    }
3538                    Equal(ident_a, ident_b) => {
3539                        contexts.insert(ident_a);
3540                        contexts.insert(ident_b);
3541                    }
3542                    NotEqual(ident_a, ident_b) => {
3543                        contexts.insert(ident_a);
3544                        contexts.insert(ident_b);
3545                    }
3546                    Descendant(ctx_a, ctx_b) => {
3547                        queue.push(*ctx_a);
3548                        queue.push(*ctx_b);
3549                    }
3550                    Not(ctx) => {
3551                        queue.push(*ctx);
3552                    }
3553                    And(ctx_a, ctx_b) => {
3554                        queue.push(*ctx_a);
3555                        queue.push(*ctx_b);
3556                    }
3557                    Or(ctx_a, ctx_b) => {
3558                        queue.push(*ctx_a);
3559                        queue.push(*ctx_b);
3560                    }
3561                }
3562            }
3563        }
3564    }
3565
3566    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
3567    contexts.sort();
3568
3569    contexts
3570}
3571
3572fn normalized_ctx_eq(
3573    a: &gpui::KeyBindingContextPredicate,
3574    b: &gpui::KeyBindingContextPredicate,
3575) -> bool {
3576    use gpui::KeyBindingContextPredicate::*;
3577    return match (a, b) {
3578        (Identifier(_), Identifier(_)) => a == b,
3579        (Equal(a_left, a_right), Equal(b_left, b_right)) => {
3580            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3581        }
3582        (NotEqual(a_left, a_right), NotEqual(b_left, b_right)) => {
3583            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3584        }
3585        (Descendant(a_parent, a_child), Descendant(b_parent, b_child)) => {
3586            normalized_ctx_eq(a_parent, b_parent) && normalized_ctx_eq(a_child, b_child)
3587        }
3588        (Not(a_expr), Not(b_expr)) => normalized_ctx_eq(a_expr, b_expr),
3589        // Handle double negation: !(!a) == a
3590        (Not(a_expr), b) if matches!(a_expr.as_ref(), Not(_)) => {
3591            let Not(a_inner) = a_expr.as_ref() else {
3592                unreachable!();
3593            };
3594            normalized_ctx_eq(b, a_inner)
3595        }
3596        (a, Not(b_expr)) if matches!(b_expr.as_ref(), Not(_)) => {
3597            let Not(b_inner) = b_expr.as_ref() else {
3598                unreachable!();
3599            };
3600            normalized_ctx_eq(a, b_inner)
3601        }
3602        (And(a_left, a_right), And(b_left, b_right))
3603            if matches!(a_left.as_ref(), And(_, _))
3604                || matches!(a_right.as_ref(), And(_, _))
3605                || matches!(b_left.as_ref(), And(_, _))
3606                || matches!(b_right.as_ref(), And(_, _)) =>
3607        {
3608            let mut a_operands = Vec::new();
3609            flatten_and(a, &mut a_operands);
3610            let mut b_operands = Vec::new();
3611            flatten_and(b, &mut b_operands);
3612            compare_operand_sets(&a_operands, &b_operands)
3613        }
3614        (And(a_left, a_right), And(b_left, b_right)) => {
3615            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3616                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3617        }
3618        (Or(a_left, a_right), Or(b_left, b_right))
3619            if matches!(a_left.as_ref(), Or(_, _))
3620                || matches!(a_right.as_ref(), Or(_, _))
3621                || matches!(b_left.as_ref(), Or(_, _))
3622                || matches!(b_right.as_ref(), Or(_, _)) =>
3623        {
3624            let mut a_operands = Vec::new();
3625            flatten_or(a, &mut a_operands);
3626            let mut b_operands = Vec::new();
3627            flatten_or(b, &mut b_operands);
3628            compare_operand_sets(&a_operands, &b_operands)
3629        }
3630        (Or(a_left, a_right), Or(b_left, b_right)) => {
3631            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3632                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3633        }
3634        _ => false,
3635    };
3636
3637    fn flatten_and<'a>(
3638        pred: &'a gpui::KeyBindingContextPredicate,
3639        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3640    ) {
3641        use gpui::KeyBindingContextPredicate::*;
3642        match pred {
3643            And(left, right) => {
3644                flatten_and(left, operands);
3645                flatten_and(right, operands);
3646            }
3647            _ => operands.push(pred),
3648        }
3649    }
3650
3651    fn flatten_or<'a>(
3652        pred: &'a gpui::KeyBindingContextPredicate,
3653        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3654    ) {
3655        use gpui::KeyBindingContextPredicate::*;
3656        match pred {
3657            Or(left, right) => {
3658                flatten_or(left, operands);
3659                flatten_or(right, operands);
3660            }
3661            _ => operands.push(pred),
3662        }
3663    }
3664
3665    fn compare_operand_sets(
3666        a: &[&gpui::KeyBindingContextPredicate],
3667        b: &[&gpui::KeyBindingContextPredicate],
3668    ) -> bool {
3669        if a.len() != b.len() {
3670            return false;
3671        }
3672
3673        // For each operand in a, find a matching operand in b
3674        let mut b_matched = vec![false; b.len()];
3675        for a_operand in a {
3676            let mut found = false;
3677            for (b_idx, b_operand) in b.iter().enumerate() {
3678                if !b_matched[b_idx] && normalized_ctx_eq(a_operand, b_operand) {
3679                    b_matched[b_idx] = true;
3680                    found = true;
3681                    break;
3682                }
3683            }
3684            if !found {
3685                return false;
3686            }
3687        }
3688
3689        true
3690    }
3691}
3692
3693impl SerializableItem for KeymapEditor {
3694    fn serialized_item_kind() -> &'static str {
3695        "KeymapEditor"
3696    }
3697
3698    fn cleanup(
3699        workspace_id: workspace::WorkspaceId,
3700        alive_items: Vec<workspace::ItemId>,
3701        _window: &mut Window,
3702        cx: &mut App,
3703    ) -> gpui::Task<gpui::Result<()>> {
3704        workspace::delete_unloaded_items(
3705            alive_items,
3706            workspace_id,
3707            "keybinding_editors",
3708            &KEYBINDING_EDITORS,
3709            cx,
3710        )
3711    }
3712
3713    fn deserialize(
3714        _project: Entity<project::Project>,
3715        workspace: WeakEntity<Workspace>,
3716        workspace_id: workspace::WorkspaceId,
3717        item_id: workspace::ItemId,
3718        window: &mut Window,
3719        cx: &mut App,
3720    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
3721        window.spawn(cx, async move |cx| {
3722            if KEYBINDING_EDITORS
3723                .get_keybinding_editor(item_id, workspace_id)?
3724                .is_some()
3725            {
3726                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
3727            } else {
3728                Err(anyhow!("No keybinding editor to deserialize"))
3729            }
3730        })
3731    }
3732
3733    fn serialize(
3734        &mut self,
3735        workspace: &mut Workspace,
3736        item_id: workspace::ItemId,
3737        _closing: bool,
3738        _window: &mut Window,
3739        cx: &mut ui::Context<Self>,
3740    ) -> Option<gpui::Task<gpui::Result<()>>> {
3741        let workspace_id = workspace.database_id()?;
3742        Some(cx.background_spawn(async move {
3743            KEYBINDING_EDITORS
3744                .save_keybinding_editor(item_id, workspace_id)
3745                .await
3746        }))
3747    }
3748
3749    fn should_serialize(&self, _event: &Self::Event) -> bool {
3750        false
3751    }
3752}
3753
3754mod persistence {
3755    use db::{query, sqlez::domain::Domain, sqlez_macros::sql};
3756    use workspace::WorkspaceDb;
3757
3758    pub struct KeybindingEditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection);
3759
3760    impl Domain for KeybindingEditorDb {
3761        const NAME: &str = stringify!(KeybindingEditorDb);
3762
3763        const MIGRATIONS: &[&str] = &[sql!(
3764                CREATE TABLE keybinding_editors (
3765                    workspace_id INTEGER,
3766                    item_id INTEGER UNIQUE,
3767
3768                    PRIMARY KEY(workspace_id, item_id),
3769                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
3770                    ON DELETE CASCADE
3771                ) STRICT;
3772        )];
3773    }
3774
3775    db::static_connection!(KEYBINDING_EDITORS, KeybindingEditorDb, [WorkspaceDb]);
3776
3777    impl KeybindingEditorDb {
3778        query! {
3779            pub async fn save_keybinding_editor(
3780                item_id: workspace::ItemId,
3781                workspace_id: workspace::WorkspaceId
3782            ) -> Result<()> {
3783                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
3784                VALUES (?, ?)
3785            }
3786        }
3787
3788        query! {
3789            pub fn get_keybinding_editor(
3790                item_id: workspace::ItemId,
3791                workspace_id: workspace::WorkspaceId
3792            ) -> Result<Option<workspace::ItemId>> {
3793                SELECT item_id
3794                FROM keybinding_editors
3795                WHERE item_id = ? AND workspace_id = ?
3796            }
3797        }
3798    }
3799}
3800
3801#[cfg(test)]
3802mod tests {
3803    use super::*;
3804
3805    #[test]
3806    fn normalized_ctx_cmp() {
3807        #[track_caller]
3808        fn cmp(a: &str, b: &str) -> bool {
3809            let a = gpui::KeyBindingContextPredicate::parse(a)
3810                .expect("Failed to parse keybinding context a");
3811            let b = gpui::KeyBindingContextPredicate::parse(b)
3812                .expect("Failed to parse keybinding context b");
3813            normalized_ctx_eq(&a, &b)
3814        }
3815
3816        // Basic equality - identical expressions
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 != b", "a != b"));
3821        assert!(cmp("a > b", "a > b"));
3822        assert!(cmp("!a", "!a"));
3823
3824        // AND operator - associative/commutative
3825        assert!(cmp("a && b", "b && a"));
3826        assert!(cmp("a && b && c", "c && b && a"));
3827        assert!(cmp("a && b && c", "b && a && c"));
3828        assert!(cmp("a && b && c && d", "d && c && b && a"));
3829
3830        // OR operator - associative/commutative
3831        assert!(cmp("a || b", "b || a"));
3832        assert!(cmp("a || b || c", "c || b || a"));
3833        assert!(cmp("a || b || c", "b || a || c"));
3834        assert!(cmp("a || b || c || d", "d || c || b || a"));
3835
3836        // Equality operator - associative/commutative
3837        assert!(cmp("a == b", "b == a"));
3838        assert!(cmp("x == y", "y == x"));
3839
3840        // Inequality operator - associative/commutative
3841        assert!(cmp("a != b", "b != a"));
3842        assert!(cmp("x != y", "y != x"));
3843
3844        // Complex nested expressions with associative operators
3845        assert!(cmp("(a && b) || c", "c || (a && b)"));
3846        assert!(cmp("(a && b) || c", "c || (b && a)"));
3847        assert!(cmp("(a || b) && c", "c && (a || b)"));
3848        assert!(cmp("(a || b) && c", "c && (b || a)"));
3849        assert!(cmp("(a && b) || (c && d)", "(c && d) || (a && b)"));
3850        assert!(cmp("(a && b) || (c && d)", "(d && c) || (b && a)"));
3851
3852        // Multiple levels of nesting
3853        assert!(cmp("((a && b) || c) && d", "d && ((a && b) || c)"));
3854        assert!(cmp("((a && b) || c) && d", "d && (c || (b && a))"));
3855        assert!(cmp("a && (b || (c && d))", "(b || (c && d)) && a"));
3856        assert!(cmp("a && (b || (c && d))", "(b || (d && c)) && a"));
3857
3858        // Negation with associative operators
3859        assert!(cmp("!a && b", "b && !a"));
3860        assert!(cmp("!a || b", "b || !a"));
3861        assert!(cmp("!(a && b) || c", "c || !(a && b)"));
3862        assert!(cmp("!(a && b) || c", "c || !(b && a)"));
3863
3864        // Descendant operator (>) - NOT associative/commutative
3865        assert!(cmp("a > b", "a > b"));
3866        assert!(!cmp("a > b", "b > a"));
3867        assert!(!cmp("a > b > c", "c > b > a"));
3868        assert!(!cmp("a > b > c", "a > c > b"));
3869
3870        // Mixed operators with descendant
3871        assert!(cmp("(a > b) && c", "c && (a > b)"));
3872        assert!(!cmp("(a > b) && c", "c && (b > a)"));
3873        assert!(cmp("(a > b) || (c > d)", "(c > d) || (a > b)"));
3874        assert!(!cmp("(a > b) || (c > d)", "(b > a) || (d > c)"));
3875
3876        // Negative cases - different operators
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        assert!(!cmp("a == b", "a && b"));
3882        assert!(!cmp("a != b", "a || b"));
3883
3884        // Negative cases - different operands
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", "c || d"));
3889        assert!(!cmp("a == b", "a == c"));
3890        assert!(!cmp("a != b", "a != c"));
3891        assert!(!cmp("a > b", "a > c"));
3892        assert!(!cmp("a > b", "c > b"));
3893
3894        // Negative cases - with negation
3895        assert!(!cmp("!a", "a"));
3896        assert!(!cmp("!a && b", "a && b"));
3897        assert!(!cmp("!(a && b)", "a && b"));
3898        assert!(!cmp("!a || b", "a || b"));
3899        assert!(!cmp("!(a || b)", "a || b"));
3900
3901        // Negative cases - complex expressions
3902        assert!(!cmp("(a && b) || c", "(a || b) && c"));
3903        assert!(!cmp("a && (b || c)", "a || (b && c)"));
3904        assert!(!cmp("(a && b) || (c && d)", "(a || b) && (c || d)"));
3905        assert!(!cmp("a > b && c", "a && b > c"));
3906
3907        // Edge cases - multiple same operands
3908        assert!(cmp("a && a", "a && a"));
3909        assert!(cmp("a || a", "a || a"));
3910        assert!(cmp("a && a && b", "b && a && a"));
3911        assert!(cmp("a || a || b", "b || a || a"));
3912
3913        // Edge cases - deeply nested
3914        assert!(cmp(
3915            "((a && b) || (c && d)) && ((e || f) && g)",
3916            "((e || f) && g) && ((c && d) || (a && b))"
3917        ));
3918        assert!(cmp(
3919            "((a && b) || (c && d)) && ((e || f) && g)",
3920            "(g && (f || e)) && ((d && c) || (b && a))"
3921        ));
3922
3923        // Edge cases - repeated patterns
3924        assert!(cmp("(a && b) || (a && b)", "(b && a) || (b && a)"));
3925        assert!(cmp("(a || b) && (a || b)", "(b || a) && (b || a)"));
3926
3927        // Negative cases - subtle differences
3928        assert!(!cmp("a && b && c", "a && b"));
3929        assert!(!cmp("a || b || c", "a || b"));
3930        assert!(!cmp("(a && b) || c", "a && (b || c)"));
3931
3932        // a > b > c is not the same as a > c, should not be equal
3933        assert!(!cmp("a > b > c", "a > c"));
3934
3935        // Double negation with complex expressions
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", "a && b"));
3940        assert!(cmp("!(!a) || b", "a || b"));
3941        assert!(cmp("!(!(a && b)) || c", "(a && b) || c"));
3942        assert!(cmp("!(!(a && b)) || c", "(b && a) || c"));
3943        assert!(cmp("!(!a)", "a"));
3944        assert!(cmp("a", "!(!a)"));
3945        assert!(cmp("!(!(!a))", "!a"));
3946        assert!(cmp("!(!(!(!a)))", "a"));
3947    }
3948}