keybindings.rs

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