keybindings.rs

   1use std::{
   2    cmp::{self},
   3    ops::{Not as _, Range},
   4    sync::Arc,
   5    time::Duration,
   6};
   7
   8use anyhow::{Context as _, anyhow};
   9use collections::{HashMap, HashSet};
  10use editor::{CompletionProvider, Editor, EditorEvent};
  11use fs::Fs;
  12use fuzzy::{StringMatch, StringMatchCandidate};
  13use gpui::{
  14    Action, AppContext as _, AsyncApp, Axis, ClickEvent, Context, DismissEvent, Entity,
  15    EventEmitter, FocusHandle, Focusable, Global, IsZero, KeyContext, Keystroke, MouseButton,
  16    Point, ScrollStrategy, ScrollWheelEvent, Stateful, StyledText, Subscription, Task,
  17    TextStyleRefinement, WeakEntity, actions, anchored, deferred, div,
  18};
  19use language::{Language, LanguageConfig, ToOffset as _};
  20use notifications::status_toast::{StatusToast, ToastIcon};
  21use project::Project;
  22use settings::{BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAssets};
  23use ui::{
  24    ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, IconButtonShape, Indicator,
  25    Modal, ModalFooter, ModalHeader, ParentElement as _, Render, Section, SharedString,
  26    Styled as _, Tooltip, Window, prelude::*, right_click_menu,
  27};
  28use ui_input::SingleLineInput;
  29use util::ResultExt;
  30use workspace::{
  31    Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _,
  32    register_serializable_item,
  33};
  34
  35use crate::{
  36    keybindings::persistence::KEYBINDING_EDITORS,
  37    ui_components::{
  38        keystroke_input::{ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording},
  39        table::{ColumnWidths, ResizeBehavior, Table, TableInteractionState},
  40    },
  41};
  42
  43const NO_ACTION_ARGUMENTS_TEXT: SharedString = SharedString::new_static("<no arguments>");
  44
  45actions!(
  46    zed,
  47    [
  48        /// Opens the keymap editor.
  49        OpenKeymapEditor
  50    ]
  51);
  52
  53actions!(
  54    keymap_editor,
  55    [
  56        /// Edits the selected key binding.
  57        EditBinding,
  58        /// Creates a new key binding for the selected action.
  59        CreateBinding,
  60        /// Deletes the selected key binding.
  61        DeleteBinding,
  62        /// Copies the action name to clipboard.
  63        CopyAction,
  64        /// Copies the context predicate to clipboard.
  65        CopyContext,
  66        /// Toggles Conflict Filtering
  67        ToggleConflictFilter,
  68        /// Toggle Keystroke search
  69        ToggleKeystrokeSearch,
  70        /// Toggles exact matching for keystroke search
  71        ToggleExactKeystrokeMatching,
  72        /// Shows matching keystrokes for the currently selected binding
  73        ShowMatchingKeybinds
  74    ]
  75);
  76
  77pub fn init(cx: &mut App) {
  78    let keymap_event_channel = KeymapEventChannel::new();
  79    cx.set_global(keymap_event_channel);
  80
  81    cx.on_action(|_: &OpenKeymapEditor, cx| {
  82        workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
  83            workspace
  84                .with_local_workspace(window, cx, |workspace, window, cx| {
  85                    let existing = workspace
  86                        .active_pane()
  87                        .read(cx)
  88                        .items()
  89                        .find_map(|item| item.downcast::<KeymapEditor>());
  90
  91                    if let Some(existing) = existing {
  92                        workspace.activate_item(&existing, true, true, window, cx);
  93                    } else {
  94                        let keymap_editor =
  95                            cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
  96                        workspace.add_item_to_active_pane(
  97                            Box::new(keymap_editor),
  98                            None,
  99                            true,
 100                            window,
 101                            cx,
 102                        );
 103                    }
 104                })
 105                .detach();
 106        })
 107    });
 108
 109    register_serializable_item::<KeymapEditor>(cx);
 110}
 111
 112pub struct KeymapEventChannel {}
 113
 114impl Global for KeymapEventChannel {}
 115
 116impl KeymapEventChannel {
 117    fn new() -> Self {
 118        Self {}
 119    }
 120
 121    pub fn trigger_keymap_changed(cx: &mut App) {
 122        let Some(_event_channel) = cx.try_global::<Self>() else {
 123            // don't panic if no global defined. This usually happens in tests
 124            return;
 125        };
 126        cx.update_global(|_event_channel: &mut Self, _| {
 127            /* triggers observers in KeymapEditors */
 128        });
 129    }
 130}
 131
 132#[derive(Default, PartialEq)]
 133enum SearchMode {
 134    #[default]
 135    Normal,
 136    KeyStroke {
 137        exact_match: bool,
 138    },
 139}
 140
 141impl SearchMode {
 142    fn invert(&self) -> Self {
 143        match self {
 144            SearchMode::Normal => SearchMode::KeyStroke { exact_match: false },
 145            SearchMode::KeyStroke { .. } => SearchMode::Normal,
 146        }
 147    }
 148
 149    fn exact_match(&self) -> bool {
 150        match self {
 151            SearchMode::Normal => false,
 152            SearchMode::KeyStroke { exact_match } => *exact_match,
 153        }
 154    }
 155}
 156
 157#[derive(Default, PartialEq, Copy, Clone)]
 158enum FilterState {
 159    #[default]
 160    All,
 161    Conflicts,
 162}
 163
 164impl FilterState {
 165    fn invert(&self) -> Self {
 166        match self {
 167            FilterState::All => FilterState::Conflicts,
 168            FilterState::Conflicts => FilterState::All,
 169        }
 170    }
 171}
 172
 173#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
 174struct ActionMapping {
 175    keystrokes: Vec<Keystroke>,
 176    context: Option<SharedString>,
 177}
 178
 179#[derive(Debug)]
 180struct KeybindConflict {
 181    first_conflict_index: usize,
 182    remaining_conflict_amount: usize,
 183}
 184
 185impl KeybindConflict {
 186    fn from_iter<'a>(mut indices: impl Iterator<Item = &'a ConflictOrigin>) -> Option<Self> {
 187        indices.next().map(|origin| Self {
 188            first_conflict_index: origin.index,
 189            remaining_conflict_amount: indices.count(),
 190        })
 191    }
 192}
 193
 194#[derive(Clone, Copy, PartialEq)]
 195struct ConflictOrigin {
 196    override_source: KeybindSource,
 197    overridden_source: Option<KeybindSource>,
 198    index: usize,
 199}
 200
 201impl ConflictOrigin {
 202    fn new(source: KeybindSource, index: usize) -> Self {
 203        Self {
 204            override_source: source,
 205            index,
 206            overridden_source: None,
 207        }
 208    }
 209
 210    fn with_overridden_source(self, source: KeybindSource) -> Self {
 211        Self {
 212            overridden_source: Some(source),
 213            ..self
 214        }
 215    }
 216
 217    fn get_conflict_with(&self, other: &Self) -> Option<Self> {
 218        if self.override_source == KeybindSource::User
 219            && other.override_source == KeybindSource::User
 220        {
 221            Some(
 222                Self::new(KeybindSource::User, other.index)
 223                    .with_overridden_source(self.override_source),
 224            )
 225        } else if self.override_source > other.override_source {
 226            Some(other.with_overridden_source(self.override_source))
 227        } else {
 228            None
 229        }
 230    }
 231
 232    fn is_user_keybind_conflict(&self) -> bool {
 233        self.override_source == KeybindSource::User
 234            && self.overridden_source == Some(KeybindSource::User)
 235    }
 236}
 237
 238#[derive(Default)]
 239struct ConflictState {
 240    conflicts: Vec<Option<ConflictOrigin>>,
 241    keybind_mapping: HashMap<ActionMapping, Vec<ConflictOrigin>>,
 242    has_user_conflicts: bool,
 243}
 244
 245impl ConflictState {
 246    fn new(key_bindings: &[ProcessedBinding]) -> Self {
 247        let mut action_keybind_mapping: HashMap<_, Vec<ConflictOrigin>> = HashMap::default();
 248
 249        let mut largest_index = 0;
 250        for (index, binding) in key_bindings
 251            .iter()
 252            .enumerate()
 253            .flat_map(|(index, binding)| Some(index).zip(binding.keybind_information()))
 254        {
 255            action_keybind_mapping
 256                .entry(binding.get_action_mapping())
 257                .or_default()
 258                .push(ConflictOrigin::new(binding.source, index));
 259            largest_index = index;
 260        }
 261
 262        let mut conflicts = vec![None; largest_index + 1];
 263        let mut has_user_conflicts = false;
 264
 265        for indices in action_keybind_mapping.values_mut() {
 266            indices.sort_unstable_by_key(|origin| origin.override_source);
 267            let Some((fst, snd)) = indices.get(0).zip(indices.get(1)) else {
 268                continue;
 269            };
 270
 271            for origin in indices.iter() {
 272                conflicts[origin.index] =
 273                    origin.get_conflict_with(if origin == fst { snd } else { fst })
 274            }
 275
 276            has_user_conflicts |= fst.override_source == KeybindSource::User
 277                && snd.override_source == KeybindSource::User;
 278        }
 279
 280        Self {
 281            conflicts,
 282            keybind_mapping: action_keybind_mapping,
 283            has_user_conflicts,
 284        }
 285    }
 286
 287    fn conflicting_indices_for_mapping(
 288        &self,
 289        action_mapping: &ActionMapping,
 290        keybind_idx: Option<usize>,
 291    ) -> Option<KeybindConflict> {
 292        self.keybind_mapping
 293            .get(action_mapping)
 294            .and_then(|indices| {
 295                KeybindConflict::from_iter(
 296                    indices
 297                        .iter()
 298                        .filter(|&conflict| Some(conflict.index) != keybind_idx),
 299                )
 300            })
 301    }
 302
 303    fn conflict_for_idx(&self, idx: usize) -> Option<ConflictOrigin> {
 304        self.conflicts.get(idx).copied().flatten()
 305    }
 306
 307    fn has_user_conflict(&self, candidate_idx: usize) -> bool {
 308        self.conflict_for_idx(candidate_idx)
 309            .is_some_and(|conflict| conflict.is_user_keybind_conflict())
 310    }
 311
 312    fn any_user_binding_conflicts(&self) -> bool {
 313        self.has_user_conflicts
 314    }
 315}
 316
 317struct KeymapEditor {
 318    workspace: WeakEntity<Workspace>,
 319    focus_handle: FocusHandle,
 320    _keymap_subscription: Subscription,
 321    keybindings: Vec<ProcessedBinding>,
 322    keybinding_conflict_state: ConflictState,
 323    filter_state: FilterState,
 324    search_mode: SearchMode,
 325    search_query_debounce: Option<Task<()>>,
 326    // corresponds 1 to 1 with keybindings
 327    string_match_candidates: Arc<Vec<StringMatchCandidate>>,
 328    matches: Vec<StringMatch>,
 329    table_interaction_state: Entity<TableInteractionState>,
 330    filter_editor: Entity<Editor>,
 331    keystroke_editor: Entity<KeystrokeInput>,
 332    selected_index: Option<usize>,
 333    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
 334    previous_edit: Option<PreviousEdit>,
 335    humanized_action_names: HumanizedActionNameCache,
 336    current_widths: Entity<ColumnWidths<6>>,
 337    show_hover_menus: bool,
 338    /// In order for the JSON LSP to run in the actions arguments editor, we
 339    /// require a backing file In order to avoid issues (primarily log spam)
 340    /// with drop order between the buffer, file, worktree, etc, we create a
 341    /// temporary directory for these backing files in the keymap editor struct
 342    /// instead of here. This has the added benefit of only having to create a
 343    /// worktree and directory once, although the perf improvement is negligible.
 344    action_args_temp_dir_worktree: Option<Entity<project::Worktree>>,
 345    action_args_temp_dir: Option<tempfile::TempDir>,
 346}
 347
 348enum PreviousEdit {
 349    /// When deleting, we want to maintain the same scroll position
 350    ScrollBarOffset(Point<Pixels>),
 351    /// When editing or creating, because the new keybinding could be in a different position in the sort order
 352    /// we store metadata about the new binding (either the modified version or newly created one)
 353    /// and upon reload, we search for this binding in the list of keybindings, and if we find the one that matches
 354    /// this metadata, we set the selected index to it and scroll to it,
 355    /// and if we don't find it, we scroll to 0 and don't set a selected index
 356    Keybinding {
 357        action_mapping: ActionMapping,
 358        action_name: &'static str,
 359        /// The scrollbar position to fallback to if we don't find the keybinding during a refresh
 360        /// this can happen if there's a filter applied to the search and the keybinding modification
 361        /// filters the binding from the search results
 362        fallback: Point<Pixels>,
 363    },
 364}
 365
 366impl EventEmitter<()> for KeymapEditor {}
 367
 368impl Focusable for KeymapEditor {
 369    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 370        if self.selected_index.is_some() {
 371            self.focus_handle.clone()
 372        } else {
 373            self.filter_editor.focus_handle(cx)
 374        }
 375    }
 376}
 377/// Helper function to check if two keystroke sequences match exactly
 378fn keystrokes_match_exactly(keystrokes1: &[Keystroke], keystrokes2: &[Keystroke]) -> bool {
 379    keystrokes1.len() == keystrokes2.len()
 380        && keystrokes1
 381            .iter()
 382            .zip(keystrokes2)
 383            .all(|(k1, k2)| k1.key == k2.key && k1.modifiers == k2.modifiers)
 384}
 385
 386impl KeymapEditor {
 387    fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
 388        let _keymap_subscription =
 389            cx.observe_global_in::<KeymapEventChannel>(window, Self::on_keymap_changed);
 390        let table_interaction_state = TableInteractionState::new(window, cx);
 391
 392        let keystroke_editor = cx.new(|cx| {
 393            let mut keystroke_editor = KeystrokeInput::new(None, window, cx);
 394            keystroke_editor.set_search(true);
 395            keystroke_editor
 396        });
 397
 398        let filter_editor = cx.new(|cx| {
 399            let mut editor = Editor::single_line(window, cx);
 400            editor.set_placeholder_text("Filter action names…", cx);
 401            editor
 402        });
 403
 404        cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
 405            if !matches!(e, EditorEvent::BufferEdited) {
 406                return;
 407            }
 408
 409            this.on_query_changed(cx);
 410        })
 411        .detach();
 412
 413        cx.subscribe(&keystroke_editor, |this, _, _, cx| {
 414            if matches!(this.search_mode, SearchMode::Normal) {
 415                return;
 416            }
 417
 418            this.on_query_changed(cx);
 419        })
 420        .detach();
 421
 422        cx.spawn({
 423            let workspace = workspace.clone();
 424            async move |this, cx| {
 425                let temp_dir = tempfile::tempdir_in(paths::temp_dir())?;
 426                let worktree = workspace
 427                    .update(cx, |ws, cx| {
 428                        ws.project()
 429                            .update(cx, |p, cx| p.create_worktree(temp_dir.path(), false, cx))
 430                    })?
 431                    .await?;
 432                this.update(cx, |this, _| {
 433                    this.action_args_temp_dir = Some(temp_dir);
 434                    this.action_args_temp_dir_worktree = Some(worktree);
 435                })
 436            }
 437        })
 438        .detach();
 439
 440        let mut this = Self {
 441            workspace,
 442            keybindings: vec![],
 443            keybinding_conflict_state: ConflictState::default(),
 444            filter_state: FilterState::default(),
 445            search_mode: SearchMode::default(),
 446            string_match_candidates: Arc::new(vec![]),
 447            matches: vec![],
 448            focus_handle: cx.focus_handle(),
 449            _keymap_subscription,
 450            table_interaction_state,
 451            filter_editor,
 452            keystroke_editor,
 453            selected_index: None,
 454            context_menu: None,
 455            previous_edit: None,
 456            search_query_debounce: None,
 457            humanized_action_names: HumanizedActionNameCache::new(cx),
 458            show_hover_menus: true,
 459            action_args_temp_dir: None,
 460            action_args_temp_dir_worktree: None,
 461            current_widths: cx.new(|cx| ColumnWidths::new(cx)),
 462        };
 463
 464        this.on_keymap_changed(window, cx);
 465
 466        this
 467    }
 468
 469    fn current_action_query(&self, cx: &App) -> String {
 470        self.filter_editor.read(cx).text(cx)
 471    }
 472
 473    fn current_keystroke_query(&self, cx: &App) -> Vec<Keystroke> {
 474        match self.search_mode {
 475            SearchMode::KeyStroke { .. } => self
 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                                right_click_menu("open-keymap-menu")
1541                                    .menu(|window, cx| {
1542                                        ContextMenu::build(window, cx, |menu, _, _| {
1543                                            menu.header("Open Keymap JSON")
1544                                                .action("User", zed_actions::OpenKeymap.boxed_clone())
1545                                                .action("Zed Default", zed_actions::OpenDefaultKeymap.boxed_clone())
1546                                                .action("Vim Default", vim::OpenDefaultKeymap.boxed_clone())
1547                                        })
1548                                    })
1549                                    .anchor(gpui::Corner::TopLeft)
1550                                    .trigger(|open, _, _|
1551                                        IconButton::new(
1552                                            "OpenKeymapJsonButton",
1553                                            IconName::Json
1554                                        )
1555                                        .shape(ui::IconButtonShape::Square)
1556                                        .when(!open, |this|
1557                                            this.tooltip(move |window, cx| {
1558                                                Tooltip::with_meta("Open Keymap JSON", Some(&zed_actions::OpenKeymap),"Right click to view more options", window, cx)
1559                                            })
1560                                        )
1561                                        .on_click(|_, window, cx| {
1562                                            window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx);
1563                                        })
1564                                    )
1565                            )
1566                            .child(
1567                                div()
1568                                    .key_context({
1569                                        let mut context = KeyContext::new_with_defaults();
1570                                        context.add("BufferSearchBar");
1571                                        context
1572                                    })
1573                                    .size_full()
1574                                    .h_8()
1575                                    .pl_2()
1576                                    .pr_1()
1577                                    .py_1()
1578                                    .border_1()
1579                                    .border_color(theme.colors().border)
1580                                    .rounded_lg()
1581                                    .child(self.filter_editor.clone()),
1582                            )
1583                            .child(
1584                                IconButton::new(
1585                                    "KeymapEditorToggleFiltersIcon",
1586                                    IconName::Keyboard,
1587                                )
1588                                .shape(ui::IconButtonShape::Square)
1589                                .tooltip({
1590                                    let focus_handle = focus_handle.clone();
1591
1592                                    move |window, cx| {
1593                                        Tooltip::for_action_in(
1594                                            "Search by Keystroke",
1595                                            &ToggleKeystrokeSearch,
1596                                            &focus_handle.clone(),
1597                                            window,
1598                                            cx,
1599                                        )
1600                                    }
1601                                })
1602                                .toggle_state(matches!(
1603                                    self.search_mode,
1604                                    SearchMode::KeyStroke { .. }
1605                                ))
1606                                .on_click(|_, window, cx| {
1607                                    window.dispatch_action(ToggleKeystrokeSearch.boxed_clone(), cx);
1608                                }),
1609                            )
1610                            .child(
1611                                IconButton::new("KeymapEditorConflictIcon", IconName::Warning)
1612                                    .shape(ui::IconButtonShape::Square)
1613                                    .when(
1614                                        self.keybinding_conflict_state.any_user_binding_conflicts(),
1615                                        |this| {
1616                                            this.indicator(Indicator::dot().color(Color::Warning))
1617                                        },
1618                                    )
1619                                    .tooltip({
1620                                        let filter_state = self.filter_state;
1621                                        let focus_handle = focus_handle.clone();
1622
1623                                        move |window, cx| {
1624                                            Tooltip::for_action_in(
1625                                                match filter_state {
1626                                                    FilterState::All => "Show Conflicts",
1627                                                    FilterState::Conflicts => "Hide Conflicts",
1628                                                },
1629                                                &ToggleConflictFilter,
1630                                                &focus_handle.clone(),
1631                                                window,
1632                                                cx,
1633                                            )
1634                                        }
1635                                    })
1636                                    .selected_icon_color(Color::Warning)
1637                                    .toggle_state(matches!(
1638                                        self.filter_state,
1639                                        FilterState::Conflicts
1640                                    ))
1641                                    .on_click(|_, window, cx| {
1642                                        window.dispatch_action(
1643                                            ToggleConflictFilter.boxed_clone(),
1644                                            cx,
1645                                        );
1646                                    }),
1647                            ),
1648                    )
1649                    .when_some(
1650                        match self.search_mode {
1651                            SearchMode::Normal => None,
1652                            SearchMode::KeyStroke { exact_match } => Some(exact_match),
1653                        },
1654                        |this, exact_match| {
1655                            this.child(
1656                                h_flex()
1657                                    .map(|this| {
1658                                        if self
1659                                            .keybinding_conflict_state
1660                                            .any_user_binding_conflicts()
1661                                        {
1662                                            this.pr(rems_from_px(54.))
1663                                        } else {
1664                                            this.pr_7()
1665                                        }
1666                                    })
1667                                    .gap_2()
1668                                    .child(self.keystroke_editor.clone())
1669                                    .child(
1670                                        IconButton::new(
1671                                            "keystrokes-exact-match",
1672                                            IconName::CaseSensitive,
1673                                        )
1674                                        .tooltip({
1675                                            let keystroke_focus_handle =
1676                                                self.keystroke_editor.read(cx).focus_handle(cx);
1677
1678                                            move |window, cx| {
1679                                                Tooltip::for_action_in(
1680                                                    "Toggle Exact Match Mode",
1681                                                    &ToggleExactKeystrokeMatching,
1682                                                    &keystroke_focus_handle,
1683                                                    window,
1684                                                    cx,
1685                                                )
1686                                            }
1687                                        })
1688                                        .shape(IconButtonShape::Square)
1689                                        .toggle_state(exact_match)
1690                                        .on_click(
1691                                            cx.listener(|_, _, window, cx| {
1692                                                window.dispatch_action(
1693                                                    ToggleExactKeystrokeMatching.boxed_clone(),
1694                                                    cx,
1695                                                );
1696                                            }),
1697                                        ),
1698                                    ),
1699                            )
1700                        },
1701                    ),
1702            )
1703            .child(
1704                Table::new()
1705                    .interactable(&self.table_interaction_state)
1706                    .striped()
1707                    .empty_table_callback({
1708                        let this = cx.entity();
1709                        move |window, cx| this.read(cx).render_no_matches_hint(window, cx)
1710                    })
1711                    .column_widths([
1712                        DefiniteLength::Absolute(AbsoluteLength::Pixels(px(36.))),
1713                        DefiniteLength::Fraction(0.25),
1714                        DefiniteLength::Fraction(0.20),
1715                        DefiniteLength::Fraction(0.14),
1716                        DefiniteLength::Fraction(0.45),
1717                        DefiniteLength::Fraction(0.08),
1718                    ])
1719                    .resizable_columns(
1720                        [
1721                            ResizeBehavior::None,
1722                            ResizeBehavior::Resizable,
1723                            ResizeBehavior::Resizable,
1724                            ResizeBehavior::Resizable,
1725                            ResizeBehavior::Resizable,
1726                            ResizeBehavior::Resizable, // this column doesn't matter
1727                        ],
1728                        &self.current_widths,
1729                        cx,
1730                    )
1731                    .header(["", "Action", "Arguments", "Keystrokes", "Context", "Source"])
1732                    .uniform_list(
1733                        "keymap-editor-table",
1734                        row_count,
1735                        cx.processor(move |this, range: Range<usize>, _window, cx| {
1736                            let context_menu_deployed = this.context_menu_deployed();
1737                            range
1738                                .filter_map(|index| {
1739                                    let candidate_id = this.matches.get(index)?.candidate_id;
1740                                    let binding = &this.keybindings[candidate_id];
1741                                    let action_name = binding.action().name;
1742                                    let conflict = this.get_conflict(index);
1743                                    let is_overridden = conflict.is_some_and(|conflict| {
1744                                        !conflict.is_user_keybind_conflict()
1745                                    });
1746
1747                                    let icon = this.create_row_button(index, conflict, cx);
1748
1749                                    let action = div()
1750                                        .id(("keymap action", index))
1751                                        .child({
1752                                            if action_name != gpui::NoAction.name() {
1753                                                binding
1754                                                    .action()
1755                                                    .humanized_name
1756                                                    .clone()
1757                                                    .into_any_element()
1758                                            } else {
1759                                                const NULL: SharedString =
1760                                                    SharedString::new_static("<null>");
1761                                                muted_styled_text(NULL.clone(), cx)
1762                                                    .into_any_element()
1763                                            }
1764                                        })
1765                                        .when(
1766                                            !context_menu_deployed
1767                                                && this.show_hover_menus
1768                                                && !is_overridden,
1769                                            |this| {
1770                                                this.tooltip({
1771                                                    let action_name = binding.action().name;
1772                                                    let action_docs =
1773                                                        binding.action().documentation;
1774                                                    move |_, cx| {
1775                                                        let action_tooltip =
1776                                                            Tooltip::new(action_name);
1777                                                        let action_tooltip = match action_docs {
1778                                                            Some(docs) => action_tooltip.meta(docs),
1779                                                            None => action_tooltip,
1780                                                        };
1781                                                        cx.new(|_| action_tooltip).into()
1782                                                    }
1783                                                })
1784                                            },
1785                                        )
1786                                        .into_any_element();
1787
1788                                    let keystrokes = binding.ui_key_binding().cloned().map_or(
1789                                        binding
1790                                            .keystroke_text()
1791                                            .cloned()
1792                                            .unwrap_or_default()
1793                                            .into_any_element(),
1794                                        IntoElement::into_any_element,
1795                                    );
1796
1797                                    let action_arguments = match binding.action().arguments.clone()
1798                                    {
1799                                        Some(arguments) => arguments.into_any_element(),
1800                                        None => {
1801                                            if binding.action().has_schema {
1802                                                muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
1803                                                    .into_any_element()
1804                                            } else {
1805                                                gpui::Empty.into_any_element()
1806                                            }
1807                                        }
1808                                    };
1809
1810                                    let context = binding.context().cloned().map_or(
1811                                        gpui::Empty.into_any_element(),
1812                                        |context| {
1813                                            let is_local = context.local().is_some();
1814
1815                                            div()
1816                                                .id(("keymap context", index))
1817                                                .child(context.clone())
1818                                                .when(
1819                                                    is_local
1820                                                        && !context_menu_deployed
1821                                                        && !is_overridden
1822                                                        && this.show_hover_menus,
1823                                                    |this| {
1824                                                        this.tooltip(Tooltip::element({
1825                                                            move |_, _| {
1826                                                                context.clone().into_any_element()
1827                                                            }
1828                                                        }))
1829                                                    },
1830                                                )
1831                                                .into_any_element()
1832                                        },
1833                                    );
1834
1835                                    let source = binding
1836                                        .keybind_source()
1837                                        .map(|source| source.name())
1838                                        .unwrap_or_default()
1839                                        .into_any_element();
1840
1841                                    Some([
1842                                        icon.into_any_element(),
1843                                        action,
1844                                        action_arguments,
1845                                        keystrokes,
1846                                        context,
1847                                        source,
1848                                    ])
1849                                })
1850                                .collect()
1851                        }),
1852                    )
1853                    .map_row(cx.processor(
1854                        |this, (row_index, row): (usize, Stateful<Div>), _window, cx| {
1855                        let conflict = this.get_conflict(row_index);
1856                            let is_selected = this.selected_index == Some(row_index);
1857
1858                            let row_id = row_group_id(row_index);
1859
1860                            div()
1861                                .id(("keymap-row-wrapper", row_index))
1862                                .child(
1863                                    row.id(row_id.clone())
1864                                        .on_any_mouse_down(cx.listener(
1865                                            move |this,
1866                                                  mouse_down_event: &gpui::MouseDownEvent,
1867                                                  window,
1868                                                  cx| {
1869                                                match mouse_down_event.button {
1870                                                    MouseButton::Right => {
1871                                                        this.select_index(
1872                                                            row_index, None, window, cx,
1873                                                        );
1874                                                        this.create_context_menu(
1875                                                            mouse_down_event.position,
1876                                                            window,
1877                                                            cx,
1878                                                        );
1879                                                    }
1880                                                    _ => {}
1881                                                }
1882                                            },
1883                                        ))
1884                                        .on_click(cx.listener(
1885                                            move |this, event: &ClickEvent, window, cx| {
1886                                                this.select_index(row_index, None, window, cx);
1887                                                if event.click_count() == 2 {
1888                                                    this.open_edit_keybinding_modal(
1889                                                        false, window, cx,
1890                                                    );
1891                                                }
1892                                            },
1893                                        ))
1894                                        .group(row_id)
1895                                        .when(
1896                                            conflict.is_some_and(|conflict| {
1897                                                !conflict.is_user_keybind_conflict()
1898                                            }),
1899                                            |row| {
1900                                                const OVERRIDDEN_OPACITY: f32 = 0.5;
1901                                                row.opacity(OVERRIDDEN_OPACITY)
1902                                            },
1903                                        )
1904                                        .when_some(
1905                                            conflict.filter(|conflict| {
1906                                                !this.context_menu_deployed() &&
1907                                                !conflict.is_user_keybind_conflict()
1908                                            }),
1909                                            |row, conflict| {
1910                                                let overriding_binding = this.keybindings.get(conflict.index);
1911                                                let context = overriding_binding.and_then(|binding| {
1912                                                    match conflict.override_source {
1913                                                        KeybindSource::User  => Some("your keymap"),
1914                                                        KeybindSource::Vim => Some("the vim keymap"),
1915                                                        KeybindSource::Base => Some("your base keymap"),
1916                                                        _ => {
1917                                                            log::error!("Unexpected override from the {} keymap", conflict.override_source.name());
1918                                                            None
1919                                                        }
1920                                                    }.map(|source| format!("This keybinding is overridden by the '{}' binding from {}.", binding.action().humanized_name, source))
1921                                                }).unwrap_or_else(|| "This binding is overridden.".to_string());
1922
1923                                                row.tooltip(Tooltip::text(context))},
1924                                        ),
1925                                )
1926                                .border_2()
1927                                .when(
1928                                    conflict.is_some_and(|conflict| {
1929                                        conflict.is_user_keybind_conflict()
1930                                    }),
1931                                    |row| row.bg(cx.theme().status().error_background),
1932                                )
1933                                .when(is_selected, |row| {
1934                                    row.border_color(cx.theme().colors().panel_focused_border)
1935                                })
1936                                .into_any_element()
1937                        }),
1938                    ),
1939            )
1940            .on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
1941                // This ensures that the menu is not dismissed in cases where scroll events
1942                // with a delta of zero are emitted
1943                if !event.delta.pixel_delta(px(1.)).y.is_zero() {
1944                    this.context_menu.take();
1945                    cx.notify();
1946                }
1947            }))
1948            .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1949                deferred(
1950                    anchored()
1951                        .position(*position)
1952                        .anchor(gpui::Corner::TopLeft)
1953                        .child(menu.clone()),
1954                )
1955                .with_priority(1)
1956            }))
1957    }
1958}
1959
1960fn row_group_id(row_index: usize) -> SharedString {
1961    SharedString::new(format!("keymap-table-row-{}", row_index))
1962}
1963
1964fn base_button_style(row_index: usize, icon: IconName) -> IconButton {
1965    IconButton::new(("keymap-icon", row_index), icon)
1966        .shape(IconButtonShape::Square)
1967        .size(ButtonSize::Compact)
1968}
1969
1970#[derive(Debug, Clone, IntoElement)]
1971struct SyntaxHighlightedText {
1972    text: SharedString,
1973    language: Arc<Language>,
1974}
1975
1976impl SyntaxHighlightedText {
1977    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
1978        Self {
1979            text: text.into(),
1980            language,
1981        }
1982    }
1983}
1984
1985impl RenderOnce for SyntaxHighlightedText {
1986    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
1987        let text_style = window.text_style();
1988        let syntax_theme = cx.theme().syntax();
1989
1990        let text = self.text.clone();
1991
1992        let highlights = self
1993            .language
1994            .highlight_text(&text.as_ref().into(), 0..text.len());
1995        let mut runs = Vec::with_capacity(highlights.len());
1996        let mut offset = 0;
1997
1998        for (highlight_range, highlight_id) in highlights {
1999            // Add un-highlighted text before the current highlight
2000            if highlight_range.start > offset {
2001                runs.push(text_style.to_run(highlight_range.start - offset));
2002            }
2003
2004            let mut run_style = text_style.clone();
2005            if let Some(highlight_style) = highlight_id.style(syntax_theme) {
2006                run_style = run_style.highlight(highlight_style);
2007            }
2008            // add the highlighted range
2009            runs.push(run_style.to_run(highlight_range.len()));
2010            offset = highlight_range.end;
2011        }
2012
2013        // Add any remaining un-highlighted text
2014        if offset < text.len() {
2015            runs.push(text_style.to_run(text.len() - offset));
2016        }
2017
2018        StyledText::new(text).with_runs(runs)
2019    }
2020}
2021
2022#[derive(PartialEq)]
2023struct InputError {
2024    severity: ui::Severity,
2025    content: SharedString,
2026}
2027
2028impl InputError {
2029    fn warning(message: impl Into<SharedString>) -> Self {
2030        Self {
2031            severity: ui::Severity::Warning,
2032            content: message.into(),
2033        }
2034    }
2035
2036    fn error(message: anyhow::Error) -> Self {
2037        Self {
2038            severity: ui::Severity::Error,
2039            content: message.to_string().into(),
2040        }
2041    }
2042}
2043
2044struct KeybindingEditorModal {
2045    creating: bool,
2046    editing_keybind: ProcessedBinding,
2047    editing_keybind_idx: usize,
2048    keybind_editor: Entity<KeystrokeInput>,
2049    context_editor: Entity<SingleLineInput>,
2050    action_arguments_editor: Option<Entity<ActionArgumentsEditor>>,
2051    fs: Arc<dyn Fs>,
2052    error: Option<InputError>,
2053    keymap_editor: Entity<KeymapEditor>,
2054    workspace: WeakEntity<Workspace>,
2055    focus_state: KeybindingEditorModalFocusState,
2056}
2057
2058impl ModalView for KeybindingEditorModal {}
2059
2060impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
2061
2062impl Focusable for KeybindingEditorModal {
2063    fn focus_handle(&self, cx: &App) -> FocusHandle {
2064        self.keybind_editor.focus_handle(cx)
2065    }
2066}
2067
2068impl KeybindingEditorModal {
2069    pub fn new(
2070        create: bool,
2071        editing_keybind: ProcessedBinding,
2072        editing_keybind_idx: usize,
2073        keymap_editor: Entity<KeymapEditor>,
2074        action_args_temp_dir: Option<&std::path::Path>,
2075        workspace: WeakEntity<Workspace>,
2076        fs: Arc<dyn Fs>,
2077        window: &mut Window,
2078        cx: &mut App,
2079    ) -> Self {
2080        let keybind_editor = cx
2081            .new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx));
2082
2083        let context_editor: Entity<SingleLineInput> = cx.new(|cx| {
2084            let input = SingleLineInput::new(window, cx, "Keybinding Context")
2085                .label("Edit Context")
2086                .label_size(LabelSize::Default);
2087
2088            if let Some(context) = editing_keybind
2089                .context()
2090                .and_then(KeybindContextString::local)
2091            {
2092                input.editor().update(cx, |editor, cx| {
2093                    editor.set_text(context.clone(), window, cx);
2094                });
2095            }
2096
2097            let editor_entity = input.editor().clone();
2098            let workspace = workspace.clone();
2099            cx.spawn(async move |_input_handle, cx| {
2100                let contexts = cx
2101                    .background_spawn(async { collect_contexts_from_assets() })
2102                    .await;
2103
2104                let language = load_keybind_context_language(workspace, cx).await;
2105                editor_entity
2106                    .update(cx, |editor, cx| {
2107                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
2108                            buffer.update(cx, |buffer, cx| {
2109                                buffer.set_language(Some(language), cx);
2110                            });
2111                        }
2112                        editor.set_completion_provider(Some(std::rc::Rc::new(
2113                            KeyContextCompletionProvider { contexts },
2114                        )));
2115                    })
2116                    .context("Failed to load completions for keybinding context")
2117            })
2118            .detach_and_log_err(cx);
2119
2120            input
2121        });
2122
2123        let action_arguments_editor = editing_keybind.action().has_schema.then(|| {
2124            let arguments = editing_keybind
2125                .action()
2126                .arguments
2127                .as_ref()
2128                .map(|args| args.text.clone());
2129            cx.new(|cx| {
2130                ActionArgumentsEditor::new(
2131                    editing_keybind.action().name,
2132                    arguments,
2133                    action_args_temp_dir,
2134                    workspace.clone(),
2135                    window,
2136                    cx,
2137                )
2138            })
2139        });
2140
2141        let focus_state = KeybindingEditorModalFocusState::new(
2142            keybind_editor.focus_handle(cx),
2143            action_arguments_editor
2144                .as_ref()
2145                .map(|args_editor| args_editor.focus_handle(cx)),
2146            context_editor.focus_handle(cx),
2147        );
2148
2149        Self {
2150            creating: create,
2151            editing_keybind,
2152            editing_keybind_idx,
2153            fs,
2154            keybind_editor,
2155            context_editor,
2156            action_arguments_editor,
2157            error: None,
2158            keymap_editor,
2159            workspace,
2160            focus_state,
2161        }
2162    }
2163
2164    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
2165        if self.error.as_ref().is_some_and(|old_error| {
2166            old_error.severity == ui::Severity::Warning && *old_error == error
2167        }) {
2168            false
2169        } else {
2170            self.error = Some(error);
2171            cx.notify();
2172            true
2173        }
2174    }
2175
2176    fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
2177        let action_arguments = self
2178            .action_arguments_editor
2179            .as_ref()
2180            .map(|arguments_editor| arguments_editor.read(cx).editor.read(cx).text(cx))
2181            .filter(|args| !args.is_empty());
2182
2183        let value = action_arguments
2184            .as_ref()
2185            .map(|args| {
2186                serde_json::from_str(args).context("Failed to parse action arguments as JSON")
2187            })
2188            .transpose()?;
2189
2190        cx.build_action(self.editing_keybind.action().name, value)
2191            .context("Failed to validate action arguments")?;
2192        Ok(action_arguments)
2193    }
2194
2195    fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<Keystroke>> {
2196        let new_keystrokes = self
2197            .keybind_editor
2198            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
2199        anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
2200        Ok(new_keystrokes)
2201    }
2202
2203    fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
2204        let new_context = self
2205            .context_editor
2206            .read_with(cx, |input, cx| input.editor().read(cx).text(cx));
2207        let Some(context) = new_context.is_empty().not().then_some(new_context) else {
2208            return Ok(None);
2209        };
2210        gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
2211
2212        Ok(Some(context))
2213    }
2214
2215    fn save_or_display_error(&mut self, cx: &mut Context<Self>) {
2216        self.save(cx).map_err(|err| self.set_error(err, cx)).ok();
2217    }
2218
2219    fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
2220        let existing_keybind = self.editing_keybind.clone();
2221        let fs = self.fs.clone();
2222        let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
2223
2224        let new_keystrokes = self
2225            .validate_keystrokes(cx)
2226            .map_err(InputError::error)?
2227            .into_iter()
2228            .map(remove_key_char)
2229            .collect::<Vec<_>>();
2230
2231        let new_context = self.validate_context(cx).map_err(InputError::error)?;
2232        let new_action_args = self
2233            .validate_action_arguments(cx)
2234            .map_err(InputError::error)?;
2235
2236        let action_mapping = ActionMapping {
2237            keystrokes: new_keystrokes,
2238            context: new_context.map(SharedString::from),
2239        };
2240
2241        let conflicting_indices = self
2242            .keymap_editor
2243            .read(cx)
2244            .keybinding_conflict_state
2245            .conflicting_indices_for_mapping(
2246                &action_mapping,
2247                self.creating.not().then_some(self.editing_keybind_idx),
2248            );
2249
2250        conflicting_indices.map(|KeybindConflict {
2251            first_conflict_index,
2252            remaining_conflict_amount,
2253        }|
2254        {
2255            let conflicting_action_name = self
2256                .keymap_editor
2257                .read(cx)
2258                .keybindings
2259                .get(first_conflict_index)
2260                .map(|keybind| keybind.action().name);
2261
2262            let warning_message = match conflicting_action_name {
2263                Some(name) => {
2264                     if remaining_conflict_amount > 0 {
2265                        format!(
2266                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
2267                            name, remaining_conflict_amount
2268                        )
2269                    } else {
2270                        format!("Your keybind would conflict with the \"{}\" action", name)
2271                    }
2272                }
2273                None => {
2274                    log::info!(
2275                        "Could not find action in keybindings with index {}",
2276                        first_conflict_index
2277                    );
2278                    "Your keybind would conflict with other actions".to_string()
2279                }
2280            };
2281
2282            let warning = InputError::warning(warning_message);
2283            if self.error.as_ref().is_some_and(|old_error| *old_error == warning) {
2284                Ok(())
2285           } else {
2286                Err(warning)
2287            }
2288        }).unwrap_or(Ok(()))?;
2289
2290        let create = self.creating;
2291
2292        cx.spawn(async move |this, cx| {
2293            let action_name = existing_keybind.action().name;
2294            let humanized_action_name = existing_keybind.action().humanized_name.clone();
2295
2296            match save_keybinding_update(
2297                create,
2298                existing_keybind,
2299                &action_mapping,
2300                new_action_args.as_deref(),
2301                &fs,
2302                tab_size,
2303            )
2304            .await
2305            {
2306                Ok(_) => {
2307                    this.update(cx, |this, cx| {
2308                        this.keymap_editor.update(cx, |keymap, cx| {
2309                            keymap.previous_edit = Some(PreviousEdit::Keybinding {
2310                                action_mapping,
2311                                action_name,
2312                                fallback: keymap
2313                                    .table_interaction_state
2314                                    .read(cx)
2315                                    .get_scrollbar_offset(Axis::Vertical),
2316                            });
2317                            let status_toast = StatusToast::new(
2318                                format!("Saved edits to the {} action.", humanized_action_name),
2319                                cx,
2320                                move |this, _cx| {
2321                                    this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
2322                                        .dismiss_button(true)
2323                                    // .action("Undo", f) todo: wire the undo functionality
2324                                },
2325                            );
2326
2327                            this.workspace
2328                                .update(cx, |workspace, cx| {
2329                                    workspace.toggle_status_toast(status_toast, cx);
2330                                })
2331                                .log_err();
2332                        });
2333                        cx.emit(DismissEvent);
2334                    })
2335                    .ok();
2336                }
2337                Err(err) => {
2338                    this.update(cx, |this, cx| {
2339                        this.set_error(InputError::error(err), cx);
2340                    })
2341                    .log_err();
2342                }
2343            }
2344        })
2345        .detach();
2346
2347        Ok(())
2348    }
2349
2350    fn key_context(&self) -> KeyContext {
2351        let mut key_context = KeyContext::new_with_defaults();
2352        key_context.add("KeybindEditorModal");
2353        key_context
2354    }
2355
2356    fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
2357        self.focus_state.focus_next(window, cx);
2358    }
2359
2360    fn focus_prev(
2361        &mut self,
2362        _: &menu::SelectPrevious,
2363        window: &mut Window,
2364        cx: &mut Context<Self>,
2365    ) {
2366        self.focus_state.focus_previous(window, cx);
2367    }
2368
2369    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
2370        self.save_or_display_error(cx);
2371    }
2372
2373    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
2374        cx.emit(DismissEvent);
2375    }
2376
2377    fn get_matching_bindings_count(&self, cx: &Context<Self>) -> usize {
2378        let current_keystrokes = self.keybind_editor.read(cx).keystrokes().to_vec();
2379
2380        if current_keystrokes.is_empty() {
2381            return 0;
2382        }
2383
2384        self.keymap_editor
2385            .read(cx)
2386            .keybindings
2387            .iter()
2388            .enumerate()
2389            .filter(|(idx, binding)| {
2390                // Don't count the binding we're currently editing
2391                if !self.creating && *idx == self.editing_keybind_idx {
2392                    return false;
2393                }
2394
2395                binding
2396                    .keystrokes()
2397                    .map(|keystrokes| keystrokes_match_exactly(keystrokes, &current_keystrokes))
2398                    .unwrap_or(false)
2399            })
2400            .count()
2401    }
2402
2403    fn show_matching_bindings(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
2404        let keystrokes = self.keybind_editor.read(cx).keystrokes().to_vec();
2405
2406        // Dismiss the modal
2407        cx.emit(DismissEvent);
2408
2409        // Update the keymap editor to show matching keystrokes
2410        self.keymap_editor.update(cx, |editor, cx| {
2411            editor.filter_state = FilterState::All;
2412            editor.search_mode = SearchMode::KeyStroke { exact_match: true };
2413            editor.keystroke_editor.update(cx, |keystroke_editor, cx| {
2414                keystroke_editor.set_keystrokes(keystrokes, cx);
2415            });
2416        });
2417    }
2418}
2419
2420fn remove_key_char(Keystroke { modifiers, key, .. }: Keystroke) -> Keystroke {
2421    Keystroke {
2422        modifiers,
2423        key,
2424        ..Default::default()
2425    }
2426}
2427
2428impl Render for KeybindingEditorModal {
2429    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2430        let theme = cx.theme().colors();
2431        let matching_bindings_count = self.get_matching_bindings_count(cx);
2432
2433        v_flex()
2434            .w(rems(34.))
2435            .elevation_3(cx)
2436            .key_context(self.key_context())
2437            .on_action(cx.listener(Self::focus_next))
2438            .on_action(cx.listener(Self::focus_prev))
2439            .on_action(cx.listener(Self::confirm))
2440            .on_action(cx.listener(Self::cancel))
2441            .child(
2442                Modal::new("keybinding_editor_modal", None)
2443                    .header(
2444                        ModalHeader::new().child(
2445                            v_flex()
2446                                .w_full()
2447                                .pb_1p5()
2448                                .mb_1()
2449                                .gap_0p5()
2450                                .border_b_1()
2451                                .border_color(theme.border_variant)
2452                                .child(Label::new(
2453                                    self.editing_keybind.action().humanized_name.clone(),
2454                                ))
2455                                .when_some(
2456                                    self.editing_keybind.action().documentation,
2457                                    |this, docs| {
2458                                        this.child(
2459                                            Label::new(docs)
2460                                                .size(LabelSize::Small)
2461                                                .color(Color::Muted),
2462                                        )
2463                                    },
2464                                ),
2465                        ),
2466                    )
2467                    .section(
2468                        Section::new().child(
2469                            v_flex()
2470                                .gap_2p5()
2471                                .child(
2472                                    v_flex()
2473                                        .gap_1()
2474                                        .child(Label::new("Edit Keystroke"))
2475                                        .child(self.keybind_editor.clone())
2476                                        .child(h_flex().gap_px().when(
2477                                            matching_bindings_count > 0,
2478                                            |this| {
2479                                                let label = format!(
2480                                                    "There {} {} {} with the same keystrokes.",
2481                                                    if matching_bindings_count == 1 {
2482                                                        "is"
2483                                                    } else {
2484                                                        "are"
2485                                                    },
2486                                                    matching_bindings_count,
2487                                                    if matching_bindings_count == 1 {
2488                                                        "binding"
2489                                                    } else {
2490                                                        "bindings"
2491                                                    }
2492                                                );
2493
2494                                                this.child(
2495                                                    Label::new(label)
2496                                                        .size(LabelSize::Small)
2497                                                        .color(Color::Muted),
2498                                                )
2499                                                .child(
2500                                                    Button::new("show_matching", "View")
2501                                                        .label_size(LabelSize::Small)
2502                                                        .icon(IconName::ArrowUpRight)
2503                                                        .icon_color(Color::Muted)
2504                                                        .icon_size(IconSize::Small)
2505                                                        .on_click(cx.listener(
2506                                                            |this, _, window, cx| {
2507                                                                this.show_matching_bindings(
2508                                                                    window, cx,
2509                                                                );
2510                                                            },
2511                                                        )),
2512                                                )
2513                                            },
2514                                        )),
2515                                )
2516                                .when_some(self.action_arguments_editor.clone(), |this, editor| {
2517                                    this.child(
2518                                        v_flex()
2519                                            .gap_1()
2520                                            .child(Label::new("Edit Arguments"))
2521                                            .child(editor),
2522                                    )
2523                                })
2524                                .child(self.context_editor.clone())
2525                                .when_some(self.error.as_ref(), |this, error| {
2526                                    this.child(
2527                                        Banner::new()
2528                                            .severity(error.severity)
2529                                            .child(Label::new(error.content.clone())),
2530                                    )
2531                                }),
2532                        ),
2533                    )
2534                    .footer(
2535                        ModalFooter::new().end_slot(
2536                            h_flex()
2537                                .gap_1()
2538                                .child(
2539                                    Button::new("cancel", "Cancel")
2540                                        .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
2541                                )
2542                                .child(Button::new("save-btn", "Save").on_click(cx.listener(
2543                                    |this, _event, _window, cx| {
2544                                        this.save_or_display_error(cx);
2545                                    },
2546                                ))),
2547                        ),
2548                    ),
2549            )
2550    }
2551}
2552
2553struct KeybindingEditorModalFocusState {
2554    handles: Vec<FocusHandle>,
2555}
2556
2557impl KeybindingEditorModalFocusState {
2558    fn new(
2559        keystrokes: FocusHandle,
2560        action_input: Option<FocusHandle>,
2561        context: FocusHandle,
2562    ) -> Self {
2563        Self {
2564            handles: Vec::from_iter(
2565                [Some(keystrokes), action_input, Some(context)]
2566                    .into_iter()
2567                    .flatten(),
2568            ),
2569        }
2570    }
2571
2572    fn focused_index(&self, window: &Window, cx: &App) -> Option<i32> {
2573        self.handles
2574            .iter()
2575            .position(|handle| handle.contains_focused(window, cx))
2576            .map(|i| i as i32)
2577    }
2578
2579    fn focus_index(&self, mut index: i32, window: &mut Window) {
2580        if index < 0 {
2581            index = self.handles.len() as i32 - 1;
2582        }
2583        if index >= self.handles.len() as i32 {
2584            index = 0;
2585        }
2586        window.focus(&self.handles[index as usize]);
2587    }
2588
2589    fn focus_next(&self, window: &mut Window, cx: &App) {
2590        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
2591            index + 1
2592        } else {
2593            0
2594        };
2595        self.focus_index(index_to_focus, window);
2596    }
2597
2598    fn focus_previous(&self, window: &mut Window, cx: &App) {
2599        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
2600            index - 1
2601        } else {
2602            self.handles.len() as i32 - 1
2603        };
2604        self.focus_index(index_to_focus, window);
2605    }
2606}
2607
2608struct ActionArgumentsEditor {
2609    editor: Entity<Editor>,
2610    focus_handle: FocusHandle,
2611    is_loading: bool,
2612    /// See documentation in `KeymapEditor` for why a temp dir is needed.
2613    /// This field exists because the keymap editor temp dir creation may fail,
2614    /// and rather than implement a complicated retry mechanism, we simply
2615    /// fallback to trying to create a temporary directory in this editor on
2616    /// demand. Of note is that the TempDir struct will remove the directory
2617    /// when dropped.
2618    backup_temp_dir: Option<tempfile::TempDir>,
2619}
2620
2621impl Focusable for ActionArgumentsEditor {
2622    fn focus_handle(&self, _cx: &App) -> FocusHandle {
2623        self.focus_handle.clone()
2624    }
2625}
2626
2627impl ActionArgumentsEditor {
2628    fn new(
2629        action_name: &'static str,
2630        arguments: Option<SharedString>,
2631        temp_dir: Option<&std::path::Path>,
2632        workspace: WeakEntity<Workspace>,
2633        window: &mut Window,
2634        cx: &mut Context<Self>,
2635    ) -> Self {
2636        let focus_handle = cx.focus_handle();
2637        cx.on_focus_in(&focus_handle, window, |this, window, cx| {
2638            this.editor.focus_handle(cx).focus(window);
2639        })
2640        .detach();
2641        let editor = cx.new(|cx| {
2642            let mut editor = Editor::auto_height_unbounded(1, window, cx);
2643            Self::set_editor_text(&mut editor, arguments.clone(), window, cx);
2644            editor.set_read_only(true);
2645            editor
2646        });
2647
2648        let temp_dir = temp_dir.map(|path| path.to_owned());
2649        cx.spawn_in(window, async move |this, cx| {
2650            let result = async {
2651                let (project, fs) = workspace.read_with(cx, |workspace, _cx| {
2652                    (
2653                        workspace.project().downgrade(),
2654                        workspace.app_state().fs.clone(),
2655                    )
2656                })?;
2657
2658                let file_name =
2659                    project::lsp_store::json_language_server_ext::normalized_action_file_name(
2660                        action_name,
2661                    );
2662
2663                let (buffer, backup_temp_dir) =
2664                    Self::create_temp_buffer(temp_dir, file_name.clone(), project.clone(), fs, cx)
2665                        .await
2666                        .context(concat!(
2667                            "Failed to create temporary buffer for action arguments. ",
2668                            "Auto-complete will not work"
2669                        ))?;
2670
2671                let editor = cx.new_window_entity(|window, cx| {
2672                    let multi_buffer = cx.new(|cx| editor::MultiBuffer::singleton(buffer, cx));
2673                    let mut editor = Editor::new(
2674                        editor::EditorMode::Full {
2675                            scale_ui_elements_with_buffer_font_size: true,
2676                            show_active_line_background: false,
2677                            sized_by_content: true,
2678                        },
2679                        multi_buffer,
2680                        project.upgrade(),
2681                        window,
2682                        cx,
2683                    );
2684                    editor.set_searchable(false);
2685                    editor.disable_scrollbars_and_minimap(window, cx);
2686                    editor.set_show_edit_predictions(Some(false), window, cx);
2687                    editor.set_show_gutter(false, cx);
2688                    Self::set_editor_text(&mut editor, arguments, window, cx);
2689                    editor
2690                })?;
2691
2692                this.update_in(cx, |this, window, cx| {
2693                    if this.editor.focus_handle(cx).is_focused(window) {
2694                        editor.focus_handle(cx).focus(window);
2695                    }
2696                    this.editor = editor;
2697                    this.backup_temp_dir = backup_temp_dir;
2698                    this.is_loading = false;
2699                })?;
2700
2701                anyhow::Ok(())
2702            }
2703            .await;
2704            if result.is_err() {
2705                let json_language = load_json_language(workspace.clone(), cx).await;
2706                this.update(cx, |this, cx| {
2707                    this.editor.update(cx, |editor, cx| {
2708                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
2709                            buffer.update(cx, |buffer, cx| {
2710                                buffer.set_language(Some(json_language.clone()), cx)
2711                            });
2712                        }
2713                    })
2714                    // .context("Failed to load JSON language for editing keybinding action arguments input")
2715                })
2716                .ok();
2717                this.update(cx, |this, _cx| {
2718                    this.is_loading = false;
2719                })
2720                .ok();
2721            }
2722            return result;
2723        })
2724        .detach_and_log_err(cx);
2725        Self {
2726            editor,
2727            focus_handle,
2728            is_loading: true,
2729            backup_temp_dir: None,
2730        }
2731    }
2732
2733    fn set_editor_text(
2734        editor: &mut Editor,
2735        arguments: Option<SharedString>,
2736        window: &mut Window,
2737        cx: &mut Context<Editor>,
2738    ) {
2739        if let Some(arguments) = arguments {
2740            editor.set_text(arguments, window, cx);
2741        } else {
2742            // TODO: default value from schema?
2743            editor.set_placeholder_text("Action Arguments", cx);
2744        }
2745    }
2746
2747    async fn create_temp_buffer(
2748        temp_dir: Option<std::path::PathBuf>,
2749        file_name: String,
2750        project: WeakEntity<Project>,
2751        fs: Arc<dyn Fs>,
2752        cx: &mut AsyncApp,
2753    ) -> anyhow::Result<(Entity<language::Buffer>, Option<tempfile::TempDir>)> {
2754        let (temp_file_path, temp_dir) = {
2755            let file_name = file_name.clone();
2756            async move {
2757                let temp_dir_backup = match temp_dir.as_ref() {
2758                    Some(_) => None,
2759                    None => {
2760                        let temp_dir = paths::temp_dir();
2761                        let sub_temp_dir = tempfile::Builder::new()
2762                            .tempdir_in(temp_dir)
2763                            .context("Failed to create temporary directory")?;
2764                        Some(sub_temp_dir)
2765                    }
2766                };
2767                let dir_path = temp_dir.as_deref().unwrap_or_else(|| {
2768                    temp_dir_backup
2769                        .as_ref()
2770                        .expect("created backup tempdir")
2771                        .path()
2772                });
2773                let path = dir_path.join(file_name);
2774                fs.create_file(
2775                    &path,
2776                    fs::CreateOptions {
2777                        ignore_if_exists: true,
2778                        overwrite: true,
2779                    },
2780                )
2781                .await
2782                .context("Failed to create temporary file")?;
2783                anyhow::Ok((path, temp_dir_backup))
2784            }
2785        }
2786        .await
2787        .context("Failed to create backing file")?;
2788
2789        project
2790            .update(cx, |project, cx| {
2791                project.open_local_buffer(temp_file_path, cx)
2792            })?
2793            .await
2794            .context("Failed to create buffer")
2795            .map(|buffer| (buffer, temp_dir))
2796    }
2797}
2798
2799impl Render for ActionArgumentsEditor {
2800    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2801        let background_color;
2802        let border_color;
2803        let text_style = {
2804            let colors = cx.theme().colors();
2805            let settings = theme::ThemeSettings::get_global(cx);
2806            background_color = colors.editor_background;
2807            border_color = if self.is_loading {
2808                colors.border_disabled
2809            } else {
2810                colors.border_variant
2811            };
2812            TextStyleRefinement {
2813                font_size: Some(rems(0.875).into()),
2814                font_weight: Some(settings.buffer_font.weight),
2815                line_height: Some(relative(1.2)),
2816                font_style: Some(gpui::FontStyle::Normal),
2817                color: self.is_loading.then_some(colors.text_disabled),
2818                ..Default::default()
2819            }
2820        };
2821
2822        self.editor
2823            .update(cx, |editor, _| editor.set_text_style_refinement(text_style));
2824
2825        return v_flex().w_full().child(
2826            h_flex()
2827                .min_h_8()
2828                .min_w_48()
2829                .px_2()
2830                .py_1p5()
2831                .flex_grow()
2832                .rounded_lg()
2833                .bg(background_color)
2834                .border_1()
2835                .border_color(border_color)
2836                .track_focus(&self.focus_handle)
2837                .child(self.editor.clone()),
2838        );
2839    }
2840}
2841
2842struct KeyContextCompletionProvider {
2843    contexts: Vec<SharedString>,
2844}
2845
2846impl CompletionProvider for KeyContextCompletionProvider {
2847    fn completions(
2848        &self,
2849        _excerpt_id: editor::ExcerptId,
2850        buffer: &Entity<language::Buffer>,
2851        buffer_position: language::Anchor,
2852        _trigger: editor::CompletionContext,
2853        _window: &mut Window,
2854        cx: &mut Context<Editor>,
2855    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
2856        let buffer = buffer.read(cx);
2857        let mut count_back = 0;
2858        for char in buffer.reversed_chars_at(buffer_position) {
2859            if char.is_ascii_alphanumeric() || char == '_' {
2860                count_back += 1;
2861            } else {
2862                break;
2863            }
2864        }
2865        let start_anchor =
2866            buffer.anchor_before(buffer_position.to_offset(buffer).saturating_sub(count_back));
2867        let replace_range = start_anchor..buffer_position;
2868        gpui::Task::ready(Ok(vec![project::CompletionResponse {
2869            completions: self
2870                .contexts
2871                .iter()
2872                .map(|context| project::Completion {
2873                    replace_range: replace_range.clone(),
2874                    label: language::CodeLabel::plain(context.to_string(), None),
2875                    new_text: context.to_string(),
2876                    documentation: None,
2877                    source: project::CompletionSource::Custom,
2878                    icon_path: None,
2879                    insert_text_mode: None,
2880                    confirm: None,
2881                })
2882                .collect(),
2883            is_incomplete: false,
2884        }]))
2885    }
2886
2887    fn is_completion_trigger(
2888        &self,
2889        _buffer: &Entity<language::Buffer>,
2890        _position: language::Anchor,
2891        text: &str,
2892        _trigger_in_words: bool,
2893        _menu_is_open: bool,
2894        _cx: &mut Context<Editor>,
2895    ) -> bool {
2896        text.chars().last().map_or(false, |last_char| {
2897            last_char.is_ascii_alphanumeric() || last_char == '_'
2898        })
2899    }
2900}
2901
2902async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
2903    let json_language_task = workspace
2904        .read_with(cx, |workspace, cx| {
2905            workspace
2906                .project()
2907                .read(cx)
2908                .languages()
2909                .language_for_name("JSON")
2910        })
2911        .context("Failed to load JSON language")
2912        .log_err();
2913    let json_language = match json_language_task {
2914        Some(task) => task.await.context("Failed to load JSON language").log_err(),
2915        None => None,
2916    };
2917    return json_language.unwrap_or_else(|| {
2918        Arc::new(Language::new(
2919            LanguageConfig {
2920                name: "JSON".into(),
2921                ..Default::default()
2922            },
2923            Some(tree_sitter_json::LANGUAGE.into()),
2924        ))
2925    });
2926}
2927
2928async fn load_keybind_context_language(
2929    workspace: WeakEntity<Workspace>,
2930    cx: &mut AsyncApp,
2931) -> Arc<Language> {
2932    let language_task = workspace
2933        .read_with(cx, |workspace, cx| {
2934            workspace
2935                .project()
2936                .read(cx)
2937                .languages()
2938                .language_for_name("Zed Keybind Context")
2939        })
2940        .context("Failed to load Zed Keybind Context language")
2941        .log_err();
2942    let language = match language_task {
2943        Some(task) => task
2944            .await
2945            .context("Failed to load Zed Keybind Context language")
2946            .log_err(),
2947        None => None,
2948    };
2949    return language.unwrap_or_else(|| {
2950        Arc::new(Language::new(
2951            LanguageConfig {
2952                name: "Zed Keybind Context".into(),
2953                ..Default::default()
2954            },
2955            Some(tree_sitter_rust::LANGUAGE.into()),
2956        ))
2957    });
2958}
2959
2960async fn save_keybinding_update(
2961    create: bool,
2962    existing: ProcessedBinding,
2963    action_mapping: &ActionMapping,
2964    new_args: Option<&str>,
2965    fs: &Arc<dyn Fs>,
2966    tab_size: usize,
2967) -> anyhow::Result<()> {
2968    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
2969        .await
2970        .context("Failed to load keymap file")?;
2971
2972    let existing_keystrokes = existing.keystrokes().unwrap_or_default();
2973    let existing_context = existing.context().and_then(KeybindContextString::local_str);
2974    let existing_args = existing
2975        .action()
2976        .arguments
2977        .as_ref()
2978        .map(|args| args.text.as_ref());
2979
2980    let target = settings::KeybindUpdateTarget {
2981        context: existing_context,
2982        keystrokes: existing_keystrokes,
2983        action_name: existing.action().name,
2984        action_arguments: existing_args,
2985    };
2986
2987    let source = settings::KeybindUpdateTarget {
2988        context: action_mapping.context.as_ref().map(|a| &***a),
2989        keystrokes: &action_mapping.keystrokes,
2990        action_name: existing.action().name,
2991        action_arguments: new_args,
2992    };
2993
2994    let operation = if !create {
2995        settings::KeybindUpdateOperation::Replace {
2996            target,
2997            target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
2998            source,
2999        }
3000    } else {
3001        settings::KeybindUpdateOperation::Add {
3002            source,
3003            from: Some(target),
3004        }
3005    };
3006
3007    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3008
3009    let updated_keymap_contents =
3010        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
3011            .map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?;
3012    fs.write(
3013        paths::keymap_file().as_path(),
3014        updated_keymap_contents.as_bytes(),
3015    )
3016    .await
3017    .context("Failed to write keymap file")?;
3018
3019    telemetry::event!(
3020        "Keybinding Updated",
3021        new_keybinding = new_keybinding,
3022        removed_keybinding = removed_keybinding,
3023        source = source
3024    );
3025    Ok(())
3026}
3027
3028async fn remove_keybinding(
3029    existing: ProcessedBinding,
3030    fs: &Arc<dyn Fs>,
3031    tab_size: usize,
3032) -> anyhow::Result<()> {
3033    let Some(keystrokes) = existing.keystrokes() else {
3034        anyhow::bail!("Cannot remove a keybinding that does not exist");
3035    };
3036    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3037        .await
3038        .context("Failed to load keymap file")?;
3039
3040    let operation = settings::KeybindUpdateOperation::Remove {
3041        target: settings::KeybindUpdateTarget {
3042            context: existing.context().and_then(KeybindContextString::local_str),
3043            keystrokes,
3044            action_name: existing.action().name,
3045            action_arguments: existing
3046                .action()
3047                .arguments
3048                .as_ref()
3049                .map(|arguments| arguments.text.as_ref()),
3050        },
3051        target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3052    };
3053
3054    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3055    let updated_keymap_contents =
3056        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
3057            .context("Failed to update keybinding")?;
3058    fs.write(
3059        paths::keymap_file().as_path(),
3060        updated_keymap_contents.as_bytes(),
3061    )
3062    .await
3063    .context("Failed to write keymap file")?;
3064
3065    telemetry::event!(
3066        "Keybinding Removed",
3067        new_keybinding = new_keybinding,
3068        removed_keybinding = removed_keybinding,
3069        source = source
3070    );
3071    Ok(())
3072}
3073
3074fn collect_contexts_from_assets() -> Vec<SharedString> {
3075    let mut keymap_assets = vec![
3076        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
3077        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
3078    ];
3079    keymap_assets.extend(
3080        BaseKeymap::OPTIONS
3081            .iter()
3082            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
3083            .map(util::asset_str::<SettingsAssets>),
3084    );
3085
3086    let mut contexts = HashSet::default();
3087
3088    for keymap_asset in keymap_assets {
3089        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
3090            continue;
3091        };
3092
3093        for section in keymap.sections() {
3094            let context_expr = &section.context;
3095            let mut queue = Vec::new();
3096            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
3097                continue;
3098            };
3099
3100            queue.push(root_context);
3101            while let Some(context) = queue.pop() {
3102                match context {
3103                    gpui::KeyBindingContextPredicate::Identifier(ident) => {
3104                        contexts.insert(ident);
3105                    }
3106                    gpui::KeyBindingContextPredicate::Equal(ident_a, ident_b) => {
3107                        contexts.insert(ident_a);
3108                        contexts.insert(ident_b);
3109                    }
3110                    gpui::KeyBindingContextPredicate::NotEqual(ident_a, ident_b) => {
3111                        contexts.insert(ident_a);
3112                        contexts.insert(ident_b);
3113                    }
3114                    gpui::KeyBindingContextPredicate::Descendant(ctx_a, ctx_b) => {
3115                        queue.push(*ctx_a);
3116                        queue.push(*ctx_b);
3117                    }
3118                    gpui::KeyBindingContextPredicate::Not(ctx) => {
3119                        queue.push(*ctx);
3120                    }
3121                    gpui::KeyBindingContextPredicate::And(ctx_a, ctx_b) => {
3122                        queue.push(*ctx_a);
3123                        queue.push(*ctx_b);
3124                    }
3125                    gpui::KeyBindingContextPredicate::Or(ctx_a, ctx_b) => {
3126                        queue.push(*ctx_a);
3127                        queue.push(*ctx_b);
3128                    }
3129                }
3130            }
3131        }
3132    }
3133
3134    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
3135    contexts.sort();
3136
3137    return contexts;
3138}
3139
3140impl SerializableItem for KeymapEditor {
3141    fn serialized_item_kind() -> &'static str {
3142        "KeymapEditor"
3143    }
3144
3145    fn cleanup(
3146        workspace_id: workspace::WorkspaceId,
3147        alive_items: Vec<workspace::ItemId>,
3148        _window: &mut Window,
3149        cx: &mut App,
3150    ) -> gpui::Task<gpui::Result<()>> {
3151        workspace::delete_unloaded_items(
3152            alive_items,
3153            workspace_id,
3154            "keybinding_editors",
3155            &KEYBINDING_EDITORS,
3156            cx,
3157        )
3158    }
3159
3160    fn deserialize(
3161        _project: Entity<project::Project>,
3162        workspace: WeakEntity<Workspace>,
3163        workspace_id: workspace::WorkspaceId,
3164        item_id: workspace::ItemId,
3165        window: &mut Window,
3166        cx: &mut App,
3167    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
3168        window.spawn(cx, async move |cx| {
3169            if KEYBINDING_EDITORS
3170                .get_keybinding_editor(item_id, workspace_id)?
3171                .is_some()
3172            {
3173                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
3174            } else {
3175                Err(anyhow!("No keybinding editor to deserialize"))
3176            }
3177        })
3178    }
3179
3180    fn serialize(
3181        &mut self,
3182        workspace: &mut Workspace,
3183        item_id: workspace::ItemId,
3184        _closing: bool,
3185        _window: &mut Window,
3186        cx: &mut ui::Context<Self>,
3187    ) -> Option<gpui::Task<gpui::Result<()>>> {
3188        let workspace_id = workspace.database_id()?;
3189        Some(cx.background_spawn(async move {
3190            KEYBINDING_EDITORS
3191                .save_keybinding_editor(item_id, workspace_id)
3192                .await
3193        }))
3194    }
3195
3196    fn should_serialize(&self, _event: &Self::Event) -> bool {
3197        false
3198    }
3199}
3200
3201mod persistence {
3202    use db::{define_connection, query, sqlez_macros::sql};
3203    use workspace::WorkspaceDb;
3204
3205    define_connection! {
3206        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
3207            &[sql!(
3208                CREATE TABLE keybinding_editors (
3209                    workspace_id INTEGER,
3210                    item_id INTEGER UNIQUE,
3211
3212                    PRIMARY KEY(workspace_id, item_id),
3213                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
3214                    ON DELETE CASCADE
3215                ) STRICT;
3216            )];
3217    }
3218
3219    impl KeybindingEditorDb {
3220        query! {
3221            pub async fn save_keybinding_editor(
3222                item_id: workspace::ItemId,
3223                workspace_id: workspace::WorkspaceId
3224            ) -> Result<()> {
3225                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
3226                VALUES (?, ?)
3227            }
3228        }
3229
3230        query! {
3231            pub fn get_keybinding_editor(
3232                item_id: workspace::ItemId,
3233                workspace_id: workspace::WorkspaceId
3234            ) -> Result<Option<workspace::ItemId>> {
3235                SELECT item_id
3236                FROM keybinding_editors
3237                WHERE item_id = ? AND workspace_id = ?
3238            }
3239        }
3240    }
3241}