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