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