keybindings.rs

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