keymap_editor.rs

   1use std::{
   2    cell::RefCell,
   3    cmp::{self},
   4    ops::{Not as _, Range},
   5    rc::Rc,
   6    sync::Arc,
   7    time::{Duration, Instant},
   8};
   9
  10mod action_completion_provider;
  11mod ui_components;
  12
  13use anyhow::{Context as _, anyhow};
  14use collections::{HashMap, HashSet};
  15use editor::{CompletionProvider, Editor, EditorEvent, EditorMode, SizingBehavior};
  16use fs::Fs;
  17use fuzzy::{StringMatch, StringMatchCandidate};
  18use gpui::{
  19    Action, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
  20    FocusHandle, Focusable, Global, IsZero,
  21    KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or},
  22    KeyContext, KeybindingKeystroke, MouseButton, PlatformKeyboardMapper, Point, ScrollStrategy,
  23    ScrollWheelEvent, Stateful, StyledText, Subscription, Task, TextStyleRefinement, WeakEntity,
  24    actions, anchored, deferred, div,
  25};
  26use language::{Language, LanguageConfig, ToOffset as _};
  27use notifications::status_toast::{StatusToast, ToastIcon};
  28use project::{CompletionDisplayOptions, Project};
  29use settings::{
  30    BaseKeymap, KeybindSource, KeymapFile, Settings as _, SettingsAssets, infer_json_indent_size,
  31};
  32use ui::{
  33    ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, IconButtonShape, IconPosition,
  34    Indicator, Modal, ModalFooter, ModalHeader, ParentElement as _, PopoverMenu, Render, Section,
  35    SharedString, Styled as _, Table, TableColumnWidths, TableInteractionState,
  36    TableResizeBehavior, Tooltip, Window, prelude::*,
  37};
  38use ui_input::InputField;
  39use util::ResultExt;
  40use workspace::{
  41    Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _,
  42    register_serializable_item, with_active_or_new_workspace,
  43};
  44
  45pub use ui_components::*;
  46use zed_actions::{ChangeKeybinding, OpenKeymap};
  47
  48use crate::{
  49    action_completion_provider::ActionCompletionProvider,
  50    persistence::KeybindingEditorDb,
  51    ui_components::keystroke_input::{
  52        ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording,
  53    },
  54};
  55
  56const NO_ACTION_ARGUMENTS_TEXT: SharedString = SharedString::new_static("<no arguments>");
  57const COLS: usize = 6;
  58
  59actions!(
  60    keymap_editor,
  61    [
  62        /// Edits the selected key binding.
  63        EditBinding,
  64        /// Creates a new key binding for the selected action.
  65        CreateBinding,
  66        /// Creates a new key binding from scratch, prompting for the action.
  67        OpenCreateKeybindingModal,
  68        /// Deletes the selected key binding.
  69        DeleteBinding,
  70        /// Copies the action name to clipboard.
  71        CopyAction,
  72        /// Copies the context predicate to clipboard.
  73        CopyContext,
  74        /// Toggles Conflict Filtering
  75        ToggleConflictFilter,
  76        /// Toggles whether NoAction bindings are shown
  77        ToggleNoActionBindings,
  78        /// Toggle Keystroke search
  79        ToggleKeystrokeSearch,
  80        /// Toggles exact matching for keystroke search
  81        ToggleExactKeystrokeMatching,
  82        /// Shows matching keystrokes for the currently selected binding
  83        ShowMatchingKeybinds
  84    ]
  85);
  86
  87pub fn init(cx: &mut App) {
  88    let keymap_event_channel = KeymapEventChannel::new();
  89    cx.set_global(keymap_event_channel);
  90
  91    fn open_keymap_editor(
  92        filter: Option<String>,
  93        workspace: &mut Workspace,
  94        window: &mut Window,
  95        cx: &mut Context<Workspace>,
  96    ) {
  97        let existing = workspace
  98            .active_pane()
  99            .read(cx)
 100            .items()
 101            .find_map(|item| item.downcast::<KeymapEditor>());
 102
 103        let keymap_editor = if let Some(existing) = existing {
 104            workspace.activate_item(&existing, true, true, window, cx);
 105            existing
 106        } else {
 107            let keymap_editor = cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
 108            workspace.add_item_to_active_pane(
 109                Box::new(keymap_editor.clone()),
 110                None,
 111                true,
 112                window,
 113                cx,
 114            );
 115            keymap_editor
 116        };
 117
 118        if let Some(filter) = filter {
 119            keymap_editor.update(cx, |editor, cx| {
 120                editor.filter_editor.update(cx, |editor, cx| {
 121                    editor.clear(window, cx);
 122                    editor.insert(&filter, window, cx);
 123                });
 124                if !editor.has_binding_for(&filter) {
 125                    open_binding_modal_after_loading(cx)
 126                }
 127            })
 128        }
 129    }
 130
 131    cx.on_action(|_: &OpenKeymap, cx| {
 132        with_active_or_new_workspace(cx, |workspace, window, cx| {
 133            open_keymap_editor(None, workspace, window, cx);
 134        });
 135    });
 136
 137    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
 138        workspace.register_action(|workspace, action: &ChangeKeybinding, window, cx| {
 139            open_keymap_editor(Some(action.action.clone()), workspace, window, cx);
 140        });
 141    })
 142    .detach();
 143
 144    register_serializable_item::<KeymapEditor>(cx);
 145}
 146
 147fn open_binding_modal_after_loading(cx: &mut Context<KeymapEditor>) {
 148    let started_at = Instant::now();
 149    let observer = Rc::new(RefCell::new(None));
 150    let handle = {
 151        let observer = Rc::clone(&observer);
 152        cx.observe(&cx.entity(), move |editor, _, cx| {
 153            let subscription = observer.borrow_mut().take();
 154
 155            if started_at.elapsed().as_secs() > 10 {
 156                return;
 157            }
 158            if !editor.matches.is_empty() {
 159                editor.selected_index = Some(0);
 160                cx.dispatch_action(&CreateBinding);
 161                return;
 162            }
 163
 164            *observer.borrow_mut() = subscription;
 165        })
 166    };
 167    *observer.borrow_mut() = Some(handle);
 168}
 169
 170pub struct KeymapEventChannel {}
 171
 172impl Global for KeymapEventChannel {}
 173
 174impl KeymapEventChannel {
 175    fn new() -> Self {
 176        Self {}
 177    }
 178
 179    pub fn trigger_keymap_changed(cx: &mut App) {
 180        let Some(_event_channel) = cx.try_global::<Self>() else {
 181            // don't panic if no global defined. This usually happens in tests
 182            return;
 183        };
 184        cx.update_global(|_event_channel: &mut Self, _| {
 185            /* triggers observers in KeymapEditors */
 186        });
 187    }
 188}
 189
 190#[derive(Default, PartialEq, Copy, Clone)]
 191enum SearchMode {
 192    #[default]
 193    Normal,
 194    KeyStroke {
 195        exact_match: bool,
 196    },
 197}
 198
 199impl SearchMode {
 200    fn invert(&self) -> Self {
 201        match self {
 202            SearchMode::Normal => SearchMode::KeyStroke { exact_match: true },
 203            SearchMode::KeyStroke { .. } => SearchMode::Normal,
 204        }
 205    }
 206
 207    fn exact_match(&self) -> bool {
 208        match self {
 209            SearchMode::Normal => false,
 210            SearchMode::KeyStroke { exact_match } => *exact_match,
 211        }
 212    }
 213}
 214
 215#[derive(Default, PartialEq, Copy, Clone)]
 216enum FilterState {
 217    #[default]
 218    All,
 219    Conflicts,
 220}
 221
 222impl FilterState {
 223    fn invert(&self) -> Self {
 224        match self {
 225            FilterState::All => FilterState::Conflicts,
 226            FilterState::Conflicts => FilterState::All,
 227        }
 228    }
 229}
 230
 231#[derive(Default, PartialEq, Eq, Copy, Clone)]
 232struct SourceFilters {
 233    user: bool,
 234    zed_defaults: bool,
 235    vim_defaults: bool,
 236}
 237
 238impl SourceFilters {
 239    fn allows(&self, source: Option<KeybindSource>) -> bool {
 240        match source {
 241            Some(KeybindSource::User) => self.user,
 242            Some(KeybindSource::Vim) => self.vim_defaults,
 243            Some(KeybindSource::Base | KeybindSource::Default | KeybindSource::Unknown) | None => {
 244                self.zed_defaults
 245            }
 246        }
 247    }
 248}
 249
 250#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
 251struct ActionMapping {
 252    keystrokes: Rc<[KeybindingKeystroke]>,
 253    context: Option<SharedString>,
 254}
 255
 256#[derive(Debug)]
 257struct KeybindConflict {
 258    first_conflict_index: usize,
 259    remaining_conflict_amount: usize,
 260}
 261
 262#[derive(Clone, Copy, PartialEq)]
 263struct ConflictOrigin {
 264    override_source: KeybindSource,
 265    overridden_source: Option<KeybindSource>,
 266    index: usize,
 267}
 268
 269impl ConflictOrigin {
 270    fn new(source: KeybindSource, index: usize) -> Self {
 271        Self {
 272            override_source: source,
 273            index,
 274            overridden_source: None,
 275        }
 276    }
 277
 278    fn with_overridden_source(self, source: KeybindSource) -> Self {
 279        Self {
 280            overridden_source: Some(source),
 281            ..self
 282        }
 283    }
 284
 285    fn get_conflict_with(&self, other: &Self) -> Option<Self> {
 286        if self.override_source == KeybindSource::User
 287            && other.override_source == KeybindSource::User
 288        {
 289            Some(
 290                Self::new(KeybindSource::User, other.index)
 291                    .with_overridden_source(self.override_source),
 292            )
 293        } else if self.override_source > other.override_source {
 294            Some(other.with_overridden_source(self.override_source))
 295        } else {
 296            None
 297        }
 298    }
 299
 300    fn is_user_keybind_conflict(&self) -> bool {
 301        self.override_source == KeybindSource::User
 302            && self.overridden_source == Some(KeybindSource::User)
 303    }
 304}
 305
 306#[derive(Default)]
 307struct ConflictState {
 308    conflicts: Vec<Option<ConflictOrigin>>,
 309    keybind_mapping: ConflictKeybindMapping,
 310    has_user_conflicts: bool,
 311}
 312
 313type ConflictKeybindMapping = HashMap<
 314    Rc<[KeybindingKeystroke]>,
 315    Vec<(
 316        Option<gpui::KeyBindingContextPredicate>,
 317        Vec<ConflictOrigin>,
 318    )>,
 319>;
 320
 321impl ConflictState {
 322    fn new(key_bindings: &[ProcessedBinding]) -> Self {
 323        let mut action_keybind_mapping = ConflictKeybindMapping::default();
 324
 325        let mut largest_index = 0;
 326        for (index, binding) in key_bindings
 327            .iter()
 328            .enumerate()
 329            .flat_map(|(index, binding)| Some(index).zip(binding.keybind_information()))
 330        {
 331            let mapping = binding.get_action_mapping();
 332            let predicate = mapping
 333                .context
 334                .and_then(|ctx| gpui::KeyBindingContextPredicate::parse(&ctx).ok());
 335            let entry = action_keybind_mapping
 336                .entry(mapping.keystrokes.clone())
 337                .or_default();
 338            let origin = ConflictOrigin::new(binding.source, index);
 339            if let Some((_, origins)) =
 340                entry
 341                    .iter_mut()
 342                    .find(|(other_predicate, _)| match (&predicate, other_predicate) {
 343                        (None, None) => true,
 344                        (Some(a), Some(b)) => normalized_ctx_eq(a, b),
 345                        _ => false,
 346                    })
 347            {
 348                origins.push(origin);
 349            } else {
 350                entry.push((predicate, vec![origin]));
 351            }
 352            largest_index = index;
 353        }
 354
 355        let mut conflicts = vec![None; largest_index + 1];
 356        let mut has_user_conflicts = false;
 357
 358        for entries in action_keybind_mapping.values_mut() {
 359            for (_, indices) in entries.iter_mut() {
 360                indices.sort_unstable_by_key(|origin| origin.override_source);
 361                let Some((fst, snd)) = indices.get(0).zip(indices.get(1)) else {
 362                    continue;
 363                };
 364
 365                for origin in indices.iter() {
 366                    conflicts[origin.index] =
 367                        origin.get_conflict_with(if origin == fst { snd } else { fst })
 368                }
 369
 370                has_user_conflicts |= fst.override_source == KeybindSource::User
 371                    && snd.override_source == KeybindSource::User;
 372            }
 373        }
 374
 375        Self {
 376            conflicts,
 377            keybind_mapping: action_keybind_mapping,
 378            has_user_conflicts,
 379        }
 380    }
 381
 382    fn conflicting_indices_for_mapping(
 383        &self,
 384        action_mapping: &ActionMapping,
 385        keybind_idx: Option<usize>,
 386    ) -> Option<KeybindConflict> {
 387        let ActionMapping {
 388            keystrokes,
 389            context,
 390        } = action_mapping;
 391        let predicate = context
 392            .as_deref()
 393            .and_then(|ctx| gpui::KeyBindingContextPredicate::parse(&ctx).ok());
 394        self.keybind_mapping.get(keystrokes).and_then(|entries| {
 395            entries
 396                .iter()
 397                .find_map(|(other_predicate, indices)| {
 398                    match (&predicate, other_predicate) {
 399                        (None, None) => true,
 400                        (Some(pred), Some(other)) => normalized_ctx_eq(pred, other),
 401                        _ => false,
 402                    }
 403                    .then_some(indices)
 404                })
 405                .and_then(|indices| {
 406                    let mut indices = indices
 407                        .iter()
 408                        .filter(|&conflict| Some(conflict.index) != keybind_idx);
 409                    indices.next().map(|origin| KeybindConflict {
 410                        first_conflict_index: origin.index,
 411                        remaining_conflict_amount: indices.count(),
 412                    })
 413                })
 414        })
 415    }
 416
 417    fn conflict_for_idx(&self, idx: usize) -> Option<ConflictOrigin> {
 418        self.conflicts.get(idx).copied().flatten()
 419    }
 420
 421    fn has_user_conflict(&self, candidate_idx: usize) -> bool {
 422        self.conflict_for_idx(candidate_idx)
 423            .is_some_and(|conflict| conflict.is_user_keybind_conflict())
 424    }
 425
 426    fn any_user_binding_conflicts(&self) -> bool {
 427        self.has_user_conflicts
 428    }
 429}
 430
 431struct KeymapEditor {
 432    workspace: WeakEntity<Workspace>,
 433    focus_handle: FocusHandle,
 434    _keymap_subscription: Subscription,
 435    keybindings: Vec<ProcessedBinding>,
 436    keybinding_conflict_state: ConflictState,
 437    filter_state: FilterState,
 438    source_filters: SourceFilters,
 439    show_no_action_bindings: bool,
 440    search_mode: SearchMode,
 441    search_query_debounce: Option<Task<()>>,
 442    // corresponds 1 to 1 with keybindings
 443    string_match_candidates: Arc<Vec<StringMatchCandidate>>,
 444    matches: Vec<StringMatch>,
 445    table_interaction_state: Entity<TableInteractionState>,
 446    filter_editor: Entity<Editor>,
 447    keystroke_editor: Entity<KeystrokeInput>,
 448    selected_index: Option<usize>,
 449    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
 450    previous_edit: Option<PreviousEdit>,
 451    humanized_action_names: HumanizedActionNameCache,
 452    current_widths: Entity<TableColumnWidths>,
 453    show_hover_menus: bool,
 454    actions_with_schemas: HashSet<&'static str>,
 455    /// In order for the JSON LSP to run in the actions arguments editor, we
 456    /// require a backing file In order to avoid issues (primarily log spam)
 457    /// with drop order between the buffer, file, worktree, etc, we create a
 458    /// temporary directory for these backing files in the keymap editor struct
 459    /// instead of here. This has the added benefit of only having to create a
 460    /// worktree and directory once, although the perf improvement is negligible.
 461    action_args_temp_dir_worktree: Option<Entity<project::Worktree>>,
 462    action_args_temp_dir: Option<tempfile::TempDir>,
 463}
 464
 465enum PreviousEdit {
 466    /// When deleting, we want to maintain the same scroll position
 467    ScrollBarOffset(Point<Pixels>),
 468    /// When editing or creating, because the new keybinding could be in a different position in the sort order
 469    /// we store metadata about the new binding (either the modified version or newly created one)
 470    /// and upon reload, we search for this binding in the list of keybindings, and if we find the one that matches
 471    /// this metadata, we set the selected index to it and scroll to it,
 472    /// and if we don't find it, we scroll to 0 and don't set a selected index
 473    Keybinding {
 474        action_mapping: ActionMapping,
 475        action_name: &'static str,
 476        /// The scrollbar position to fallback to if we don't find the keybinding during a refresh
 477        /// this can happen if there's a filter applied to the search and the keybinding modification
 478        /// filters the binding from the search results
 479        fallback: Point<Pixels>,
 480    },
 481}
 482
 483impl EventEmitter<()> for KeymapEditor {}
 484
 485impl Focusable for KeymapEditor {
 486    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 487        if self.selected_index.is_some() {
 488            self.focus_handle.clone()
 489        } else {
 490            self.filter_editor.focus_handle(cx)
 491        }
 492    }
 493}
 494/// Helper function to check if two keystroke sequences match exactly
 495fn keystrokes_match_exactly(
 496    keystrokes1: &[KeybindingKeystroke],
 497    keystrokes2: &[KeybindingKeystroke],
 498) -> bool {
 499    keystrokes1.len() == keystrokes2.len()
 500        && keystrokes1.iter().zip(keystrokes2).all(|(k1, k2)| {
 501            k1.inner().key == k2.inner().key && k1.inner().modifiers == k2.inner().modifiers
 502        })
 503}
 504
 505fn disabled_binding_matches_context(
 506    disabled_binding: &gpui::KeyBinding,
 507    binding: &gpui::KeyBinding,
 508) -> bool {
 509    match (
 510        disabled_binding.predicate().as_deref(),
 511        binding.predicate().as_deref(),
 512    ) {
 513        (None, _) => true,
 514        (Some(_), None) => false,
 515        (Some(disabled_predicate), Some(predicate)) => disabled_predicate.is_superset(predicate),
 516    }
 517}
 518
 519fn binding_is_unbound_by_unbind(
 520    binding: &gpui::KeyBinding,
 521    binding_index: usize,
 522    all_bindings: &[&gpui::KeyBinding],
 523) -> bool {
 524    all_bindings[binding_index + 1..]
 525        .iter()
 526        .rev()
 527        .any(|disabled_binding| {
 528            gpui::is_unbind(disabled_binding.action())
 529                && keystrokes_match_exactly(disabled_binding.keystrokes(), binding.keystrokes())
 530                && disabled_binding
 531                    .action()
 532                    .as_any()
 533                    .downcast_ref::<gpui::Unbind>()
 534                    .is_some_and(|unbind| unbind.0.as_ref() == binding.action().name())
 535                && disabled_binding_matches_context(disabled_binding, binding)
 536        })
 537}
 538
 539impl KeymapEditor {
 540    fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
 541        let _keymap_subscription =
 542            cx.observe_global_in::<KeymapEventChannel>(window, Self::on_keymap_changed);
 543        let table_interaction_state = cx.new(|cx| {
 544            TableInteractionState::new(cx)
 545                .with_custom_scrollbar(ui::Scrollbars::for_settings::<editor::EditorSettings>())
 546        });
 547
 548        let keystroke_editor = cx.new(|cx| {
 549            let mut keystroke_editor = KeystrokeInput::new(None, window, cx);
 550            keystroke_editor.set_search(true);
 551            keystroke_editor
 552        });
 553
 554        let filter_editor = cx.new(|cx| {
 555            let mut editor = Editor::single_line(window, cx);
 556            editor.set_placeholder_text("Filter action names…", window, cx);
 557            editor
 558        });
 559
 560        cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
 561            if !matches!(e, EditorEvent::BufferEdited) {
 562                return;
 563            }
 564
 565            this.on_query_changed(cx);
 566        })
 567        .detach();
 568
 569        cx.subscribe(&keystroke_editor, |this, _, _, cx| {
 570            if matches!(this.search_mode, SearchMode::Normal) {
 571                return;
 572            }
 573
 574            this.on_query_changed(cx);
 575        })
 576        .detach();
 577
 578        cx.spawn({
 579            let workspace = workspace.clone();
 580            async move |this, cx| {
 581                let temp_dir = tempfile::tempdir_in(paths::temp_dir())?;
 582                let worktree = workspace
 583                    .update(cx, |ws, cx| {
 584                        ws.project()
 585                            .update(cx, |p, cx| p.create_worktree(temp_dir.path(), false, cx))
 586                    })?
 587                    .await?;
 588                this.update(cx, |this, _| {
 589                    this.action_args_temp_dir = Some(temp_dir);
 590                    this.action_args_temp_dir_worktree = Some(worktree);
 591                })
 592            }
 593        })
 594        .detach();
 595
 596        let mut this = Self {
 597            workspace,
 598            keybindings: vec![],
 599            keybinding_conflict_state: ConflictState::default(),
 600            filter_state: FilterState::default(),
 601            source_filters: SourceFilters {
 602                user: true,
 603                zed_defaults: true,
 604                vim_defaults: true,
 605            },
 606            show_no_action_bindings: true,
 607            search_mode: SearchMode::default(),
 608            string_match_candidates: Arc::new(vec![]),
 609            matches: vec![],
 610            focus_handle: cx.focus_handle(),
 611            _keymap_subscription,
 612            table_interaction_state,
 613            filter_editor,
 614            keystroke_editor,
 615            selected_index: None,
 616            context_menu: None,
 617            previous_edit: None,
 618            search_query_debounce: None,
 619            humanized_action_names: HumanizedActionNameCache::new(cx),
 620            show_hover_menus: true,
 621            actions_with_schemas: HashSet::default(),
 622            action_args_temp_dir: None,
 623            action_args_temp_dir_worktree: None,
 624            current_widths: cx.new(|cx| TableColumnWidths::new(COLS, cx)),
 625        };
 626
 627        this.on_keymap_changed(window, cx);
 628
 629        this
 630    }
 631
 632    fn current_action_query(&self, cx: &App) -> String {
 633        self.filter_editor.read(cx).text(cx)
 634    }
 635
 636    fn current_keystroke_query(&self, cx: &App) -> Vec<KeybindingKeystroke> {
 637        match self.search_mode {
 638            SearchMode::KeyStroke { .. } => self.keystroke_editor.read(cx).keystrokes().to_vec(),
 639            SearchMode::Normal => Default::default(),
 640        }
 641    }
 642
 643    fn clear_action_query(&self, window: &mut Window, cx: &mut Context<Self>) {
 644        self.filter_editor
 645            .update(cx, |editor, cx| editor.clear(window, cx))
 646    }
 647
 648    fn on_query_changed(&mut self, cx: &mut Context<Self>) {
 649        let action_query = self.current_action_query(cx);
 650        let keystroke_query = self.current_keystroke_query(cx);
 651        let exact_match = self.search_mode.exact_match();
 652
 653        let timer = cx.background_executor().timer(Duration::from_secs(1));
 654        self.search_query_debounce = Some(cx.background_spawn({
 655            let action_query = action_query.clone();
 656            let keystroke_query = keystroke_query.clone();
 657            async move {
 658                timer.await;
 659
 660                let keystroke_query = keystroke_query
 661                    .into_iter()
 662                    .map(|keystroke| keystroke.inner().unparse())
 663                    .collect::<Vec<String>>()
 664                    .join(" ");
 665
 666                telemetry::event!(
 667                    "Keystroke Search Completed",
 668                    action_query = action_query,
 669                    keystroke_query = keystroke_query,
 670                    keystroke_exact_match = exact_match
 671                )
 672            }
 673        }));
 674        cx.spawn(async move |this, cx| {
 675            Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
 676            this.update(cx, |this, cx| {
 677                this.scroll_to_item(0, ScrollStrategy::Top, cx)
 678            })
 679        })
 680        .detach();
 681    }
 682
 683    async fn update_matches(
 684        this: WeakEntity<Self>,
 685        action_query: String,
 686        keystroke_query: Vec<KeybindingKeystroke>,
 687        cx: &mut AsyncApp,
 688    ) -> anyhow::Result<()> {
 689        let action_query = command_palette::normalize_action_query(&action_query);
 690        let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
 691            (this.string_match_candidates.clone(), this.keybindings.len())
 692        })?;
 693        let executor = cx.background_executor().clone();
 694        let mut matches = fuzzy::match_strings(
 695            &string_match_candidates,
 696            &action_query,
 697            true,
 698            true,
 699            keybind_count,
 700            &Default::default(),
 701            executor,
 702        )
 703        .await;
 704        this.update(cx, |this, cx| {
 705            matches.retain(|candidate| {
 706                this.source_filters
 707                    .allows(this.keybindings[candidate.candidate_id].keybind_source())
 708            });
 709
 710            match this.filter_state {
 711                FilterState::Conflicts => {
 712                    matches.retain(|candidate| {
 713                        this.keybinding_conflict_state
 714                            .has_user_conflict(candidate.candidate_id)
 715                    });
 716                }
 717                FilterState::All => {}
 718            }
 719
 720            match this.search_mode {
 721                SearchMode::KeyStroke { exact_match } => {
 722                    matches.retain(|item| {
 723                        this.keybindings[item.candidate_id]
 724                            .keystrokes()
 725                            .is_some_and(|keystrokes| {
 726                                if exact_match {
 727                                    keystrokes_match_exactly(&keystroke_query, keystrokes)
 728                                } else if keystroke_query.len() > keystrokes.len() {
 729                                    false
 730                                } else {
 731                                    for keystroke_offset in 0..keystrokes.len() {
 732                                        let mut found_count = 0;
 733                                        let mut query_cursor = 0;
 734                                        let mut keystroke_cursor = keystroke_offset;
 735                                        while query_cursor < keystroke_query.len()
 736                                            && keystroke_cursor < keystrokes.len()
 737                                        {
 738                                            let query = &keystroke_query[query_cursor];
 739                                            let keystroke = &keystrokes[keystroke_cursor];
 740                                            let matches = query
 741                                                .inner()
 742                                                .modifiers
 743                                                .is_subset_of(&keystroke.inner().modifiers)
 744                                                && ((query.inner().key.is_empty()
 745                                                    || query.inner().key == keystroke.inner().key)
 746                                                    && query.inner().key_char.as_ref().is_none_or(
 747                                                        |q_kc| q_kc == &keystroke.inner().key,
 748                                                    ));
 749                                            if matches {
 750                                                found_count += 1;
 751                                                query_cursor += 1;
 752                                            }
 753                                            keystroke_cursor += 1;
 754                                        }
 755
 756                                        if found_count == keystroke_query.len() {
 757                                            return true;
 758                                        }
 759                                    }
 760                                    false
 761                                }
 762                            })
 763                    });
 764                }
 765                SearchMode::Normal => {}
 766            }
 767
 768            if !this.show_no_action_bindings {
 769                matches.retain(|item| !this.keybindings[item.candidate_id].is_no_action());
 770            }
 771
 772            if action_query.is_empty() {
 773                matches.sort_by(|item1, item2| {
 774                    let binding1 = &this.keybindings[item1.candidate_id];
 775                    let binding2 = &this.keybindings[item2.candidate_id];
 776
 777                    binding1.cmp(binding2)
 778                });
 779            }
 780            this.selected_index.take();
 781            this.matches = matches;
 782
 783            cx.notify();
 784        })
 785    }
 786
 787    fn get_conflict(&self, row_index: usize) -> Option<ConflictOrigin> {
 788        self.matches.get(row_index).and_then(|candidate| {
 789            self.keybinding_conflict_state
 790                .conflict_for_idx(candidate.candidate_id)
 791        })
 792    }
 793
 794    fn process_bindings(
 795        json_language: Arc<Language>,
 796        zed_keybind_context_language: Arc<Language>,
 797        humanized_action_names: &HumanizedActionNameCache,
 798        cx: &mut App,
 799    ) -> (
 800        Vec<ProcessedBinding>,
 801        Vec<StringMatchCandidate>,
 802        HashSet<&'static str>,
 803    ) {
 804        let key_bindings_ptr = cx.key_bindings();
 805        let lock = key_bindings_ptr.borrow();
 806        let key_bindings = lock.bindings().collect::<Vec<_>>();
 807        let mut unmapped_action_names = HashSet::from_iter(cx.all_action_names().iter().copied());
 808        let action_documentation = cx.action_documentation();
 809        let mut generator = KeymapFile::action_schema_generator();
 810        let actions_with_schemas = HashSet::from_iter(
 811            cx.action_schemas(&mut generator)
 812                .into_iter()
 813                .filter_map(|(name, schema)| schema.is_some().then_some(name)),
 814        );
 815
 816        let mut processed_bindings = Vec::new();
 817        let mut string_match_candidates = Vec::new();
 818
 819        for (binding_index, &key_binding) in key_bindings.iter().enumerate() {
 820            if gpui::is_unbind(key_binding.action()) {
 821                continue;
 822            }
 823
 824            let source = key_binding
 825                .meta()
 826                .map(KeybindSource::from_meta)
 827                .unwrap_or(KeybindSource::Unknown);
 828
 829            let keystroke_text = ui::text_for_keybinding_keystrokes(key_binding.keystrokes(), cx);
 830            let is_no_action = gpui::is_no_action(key_binding.action());
 831            let is_unbound_by_unbind =
 832                binding_is_unbound_by_unbind(key_binding, binding_index, &key_bindings);
 833            let binding = KeyBinding::new(key_binding, source);
 834
 835            let context = key_binding
 836                .predicate()
 837                .map(|predicate| {
 838                    KeybindContextString::Local(
 839                        predicate.to_string().into(),
 840                        zed_keybind_context_language.clone(),
 841                    )
 842                })
 843                .unwrap_or(KeybindContextString::Global);
 844
 845            let action_name = key_binding.action().name();
 846            unmapped_action_names.remove(&action_name);
 847
 848            let action_arguments = key_binding
 849                .action_input()
 850                .map(|arguments| SyntaxHighlightedText::new(arguments, json_language.clone()));
 851            let action_information = ActionInformation::new(
 852                action_name,
 853                action_arguments,
 854                &actions_with_schemas,
 855                action_documentation,
 856                humanized_action_names,
 857            );
 858
 859            let index = processed_bindings.len();
 860            let string_match_candidate =
 861                StringMatchCandidate::new(index, &action_information.humanized_name);
 862            processed_bindings.push(ProcessedBinding::new_mapped(
 863                keystroke_text,
 864                binding,
 865                context,
 866                source,
 867                is_no_action,
 868                is_unbound_by_unbind,
 869                action_information,
 870            ));
 871            string_match_candidates.push(string_match_candidate);
 872        }
 873
 874        for action_name in unmapped_action_names.into_iter() {
 875            let index = processed_bindings.len();
 876            let action_information = ActionInformation::new(
 877                action_name,
 878                None,
 879                &actions_with_schemas,
 880                action_documentation,
 881                humanized_action_names,
 882            );
 883            let string_match_candidate =
 884                StringMatchCandidate::new(index, &action_information.humanized_name);
 885
 886            processed_bindings.push(ProcessedBinding::Unmapped(action_information));
 887            string_match_candidates.push(string_match_candidate);
 888        }
 889        (
 890            processed_bindings,
 891            string_match_candidates,
 892            actions_with_schemas,
 893        )
 894    }
 895
 896    fn on_keymap_changed(&mut self, window: &mut Window, cx: &mut Context<KeymapEditor>) {
 897        let workspace = self.workspace.clone();
 898        cx.spawn_in(window, async move |this, cx| {
 899            let json_language = load_json_language(workspace.clone(), cx).await;
 900            let zed_keybind_context_language =
 901                load_keybind_context_language(workspace.clone(), cx).await;
 902
 903            let (action_query, keystroke_query) = this.update(cx, |this, cx| {
 904                let (key_bindings, string_match_candidates, actions_with_schemas) =
 905                    Self::process_bindings(
 906                        json_language,
 907                        zed_keybind_context_language,
 908                        &this.humanized_action_names,
 909                        cx,
 910                    );
 911
 912                this.keybinding_conflict_state = ConflictState::new(&key_bindings);
 913
 914                this.keybindings = key_bindings;
 915                this.actions_with_schemas = actions_with_schemas;
 916                this.string_match_candidates = Arc::new(string_match_candidates);
 917                this.matches = this
 918                    .string_match_candidates
 919                    .iter()
 920                    .enumerate()
 921                    .map(|(ix, candidate)| StringMatch {
 922                        candidate_id: ix,
 923                        score: 0.0,
 924                        positions: vec![],
 925                        string: candidate.string.clone(),
 926                    })
 927                    .collect();
 928                (
 929                    this.current_action_query(cx),
 930                    this.current_keystroke_query(cx),
 931                )
 932            })?;
 933            // calls cx.notify
 934            Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
 935            this.update_in(cx, |this, window, cx| {
 936                if let Some(previous_edit) = this.previous_edit.take() {
 937                    match previous_edit {
 938                        // should remove scroll from process_query
 939                        PreviousEdit::ScrollBarOffset(offset) => {
 940                            this.table_interaction_state
 941                                .update(cx, |table, _| table.set_scroll_offset(offset))
 942                            // set selected index and scroll
 943                        }
 944                        PreviousEdit::Keybinding {
 945                            action_mapping,
 946                            action_name,
 947                            fallback,
 948                        } => {
 949                            let scroll_position =
 950                                this.matches.iter().enumerate().find_map(|(index, item)| {
 951                                    let binding = &this.keybindings[item.candidate_id];
 952                                    if binding.get_action_mapping().is_some_and(|binding_mapping| {
 953                                        binding_mapping == action_mapping
 954                                    }) && binding.action().name == action_name
 955                                    {
 956                                        Some(index)
 957                                    } else {
 958                                        None
 959                                    }
 960                                });
 961
 962                            if let Some(scroll_position) = scroll_position {
 963                                this.select_index(
 964                                    scroll_position,
 965                                    Some(ScrollStrategy::Top),
 966                                    window,
 967                                    cx,
 968                                );
 969                            } else {
 970                                this.table_interaction_state
 971                                    .update(cx, |table, _| table.set_scroll_offset(fallback));
 972                            }
 973                            cx.notify();
 974                        }
 975                    }
 976                }
 977            })
 978        })
 979        .detach_and_log_err(cx);
 980    }
 981
 982    fn key_context(&self) -> KeyContext {
 983        let mut dispatch_context = KeyContext::new_with_defaults();
 984        dispatch_context.add("KeymapEditor");
 985        dispatch_context.add("menu");
 986
 987        dispatch_context
 988    }
 989
 990    fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
 991        let index = usize::min(index, self.matches.len().saturating_sub(1));
 992        self.table_interaction_state.update(cx, |this, _cx| {
 993            this.scroll_handle.scroll_to_item(index, strategy);
 994        });
 995    }
 996
 997    fn focus_search(
 998        &mut self,
 999        _: &search::FocusSearch,
1000        window: &mut Window,
1001        cx: &mut Context<Self>,
1002    ) {
1003        if !self
1004            .filter_editor
1005            .focus_handle(cx)
1006            .contains_focused(window, cx)
1007        {
1008            window.focus(&self.filter_editor.focus_handle(cx), cx);
1009        } else {
1010            self.filter_editor.update(cx, |editor, cx| {
1011                editor.select_all(&Default::default(), window, cx);
1012            });
1013        }
1014        self.selected_index.take();
1015    }
1016
1017    fn selected_keybind_index(&self) -> Option<usize> {
1018        self.selected_index
1019            .and_then(|match_index| self.matches.get(match_index))
1020            .map(|r#match| r#match.candidate_id)
1021    }
1022
1023    fn selected_keybind_and_index(&self) -> Option<(&ProcessedBinding, usize)> {
1024        self.selected_keybind_index()
1025            .map(|keybind_index| (&self.keybindings[keybind_index], keybind_index))
1026    }
1027
1028    fn selected_binding(&self) -> Option<&ProcessedBinding> {
1029        self.selected_keybind_index()
1030            .and_then(|keybind_index| self.keybindings.get(keybind_index))
1031    }
1032
1033    fn select_index(
1034        &mut self,
1035        index: usize,
1036        scroll: Option<ScrollStrategy>,
1037        window: &mut Window,
1038        cx: &mut Context<Self>,
1039    ) {
1040        if self.selected_index != Some(index) {
1041            self.selected_index = Some(index);
1042            if let Some(scroll_strategy) = scroll {
1043                self.scroll_to_item(index, scroll_strategy, cx);
1044            }
1045            window.focus(&self.focus_handle, cx);
1046            cx.notify();
1047        }
1048    }
1049
1050    fn create_context_menu(
1051        &mut self,
1052        position: Point<Pixels>,
1053        window: &mut Window,
1054        cx: &mut Context<Self>,
1055    ) {
1056        self.context_menu = self.selected_binding().map(|selected_binding| {
1057            let selected_binding_has_no_context = selected_binding
1058                .context()
1059                .and_then(KeybindContextString::local)
1060                .is_none();
1061
1062            let selected_binding_is_unmapped = selected_binding.is_unbound();
1063            let selected_binding_is_suppressed = selected_binding.is_unbound_by_unbind();
1064            let selected_binding_is_non_interactable =
1065                selected_binding_is_unmapped || selected_binding_is_suppressed;
1066
1067            let context_menu = ContextMenu::build(window, cx, |menu, _window, _cx| {
1068                menu.context(self.focus_handle.clone())
1069                    .when(selected_binding_is_unmapped, |this| {
1070                        this.action("Create", Box::new(CreateBinding))
1071                    })
1072                    .action_disabled_when(
1073                        selected_binding_is_non_interactable,
1074                        "Edit",
1075                        Box::new(EditBinding),
1076                    )
1077                    .action_disabled_when(
1078                        selected_binding_is_non_interactable,
1079                        "Delete",
1080                        Box::new(DeleteBinding),
1081                    )
1082                    .separator()
1083                    .action("Copy Action", Box::new(CopyAction))
1084                    .action_disabled_when(
1085                        selected_binding_has_no_context,
1086                        "Copy Context",
1087                        Box::new(CopyContext),
1088                    )
1089                    .separator()
1090                    .action_disabled_when(
1091                        selected_binding_has_no_context,
1092                        "Show Matching Keybindings",
1093                        Box::new(ShowMatchingKeybinds),
1094                    )
1095            });
1096
1097            let context_menu_handle = context_menu.focus_handle(cx);
1098            window.defer(cx, move |window, cx| window.focus(&context_menu_handle, cx));
1099            let subscription = cx.subscribe_in(
1100                &context_menu,
1101                window,
1102                |this, _, _: &DismissEvent, window, cx| {
1103                    this.dismiss_context_menu(window, cx);
1104                },
1105            );
1106            (context_menu, position, subscription)
1107        });
1108
1109        cx.notify();
1110    }
1111
1112    fn dismiss_context_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1113        self.context_menu.take();
1114        window.focus(&self.focus_handle, cx);
1115        cx.notify();
1116    }
1117
1118    fn context_menu_deployed(&self) -> bool {
1119        self.context_menu.is_some()
1120    }
1121
1122    fn create_row_button(
1123        &self,
1124        index: usize,
1125        conflict: Option<ConflictOrigin>,
1126        is_unbound_by_unbind: bool,
1127        cx: &mut Context<Self>,
1128    ) -> IconButton {
1129        if is_unbound_by_unbind {
1130            base_button_style(index, IconName::Warning)
1131                .icon_color(Color::Warning)
1132                .disabled(true)
1133                .tooltip(Tooltip::text("This action is unbound"))
1134        } else if self.filter_state != FilterState::Conflicts
1135            && let Some(conflict) = conflict
1136        {
1137            if conflict.is_user_keybind_conflict() {
1138                base_button_style(index, IconName::Warning)
1139                    .icon_color(Color::Warning)
1140                    .tooltip(|_window, cx| {
1141                        Tooltip::with_meta(
1142                            "View conflicts",
1143                            Some(&ToggleConflictFilter),
1144                            "Use alt+click to show all conflicts",
1145                            cx,
1146                        )
1147                    })
1148                    .on_click(cx.listener(move |this, click: &ClickEvent, window, cx| {
1149                        if click.modifiers().alt {
1150                            this.set_filter_state(FilterState::Conflicts, cx);
1151                        } else {
1152                            this.select_index(index, None, window, cx);
1153                            this.open_edit_keybinding_modal(false, window, cx);
1154                            cx.stop_propagation();
1155                        }
1156                    }))
1157            } else if self.search_mode.exact_match() {
1158                base_button_style(index, IconName::Info)
1159                    .tooltip(|_window, cx| {
1160                        Tooltip::with_meta(
1161                            "Edit this binding",
1162                            Some(&ShowMatchingKeybinds),
1163                            "This binding is overridden by other bindings.",
1164                            cx,
1165                        )
1166                    })
1167                    .on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
1168                        this.select_index(index, None, window, cx);
1169                        this.open_edit_keybinding_modal(false, window, cx);
1170                        cx.stop_propagation();
1171                    }))
1172            } else {
1173                base_button_style(index, IconName::Info)
1174                    .tooltip(|_window, cx|  {
1175                        Tooltip::with_meta(
1176                            "Show matching keybinds",
1177                            Some(&ShowMatchingKeybinds),
1178                            "This binding is overridden by other bindings.\nUse alt+click to edit this binding",
1179                            cx,
1180                        )
1181                    })
1182                    .on_click(cx.listener(move |this, click: &ClickEvent, window, cx| {
1183                        if click.modifiers().alt {
1184                            this.select_index(index, None, window, cx);
1185                            this.open_edit_keybinding_modal(false, window, cx);
1186                            cx.stop_propagation();
1187                        } else {
1188                            this.show_matching_keystrokes(&Default::default(), window, cx);
1189                        }
1190                    }))
1191            }
1192        } else {
1193            base_button_style(index, IconName::Pencil)
1194                .visible_on_hover(if self.selected_index == Some(index) {
1195                    "".into()
1196                } else if self.show_hover_menus {
1197                    row_group_id(index)
1198                } else {
1199                    "never-show".into()
1200                })
1201                .when(
1202                    self.show_hover_menus && !self.context_menu_deployed(),
1203                    |this| this.tooltip(Tooltip::for_action_title("Edit Keybinding", &EditBinding)),
1204                )
1205                .on_click(cx.listener(move |this, _, window, cx| {
1206                    this.select_index(index, None, window, cx);
1207                    this.open_edit_keybinding_modal(false, window, cx);
1208                    cx.stop_propagation();
1209                }))
1210        }
1211    }
1212
1213    fn render_no_matches_hint(&self, _window: &mut Window, _cx: &App) -> AnyElement {
1214        let hint = match (self.filter_state, &self.search_mode) {
1215            (FilterState::Conflicts, _) => {
1216                if self.keybinding_conflict_state.any_user_binding_conflicts() {
1217                    "No conflicting keybinds found that match the provided query"
1218                } else {
1219                    "No conflicting keybinds found"
1220                }
1221            }
1222            (FilterState::All, SearchMode::KeyStroke { .. }) => {
1223                "No keybinds found matching the entered keystrokes"
1224            }
1225            (FilterState::All, SearchMode::Normal) => "No matches found for the provided query",
1226        };
1227
1228        Label::new(hint).color(Color::Muted).into_any_element()
1229    }
1230
1231    fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
1232        self.show_hover_menus = false;
1233        if let Some(selected) = self.selected_index {
1234            let selected = selected + 1;
1235            if selected >= self.matches.len() {
1236                self.select_last(&Default::default(), window, cx);
1237            } else {
1238                self.select_index(selected, Some(ScrollStrategy::Center), window, cx);
1239            }
1240        } else {
1241            self.select_first(&Default::default(), window, cx);
1242        }
1243    }
1244
1245    fn select_previous(
1246        &mut self,
1247        _: &menu::SelectPrevious,
1248        window: &mut Window,
1249        cx: &mut Context<Self>,
1250    ) {
1251        self.show_hover_menus = false;
1252        if let Some(selected) = self.selected_index {
1253            if selected == 0 {
1254                return;
1255            }
1256
1257            let selected = selected - 1;
1258
1259            if selected >= self.matches.len() {
1260                self.select_last(&Default::default(), window, cx);
1261            } else {
1262                self.select_index(selected, Some(ScrollStrategy::Center), window, cx);
1263            }
1264        } else {
1265            self.select_last(&Default::default(), window, cx);
1266        }
1267    }
1268
1269    fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
1270        self.show_hover_menus = false;
1271        if self.matches.get(0).is_some() {
1272            self.select_index(0, Some(ScrollStrategy::Center), window, cx);
1273        }
1274    }
1275
1276    fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
1277        self.show_hover_menus = false;
1278        if self.matches.last().is_some() {
1279            let index = self.matches.len() - 1;
1280            self.select_index(index, Some(ScrollStrategy::Center), window, cx);
1281        }
1282    }
1283
1284    fn open_edit_keybinding_modal(
1285        &mut self,
1286        create: bool,
1287        window: &mut Window,
1288        cx: &mut Context<Self>,
1289    ) {
1290        self.show_hover_menus = false;
1291        let Some((keybind, keybind_index)) = self.selected_keybind_and_index() else {
1292            return;
1293        };
1294        if !create && keybind.is_unbound_by_unbind() {
1295            return;
1296        }
1297        let keybind = keybind.clone();
1298        let keymap_editor = cx.entity();
1299
1300        let keystroke = keybind.keystroke_text().cloned().unwrap_or_default();
1301        let arguments = keybind
1302            .action()
1303            .arguments
1304            .as_ref()
1305            .map(|arguments| arguments.text.clone());
1306        let context = keybind
1307            .context()
1308            .map(|context| context.local_str().unwrap_or("global"));
1309        let action = keybind.action().name;
1310        let source = keybind.keybind_source().map(|source| source.name());
1311
1312        telemetry::event!(
1313            "Edit Keybinding Modal Opened",
1314            keystroke = keystroke,
1315            action = action,
1316            source = source,
1317            context = context,
1318            arguments = arguments,
1319        );
1320
1321        let temp_dir = self.action_args_temp_dir.as_ref().map(|dir| dir.path());
1322
1323        self.workspace
1324            .update(cx, |workspace, cx| {
1325                let fs = workspace.app_state().fs.clone();
1326                let workspace_weak = cx.weak_entity();
1327                workspace.toggle_modal(window, cx, |window, cx| {
1328                    let modal = KeybindingEditorModal::new(
1329                        create,
1330                        keybind,
1331                        keybind_index,
1332                        keymap_editor,
1333                        temp_dir,
1334                        workspace_weak,
1335                        fs,
1336                        window,
1337                        cx,
1338                    );
1339                    window.focus(&modal.focus_handle(cx), cx);
1340                    modal
1341                });
1342            })
1343            .log_err();
1344    }
1345
1346    fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
1347        self.open_edit_keybinding_modal(false, window, cx);
1348    }
1349
1350    fn create_binding(&mut self, _: &CreateBinding, window: &mut Window, cx: &mut Context<Self>) {
1351        self.open_edit_keybinding_modal(true, window, cx);
1352    }
1353
1354    fn open_create_keybinding_modal(
1355        &mut self,
1356        _: &OpenCreateKeybindingModal,
1357        window: &mut Window,
1358        cx: &mut Context<Self>,
1359    ) {
1360        let keymap_editor = cx.entity();
1361
1362        let action_information = ActionInformation::new(
1363            gpui::NoAction.name(),
1364            None,
1365            &HashSet::default(),
1366            cx.action_documentation(),
1367            &self.humanized_action_names,
1368        );
1369
1370        let dummy_binding = ProcessedBinding::Unmapped(action_information);
1371        let dummy_index = self.keybindings.len();
1372
1373        let temp_dir = self.action_args_temp_dir.as_ref().map(|dir| dir.path());
1374
1375        self.workspace
1376            .update(cx, |workspace, cx| {
1377                let fs = workspace.app_state().fs.clone();
1378                let workspace_weak = cx.weak_entity();
1379                workspace.toggle_modal(window, cx, |window, cx| {
1380                    let modal = KeybindingEditorModal::new(
1381                        true,
1382                        dummy_binding,
1383                        dummy_index,
1384                        keymap_editor,
1385                        temp_dir,
1386                        workspace_weak,
1387                        fs,
1388                        window,
1389                        cx,
1390                    );
1391
1392                    window.focus(&modal.focus_handle(cx), cx);
1393                    modal
1394                });
1395            })
1396            .log_err();
1397    }
1398
1399    fn delete_binding(&mut self, _: &DeleteBinding, window: &mut Window, cx: &mut Context<Self>) {
1400        let Some(to_remove) = self.selected_binding().cloned() else {
1401            return;
1402        };
1403        if to_remove.is_unbound_by_unbind() {
1404            return;
1405        }
1406
1407        let std::result::Result::Ok(fs) = self
1408            .workspace
1409            .read_with(cx, |workspace, _| workspace.app_state().fs.clone())
1410        else {
1411            return;
1412        };
1413        self.previous_edit = Some(PreviousEdit::ScrollBarOffset(
1414            self.table_interaction_state.read(cx).scroll_offset(),
1415        ));
1416        let keyboard_mapper = cx.keyboard_mapper().clone();
1417        cx.spawn(async move |_, _| {
1418            remove_keybinding(to_remove, &fs, keyboard_mapper.as_ref()).await
1419        })
1420        .detach_and_notify_err(self.workspace.clone(), window, cx);
1421    }
1422
1423    fn copy_context_to_clipboard(
1424        &mut self,
1425        _: &CopyContext,
1426        _window: &mut Window,
1427        cx: &mut Context<Self>,
1428    ) {
1429        let context = self
1430            .selected_binding()
1431            .and_then(|binding| binding.context())
1432            .and_then(KeybindContextString::local_str)
1433            .map(|context| context.to_string());
1434        let Some(context) = context else {
1435            return;
1436        };
1437
1438        telemetry::event!("Keybinding Context Copied", context = context);
1439        cx.write_to_clipboard(gpui::ClipboardItem::new_string(context));
1440    }
1441
1442    fn copy_action_to_clipboard(
1443        &mut self,
1444        _: &CopyAction,
1445        _window: &mut Window,
1446        cx: &mut Context<Self>,
1447    ) {
1448        let action = self
1449            .selected_binding()
1450            .map(|binding| binding.action().name.to_string());
1451        let Some(action) = action else {
1452            return;
1453        };
1454
1455        telemetry::event!("Keybinding Action Copied", action = action);
1456        cx.write_to_clipboard(gpui::ClipboardItem::new_string(action));
1457    }
1458
1459    fn toggle_conflict_filter(
1460        &mut self,
1461        _: &ToggleConflictFilter,
1462        _: &mut Window,
1463        cx: &mut Context<Self>,
1464    ) {
1465        self.set_filter_state(self.filter_state.invert(), cx);
1466    }
1467
1468    fn toggle_no_action_bindings(
1469        &mut self,
1470        _: &ToggleNoActionBindings,
1471        _: &mut Window,
1472        cx: &mut Context<Self>,
1473    ) {
1474        self.show_no_action_bindings = !self.show_no_action_bindings;
1475        self.on_query_changed(cx);
1476    }
1477
1478    fn toggle_user_bindings_filter(&mut self, cx: &mut Context<Self>) {
1479        self.source_filters.user = !self.source_filters.user;
1480        self.on_query_changed(cx);
1481    }
1482
1483    fn toggle_zed_defaults_filter(&mut self, cx: &mut Context<Self>) {
1484        self.source_filters.zed_defaults = !self.source_filters.zed_defaults;
1485        self.on_query_changed(cx);
1486    }
1487
1488    fn toggle_vim_defaults_filter(&mut self, cx: &mut Context<Self>) {
1489        self.source_filters.vim_defaults = !self.source_filters.vim_defaults;
1490        self.on_query_changed(cx);
1491    }
1492
1493    fn set_filter_state(&mut self, filter_state: FilterState, cx: &mut Context<Self>) {
1494        if self.filter_state != filter_state {
1495            self.filter_state = filter_state;
1496            self.on_query_changed(cx);
1497        }
1498    }
1499
1500    fn toggle_keystroke_search(
1501        &mut self,
1502        _: &ToggleKeystrokeSearch,
1503        window: &mut Window,
1504        cx: &mut Context<Self>,
1505    ) {
1506        self.search_mode = self.search_mode.invert();
1507        self.on_query_changed(cx);
1508
1509        match self.search_mode {
1510            SearchMode::KeyStroke { .. } => {
1511                self.keystroke_editor.update(cx, |editor, cx| {
1512                    editor.start_recording(&StartRecording, window, cx);
1513                });
1514            }
1515            SearchMode::Normal => {
1516                self.keystroke_editor.update(cx, |editor, cx| {
1517                    editor.stop_recording(&StopRecording, window, cx);
1518                    editor.clear_keystrokes(&ClearKeystrokes, window, cx);
1519                });
1520                window.focus(&self.filter_editor.focus_handle(cx), cx);
1521            }
1522        }
1523    }
1524
1525    fn toggle_exact_keystroke_matching(
1526        &mut self,
1527        _: &ToggleExactKeystrokeMatching,
1528        _: &mut Window,
1529        cx: &mut Context<Self>,
1530    ) {
1531        let SearchMode::KeyStroke { exact_match } = &mut self.search_mode else {
1532            return;
1533        };
1534
1535        *exact_match = !(*exact_match);
1536        self.on_query_changed(cx);
1537    }
1538
1539    fn show_matching_keystrokes(
1540        &mut self,
1541        _: &ShowMatchingKeybinds,
1542        _: &mut Window,
1543        cx: &mut Context<Self>,
1544    ) {
1545        let Some(selected_binding) = self.selected_binding() else {
1546            return;
1547        };
1548
1549        let keystrokes = selected_binding
1550            .keystrokes()
1551            .map(Vec::from)
1552            .unwrap_or_default();
1553
1554        self.filter_state = FilterState::All;
1555        self.search_mode = SearchMode::KeyStroke { exact_match: true };
1556
1557        self.keystroke_editor.update(cx, |editor, cx| {
1558            editor.set_keystrokes(keystrokes, cx);
1559        });
1560    }
1561
1562    fn has_binding_for(&self, action_name: &str) -> bool {
1563        self.keybindings
1564            .iter()
1565            .filter(|kb| kb.keystrokes().is_some())
1566            .any(|kb| kb.action().name == action_name)
1567    }
1568
1569    fn render_filter_dropdown(
1570        &self,
1571        focus_handle: &FocusHandle,
1572        cx: &mut Context<KeymapEditor>,
1573    ) -> impl IntoElement {
1574        let focus_handle = focus_handle.clone();
1575        let keymap_editor = cx.entity();
1576        return PopoverMenu::new("keymap-editor-filter-menu")
1577            .menu(move |window, cx| {
1578                Some(ContextMenu::build_persistent(window, cx, {
1579                    let focus_handle = focus_handle.clone();
1580                    let keymap_editor = keymap_editor.clone();
1581                    move |mut menu, _window, cx| {
1582                        let (filter_state, source_filters, show_no_action_bindings) = keymap_editor
1583                            .read_with(cx, |editor, _| {
1584                                (
1585                                    editor.filter_state,
1586                                    editor.source_filters,
1587                                    editor.show_no_action_bindings,
1588                                )
1589                            });
1590
1591                        menu = menu
1592                            .context(focus_handle.clone())
1593                            .header("Filters")
1594                            .map(add_filter(
1595                                "Conflicts",
1596                                matches!(filter_state, FilterState::Conflicts),
1597                                Some(ToggleConflictFilter.boxed_clone()),
1598                                &focus_handle,
1599                                &keymap_editor,
1600                                None,
1601                            ))
1602                            .map(add_filter(
1603                                "No Action",
1604                                show_no_action_bindings,
1605                                Some(ToggleNoActionBindings.boxed_clone()),
1606                                &focus_handle,
1607                                &keymap_editor,
1608                                None,
1609                            ))
1610                            .separator()
1611                            .header("Categories")
1612                            .map(add_filter(
1613                                "User",
1614                                source_filters.user,
1615                                None,
1616                                &focus_handle,
1617                                &keymap_editor,
1618                                Some(|editor, cx| {
1619                                    editor.toggle_user_bindings_filter(cx);
1620                                }),
1621                            ))
1622                            .map(add_filter(
1623                                "Default",
1624                                source_filters.zed_defaults,
1625                                None,
1626                                &focus_handle,
1627                                &keymap_editor,
1628                                Some(|editor, cx| {
1629                                    editor.toggle_zed_defaults_filter(cx);
1630                                }),
1631                            ))
1632                            .map(add_filter(
1633                                "Vim",
1634                                source_filters.vim_defaults,
1635                                None,
1636                                &focus_handle,
1637                                &keymap_editor,
1638                                Some(|editor, cx| {
1639                                    editor.toggle_vim_defaults_filter(cx);
1640                                }),
1641                            ));
1642                        menu
1643                    }
1644                }))
1645            })
1646            .anchor(gpui::Corner::TopRight)
1647            .offset(gpui::Point {
1648                x: px(0.0),
1649                y: px(2.0),
1650            })
1651            .trigger_with_tooltip(
1652                IconButton::new("KeymapEditorFilterMenuButton", IconName::Sliders)
1653                    .icon_size(IconSize::Small)
1654                    .when(
1655                        self.keybinding_conflict_state.any_user_binding_conflicts(),
1656                        |this| this.indicator(Indicator::dot().color(Color::Warning)),
1657                    ),
1658                Tooltip::text("Filters"),
1659            );
1660
1661        fn add_filter(
1662            name: &'static str,
1663            toggled: bool,
1664            action: Option<Box<dyn Action>>,
1665            focus_handle: &FocusHandle,
1666            keymap_editor: &Entity<KeymapEditor>,
1667            cb: Option<fn(&mut KeymapEditor, &mut Context<KeymapEditor>)>,
1668        ) -> impl FnOnce(ContextMenu) -> ContextMenu {
1669            let focus_handle = focus_handle.clone();
1670            let keymap_editor = keymap_editor.clone();
1671            return move |menu: ContextMenu| {
1672                menu.toggleable_entry(
1673                    name,
1674                    toggled,
1675                    IconPosition::End,
1676                    action.as_ref().map(|a| a.boxed_clone()),
1677                    move |window, cx| {
1678                        window.focus(&focus_handle, cx);
1679                        if let Some(action) = &action {
1680                            window.dispatch_action(action.boxed_clone(), cx);
1681                        } else if let Some(cb) = cb {
1682                            keymap_editor.update(cx, cb);
1683                        }
1684                    },
1685                )
1686            };
1687        }
1688    }
1689}
1690
1691struct HumanizedActionNameCache {
1692    cache: HashMap<&'static str, SharedString>,
1693}
1694
1695impl HumanizedActionNameCache {
1696    fn new(cx: &App) -> Self {
1697        let cache = HashMap::from_iter(cx.all_action_names().iter().map(|&action_name| {
1698            (
1699                action_name,
1700                command_palette::humanize_action_name(action_name).into(),
1701            )
1702        }));
1703        Self { cache }
1704    }
1705
1706    fn get(&self, action_name: &'static str) -> SharedString {
1707        match self.cache.get(action_name) {
1708            Some(name) => name.clone(),
1709            None => action_name.into(),
1710        }
1711    }
1712}
1713
1714#[derive(Clone)]
1715struct KeyBinding {
1716    keystrokes: Rc<[KeybindingKeystroke]>,
1717    source: KeybindSource,
1718}
1719
1720impl KeyBinding {
1721    fn new(binding: &gpui::KeyBinding, source: KeybindSource) -> Self {
1722        Self {
1723            keystrokes: Rc::from(binding.keystrokes()),
1724            source,
1725        }
1726    }
1727}
1728
1729#[derive(Clone)]
1730struct KeybindInformation {
1731    keystroke_text: SharedString,
1732    binding: KeyBinding,
1733    context: KeybindContextString,
1734    source: KeybindSource,
1735    is_no_action: bool,
1736    is_unbound_by_unbind: bool,
1737}
1738
1739impl KeybindInformation {
1740    fn get_action_mapping(&self) -> ActionMapping {
1741        ActionMapping {
1742            keystrokes: self.binding.keystrokes.clone(),
1743            context: self.context.local().cloned(),
1744        }
1745    }
1746}
1747
1748#[derive(Clone)]
1749struct ActionInformation {
1750    name: &'static str,
1751    humanized_name: SharedString,
1752    arguments: Option<SyntaxHighlightedText>,
1753    documentation: Option<&'static str>,
1754    has_schema: bool,
1755}
1756
1757impl ActionInformation {
1758    fn new(
1759        action_name: &'static str,
1760        action_arguments: Option<SyntaxHighlightedText>,
1761        actions_with_schemas: &HashSet<&'static str>,
1762        action_documentation: &HashMap<&'static str, &'static str>,
1763        action_name_cache: &HumanizedActionNameCache,
1764    ) -> Self {
1765        Self {
1766            humanized_name: action_name_cache.get(action_name),
1767            has_schema: actions_with_schemas.contains(action_name),
1768            arguments: action_arguments,
1769            documentation: action_documentation.get(action_name).copied(),
1770            name: action_name,
1771        }
1772    }
1773}
1774
1775#[derive(Clone)]
1776enum ProcessedBinding {
1777    Mapped(KeybindInformation, ActionInformation),
1778    Unmapped(ActionInformation),
1779}
1780
1781impl ProcessedBinding {
1782    fn new_mapped(
1783        keystroke_text: impl Into<SharedString>,
1784        binding: KeyBinding,
1785        context: KeybindContextString,
1786        source: KeybindSource,
1787        is_no_action: bool,
1788        is_unbound_by_unbind: bool,
1789        action_information: ActionInformation,
1790    ) -> Self {
1791        Self::Mapped(
1792            KeybindInformation {
1793                keystroke_text: keystroke_text.into(),
1794                binding,
1795                context,
1796                source,
1797                is_no_action,
1798                is_unbound_by_unbind,
1799            },
1800            action_information,
1801        )
1802    }
1803
1804    fn is_unbound(&self) -> bool {
1805        matches!(self, Self::Unmapped(_))
1806    }
1807
1808    fn get_action_mapping(&self) -> Option<ActionMapping> {
1809        self.keybind_information()
1810            .map(|keybind| keybind.get_action_mapping())
1811    }
1812
1813    fn keystrokes(&self) -> Option<&[KeybindingKeystroke]> {
1814        self.key_binding()
1815            .map(|binding| binding.keystrokes.as_ref())
1816    }
1817
1818    fn keybind_information(&self) -> Option<&KeybindInformation> {
1819        match self {
1820            Self::Mapped(keybind_information, _) => Some(keybind_information),
1821            Self::Unmapped(_) => None,
1822        }
1823    }
1824
1825    fn keybind_source(&self) -> Option<KeybindSource> {
1826        self.keybind_information().map(|keybind| keybind.source)
1827    }
1828
1829    fn context(&self) -> Option<&KeybindContextString> {
1830        self.keybind_information().map(|keybind| &keybind.context)
1831    }
1832
1833    fn key_binding(&self) -> Option<&KeyBinding> {
1834        self.keybind_information().map(|keybind| &keybind.binding)
1835    }
1836
1837    fn is_no_action(&self) -> bool {
1838        self.keybind_information()
1839            .is_some_and(|keybind| keybind.is_no_action)
1840    }
1841
1842    fn is_unbound_by_unbind(&self) -> bool {
1843        self.keybind_information()
1844            .is_some_and(|keybind| keybind.is_unbound_by_unbind)
1845    }
1846
1847    fn keystroke_text(&self) -> Option<&SharedString> {
1848        self.keybind_information()
1849            .map(|binding| &binding.keystroke_text)
1850    }
1851
1852    fn action(&self) -> &ActionInformation {
1853        match self {
1854            Self::Mapped(_, action) | Self::Unmapped(action) => action,
1855        }
1856    }
1857
1858    fn cmp(&self, other: &Self) -> cmp::Ordering {
1859        match (self, other) {
1860            (Self::Mapped(keybind1, action1), Self::Mapped(keybind2, action2)) => {
1861                match keybind1.source.cmp(&keybind2.source) {
1862                    cmp::Ordering::Equal => action1.humanized_name.cmp(&action2.humanized_name),
1863                    ordering => ordering,
1864                }
1865            }
1866            (Self::Mapped(_, _), Self::Unmapped(_)) => cmp::Ordering::Less,
1867            (Self::Unmapped(_), Self::Mapped(_, _)) => cmp::Ordering::Greater,
1868            (Self::Unmapped(action1), Self::Unmapped(action2)) => {
1869                action1.humanized_name.cmp(&action2.humanized_name)
1870            }
1871        }
1872    }
1873}
1874
1875#[derive(Clone, Debug, IntoElement, PartialEq, Eq, Hash)]
1876enum KeybindContextString {
1877    Global,
1878    Local(SharedString, Arc<Language>),
1879}
1880
1881impl KeybindContextString {
1882    const GLOBAL: SharedString = SharedString::new_static("<global>");
1883
1884    pub fn local(&self) -> Option<&SharedString> {
1885        match self {
1886            KeybindContextString::Global => None,
1887            KeybindContextString::Local(name, _) => Some(name),
1888        }
1889    }
1890
1891    pub fn local_str(&self) -> Option<&str> {
1892        match self {
1893            KeybindContextString::Global => None,
1894            KeybindContextString::Local(name, _) => Some(name),
1895        }
1896    }
1897}
1898
1899impl RenderOnce for KeybindContextString {
1900    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
1901        match self {
1902            KeybindContextString::Global => {
1903                muted_styled_text(KeybindContextString::GLOBAL, cx).into_any_element()
1904            }
1905            KeybindContextString::Local(name, language) => {
1906                SyntaxHighlightedText::new(name, language).into_any_element()
1907            }
1908        }
1909    }
1910}
1911
1912fn muted_styled_text(text: SharedString, cx: &App) -> StyledText {
1913    let len = text.len();
1914    StyledText::new(text).with_highlights([(
1915        0..len,
1916        gpui::HighlightStyle::color(cx.theme().colors().text_muted),
1917    )])
1918}
1919
1920impl Item for KeymapEditor {
1921    type Event = ();
1922
1923    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
1924        "Keymap Editor".into()
1925    }
1926}
1927
1928impl Render for KeymapEditor {
1929    fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
1930        if let SearchMode::KeyStroke { exact_match } = self.search_mode {
1931            let button = IconButton::new("keystrokes-exact-match", IconName::CaseSensitive)
1932                .tooltip(move |_window, cx| {
1933                    Tooltip::for_action(
1934                        "Toggle Exact Match Mode",
1935                        &ToggleExactKeystrokeMatching,
1936                        cx,
1937                    )
1938                })
1939                .shape(IconButtonShape::Square)
1940                .toggle_state(exact_match)
1941                .on_click(cx.listener(|_, _, window, cx| {
1942                    window.dispatch_action(ToggleExactKeystrokeMatching.boxed_clone(), cx);
1943                }));
1944
1945            self.keystroke_editor.update(cx, |editor, _| {
1946                editor.actions_slot = Some(button.into_any_element());
1947            });
1948        } else {
1949            self.keystroke_editor.update(cx, |editor, _| {
1950                editor.actions_slot = None;
1951            });
1952        }
1953
1954        let row_count = self.matches.len();
1955        let focus_handle = &self.focus_handle;
1956        let theme = cx.theme();
1957        let search_mode = self.search_mode;
1958
1959        v_flex()
1960            .id("keymap-editor")
1961            .track_focus(focus_handle)
1962            .key_context(self.key_context())
1963            .on_action(cx.listener(Self::select_next))
1964            .on_action(cx.listener(Self::select_previous))
1965            .on_action(cx.listener(Self::select_first))
1966            .on_action(cx.listener(Self::select_last))
1967            .on_action(cx.listener(Self::focus_search))
1968            .on_action(cx.listener(Self::edit_binding))
1969            .on_action(cx.listener(Self::create_binding))
1970            .on_action(cx.listener(Self::open_create_keybinding_modal))
1971            .on_action(cx.listener(Self::delete_binding))
1972            .on_action(cx.listener(Self::copy_action_to_clipboard))
1973            .on_action(cx.listener(Self::copy_context_to_clipboard))
1974            .on_action(cx.listener(Self::toggle_conflict_filter))
1975            .on_action(cx.listener(Self::toggle_no_action_bindings))
1976            .on_action(cx.listener(Self::toggle_keystroke_search))
1977            .on_action(cx.listener(Self::toggle_exact_keystroke_matching))
1978            .on_action(cx.listener(Self::show_matching_keystrokes))
1979            .on_mouse_move(cx.listener(|this, _, _window, _cx| {
1980                this.show_hover_menus = true;
1981            }))
1982            .size_full()
1983            .p_2()
1984            .gap_1()
1985            .bg(theme.colors().editor_background)
1986            .child(
1987                v_flex()
1988                    .gap_2()
1989                    .child(
1990                        h_flex()
1991                            .gap_2()
1992                            .items_center()
1993                            .child(
1994                                h_flex()
1995                                    .key_context({
1996                                        let mut context = KeyContext::new_with_defaults();
1997                                        context.add("BufferSearchBar");
1998                                        context
1999                                    })
2000                                    .size_full()
2001                                    .h_8()
2002                                    .pl_2()
2003                                    .pr_1()
2004                                    .py_1()
2005                                    .border_1()
2006                                    .border_color(theme.colors().border)
2007                                    .rounded_md()
2008                                    .child(self.filter_editor.clone()),
2009                            )
2010                            .child(
2011                                h_flex()
2012                                    .gap_1()
2013                                    .min_w_96()
2014                                    .items_center()
2015                                    .child(
2016                                        IconButton::new(
2017                                            "KeymapEditorKeystrokeSearchButton",
2018                                            IconName::Keyboard,
2019                                        )
2020                                        .icon_size(IconSize::Small)
2021                                        .toggle_state(matches!(
2022                                            search_mode,
2023                                            SearchMode::KeyStroke { .. }
2024                                        ))
2025                                        .tooltip({
2026                                            let focus_handle = focus_handle.clone();
2027                                            move |_window, cx| {
2028                                                Tooltip::for_action_in(
2029                                                    "Search by Keystrokes",
2030                                                    &ToggleKeystrokeSearch,
2031                                                    &focus_handle,
2032                                                    cx,
2033                                                )
2034                                            }
2035                                        })
2036                                        .on_click(cx.listener(|_, _, window, cx| {
2037                                            window.dispatch_action(
2038                                                ToggleKeystrokeSearch.boxed_clone(),
2039                                                cx,
2040                                            );
2041                                        })),
2042                                    )
2043                                    .child(
2044                                        self.render_filter_dropdown(focus_handle, cx)
2045                                    )
2046                                    .child(
2047                                        Button::new("edit-in-json", "Edit in JSON")
2048                                            .style(ButtonStyle::Subtle)
2049                                            .key_binding(
2050                                                ui::KeyBinding::for_action_in(&zed_actions::OpenKeymapFile, &focus_handle, cx)
2051                                                    .map(|kb| kb.size(rems_from_px(10.))),
2052                                            )
2053                                            .on_click(|_, window, cx| {
2054                                                window.dispatch_action(
2055                                                    zed_actions::OpenKeymapFile.boxed_clone(),
2056                                                    cx,
2057                                                );
2058                                            })
2059                                    )
2060                                    .child(
2061                                        Button::new("create", "Create Keybinding")
2062                                            .style(ButtonStyle::Outlined)
2063                                            .key_binding(
2064                                                ui::KeyBinding::for_action_in(&OpenCreateKeybindingModal, &focus_handle, cx)
2065                                                    .map(|kb| kb.size(rems_from_px(10.))),
2066                                            )
2067                                            .on_click(|_, window, cx| {
2068                                                window.dispatch_action(
2069                                                    OpenCreateKeybindingModal.boxed_clone(),
2070                                                    cx,
2071                                                );
2072                                            })
2073                                    )
2074                            ),
2075                    )
2076                    .when(
2077                        matches!(self.search_mode, SearchMode::KeyStroke { .. }),
2078                        |this| {
2079                            this.child(
2080                                h_flex()
2081                                    .gap_2()
2082                                    .child(self.keystroke_editor.clone())
2083                                    .child(div().min_w_96()), // Spacer div to align with the search input
2084                            )
2085                        },
2086                    ),
2087            )
2088            .child(
2089                Table::new(COLS)
2090                    .interactable(&self.table_interaction_state)
2091                    .striped()
2092                    .empty_table_callback({
2093                        let this = cx.entity();
2094                        move |window, cx| this.read(cx).render_no_matches_hint(window, cx)
2095                    })
2096                    .column_widths(vec![
2097                        DefiniteLength::Absolute(AbsoluteLength::Pixels(px(36.))),
2098                        DefiniteLength::Fraction(0.25),
2099                        DefiniteLength::Fraction(0.20),
2100                        DefiniteLength::Fraction(0.14),
2101                        DefiniteLength::Fraction(0.45),
2102                        DefiniteLength::Fraction(0.08),
2103                    ])
2104                    .resizable_columns(
2105                        vec![
2106                            TableResizeBehavior::None,
2107                            TableResizeBehavior::Resizable,
2108                            TableResizeBehavior::Resizable,
2109                            TableResizeBehavior::Resizable,
2110                            TableResizeBehavior::Resizable,
2111                            TableResizeBehavior::Resizable, // this column doesn't matter
2112                        ],
2113                        &self.current_widths,
2114                        cx,
2115                    )
2116                    .header(vec!["", "Action", "Arguments", "Keystrokes", "Context", "Source"])
2117                    .uniform_list(
2118                        "keymap-editor-table",
2119                        row_count,
2120                        cx.processor(move |this, range: Range<usize>, _window, cx| {
2121                            let context_menu_deployed = this.context_menu_deployed();
2122                            range
2123                                .filter_map(|index| {
2124                                    let candidate_id = this.matches.get(index)?.candidate_id;
2125                                    let binding = &this.keybindings[candidate_id];
2126                                    let action_name = binding.action().name;
2127                                    let conflict = this.get_conflict(index);
2128                                    let is_unbound_by_unbind = binding.is_unbound_by_unbind();
2129                                    let is_overridden = conflict.is_some_and(|conflict| {
2130                                        !conflict.is_user_keybind_conflict()
2131                                    });
2132                                    let is_dimmed = is_overridden || is_unbound_by_unbind;
2133
2134                                    let icon = this.create_row_button(
2135                                        index,
2136                                        conflict,
2137                                        is_unbound_by_unbind,
2138                                        cx,
2139                                    );
2140
2141                                    let action = div()
2142                                        .id(("keymap action", index))
2143                                        .child({
2144                                            if action_name != gpui::NoAction.name() {
2145                                                binding
2146                                                    .action()
2147                                                    .humanized_name
2148                                                    .clone()
2149                                                    .into_any_element()
2150                                            } else {
2151                                                const NULL: SharedString =
2152                                                    SharedString::new_static("<null>");
2153                                                muted_styled_text(NULL, cx)
2154                                                    .into_any_element()
2155                                            }
2156                                        })
2157                                        .when(
2158                                            !context_menu_deployed
2159                                                && this.show_hover_menus
2160                                                && !is_dimmed,
2161                                            |this| {
2162                                                this.tooltip({
2163                                                    let action_name = binding.action().name;
2164                                                    let action_docs =
2165                                                        binding.action().documentation;
2166                                                    move |_, cx| {
2167                                                        let action_tooltip =
2168                                                            Tooltip::new(action_name);
2169                                                        let action_tooltip = match action_docs {
2170                                                            Some(docs) => action_tooltip.meta(docs),
2171                                                            None => action_tooltip,
2172                                                        };
2173                                                        cx.new(|_| action_tooltip).into()
2174                                                    }
2175                                                })
2176                                            },
2177                                        )
2178                                        .into_any_element();
2179
2180                                    let keystrokes = binding.key_binding().map_or(
2181                                        binding
2182                                            .keystroke_text()
2183                                            .cloned()
2184                                            .unwrap_or_default()
2185                                            .into_any_element(),
2186                                        |binding| ui::KeyBinding::from_keystrokes(binding.keystrokes.clone(), binding.source).into_any_element()
2187                                    );
2188
2189                                    let action_arguments = match binding.action().arguments.clone()
2190                                    {
2191                                        Some(arguments) => arguments.into_any_element(),
2192                                        None => {
2193                                            if binding.action().has_schema {
2194                                                muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
2195                                                    .into_any_element()
2196                                            } else {
2197                                                gpui::Empty.into_any_element()
2198                                            }
2199                                        }
2200                                    };
2201
2202                                    let context = binding.context().cloned().map_or(
2203                                        gpui::Empty.into_any_element(),
2204                                        |context| {
2205                                            let is_local = context.local().is_some();
2206
2207                                            div()
2208                                                .id(("keymap context", index))
2209                                                .child(context.clone())
2210                                                .when(
2211                                                    is_local
2212                                                        && !context_menu_deployed
2213                                                        && !is_dimmed
2214                                                        && this.show_hover_menus,
2215                                                    |this| {
2216                                                        this.tooltip(Tooltip::element({
2217                                                            move |_, _| {
2218                                                                context.clone().into_any_element()
2219                                                            }
2220                                                        }))
2221                                                    },
2222                                                )
2223                                                .into_any_element()
2224                                        },
2225                                    );
2226
2227                                    let source = binding
2228                                        .keybind_source()
2229                                        .map(|source| source.name())
2230                                        .unwrap_or_default()
2231                                        .into_any_element();
2232
2233                                    Some(vec![
2234                                        icon.into_any_element(),
2235                                        action,
2236                                        action_arguments,
2237                                        keystrokes,
2238                                        context,
2239                                        source,
2240                                    ])
2241                                })
2242                                .collect()
2243                        }),
2244                    )
2245                    .map_row(cx.processor(
2246                        |this, (row_index, row): (usize, Stateful<Div>), _window, cx| {
2247                        let conflict = this.get_conflict(row_index);
2248                            let candidate_id = this.matches.get(row_index).map(|candidate| candidate.candidate_id);
2249                            let is_unbound_by_unbind = candidate_id
2250                                .and_then(|candidate_id| this.keybindings.get(candidate_id))
2251                                .is_some_and(ProcessedBinding::is_unbound_by_unbind);
2252                            let is_selected = this.selected_index == Some(row_index);
2253
2254                            let row_id = row_group_id(row_index);
2255
2256                            div()
2257                                .id(("keymap-row-wrapper", row_index))
2258                                .child(
2259                                    row.id(row_id.clone())
2260                                        .when(!is_unbound_by_unbind, |row| {
2261                                            row.on_any_mouse_down(cx.listener(
2262                                                move |this,
2263                                                      mouse_down_event: &gpui::MouseDownEvent,
2264                                                      window,
2265                                                      cx| {
2266                                                    if mouse_down_event.button == MouseButton::Right {
2267                                                        this.select_index(
2268                                                            row_index, None, window, cx,
2269                                                        );
2270                                                        this.create_context_menu(
2271                                                            mouse_down_event.position,
2272                                                            window,
2273                                                            cx,
2274                                                        );
2275                                                    }
2276                                                },
2277                                            ))
2278                                        })
2279                                        .when(!is_unbound_by_unbind, |row| {
2280                                            row.on_click(cx.listener(
2281                                                move |this, event: &ClickEvent, window, cx| {
2282                                                    this.select_index(row_index, None, window, cx);
2283                                                    if event.click_count() == 2 {
2284                                                        this.open_edit_keybinding_modal(
2285                                                            false, window, cx,
2286                                                        );
2287                                                    }
2288                                                },
2289                                            ))
2290                                        })
2291                                        .group(row_id)
2292                                        .when(
2293                                            is_unbound_by_unbind
2294                                                || conflict.is_some_and(|conflict| {
2295                                                    !conflict.is_user_keybind_conflict()
2296                                                }),
2297                                            |row| {
2298                                                const OVERRIDDEN_OPACITY: f32 = 0.5;
2299                                                row.opacity(OVERRIDDEN_OPACITY)
2300                                            },
2301                                        )
2302                                        .when_some(
2303                                            conflict.filter(|conflict| {
2304                                                !is_unbound_by_unbind
2305                                                    && !this.context_menu_deployed() &&
2306                                                !conflict.is_user_keybind_conflict()
2307                                            }),
2308                                            |row, conflict| {
2309                                                let overriding_binding = this.keybindings.get(conflict.index);
2310                                                let context = overriding_binding.and_then(|binding| {
2311                                                    match conflict.override_source {
2312                                                        KeybindSource::User  => Some("your keymap"),
2313                                                        KeybindSource::Vim => Some("the vim keymap"),
2314                                                        KeybindSource::Base => Some("your base keymap"),
2315                                                        _ => {
2316                                                            log::error!("Unexpected override from the {} keymap", conflict.override_source.name());
2317                                                            None
2318                                                        }
2319                                                    }.map(|source| format!("This keybinding is overridden by the '{}' binding from {}.", binding.action().humanized_name, source))
2320                                                }).unwrap_or_else(|| "This binding is overridden.".to_string());
2321
2322                                                row.tooltip(Tooltip::text(context))
2323                                            },
2324                                        )
2325                                        .when(is_unbound_by_unbind, |row| {
2326                                            row.tooltip(Tooltip::text("This action is unbound"))
2327                                        }),
2328                                )
2329                                .border_2()
2330                                .when(
2331                                    conflict.is_some_and(|conflict| {
2332                                        conflict.is_user_keybind_conflict()
2333                                    }),
2334                                    |row| row.bg(cx.theme().status().error_background),
2335                                )
2336                                .when(is_selected, |row| {
2337                                    row.border_color(cx.theme().colors().panel_focused_border)
2338                                })
2339                                .into_any_element()
2340                        }),
2341                    ),
2342            )
2343            .on_scroll_wheel(cx.listener(|this, event: &ScrollWheelEvent, _, cx| {
2344                // This ensures that the menu is not dismissed in cases where scroll events
2345                // with a delta of zero are emitted
2346                if !event.delta.pixel_delta(px(1.)).y.is_zero() {
2347                    this.context_menu.take();
2348                    cx.notify();
2349                }
2350            }))
2351            .children(self.context_menu.as_ref().map(|(menu, position, _)| {
2352                deferred(
2353                    anchored()
2354                        .position(*position)
2355                        .anchor(gpui::Corner::TopLeft)
2356                        .child(menu.clone()),
2357                )
2358                .with_priority(1)
2359            }))
2360    }
2361}
2362
2363fn row_group_id(row_index: usize) -> SharedString {
2364    SharedString::new(format!("keymap-table-row-{}", row_index))
2365}
2366
2367fn base_button_style(row_index: usize, icon: IconName) -> IconButton {
2368    IconButton::new(("keymap-icon", row_index), icon)
2369        .shape(IconButtonShape::Square)
2370        .size(ButtonSize::Compact)
2371}
2372
2373#[derive(Debug, Clone, IntoElement)]
2374struct SyntaxHighlightedText {
2375    text: SharedString,
2376    language: Arc<Language>,
2377}
2378
2379impl SyntaxHighlightedText {
2380    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
2381        Self {
2382            text: text.into(),
2383            language,
2384        }
2385    }
2386}
2387
2388impl RenderOnce for SyntaxHighlightedText {
2389    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
2390        let text_style = window.text_style();
2391        let syntax_theme = cx.theme().syntax();
2392
2393        let text = self.text.clone();
2394
2395        let highlights = self
2396            .language
2397            .highlight_text(&text.as_ref().into(), 0..text.len());
2398        let mut runs = Vec::with_capacity(highlights.len());
2399        let mut offset = 0;
2400
2401        for (highlight_range, highlight_id) in highlights {
2402            // Add un-highlighted text before the current highlight
2403            if highlight_range.start > offset {
2404                runs.push(text_style.to_run(highlight_range.start - offset));
2405            }
2406
2407            let mut run_style = text_style.clone();
2408            if let Some(highlight_style) = highlight_id.style(syntax_theme) {
2409                run_style = run_style.highlight(highlight_style);
2410            }
2411            // add the highlighted range
2412            runs.push(run_style.to_run(highlight_range.len()));
2413            offset = highlight_range.end;
2414        }
2415
2416        // Add any remaining un-highlighted text
2417        if offset < text.len() {
2418            runs.push(text_style.to_run(text.len() - offset));
2419        }
2420
2421        StyledText::new(text).with_runs(runs)
2422    }
2423}
2424
2425#[derive(PartialEq)]
2426struct InputError {
2427    severity: Severity,
2428    content: SharedString,
2429}
2430
2431impl InputError {
2432    fn warning(message: impl Into<SharedString>) -> Self {
2433        Self {
2434            severity: Severity::Warning,
2435            content: message.into(),
2436        }
2437    }
2438
2439    fn error(message: anyhow::Error) -> Self {
2440        Self {
2441            severity: Severity::Error,
2442            content: message.to_string().into(),
2443        }
2444    }
2445}
2446
2447struct KeybindingEditorModal {
2448    creating: bool,
2449    editing_keybind: ProcessedBinding,
2450    editing_keybind_idx: usize,
2451    keybind_editor: Entity<KeystrokeInput>,
2452    context_editor: Entity<InputField>,
2453    action_editor: Option<Entity<InputField>>,
2454    action_arguments_editor: Option<Entity<ActionArgumentsEditor>>,
2455    action_name_to_static: HashMap<String, &'static str>,
2456    selected_action_name: Option<&'static str>,
2457    fs: Arc<dyn Fs>,
2458    error: Option<InputError>,
2459    keymap_editor: Entity<KeymapEditor>,
2460    workspace: WeakEntity<Workspace>,
2461    focus_state: KeybindingEditorModalFocusState,
2462}
2463
2464impl ModalView for KeybindingEditorModal {}
2465
2466impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
2467
2468impl Focusable for KeybindingEditorModal {
2469    fn focus_handle(&self, cx: &App) -> FocusHandle {
2470        if let Some(action_editor) = &self.action_editor {
2471            return action_editor.focus_handle(cx);
2472        }
2473        self.keybind_editor.focus_handle(cx)
2474    }
2475}
2476
2477impl KeybindingEditorModal {
2478    pub fn new(
2479        create: bool,
2480        editing_keybind: ProcessedBinding,
2481        editing_keybind_idx: usize,
2482        keymap_editor: Entity<KeymapEditor>,
2483        action_args_temp_dir: Option<&std::path::Path>,
2484        workspace: WeakEntity<Workspace>,
2485        fs: Arc<dyn Fs>,
2486        window: &mut Window,
2487        cx: &mut App,
2488    ) -> Self {
2489        let keybind_editor = cx
2490            .new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx));
2491
2492        let context_editor: Entity<InputField> = cx.new(|cx| {
2493            let input = InputField::new(window, cx, "Keybinding Context")
2494                .label("Edit Context")
2495                .label_size(LabelSize::Default);
2496
2497            if let Some(context) = editing_keybind
2498                .context()
2499                .and_then(KeybindContextString::local)
2500            {
2501                input.set_text(&context, window, cx);
2502            }
2503
2504            let editor_entity = input.editor();
2505            let editor_entity = editor_entity
2506                .as_any()
2507                .downcast_ref::<Entity<Editor>>()
2508                .unwrap()
2509                .clone();
2510            let workspace = workspace.clone();
2511            cx.spawn(async move |_input_handle, cx| {
2512                let contexts = cx
2513                    .background_spawn(async { collect_contexts_from_assets() })
2514                    .await;
2515
2516                let language = load_keybind_context_language(workspace, cx).await;
2517                editor_entity.update(cx, |editor, cx| {
2518                    if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
2519                        buffer.update(cx, |buffer, cx| {
2520                            buffer.set_language(Some(language), cx);
2521                        });
2522                    }
2523                    editor.set_completion_provider(Some(std::rc::Rc::new(
2524                        KeyContextCompletionProvider { contexts },
2525                    )));
2526                });
2527            })
2528            .detach();
2529
2530            input
2531        });
2532
2533        let has_action_editor = create && editing_keybind.action().name == gpui::NoAction.name();
2534
2535        let (action_editor, action_name_to_static) = if has_action_editor {
2536            let actions: Vec<&'static str> = cx.all_action_names().to_vec();
2537
2538            let humanized_names: HashMap<&'static str, SharedString> = actions
2539                .iter()
2540                .map(|&name| (name, command_palette::humanize_action_name(name).into()))
2541                .collect();
2542
2543            let action_name_to_static: HashMap<String, &'static str> = actions
2544                .iter()
2545                .map(|&name| (name.to_string(), name))
2546                .collect();
2547
2548            let editor = cx.new(|cx| {
2549                let input = InputField::new(window, cx, "Type an action name")
2550                    .label("Action")
2551                    .label_size(LabelSize::Default);
2552
2553                let editor_entity = input.editor();
2554                let editor_entity = editor_entity
2555                    .as_any()
2556                    .downcast_ref::<Entity<Editor>>()
2557                    .unwrap();
2558                editor_entity.update(cx, |editor, _cx| {
2559                    editor.set_completion_provider(Some(std::rc::Rc::new(
2560                        ActionCompletionProvider::new(actions, humanized_names),
2561                    )));
2562                });
2563
2564                input
2565            });
2566
2567            (Some(editor), action_name_to_static)
2568        } else {
2569            (None, HashMap::default())
2570        };
2571
2572        let action_has_schema = editing_keybind.action().has_schema;
2573        let action_name_for_args = editing_keybind.action().name;
2574        let action_args = editing_keybind
2575            .action()
2576            .arguments
2577            .as_ref()
2578            .map(|args| args.text.clone());
2579
2580        let action_arguments_editor = action_has_schema.then(|| {
2581            cx.new(|cx| {
2582                ActionArgumentsEditor::new(
2583                    action_name_for_args,
2584                    action_args.clone(),
2585                    action_args_temp_dir,
2586                    workspace.clone(),
2587                    window,
2588                    cx,
2589                )
2590            })
2591        });
2592
2593        let focus_state = KeybindingEditorModalFocusState::new(
2594            action_editor.as_ref().map(|e| e.focus_handle(cx)),
2595            keybind_editor.focus_handle(cx),
2596            action_arguments_editor
2597                .as_ref()
2598                .map(|args_editor| args_editor.focus_handle(cx)),
2599            context_editor.focus_handle(cx),
2600        );
2601
2602        Self {
2603            creating: create,
2604            editing_keybind,
2605            editing_keybind_idx,
2606            fs,
2607            keybind_editor,
2608            context_editor,
2609            action_editor,
2610            action_arguments_editor,
2611            action_name_to_static,
2612            selected_action_name: None,
2613            error: None,
2614            keymap_editor,
2615            workspace,
2616            focus_state,
2617        }
2618    }
2619
2620    fn add_action_arguments_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2621        let Some(action_editor) = &self.action_editor else {
2622            return;
2623        };
2624
2625        let action_name_str = action_editor.read(cx).text(cx);
2626        let current_action = self.action_name_to_static.get(&action_name_str).copied();
2627
2628        if current_action == self.selected_action_name {
2629            return;
2630        }
2631
2632        self.selected_action_name = current_action;
2633
2634        let Some(action_name) = current_action else {
2635            if self.action_arguments_editor.is_some() {
2636                self.action_arguments_editor = None;
2637                self.rebuild_focus_state(cx);
2638                cx.notify();
2639            }
2640            return;
2641        };
2642
2643        let (action_has_schema, temp_dir) = {
2644            let keymap_editor = self.keymap_editor.read(cx);
2645            let has_schema = keymap_editor.actions_with_schemas.contains(action_name);
2646            let temp_dir = keymap_editor
2647                .action_args_temp_dir
2648                .as_ref()
2649                .map(|dir| dir.path().to_path_buf());
2650            (has_schema, temp_dir)
2651        };
2652
2653        let currently_has_editor = self.action_arguments_editor.is_some();
2654
2655        if action_has_schema && !currently_has_editor {
2656            let workspace = self.workspace.clone();
2657
2658            let new_editor = cx.new(|cx| {
2659                ActionArgumentsEditor::new(
2660                    action_name,
2661                    None,
2662                    temp_dir.as_deref(),
2663                    workspace,
2664                    window,
2665                    cx,
2666                )
2667            });
2668
2669            self.action_arguments_editor = Some(new_editor);
2670            self.rebuild_focus_state(cx);
2671            cx.notify();
2672        } else if !action_has_schema && currently_has_editor {
2673            self.action_arguments_editor = None;
2674            self.rebuild_focus_state(cx);
2675            cx.notify();
2676        }
2677    }
2678
2679    fn rebuild_focus_state(&mut self, cx: &App) {
2680        self.focus_state = KeybindingEditorModalFocusState::new(
2681            self.action_editor.as_ref().map(|e| e.focus_handle(cx)),
2682            self.keybind_editor.focus_handle(cx),
2683            self.action_arguments_editor
2684                .as_ref()
2685                .map(|args_editor| args_editor.focus_handle(cx)),
2686            self.context_editor.focus_handle(cx),
2687        );
2688    }
2689
2690    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
2691        if self
2692            .error
2693            .as_ref()
2694            .is_some_and(|old_error| old_error.severity == Severity::Warning && *old_error == error)
2695        {
2696            false
2697        } else {
2698            self.error = Some(error);
2699            cx.notify();
2700            true
2701        }
2702    }
2703
2704    fn get_selected_action_name(&self, cx: &App) -> anyhow::Result<&'static str> {
2705        if let Some(selector) = self.action_editor.as_ref() {
2706            let action_name_str = selector.read(cx).text(cx);
2707
2708            if action_name_str.is_empty() {
2709                anyhow::bail!("Action name is required");
2710            }
2711
2712            self.action_name_to_static
2713                .get(&action_name_str)
2714                .copied()
2715                .ok_or_else(|| anyhow::anyhow!("Action '{}' not found", action_name_str))
2716        } else {
2717            Ok(self.editing_keybind.action().name)
2718        }
2719    }
2720
2721    fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
2722        let action_name = self.get_selected_action_name(cx)?;
2723        let action_arguments = self
2724            .action_arguments_editor
2725            .as_ref()
2726            .map(|arguments_editor| arguments_editor.read(cx).editor.read(cx).text(cx))
2727            .filter(|args| !args.is_empty());
2728
2729        let value = action_arguments
2730            .as_ref()
2731            .map(|args| {
2732                serde_json::from_str(args).context("Failed to parse action arguments as JSON")
2733            })
2734            .transpose()?;
2735
2736        cx.build_action(action_name, value)
2737            .context("Failed to validate action arguments")?;
2738        Ok(action_arguments)
2739    }
2740
2741    fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<KeybindingKeystroke>> {
2742        let new_keystrokes = self
2743            .keybind_editor
2744            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
2745        anyhow::ensure!(!new_keystrokes.is_empty(), "Keystrokes cannot be empty");
2746        Ok(new_keystrokes)
2747    }
2748
2749    fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
2750        let new_context = self
2751            .context_editor
2752            .read_with(cx, |input, cx| input.text(cx));
2753        let Some(context) = new_context.is_empty().not().then_some(new_context) else {
2754            return Ok(None);
2755        };
2756        gpui::KeyBindingContextPredicate::parse(&context).context("Failed to parse key context")?;
2757
2758        Ok(Some(context))
2759    }
2760
2761    fn save_or_display_error(&mut self, cx: &mut Context<Self>) {
2762        self.save(cx).map_err(|err| self.set_error(err, cx)).ok();
2763    }
2764
2765    fn save(&mut self, cx: &mut Context<Self>) -> Result<(), InputError> {
2766        let existing_keybind = self.editing_keybind.clone();
2767        let fs = self.fs.clone();
2768
2769        let mut new_keystrokes = self.validate_keystrokes(cx).map_err(InputError::error)?;
2770        new_keystrokes
2771            .iter_mut()
2772            .for_each(|ks| ks.remove_key_char());
2773
2774        let new_context = self.validate_context(cx).map_err(InputError::error)?;
2775        let new_action_args = self
2776            .validate_action_arguments(cx)
2777            .map_err(InputError::error)?;
2778
2779        let action_mapping = ActionMapping {
2780            keystrokes: Rc::from(new_keystrokes.as_slice()),
2781            context: new_context.map(SharedString::from),
2782        };
2783
2784        let conflicting_indices = self
2785            .keymap_editor
2786            .read(cx)
2787            .keybinding_conflict_state
2788            .conflicting_indices_for_mapping(
2789                &action_mapping,
2790                self.creating.not().then_some(self.editing_keybind_idx),
2791            );
2792
2793        conflicting_indices.map(|KeybindConflict {
2794            first_conflict_index,
2795            remaining_conflict_amount,
2796        }|
2797        {
2798            let conflicting_action_name = self
2799                .keymap_editor
2800                .read(cx)
2801                .keybindings
2802                .get(first_conflict_index)
2803                .map(|keybind| keybind.action().name);
2804
2805            let warning_message = match conflicting_action_name {
2806                Some(name) => {
2807                     if remaining_conflict_amount > 0 {
2808                        format!(
2809                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
2810                            name, remaining_conflict_amount
2811                        )
2812                    } else {
2813                        format!("Your keybind would conflict with the \"{}\" action", name)
2814                    }
2815                }
2816                None => {
2817                    log::info!(
2818                        "Could not find action in keybindings with index {}",
2819                        first_conflict_index
2820                    );
2821                    "Your keybind would conflict with other actions".to_string()
2822                }
2823            };
2824
2825            let warning = InputError::warning(warning_message);
2826            if self.error.as_ref().is_some_and(|old_error| *old_error == warning) {
2827                Ok(())
2828           } else {
2829                Err(warning)
2830            }
2831        }).unwrap_or(Ok(()))?;
2832
2833        let create = self.creating;
2834        let keyboard_mapper = cx.keyboard_mapper().clone();
2835
2836        let action_name = self
2837            .get_selected_action_name(cx)
2838            .map_err(InputError::error)?;
2839
2840        let humanized_action_name: SharedString =
2841            command_palette::humanize_action_name(action_name).into();
2842
2843        let action_information = ActionInformation::new(
2844            action_name,
2845            None,
2846            &HashSet::default(),
2847            cx.action_documentation(),
2848            &self.keymap_editor.read(cx).humanized_action_names,
2849        );
2850
2851        let keybind_for_save = if create {
2852            ProcessedBinding::Unmapped(action_information)
2853        } else {
2854            existing_keybind
2855        };
2856
2857        cx.spawn(async move |this, cx| {
2858            match save_keybinding_update(
2859                create,
2860                keybind_for_save,
2861                &action_mapping,
2862                new_action_args.as_deref(),
2863                &fs,
2864                keyboard_mapper.as_ref(),
2865            )
2866            .await
2867            {
2868                Ok(_) => {
2869                    this.update(cx, |this, cx| {
2870                        this.keymap_editor.update(cx, |keymap, cx| {
2871                            keymap.previous_edit = Some(PreviousEdit::Keybinding {
2872                                action_mapping,
2873                                action_name,
2874                                fallback: keymap.table_interaction_state.read(cx).scroll_offset(),
2875                            });
2876                            let status_toast = StatusToast::new(
2877                                format!("Saved edits to the {} action.", humanized_action_name),
2878                                cx,
2879                                move |this, _cx| {
2880                                    this.icon(ToastIcon::new(IconName::Check).color(Color::Success))
2881                                        .dismiss_button(true)
2882                                    // .action("Undo", f) todo: wire the undo functionality
2883                                },
2884                            );
2885
2886                            this.workspace
2887                                .update(cx, |workspace, cx| {
2888                                    workspace.toggle_status_toast(status_toast, cx);
2889                                })
2890                                .log_err();
2891                        });
2892                        cx.emit(DismissEvent);
2893                    })
2894                    .ok();
2895                }
2896                Err(err) => {
2897                    this.update(cx, |this, cx| {
2898                        this.set_error(InputError::error(err), cx);
2899                    })
2900                    .log_err();
2901                }
2902            }
2903        })
2904        .detach();
2905
2906        Ok(())
2907    }
2908
2909    fn is_any_editor_showing_completions(&self, window: &Window, cx: &App) -> bool {
2910        let is_editor_showing_completions =
2911            |focus_handle: &FocusHandle, editor_entity: &Entity<Editor>| -> bool {
2912                focus_handle.contains_focused(window, cx)
2913                    && editor_entity.read_with(cx, |editor, _cx| {
2914                        editor
2915                            .context_menu()
2916                            .borrow()
2917                            .as_ref()
2918                            .is_some_and(|menu| menu.visible())
2919                    })
2920            };
2921
2922        self.action_editor.as_ref().is_some_and(|action_editor| {
2923            let focus_handle = action_editor.read(cx).focus_handle(cx);
2924            let editor_entity = action_editor.read(cx).editor();
2925            let editor_entity = editor_entity
2926                .as_any()
2927                .downcast_ref::<Entity<Editor>>()
2928                .unwrap();
2929            is_editor_showing_completions(&focus_handle, editor_entity)
2930        }) || {
2931            let focus_handle = self.context_editor.read(cx).focus_handle(cx);
2932            let editor_entity = self.context_editor.read(cx).editor();
2933            let editor_entity = editor_entity
2934                .as_any()
2935                .downcast_ref::<Entity<Editor>>()
2936                .unwrap();
2937            is_editor_showing_completions(&focus_handle, editor_entity)
2938        } || self
2939            .action_arguments_editor
2940            .as_ref()
2941            .is_some_and(|args_editor| {
2942                let focus_handle = args_editor.read(cx).focus_handle(cx);
2943                let editor_entity = &args_editor.read(cx).editor;
2944                is_editor_showing_completions(&focus_handle, editor_entity)
2945            })
2946    }
2947
2948    fn key_context(&self) -> KeyContext {
2949        let mut key_context = KeyContext::new_with_defaults();
2950        key_context.add("KeybindEditorModal");
2951        key_context
2952    }
2953
2954    fn key_context_internal(&self, window: &Window, cx: &App) -> KeyContext {
2955        let mut key_context = self.key_context();
2956
2957        if self.is_any_editor_showing_completions(window, cx) {
2958            key_context.add("showing_completions");
2959        }
2960
2961        key_context
2962    }
2963
2964    fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
2965        if self.is_any_editor_showing_completions(window, cx) {
2966            return;
2967        }
2968        self.focus_state.focus_next(window, cx);
2969    }
2970
2971    fn focus_prev(
2972        &mut self,
2973        _: &menu::SelectPrevious,
2974        window: &mut Window,
2975        cx: &mut Context<Self>,
2976    ) {
2977        if self.is_any_editor_showing_completions(window, cx) {
2978            return;
2979        }
2980        self.focus_state.focus_previous(window, cx);
2981    }
2982
2983    fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
2984        self.save_or_display_error(cx);
2985    }
2986
2987    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
2988        cx.emit(DismissEvent);
2989    }
2990
2991    fn get_matching_bindings_count(&self, cx: &Context<Self>) -> usize {
2992        let current_keystrokes = self.keybind_editor.read(cx).keystrokes();
2993
2994        if current_keystrokes.is_empty() {
2995            return 0;
2996        }
2997
2998        self.keymap_editor
2999            .read(cx)
3000            .keybindings
3001            .iter()
3002            .enumerate()
3003            .filter(|(idx, binding)| {
3004                // Don't count the binding we're currently editing
3005                if !self.creating && *idx == self.editing_keybind_idx {
3006                    return false;
3007                }
3008
3009                binding.keystrokes().is_some_and(|keystrokes| {
3010                    keystrokes_match_exactly(keystrokes, current_keystrokes)
3011                })
3012            })
3013            .count()
3014    }
3015
3016    fn show_matching_bindings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3017        let keystrokes = self.keybind_editor.read(cx).keystrokes().to_vec();
3018
3019        self.keymap_editor.update(cx, |keymap_editor, cx| {
3020            keymap_editor.clear_action_query(window, cx)
3021        });
3022
3023        // Dismiss the modal
3024        cx.emit(DismissEvent);
3025
3026        // Update the keymap editor to show matching keystrokes
3027        self.keymap_editor.update(cx, |editor, cx| {
3028            editor.filter_state = FilterState::All;
3029            editor.search_mode = SearchMode::KeyStroke { exact_match: true };
3030            editor.keystroke_editor.update(cx, |keystroke_editor, cx| {
3031                keystroke_editor.set_keystrokes(keystrokes, cx);
3032            });
3033        });
3034    }
3035}
3036
3037impl Render for KeybindingEditorModal {
3038    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3039        self.add_action_arguments_input(window, cx);
3040
3041        let theme = cx.theme().colors();
3042        let matching_bindings_count = self.get_matching_bindings_count(cx);
3043        let key_context = self.key_context_internal(window, cx);
3044        let showing_completions = key_context.contains("showing_completions");
3045
3046        v_flex()
3047            .w(rems(34.))
3048            .elevation_3(cx)
3049            .key_context(key_context)
3050            .on_action(cx.listener(Self::confirm))
3051            .on_action(cx.listener(Self::cancel))
3052            .when(!showing_completions, |this| {
3053                this.on_action(cx.listener(Self::focus_next))
3054                    .on_action(cx.listener(Self::focus_prev))
3055            })
3056            .child(
3057                Modal::new("keybinding_editor_modal", None)
3058                    .header(
3059                        ModalHeader::new().child(
3060                            v_flex()
3061                                .w_full()
3062                                .pb_1p5()
3063                                .mb_1()
3064                                .gap_0p5()
3065                                .border_b_1()
3066                                .border_color(theme.border_variant)
3067                                .when(!self.creating, |this| {
3068                                    this.child(Label::new(
3069                                        self.editing_keybind.action().humanized_name.clone(),
3070                                    ))
3071                                    .when_some(
3072                                        self.editing_keybind.action().documentation,
3073                                        |this, docs| {
3074                                            this.child(
3075                                                Label::new(docs)
3076                                                    .size(LabelSize::Small)
3077                                                    .color(Color::Muted),
3078                                            )
3079                                        },
3080                                    )
3081                                })
3082                                .when(self.creating, |this| {
3083                                    this.child(Label::new("Create Keybinding"))
3084                                }),
3085                        ),
3086                    )
3087                    .section(
3088                        Section::new().child(
3089                            v_flex()
3090                                .gap_2p5()
3091                                .when_some(
3092                                    self.creating
3093                                        .then_some(())
3094                                        .and_then(|_| self.action_editor.as_ref()),
3095                                    |this, selector| this.child(selector.clone()),
3096                                )
3097                                .child(
3098                                    v_flex()
3099                                        .gap_1()
3100                                        .child(Label::new("Edit Keystroke"))
3101                                        .child(self.keybind_editor.clone())
3102                                        .child(h_flex().gap_px().when(
3103                                            matching_bindings_count > 0,
3104                                            |this| {
3105                                                let label = format!(
3106                                                    "There {} {} {} with the same keystrokes.",
3107                                                    if matching_bindings_count == 1 {
3108                                                        "is"
3109                                                    } else {
3110                                                        "are"
3111                                                    },
3112                                                    matching_bindings_count,
3113                                                    if matching_bindings_count == 1 {
3114                                                        "binding"
3115                                                    } else {
3116                                                        "bindings"
3117                                                    }
3118                                                );
3119
3120                                                this.child(
3121                                                    Label::new(label)
3122                                                        .size(LabelSize::Small)
3123                                                        .color(Color::Muted),
3124                                                )
3125                                                .child(
3126                                                    Button::new("show_matching", "View")
3127                                                        .label_size(LabelSize::Small)
3128                                                        .end_icon(
3129                                                            Icon::new(IconName::ArrowUpRight)
3130                                                                .size(IconSize::Small)
3131                                                                .color(Color::Muted),
3132                                                        )
3133                                                        .on_click(cx.listener(
3134                                                            |this, _, window, cx| {
3135                                                                this.show_matching_bindings(
3136                                                                    window, cx,
3137                                                                );
3138                                                            },
3139                                                        )),
3140                                                )
3141                                            },
3142                                        )),
3143                                )
3144                                .when_some(self.action_arguments_editor.clone(), |this, editor| {
3145                                    this.child(
3146                                        v_flex()
3147                                            .gap_1()
3148                                            .child(Label::new("Edit Arguments"))
3149                                            .child(editor),
3150                                    )
3151                                })
3152                                .child(self.context_editor.clone())
3153                                .when_some(self.error.as_ref(), |this, error| {
3154                                    this.child(
3155                                        Banner::new()
3156                                            .severity(error.severity)
3157                                            .child(Label::new(error.content.clone())),
3158                                    )
3159                                }),
3160                        ),
3161                    )
3162                    .footer(
3163                        ModalFooter::new().end_slot(
3164                            h_flex()
3165                                .gap_1()
3166                                .child(
3167                                    Button::new("cancel", "Cancel")
3168                                        .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
3169                                )
3170                                .child(Button::new("save-btn", "Save").on_click(cx.listener(
3171                                    |this, _event, _window, cx| {
3172                                        this.save_or_display_error(cx);
3173                                    },
3174                                ))),
3175                        ),
3176                    ),
3177            )
3178    }
3179}
3180
3181struct KeybindingEditorModalFocusState {
3182    handles: Vec<FocusHandle>,
3183}
3184
3185impl KeybindingEditorModalFocusState {
3186    fn new(
3187        action_editor: Option<FocusHandle>,
3188        keystrokes: FocusHandle,
3189        action_arguments: Option<FocusHandle>,
3190        context: FocusHandle,
3191    ) -> Self {
3192        Self {
3193            handles: Vec::from_iter(
3194                [
3195                    action_editor,
3196                    Some(keystrokes),
3197                    action_arguments,
3198                    Some(context),
3199                ]
3200                .into_iter()
3201                .flatten(),
3202            ),
3203        }
3204    }
3205
3206    fn focused_index(&self, window: &Window, cx: &App) -> Option<i32> {
3207        self.handles
3208            .iter()
3209            .position(|handle| handle.contains_focused(window, cx))
3210            .map(|i| i as i32)
3211    }
3212
3213    fn focus_index(&self, mut index: i32, window: &mut Window, cx: &mut App) {
3214        if index < 0 {
3215            index = self.handles.len() as i32 - 1;
3216        }
3217        if index >= self.handles.len() as i32 {
3218            index = 0;
3219        }
3220        window.focus(&self.handles[index as usize], cx);
3221    }
3222
3223    fn focus_next(&self, window: &mut Window, cx: &mut App) {
3224        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3225            index + 1
3226        } else {
3227            0
3228        };
3229        self.focus_index(index_to_focus, window, cx);
3230    }
3231
3232    fn focus_previous(&self, window: &mut Window, cx: &mut App) {
3233        let index_to_focus = if let Some(index) = self.focused_index(window, cx) {
3234            index - 1
3235        } else {
3236            self.handles.len() as i32 - 1
3237        };
3238        self.focus_index(index_to_focus, window, cx);
3239    }
3240}
3241
3242struct ActionArgumentsEditor {
3243    editor: Entity<Editor>,
3244    focus_handle: FocusHandle,
3245    is_loading: bool,
3246    /// See documentation in `KeymapEditor` for why a temp dir is needed.
3247    /// This field exists because the keymap editor temp dir creation may fail,
3248    /// and rather than implement a complicated retry mechanism, we simply
3249    /// fallback to trying to create a temporary directory in this editor on
3250    /// demand. Of note is that the TempDir struct will remove the directory
3251    /// when dropped.
3252    backup_temp_dir: Option<tempfile::TempDir>,
3253}
3254
3255impl Focusable for ActionArgumentsEditor {
3256    fn focus_handle(&self, _cx: &App) -> FocusHandle {
3257        self.focus_handle.clone()
3258    }
3259}
3260
3261impl ActionArgumentsEditor {
3262    fn new(
3263        action_name: &'static str,
3264        arguments: Option<SharedString>,
3265        temp_dir: Option<&std::path::Path>,
3266        workspace: WeakEntity<Workspace>,
3267        window: &mut Window,
3268        cx: &mut Context<Self>,
3269    ) -> Self {
3270        let focus_handle = cx.focus_handle();
3271        cx.on_focus_in(&focus_handle, window, |this, window, cx| {
3272            this.editor.focus_handle(cx).focus(window, cx);
3273        })
3274        .detach();
3275        let editor = cx.new(|cx| {
3276            let mut editor = Editor::auto_height_unbounded(1, window, cx);
3277            Self::set_editor_text(&mut editor, arguments.clone(), window, cx);
3278            editor.set_read_only(true);
3279            editor
3280        });
3281
3282        let temp_dir = temp_dir.map(|path| path.to_owned());
3283        cx.spawn_in(window, async move |this, cx| {
3284            let result = async {
3285                let (project, fs) = workspace.read_with(cx, |workspace, _cx| {
3286                    (
3287                        workspace.project().downgrade(),
3288                        workspace.app_state().fs.clone(),
3289                    )
3290                })?;
3291
3292                let file_name = json_schema_store::normalized_action_file_name(action_name);
3293
3294                let (buffer, backup_temp_dir) =
3295                    Self::create_temp_buffer(temp_dir, file_name.clone(), project.clone(), fs, cx)
3296                        .await
3297                        .context(concat!(
3298                            "Failed to create temporary buffer for action arguments. ",
3299                            "Auto-complete will not work"
3300                        ))?;
3301
3302                let editor = cx.new_window_entity(|window, cx| {
3303                    let multi_buffer = cx.new(|cx| editor::MultiBuffer::singleton(buffer, cx));
3304                    let mut editor = Editor::new(
3305                        EditorMode::Full {
3306                            scale_ui_elements_with_buffer_font_size: true,
3307                            show_active_line_background: false,
3308                            sizing_behavior: SizingBehavior::SizeByContent,
3309                        },
3310                        multi_buffer,
3311                        project.upgrade(),
3312                        window,
3313                        cx,
3314                    );
3315                    editor.set_searchable(false);
3316                    editor.disable_scrollbars_and_minimap(window, cx);
3317                    editor.set_show_edit_predictions(Some(false), window, cx);
3318                    editor.set_show_gutter(false, cx);
3319                    Self::set_editor_text(&mut editor, arguments, window, cx);
3320                    editor
3321                })?;
3322
3323                this.update_in(cx, |this, window, cx| {
3324                    if this.editor.focus_handle(cx).is_focused(window) {
3325                        editor.focus_handle(cx).focus(window, cx);
3326                    }
3327                    this.editor = editor;
3328                    this.backup_temp_dir = backup_temp_dir;
3329                    this.is_loading = false;
3330                })?;
3331
3332                anyhow::Ok(())
3333            }
3334            .await;
3335            if result.is_err() {
3336                let json_language = load_json_language(workspace.clone(), cx).await;
3337                this.update(cx, |this, cx| {
3338                    this.editor.update(cx, |editor, cx| {
3339                        if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
3340                            buffer.update(cx, |buffer, cx| {
3341                                buffer.set_language(Some(json_language.clone()), cx)
3342                            });
3343                        }
3344                    })
3345                    // .context("Failed to load JSON language for editing keybinding action arguments input")
3346                })
3347                .ok();
3348                this.update(cx, |this, _cx| {
3349                    this.is_loading = false;
3350                })
3351                .ok();
3352            }
3353            result
3354        })
3355        .detach_and_log_err(cx);
3356        Self {
3357            editor,
3358            focus_handle,
3359            is_loading: true,
3360            backup_temp_dir: None,
3361        }
3362    }
3363
3364    fn set_editor_text(
3365        editor: &mut Editor,
3366        arguments: Option<SharedString>,
3367        window: &mut Window,
3368        cx: &mut Context<Editor>,
3369    ) {
3370        if let Some(arguments) = arguments {
3371            editor.set_text(arguments, window, cx);
3372        } else {
3373            // TODO: default value from schema?
3374            editor.set_placeholder_text("Action Arguments", window, cx);
3375        }
3376    }
3377
3378    async fn create_temp_buffer(
3379        temp_dir: Option<std::path::PathBuf>,
3380        file_name: String,
3381        project: WeakEntity<Project>,
3382        fs: Arc<dyn Fs>,
3383        cx: &mut AsyncApp,
3384    ) -> anyhow::Result<(Entity<language::Buffer>, Option<tempfile::TempDir>)> {
3385        let (temp_file_path, temp_dir) = {
3386            let file_name = file_name.clone();
3387            async move {
3388                let temp_dir_backup = match temp_dir.as_ref() {
3389                    Some(_) => None,
3390                    None => {
3391                        let temp_dir = paths::temp_dir();
3392                        let sub_temp_dir = tempfile::Builder::new()
3393                            .tempdir_in(temp_dir)
3394                            .context("Failed to create temporary directory")?;
3395                        Some(sub_temp_dir)
3396                    }
3397                };
3398                let dir_path = temp_dir.as_deref().unwrap_or_else(|| {
3399                    temp_dir_backup
3400                        .as_ref()
3401                        .expect("created backup tempdir")
3402                        .path()
3403                });
3404                let path = dir_path.join(file_name);
3405                fs.create_file(
3406                    &path,
3407                    fs::CreateOptions {
3408                        ignore_if_exists: true,
3409                        overwrite: true,
3410                    },
3411                )
3412                .await
3413                .context("Failed to create temporary file")?;
3414                anyhow::Ok((path, temp_dir_backup))
3415            }
3416        }
3417        .await
3418        .context("Failed to create backing file")?;
3419
3420        project
3421            .update(cx, |project, cx| {
3422                project.open_local_buffer(temp_file_path, cx)
3423            })?
3424            .await
3425            .context("Failed to create buffer")
3426            .map(|buffer| (buffer, temp_dir))
3427    }
3428}
3429
3430impl Render for ActionArgumentsEditor {
3431    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3432        let settings = theme::ThemeSettings::get_global(cx);
3433        let colors = cx.theme().colors();
3434
3435        let border_color = if self.is_loading {
3436            colors.border_disabled
3437        } else if self.focus_handle.contains_focused(window, cx) {
3438            colors.border_focused
3439        } else {
3440            colors.border_variant
3441        };
3442
3443        let text_style = {
3444            TextStyleRefinement {
3445                font_size: Some(rems(0.875).into()),
3446                font_weight: Some(settings.buffer_font.weight),
3447                line_height: Some(relative(1.2)),
3448                color: self.is_loading.then_some(colors.text_disabled),
3449                ..Default::default()
3450            }
3451        };
3452
3453        self.editor
3454            .update(cx, |editor, _| editor.set_text_style_refinement(text_style));
3455
3456        h_flex()
3457            .min_h_8()
3458            .min_w_48()
3459            .px_2()
3460            .flex_grow()
3461            .rounded_md()
3462            .bg(cx.theme().colors().editor_background)
3463            .border_1()
3464            .border_color(border_color)
3465            .track_focus(&self.focus_handle)
3466            .child(self.editor.clone())
3467    }
3468}
3469
3470struct KeyContextCompletionProvider {
3471    contexts: Vec<SharedString>,
3472}
3473
3474impl CompletionProvider for KeyContextCompletionProvider {
3475    fn completions(
3476        &self,
3477        _excerpt_id: editor::ExcerptId,
3478        buffer: &Entity<language::Buffer>,
3479        buffer_position: language::Anchor,
3480        _trigger: editor::CompletionContext,
3481        _window: &mut Window,
3482        cx: &mut Context<Editor>,
3483    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
3484        let buffer = buffer.read(cx);
3485        let mut count_back = 0;
3486        for char in buffer.reversed_chars_at(buffer_position) {
3487            if char.is_ascii_alphanumeric() || char == '_' {
3488                count_back += 1;
3489            } else {
3490                break;
3491            }
3492        }
3493        let start_anchor =
3494            buffer.anchor_before(buffer_position.to_offset(buffer).saturating_sub(count_back));
3495        let replace_range = start_anchor..buffer_position;
3496        gpui::Task::ready(Ok(vec![project::CompletionResponse {
3497            completions: self
3498                .contexts
3499                .iter()
3500                .map(|context| project::Completion {
3501                    replace_range: replace_range.clone(),
3502                    label: language::CodeLabel::plain(context.to_string(), None),
3503                    new_text: context.to_string(),
3504                    documentation: None,
3505                    source: project::CompletionSource::Custom,
3506                    icon_path: None,
3507                    match_start: None,
3508                    snippet_deduplication_key: None,
3509                    insert_text_mode: None,
3510                    confirm: None,
3511                })
3512                .collect(),
3513            display_options: CompletionDisplayOptions::default(),
3514            is_incomplete: false,
3515        }]))
3516    }
3517
3518    fn is_completion_trigger(
3519        &self,
3520        _buffer: &Entity<language::Buffer>,
3521        _position: language::Anchor,
3522        text: &str,
3523        _trigger_in_words: bool,
3524        _cx: &mut Context<Editor>,
3525    ) -> bool {
3526        text.chars()
3527            .last()
3528            .is_some_and(|last_char| last_char.is_ascii_alphanumeric() || last_char == '_')
3529    }
3530}
3531
3532async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
3533    let json_language_task = workspace
3534        .read_with(cx, |workspace, cx| {
3535            workspace
3536                .project()
3537                .read(cx)
3538                .languages()
3539                .language_for_name("JSON")
3540        })
3541        .context("Failed to load JSON language")
3542        .log_err();
3543    let json_language = match json_language_task {
3544        Some(task) => task.await.context("Failed to load JSON language").log_err(),
3545        None => None,
3546    };
3547    json_language.unwrap_or_else(|| {
3548        Arc::new(Language::new(
3549            LanguageConfig {
3550                name: "JSON".into(),
3551                ..Default::default()
3552            },
3553            Some(tree_sitter_json::LANGUAGE.into()),
3554        ))
3555    })
3556}
3557
3558async fn load_keybind_context_language(
3559    workspace: WeakEntity<Workspace>,
3560    cx: &mut AsyncApp,
3561) -> Arc<Language> {
3562    let language_task = workspace
3563        .read_with(cx, |workspace, cx| {
3564            workspace
3565                .project()
3566                .read(cx)
3567                .languages()
3568                .language_for_name("Zed Keybind Context")
3569        })
3570        .context("Failed to load Zed Keybind Context language")
3571        .log_err();
3572    let language = match language_task {
3573        Some(task) => task
3574            .await
3575            .context("Failed to load Zed Keybind Context language")
3576            .log_err(),
3577        None => None,
3578    };
3579    language.unwrap_or_else(|| {
3580        Arc::new(Language::new(
3581            LanguageConfig {
3582                name: "Zed Keybind Context".into(),
3583                ..Default::default()
3584            },
3585            Some(tree_sitter_rust::LANGUAGE.into()),
3586        ))
3587    })
3588}
3589
3590async fn save_keybinding_update(
3591    create: bool,
3592    existing: ProcessedBinding,
3593    action_mapping: &ActionMapping,
3594    new_args: Option<&str>,
3595    fs: &Arc<dyn Fs>,
3596    keyboard_mapper: &dyn PlatformKeyboardMapper,
3597) -> anyhow::Result<()> {
3598    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3599        .await
3600        .context("Failed to load keymap file")?;
3601
3602    let tab_size = infer_json_indent_size(&keymap_contents);
3603
3604    let existing_keystrokes = existing.keystrokes().unwrap_or_default();
3605    let existing_context = existing.context().and_then(KeybindContextString::local_str);
3606    let existing_args = existing
3607        .action()
3608        .arguments
3609        .as_ref()
3610        .map(|args| args.text.as_ref());
3611
3612    let target = settings::KeybindUpdateTarget {
3613        context: existing_context,
3614        keystrokes: existing_keystrokes,
3615        action_name: existing.action().name,
3616        action_arguments: existing_args,
3617    };
3618
3619    let source = settings::KeybindUpdateTarget {
3620        context: action_mapping.context.as_ref().map(|a| &***a),
3621        keystrokes: &action_mapping.keystrokes,
3622        action_name: existing.action().name,
3623        action_arguments: new_args,
3624    };
3625
3626    let operation = if !create {
3627        settings::KeybindUpdateOperation::Replace {
3628            target,
3629            target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3630            source,
3631        }
3632    } else {
3633        settings::KeybindUpdateOperation::Add {
3634            source,
3635            from: Some(target),
3636        }
3637    };
3638
3639    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3640
3641    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3642        operation,
3643        keymap_contents,
3644        tab_size,
3645        keyboard_mapper,
3646    )
3647    .map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?;
3648    fs.write(
3649        paths::keymap_file().as_path(),
3650        updated_keymap_contents.as_bytes(),
3651    )
3652    .await
3653    .context("Failed to write keymap file")?;
3654
3655    telemetry::event!(
3656        "Keybinding Updated",
3657        new_keybinding = new_keybinding,
3658        removed_keybinding = removed_keybinding,
3659        source = source
3660    );
3661    Ok(())
3662}
3663
3664async fn remove_keybinding(
3665    existing: ProcessedBinding,
3666    fs: &Arc<dyn Fs>,
3667    keyboard_mapper: &dyn PlatformKeyboardMapper,
3668) -> anyhow::Result<()> {
3669    let Some(keystrokes) = existing.keystrokes() else {
3670        anyhow::bail!("Cannot remove a keybinding that does not exist");
3671    };
3672    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
3673        .await
3674        .context("Failed to load keymap file")?;
3675    let tab_size = infer_json_indent_size(&keymap_contents);
3676
3677    let operation = settings::KeybindUpdateOperation::Remove {
3678        target: settings::KeybindUpdateTarget {
3679            context: existing.context().and_then(KeybindContextString::local_str),
3680            keystrokes,
3681            action_name: existing.action().name,
3682            action_arguments: existing
3683                .action()
3684                .arguments
3685                .as_ref()
3686                .map(|arguments| arguments.text.as_ref()),
3687        },
3688        target_keybind_source: existing.keybind_source().unwrap_or(KeybindSource::User),
3689    };
3690
3691    let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry();
3692    let updated_keymap_contents = settings::KeymapFile::update_keybinding(
3693        operation,
3694        keymap_contents,
3695        tab_size,
3696        keyboard_mapper,
3697    )
3698    .context("Failed to update keybinding")?;
3699    fs.write(
3700        paths::keymap_file().as_path(),
3701        updated_keymap_contents.as_bytes(),
3702    )
3703    .await
3704    .context("Failed to write keymap file")?;
3705
3706    telemetry::event!(
3707        "Keybinding Removed",
3708        new_keybinding = new_keybinding,
3709        removed_keybinding = removed_keybinding,
3710        source = source
3711    );
3712    Ok(())
3713}
3714
3715fn collect_contexts_from_assets() -> Vec<SharedString> {
3716    let mut keymap_assets = vec![
3717        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
3718        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
3719    ];
3720    keymap_assets.extend(
3721        BaseKeymap::OPTIONS
3722            .iter()
3723            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
3724            .map(util::asset_str::<SettingsAssets>),
3725    );
3726
3727    let mut contexts = HashSet::default();
3728
3729    for keymap_asset in keymap_assets {
3730        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
3731            continue;
3732        };
3733
3734        for section in keymap.sections() {
3735            let context_expr = &section.context;
3736            let mut queue = Vec::new();
3737            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
3738                continue;
3739            };
3740
3741            queue.push(root_context);
3742            while let Some(context) = queue.pop() {
3743                match context {
3744                    Identifier(ident) => {
3745                        contexts.insert(ident);
3746                    }
3747                    Equal(ident_a, ident_b) => {
3748                        contexts.insert(ident_a);
3749                        contexts.insert(ident_b);
3750                    }
3751                    NotEqual(ident_a, ident_b) => {
3752                        contexts.insert(ident_a);
3753                        contexts.insert(ident_b);
3754                    }
3755                    Descendant(ctx_a, ctx_b) => {
3756                        queue.push(*ctx_a);
3757                        queue.push(*ctx_b);
3758                    }
3759                    Not(ctx) => {
3760                        queue.push(*ctx);
3761                    }
3762                    And(ctx_a, ctx_b) => {
3763                        queue.push(*ctx_a);
3764                        queue.push(*ctx_b);
3765                    }
3766                    Or(ctx_a, ctx_b) => {
3767                        queue.push(*ctx_a);
3768                        queue.push(*ctx_b);
3769                    }
3770                }
3771            }
3772        }
3773    }
3774
3775    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
3776    contexts.sort();
3777
3778    contexts
3779}
3780
3781fn normalized_ctx_eq(
3782    a: &gpui::KeyBindingContextPredicate,
3783    b: &gpui::KeyBindingContextPredicate,
3784) -> bool {
3785    use gpui::KeyBindingContextPredicate::*;
3786    return match (a, b) {
3787        (Identifier(_), Identifier(_)) => a == b,
3788        (Equal(a_left, a_right), Equal(b_left, b_right)) => {
3789            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3790        }
3791        (NotEqual(a_left, a_right), NotEqual(b_left, b_right)) => {
3792            (a_left == b_left && a_right == b_right) || (a_left == b_right && a_right == b_left)
3793        }
3794        (Descendant(a_parent, a_child), Descendant(b_parent, b_child)) => {
3795            normalized_ctx_eq(a_parent, b_parent) && normalized_ctx_eq(a_child, b_child)
3796        }
3797        (Not(a_expr), Not(b_expr)) => normalized_ctx_eq(a_expr, b_expr),
3798        // Handle double negation: !(!a) == a
3799        (Not(a_expr), b) if matches!(a_expr.as_ref(), Not(_)) => {
3800            let Not(a_inner) = a_expr.as_ref() else {
3801                unreachable!();
3802            };
3803            normalized_ctx_eq(b, a_inner)
3804        }
3805        (a, Not(b_expr)) if matches!(b_expr.as_ref(), Not(_)) => {
3806            let Not(b_inner) = b_expr.as_ref() else {
3807                unreachable!();
3808            };
3809            normalized_ctx_eq(a, b_inner)
3810        }
3811        (And(a_left, a_right), And(b_left, b_right))
3812            if matches!(a_left.as_ref(), And(_, _))
3813                || matches!(a_right.as_ref(), And(_, _))
3814                || matches!(b_left.as_ref(), And(_, _))
3815                || matches!(b_right.as_ref(), And(_, _)) =>
3816        {
3817            let mut a_operands = Vec::new();
3818            flatten_and(a, &mut a_operands);
3819            let mut b_operands = Vec::new();
3820            flatten_and(b, &mut b_operands);
3821            compare_operand_sets(&a_operands, &b_operands)
3822        }
3823        (And(a_left, a_right), And(b_left, b_right)) => {
3824            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3825                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3826        }
3827        (Or(a_left, a_right), Or(b_left, b_right))
3828            if matches!(a_left.as_ref(), Or(_, _))
3829                || matches!(a_right.as_ref(), Or(_, _))
3830                || matches!(b_left.as_ref(), Or(_, _))
3831                || matches!(b_right.as_ref(), Or(_, _)) =>
3832        {
3833            let mut a_operands = Vec::new();
3834            flatten_or(a, &mut a_operands);
3835            let mut b_operands = Vec::new();
3836            flatten_or(b, &mut b_operands);
3837            compare_operand_sets(&a_operands, &b_operands)
3838        }
3839        (Or(a_left, a_right), Or(b_left, b_right)) => {
3840            (normalized_ctx_eq(a_left, b_left) && normalized_ctx_eq(a_right, b_right))
3841                || (normalized_ctx_eq(a_left, b_right) && normalized_ctx_eq(a_right, b_left))
3842        }
3843        _ => false,
3844    };
3845
3846    fn flatten_and<'a>(
3847        pred: &'a gpui::KeyBindingContextPredicate,
3848        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3849    ) {
3850        use gpui::KeyBindingContextPredicate::*;
3851        match pred {
3852            And(left, right) => {
3853                flatten_and(left, operands);
3854                flatten_and(right, operands);
3855            }
3856            _ => operands.push(pred),
3857        }
3858    }
3859
3860    fn flatten_or<'a>(
3861        pred: &'a gpui::KeyBindingContextPredicate,
3862        operands: &mut Vec<&'a gpui::KeyBindingContextPredicate>,
3863    ) {
3864        use gpui::KeyBindingContextPredicate::*;
3865        match pred {
3866            Or(left, right) => {
3867                flatten_or(left, operands);
3868                flatten_or(right, operands);
3869            }
3870            _ => operands.push(pred),
3871        }
3872    }
3873
3874    fn compare_operand_sets(
3875        a: &[&gpui::KeyBindingContextPredicate],
3876        b: &[&gpui::KeyBindingContextPredicate],
3877    ) -> bool {
3878        if a.len() != b.len() {
3879            return false;
3880        }
3881
3882        // For each operand in a, find a matching operand in b
3883        let mut b_matched = vec![false; b.len()];
3884        for a_operand in a {
3885            let mut found = false;
3886            for (b_idx, b_operand) in b.iter().enumerate() {
3887                if !b_matched[b_idx] && normalized_ctx_eq(a_operand, b_operand) {
3888                    b_matched[b_idx] = true;
3889                    found = true;
3890                    break;
3891                }
3892            }
3893            if !found {
3894                return false;
3895            }
3896        }
3897
3898        true
3899    }
3900}
3901
3902impl SerializableItem for KeymapEditor {
3903    fn serialized_item_kind() -> &'static str {
3904        "KeymapEditor"
3905    }
3906
3907    fn cleanup(
3908        workspace_id: workspace::WorkspaceId,
3909        alive_items: Vec<workspace::ItemId>,
3910        _window: &mut Window,
3911        cx: &mut App,
3912    ) -> gpui::Task<gpui::Result<()>> {
3913        let db = KeybindingEditorDb::global(cx);
3914        workspace::delete_unloaded_items(alive_items, workspace_id, "keybinding_editors", &db, cx)
3915    }
3916
3917    fn deserialize(
3918        _project: Entity<project::Project>,
3919        workspace: WeakEntity<Workspace>,
3920        workspace_id: workspace::WorkspaceId,
3921        item_id: workspace::ItemId,
3922        window: &mut Window,
3923        cx: &mut App,
3924    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
3925        let db = KeybindingEditorDb::global(cx);
3926        window.spawn(cx, async move |cx| {
3927            if db.get_keybinding_editor(item_id, workspace_id)?.is_some() {
3928                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
3929            } else {
3930                Err(anyhow!("No keybinding editor to deserialize"))
3931            }
3932        })
3933    }
3934
3935    fn serialize(
3936        &mut self,
3937        workspace: &mut Workspace,
3938        item_id: workspace::ItemId,
3939        _closing: bool,
3940        _window: &mut Window,
3941        cx: &mut ui::Context<Self>,
3942    ) -> Option<gpui::Task<gpui::Result<()>>> {
3943        let workspace_id = workspace.database_id()?;
3944        let db = KeybindingEditorDb::global(cx);
3945        Some(cx.background_spawn(
3946            async move { db.save_keybinding_editor(item_id, workspace_id).await },
3947        ))
3948    }
3949
3950    fn should_serialize(&self, _event: &Self::Event) -> bool {
3951        false
3952    }
3953}
3954
3955mod persistence {
3956    use db::{query, sqlez::domain::Domain, sqlez_macros::sql};
3957    use workspace::WorkspaceDb;
3958
3959    pub struct KeybindingEditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection);
3960
3961    impl Domain for KeybindingEditorDb {
3962        const NAME: &str = stringify!(KeybindingEditorDb);
3963
3964        const MIGRATIONS: &[&str] = &[sql!(
3965                CREATE TABLE keybinding_editors (
3966                    workspace_id INTEGER,
3967                    item_id INTEGER UNIQUE,
3968
3969                    PRIMARY KEY(workspace_id, item_id),
3970                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
3971                    ON DELETE CASCADE
3972                ) STRICT;
3973        )];
3974    }
3975
3976    db::static_connection!(KeybindingEditorDb, [WorkspaceDb]);
3977
3978    impl KeybindingEditorDb {
3979        query! {
3980            pub async fn save_keybinding_editor(
3981                item_id: workspace::ItemId,
3982                workspace_id: workspace::WorkspaceId
3983            ) -> Result<()> {
3984                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
3985                VALUES (?, ?)
3986            }
3987        }
3988
3989        query! {
3990            pub fn get_keybinding_editor(
3991                item_id: workspace::ItemId,
3992                workspace_id: workspace::WorkspaceId
3993            ) -> Result<Option<workspace::ItemId>> {
3994                SELECT item_id
3995                FROM keybinding_editors
3996                WHERE item_id = ? AND workspace_id = ?
3997            }
3998        }
3999    }
4000}
4001
4002#[cfg(test)]
4003mod tests {
4004    use super::*;
4005
4006    #[test]
4007    fn normalized_ctx_cmp() {
4008        #[track_caller]
4009        fn cmp(a: &str, b: &str) -> bool {
4010            let a = gpui::KeyBindingContextPredicate::parse(a)
4011                .expect("Failed to parse keybinding context a");
4012            let b = gpui::KeyBindingContextPredicate::parse(b)
4013                .expect("Failed to parse keybinding context b");
4014            normalized_ctx_eq(&a, &b)
4015        }
4016
4017        // Basic equality - identical expressions
4018        assert!(cmp("a && b", "a && b"));
4019        assert!(cmp("a || b", "a || b"));
4020        assert!(cmp("a == b", "a == b"));
4021        assert!(cmp("a != b", "a != b"));
4022        assert!(cmp("a > b", "a > b"));
4023        assert!(cmp("!a", "!a"));
4024
4025        // AND operator - associative/commutative
4026        assert!(cmp("a && b", "b && a"));
4027        assert!(cmp("a && b && c", "c && b && a"));
4028        assert!(cmp("a && b && c", "b && a && c"));
4029        assert!(cmp("a && b && c && d", "d && c && b && a"));
4030
4031        // OR operator - associative/commutative
4032        assert!(cmp("a || b", "b || a"));
4033        assert!(cmp("a || b || c", "c || b || a"));
4034        assert!(cmp("a || b || c", "b || a || c"));
4035        assert!(cmp("a || b || c || d", "d || c || b || a"));
4036
4037        // Equality operator - associative/commutative
4038        assert!(cmp("a == b", "b == a"));
4039        assert!(cmp("x == y", "y == x"));
4040
4041        // Inequality operator - associative/commutative
4042        assert!(cmp("a != b", "b != a"));
4043        assert!(cmp("x != y", "y != x"));
4044
4045        // Complex nested expressions with associative operators
4046        assert!(cmp("(a && b) || c", "c || (a && b)"));
4047        assert!(cmp("(a && b) || c", "c || (b && a)"));
4048        assert!(cmp("(a || b) && c", "c && (a || b)"));
4049        assert!(cmp("(a || b) && c", "c && (b || a)"));
4050        assert!(cmp("(a && b) || (c && d)", "(c && d) || (a && b)"));
4051        assert!(cmp("(a && b) || (c && d)", "(d && c) || (b && a)"));
4052
4053        // Multiple levels of nesting
4054        assert!(cmp("((a && b) || c) && d", "d && ((a && b) || c)"));
4055        assert!(cmp("((a && b) || c) && d", "d && (c || (b && a))"));
4056        assert!(cmp("a && (b || (c && d))", "(b || (c && d)) && a"));
4057        assert!(cmp("a && (b || (c && d))", "(b || (d && c)) && a"));
4058
4059        // Negation with associative operators
4060        assert!(cmp("!a && b", "b && !a"));
4061        assert!(cmp("!a || b", "b || !a"));
4062        assert!(cmp("!(a && b) || c", "c || !(a && b)"));
4063        assert!(cmp("!(a && b) || c", "c || !(b && a)"));
4064
4065        // Descendant operator (>) - NOT associative/commutative
4066        assert!(cmp("a > b", "a > b"));
4067        assert!(!cmp("a > b", "b > a"));
4068        assert!(!cmp("a > b > c", "c > b > a"));
4069        assert!(!cmp("a > b > c", "a > c > b"));
4070
4071        // Mixed operators with descendant
4072        assert!(cmp("(a > b) && c", "c && (a > b)"));
4073        assert!(!cmp("(a > b) && c", "c && (b > a)"));
4074        assert!(cmp("(a > b) || (c > d)", "(c > d) || (a > b)"));
4075        assert!(!cmp("(a > b) || (c > d)", "(b > a) || (d > c)"));
4076
4077        // Negative cases - different operators
4078        assert!(!cmp("a && b", "a || b"));
4079        assert!(!cmp("a == b", "a != b"));
4080        assert!(!cmp("a && b", "a > b"));
4081        assert!(!cmp("a || b", "a > b"));
4082        assert!(!cmp("a == b", "a && b"));
4083        assert!(!cmp("a != b", "a || b"));
4084
4085        // Negative cases - different operands
4086        assert!(!cmp("a && b", "a && c"));
4087        assert!(!cmp("a && b", "c && d"));
4088        assert!(!cmp("a || b", "a || c"));
4089        assert!(!cmp("a || b", "c || d"));
4090        assert!(!cmp("a == b", "a == c"));
4091        assert!(!cmp("a != b", "a != c"));
4092        assert!(!cmp("a > b", "a > c"));
4093        assert!(!cmp("a > b", "c > b"));
4094
4095        // Negative cases - with negation
4096        assert!(!cmp("!a", "a"));
4097        assert!(!cmp("!a && b", "a && b"));
4098        assert!(!cmp("!(a && b)", "a && b"));
4099        assert!(!cmp("!a || b", "a || b"));
4100        assert!(!cmp("!(a || b)", "a || b"));
4101
4102        // Negative cases - complex expressions
4103        assert!(!cmp("(a && b) || c", "(a || b) && c"));
4104        assert!(!cmp("a && (b || c)", "a || (b && c)"));
4105        assert!(!cmp("(a && b) || (c && d)", "(a || b) && (c || d)"));
4106        assert!(!cmp("a > b && c", "a && b > c"));
4107
4108        // Edge cases - multiple same operands
4109        assert!(cmp("a && a", "a && a"));
4110        assert!(cmp("a || a", "a || a"));
4111        assert!(cmp("a && a && b", "b && a && a"));
4112        assert!(cmp("a || a || b", "b || a || a"));
4113
4114        // Edge cases - deeply nested
4115        assert!(cmp(
4116            "((a && b) || (c && d)) && ((e || f) && g)",
4117            "((e || f) && g) && ((c && d) || (a && b))"
4118        ));
4119        assert!(cmp(
4120            "((a && b) || (c && d)) && ((e || f) && g)",
4121            "(g && (f || e)) && ((d && c) || (b && a))"
4122        ));
4123
4124        // Edge cases - repeated patterns
4125        assert!(cmp("(a && b) || (a && b)", "(b && a) || (b && a)"));
4126        assert!(cmp("(a || b) && (a || b)", "(b || a) && (b || a)"));
4127
4128        // Negative cases - subtle differences
4129        assert!(!cmp("a && b && c", "a && b"));
4130        assert!(!cmp("a || b || c", "a || b"));
4131        assert!(!cmp("(a && b) || c", "a && (b || c)"));
4132
4133        // a > b > c is not the same as a > c, should not be equal
4134        assert!(!cmp("a > b > c", "a > c"));
4135
4136        // Double negation with complex expressions
4137        assert!(cmp("!(!(a && b))", "a && b"));
4138        assert!(cmp("!(!(a || b))", "a || b"));
4139        assert!(cmp("!(!(a > b))", "a > b"));
4140        assert!(cmp("!(!a) && b", "a && b"));
4141        assert!(cmp("!(!a) || b", "a || b"));
4142        assert!(cmp("!(!(a && b)) || c", "(a && b) || c"));
4143        assert!(cmp("!(!(a && b)) || c", "(b && a) || c"));
4144        assert!(cmp("!(!a)", "a"));
4145        assert!(cmp("a", "!(!a)"));
4146        assert!(cmp("!(!(!a))", "!a"));
4147        assert!(cmp("!(!(!(!a)))", "a"));
4148    }
4149
4150    #[test]
4151    fn binding_is_unbound_by_unbind_respects_precedence() {
4152        let binding = gpui::KeyBinding::new("tab", zed_actions::OpenKeymap, None);
4153        let unbind =
4154            gpui::KeyBinding::new("tab", gpui::Unbind(binding.action().name().into()), None);
4155
4156        let unbind_then_binding = vec![&unbind, &binding];
4157        assert!(!binding_is_unbound_by_unbind(
4158            &binding,
4159            1,
4160            &unbind_then_binding,
4161        ));
4162
4163        let binding_then_unbind = vec![&binding, &unbind];
4164        assert!(binding_is_unbound_by_unbind(
4165            &binding,
4166            0,
4167            &binding_then_unbind,
4168        ));
4169    }
4170}