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