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