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