keybindings.rs

   1use std::{
   2    ops::{Not, Range},
   3    sync::Arc,
   4};
   5
   6use anyhow::{Context as _, anyhow};
   7use collections::{HashMap, HashSet};
   8use editor::{CompletionProvider, Editor, EditorEvent};
   9use feature_flags::FeatureFlagViewExt;
  10use fs::Fs;
  11use fuzzy::{StringMatch, StringMatchCandidate};
  12use gpui::{
  13    Action, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
  14    FocusHandle, Focusable, Global, KeyContext, Keystroke, ModifiersChangedEvent, MouseButton,
  15    Point, ScrollStrategy, StyledText, Subscription, WeakEntity, actions, anchored, deferred, div,
  16};
  17use language::{Language, LanguageConfig, ToOffset as _};
  18use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets};
  19
  20use util::ResultExt;
  21
  22use ui::{
  23    ActiveTheme as _, App, Banner, BorrowAppContext, ContextMenu, ParentElement as _, Render,
  24    SharedString, Styled as _, Tooltip, Window, prelude::*,
  25};
  26use workspace::{
  27    Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _,
  28    register_serializable_item,
  29};
  30
  31use crate::{
  32    SettingsUiFeatureFlag,
  33    keybindings::persistence::KEYBINDING_EDITORS,
  34    ui_components::table::{Table, TableInteractionState},
  35};
  36
  37const NO_ACTION_ARGUMENTS_TEXT: SharedString = SharedString::new_static("<no arguments>");
  38
  39actions!(
  40    zed,
  41    [
  42        /// Opens the keymap editor.
  43        OpenKeymapEditor
  44    ]
  45);
  46
  47const KEYMAP_EDITOR_NAMESPACE: &'static str = "keymap_editor";
  48actions!(
  49    keymap_editor,
  50    [
  51        /// Edits the selected key binding.
  52        EditBinding,
  53        /// Creates a new key binding for the selected action.
  54        CreateBinding,
  55        /// Deletes the selected key binding.
  56        DeleteBinding,
  57        /// Copies the action name to clipboard.
  58        CopyAction,
  59        /// Copies the context predicate to clipboard.
  60        CopyContext,
  61        /// Toggles Conflict Filtering
  62        ToggleConflictFilter,
  63        /// Toggle Keystroke search
  64        ToggleKeystrokeSearch,
  65    ]
  66);
  67
  68pub fn init(cx: &mut App) {
  69    let keymap_event_channel = KeymapEventChannel::new();
  70    cx.set_global(keymap_event_channel);
  71
  72    cx.on_action(|_: &OpenKeymapEditor, cx| {
  73        workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
  74            let existing = workspace
  75                .active_pane()
  76                .read(cx)
  77                .items()
  78                .find_map(|item| item.downcast::<KeymapEditor>());
  79
  80            if let Some(existing) = existing {
  81                workspace.activate_item(&existing, true, true, window, cx);
  82            } else {
  83                let keymap_editor =
  84                    cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
  85                workspace.add_item_to_active_pane(Box::new(keymap_editor), None, true, window, cx);
  86            }
  87        });
  88    });
  89
  90    cx.observe_new(|_workspace: &mut Workspace, window, cx| {
  91        let Some(window) = window else { return };
  92
  93        let keymap_ui_actions = [std::any::TypeId::of::<OpenKeymapEditor>()];
  94
  95        command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
  96            filter.hide_action_types(&keymap_ui_actions);
  97            filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
  98        });
  99
 100        cx.observe_flag::<SettingsUiFeatureFlag, _>(
 101            window,
 102            move |is_enabled, _workspace, _, cx| {
 103                if is_enabled {
 104                    command_palette_hooks::CommandPaletteFilter::update_global(
 105                        cx,
 106                        |filter, _cx| {
 107                            filter.show_action_types(keymap_ui_actions.iter());
 108                            filter.show_namespace(KEYMAP_EDITOR_NAMESPACE);
 109                        },
 110                    );
 111                } else {
 112                    command_palette_hooks::CommandPaletteFilter::update_global(
 113                        cx,
 114                        |filter, _cx| {
 115                            filter.hide_action_types(&keymap_ui_actions);
 116                            filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
 117                        },
 118                    );
 119                }
 120            },
 121        )
 122        .detach();
 123    })
 124    .detach();
 125
 126    register_serializable_item::<KeymapEditor>(cx);
 127}
 128
 129pub struct KeymapEventChannel {}
 130
 131impl Global for KeymapEventChannel {}
 132
 133impl KeymapEventChannel {
 134    fn new() -> Self {
 135        Self {}
 136    }
 137
 138    pub fn trigger_keymap_changed(cx: &mut App) {
 139        let Some(_event_channel) = cx.try_global::<Self>() else {
 140            // don't panic if no global defined. This usually happens in tests
 141            return;
 142        };
 143        cx.update_global(|_event_channel: &mut Self, _| {
 144            /* triggers observers in KeymapEditors */
 145        });
 146    }
 147}
 148
 149#[derive(Default, PartialEq)]
 150enum SearchMode {
 151    #[default]
 152    Normal,
 153    KeyStroke,
 154}
 155
 156impl SearchMode {
 157    fn invert(&self) -> Self {
 158        match self {
 159            SearchMode::Normal => SearchMode::KeyStroke,
 160            SearchMode::KeyStroke => SearchMode::Normal,
 161        }
 162    }
 163}
 164
 165#[derive(Default, PartialEq, Copy, Clone)]
 166enum FilterState {
 167    #[default]
 168    All,
 169    Conflicts,
 170}
 171
 172impl FilterState {
 173    fn invert(&self) -> Self {
 174        match self {
 175            FilterState::All => FilterState::Conflicts,
 176            FilterState::Conflicts => FilterState::All,
 177        }
 178    }
 179}
 180
 181type ActionMapping = (SharedString, Option<SharedString>);
 182
 183#[derive(Default)]
 184struct ConflictState {
 185    conflicts: Vec<usize>,
 186    action_keybind_mapping: HashMap<ActionMapping, Vec<usize>>,
 187}
 188
 189impl ConflictState {
 190    fn new(key_bindings: &[ProcessedKeybinding]) -> Self {
 191        let mut action_keybind_mapping: HashMap<_, Vec<usize>> = HashMap::default();
 192
 193        key_bindings
 194            .iter()
 195            .enumerate()
 196            .filter(|(_, binding)| {
 197                !binding.keystroke_text.is_empty()
 198                    && binding
 199                        .source
 200                        .as_ref()
 201                        .is_some_and(|source| matches!(source.0, KeybindSource::User))
 202            })
 203            .for_each(|(index, binding)| {
 204                action_keybind_mapping
 205                    .entry(binding.get_action_mapping())
 206                    .or_default()
 207                    .push(index);
 208            });
 209
 210        Self {
 211            conflicts: action_keybind_mapping
 212                .values()
 213                .filter(|indices| indices.len() > 1)
 214                .flatten()
 215                .copied()
 216                .collect(),
 217            action_keybind_mapping,
 218        }
 219    }
 220
 221    fn conflicting_indices_for_mapping(
 222        &self,
 223        action_mapping: ActionMapping,
 224        keybind_idx: usize,
 225    ) -> Option<Vec<usize>> {
 226        self.action_keybind_mapping
 227            .get(&action_mapping)
 228            .and_then(|indices| {
 229                let mut indices = indices.iter().filter(|&idx| *idx != keybind_idx).peekable();
 230                indices.peek().is_some().then(|| indices.copied().collect())
 231            })
 232    }
 233
 234    fn has_conflict(&self, candidate_idx: &usize) -> bool {
 235        self.conflicts.contains(candidate_idx)
 236    }
 237
 238    fn any_conflicts(&self) -> bool {
 239        !self.conflicts.is_empty()
 240    }
 241}
 242
 243struct KeymapEditor {
 244    workspace: WeakEntity<Workspace>,
 245    focus_handle: FocusHandle,
 246    _keymap_subscription: Subscription,
 247    keybindings: Vec<ProcessedKeybinding>,
 248    keybinding_conflict_state: ConflictState,
 249    filter_state: FilterState,
 250    search_mode: SearchMode,
 251    // corresponds 1 to 1 with keybindings
 252    string_match_candidates: Arc<Vec<StringMatchCandidate>>,
 253    matches: Vec<StringMatch>,
 254    table_interaction_state: Entity<TableInteractionState>,
 255    filter_editor: Entity<Editor>,
 256    keystroke_editor: Entity<KeystrokeInput>,
 257    selected_index: Option<usize>,
 258    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
 259}
 260
 261impl EventEmitter<()> for KeymapEditor {}
 262
 263impl Focusable for KeymapEditor {
 264    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 265        return self.filter_editor.focus_handle(cx);
 266    }
 267}
 268
 269impl KeymapEditor {
 270    fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
 271        let _keymap_subscription =
 272            cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
 273        let table_interaction_state = TableInteractionState::new(window, cx);
 274
 275        let keystroke_editor = cx.new(|cx| {
 276            let mut keystroke_editor = KeystrokeInput::new(window, cx);
 277            keystroke_editor.highlight_on_focus = false;
 278            keystroke_editor
 279        });
 280
 281        let filter_editor = cx.new(|cx| {
 282            let mut editor = Editor::single_line(window, cx);
 283            editor.set_placeholder_text("Filter action names…", cx);
 284            editor
 285        });
 286
 287        cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
 288            if !matches!(e, EditorEvent::BufferEdited) {
 289                return;
 290            }
 291
 292            this.update_matches(cx);
 293        })
 294        .detach();
 295
 296        cx.subscribe(&keystroke_editor, |this, _, _, cx| {
 297            if matches!(this.search_mode, SearchMode::Normal) {
 298                return;
 299            }
 300
 301            this.update_matches(cx);
 302        })
 303        .detach();
 304
 305        let mut this = Self {
 306            workspace,
 307            keybindings: vec![],
 308            keybinding_conflict_state: ConflictState::default(),
 309            filter_state: FilterState::default(),
 310            search_mode: SearchMode::default(),
 311            string_match_candidates: Arc::new(vec![]),
 312            matches: vec![],
 313            focus_handle: cx.focus_handle(),
 314            _keymap_subscription,
 315            table_interaction_state,
 316            filter_editor,
 317            keystroke_editor,
 318            selected_index: None,
 319            context_menu: None,
 320        };
 321
 322        this.update_keybindings(cx);
 323
 324        this
 325    }
 326
 327    fn current_action_query(&self, cx: &App) -> String {
 328        self.filter_editor.read(cx).text(cx)
 329    }
 330
 331    fn current_keystroke_query(&self, cx: &App) -> Vec<Keystroke> {
 332        match self.search_mode {
 333            SearchMode::KeyStroke => self
 334                .keystroke_editor
 335                .read(cx)
 336                .keystrokes()
 337                .iter()
 338                .cloned()
 339                .collect(),
 340            SearchMode::Normal => Default::default(),
 341        }
 342    }
 343
 344    fn update_matches(&self, cx: &mut Context<Self>) {
 345        let action_query = self.current_action_query(cx);
 346        let keystroke_query = self.current_keystroke_query(cx);
 347
 348        cx.spawn(async move |this, cx| {
 349            Self::process_query(this, action_query, keystroke_query, cx).await
 350        })
 351        .detach();
 352    }
 353
 354    async fn process_query(
 355        this: WeakEntity<Self>,
 356        action_query: String,
 357        keystroke_query: Vec<Keystroke>,
 358        cx: &mut AsyncApp,
 359    ) -> anyhow::Result<()> {
 360        let action_query = command_palette::normalize_action_query(&action_query);
 361        let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
 362            (this.string_match_candidates.clone(), this.keybindings.len())
 363        })?;
 364        let executor = cx.background_executor().clone();
 365        let mut matches = fuzzy::match_strings(
 366            &string_match_candidates,
 367            &action_query,
 368            true,
 369            true,
 370            keybind_count,
 371            &Default::default(),
 372            executor,
 373        )
 374        .await;
 375        this.update(cx, |this, cx| {
 376            match this.filter_state {
 377                FilterState::Conflicts => {
 378                    matches.retain(|candidate| {
 379                        this.keybinding_conflict_state
 380                            .has_conflict(&candidate.candidate_id)
 381                    });
 382                }
 383                FilterState::All => {}
 384            }
 385
 386            match this.search_mode {
 387                SearchMode::KeyStroke => {
 388                    matches.retain(|item| {
 389                        this.keybindings[item.candidate_id]
 390                            .ui_key_binding
 391                            .as_ref()
 392                            .is_some_and(|binding| {
 393                                keystroke_query.iter().all(|key| {
 394                                    binding.keystrokes.iter().any(|keystroke| {
 395                                        keystroke.key == key.key
 396                                            && keystroke.modifiers == key.modifiers
 397                                    })
 398                                })
 399                            })
 400                    });
 401                }
 402                SearchMode::Normal => {}
 403            }
 404
 405            if action_query.is_empty() {
 406                // apply default sort
 407                // sorts by source precedence, and alphabetically by action name within each source
 408                matches.sort_by_key(|match_item| {
 409                    let keybind = &this.keybindings[match_item.candidate_id];
 410                    let source = keybind.source.as_ref().map(|s| s.0);
 411                    use KeybindSource::*;
 412                    let source_precedence = match source {
 413                        Some(User) => 0,
 414                        Some(Vim) => 1,
 415                        Some(Base) => 2,
 416                        Some(Default) => 3,
 417                        None => 4,
 418                    };
 419                    return (source_precedence, keybind.action_name.as_ref());
 420                });
 421            }
 422            this.selected_index.take();
 423            this.scroll_to_item(0, ScrollStrategy::Top, cx);
 424            this.matches = matches;
 425            cx.notify();
 426        })
 427    }
 428
 429    fn process_bindings(
 430        json_language: Arc<Language>,
 431        rust_language: Arc<Language>,
 432        cx: &mut App,
 433    ) -> (Vec<ProcessedKeybinding>, Vec<StringMatchCandidate>) {
 434        let key_bindings_ptr = cx.key_bindings();
 435        let lock = key_bindings_ptr.borrow();
 436        let key_bindings = lock.bindings();
 437        let mut unmapped_action_names =
 438            HashSet::from_iter(cx.all_action_names().into_iter().copied());
 439        let action_documentation = cx.action_documentation();
 440        let mut generator = KeymapFile::action_schema_generator();
 441        let action_schema = HashMap::from_iter(
 442            cx.action_schemas(&mut generator)
 443                .into_iter()
 444                .filter_map(|(name, schema)| schema.map(|schema| (name, schema))),
 445        );
 446
 447        let mut processed_bindings = Vec::new();
 448        let mut string_match_candidates = Vec::new();
 449
 450        for key_binding in key_bindings {
 451            let source = key_binding.meta().map(settings::KeybindSource::from_meta);
 452
 453            let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
 454            let ui_key_binding = Some(
 455                ui::KeyBinding::new_from_gpui(key_binding.clone(), cx)
 456                    .vim_mode(source == Some(settings::KeybindSource::Vim)),
 457            );
 458
 459            let context = key_binding
 460                .predicate()
 461                .map(|predicate| {
 462                    KeybindContextString::Local(predicate.to_string().into(), rust_language.clone())
 463                })
 464                .unwrap_or(KeybindContextString::Global);
 465
 466            let source = source.map(|source| (source, source.name().into()));
 467
 468            let action_name = key_binding.action().name();
 469            unmapped_action_names.remove(&action_name);
 470            let action_input = key_binding
 471                .action_input()
 472                .map(|input| SyntaxHighlightedText::new(input, json_language.clone()));
 473            let action_docs = action_documentation.get(action_name).copied();
 474
 475            let index = processed_bindings.len();
 476            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
 477            processed_bindings.push(ProcessedKeybinding {
 478                keystroke_text: keystroke_text.into(),
 479                ui_key_binding,
 480                action_name: action_name.into(),
 481                action_input,
 482                action_docs,
 483                action_schema: action_schema.get(action_name).cloned(),
 484                context: Some(context),
 485                source,
 486            });
 487            string_match_candidates.push(string_match_candidate);
 488        }
 489
 490        let empty = SharedString::new_static("");
 491        for action_name in unmapped_action_names.into_iter() {
 492            let index = processed_bindings.len();
 493            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
 494            processed_bindings.push(ProcessedKeybinding {
 495                keystroke_text: empty.clone(),
 496                ui_key_binding: None,
 497                action_name: action_name.into(),
 498                action_input: None,
 499                action_docs: action_documentation.get(action_name).copied(),
 500                action_schema: action_schema.get(action_name).cloned(),
 501                context: None,
 502                source: None,
 503            });
 504            string_match_candidates.push(string_match_candidate);
 505        }
 506
 507        (processed_bindings, string_match_candidates)
 508    }
 509
 510    fn update_keybindings(&mut self, cx: &mut Context<KeymapEditor>) {
 511        let workspace = self.workspace.clone();
 512        cx.spawn(async move |this, cx| {
 513            let json_language = load_json_language(workspace.clone(), cx).await;
 514            let rust_language = load_rust_language(workspace.clone(), cx).await;
 515
 516            let (action_query, keystroke_query) = this.update(cx, |this, cx| {
 517                let (key_bindings, string_match_candidates) =
 518                    Self::process_bindings(json_language, rust_language, cx);
 519
 520                this.keybinding_conflict_state = ConflictState::new(&key_bindings);
 521
 522                if !this.keybinding_conflict_state.any_conflicts() {
 523                    this.filter_state = FilterState::All;
 524                }
 525
 526                this.keybindings = key_bindings;
 527                this.string_match_candidates = Arc::new(string_match_candidates);
 528                this.matches = this
 529                    .string_match_candidates
 530                    .iter()
 531                    .enumerate()
 532                    .map(|(ix, candidate)| StringMatch {
 533                        candidate_id: ix,
 534                        score: 0.0,
 535                        positions: vec![],
 536                        string: candidate.string.clone(),
 537                    })
 538                    .collect();
 539                (
 540                    this.current_action_query(cx),
 541                    this.current_keystroke_query(cx),
 542                )
 543            })?;
 544            // calls cx.notify
 545            Self::process_query(this, action_query, keystroke_query, cx).await
 546        })
 547        .detach_and_log_err(cx);
 548    }
 549
 550    fn dispatch_context(&self, _window: &Window, _cx: &Context<Self>) -> KeyContext {
 551        let mut dispatch_context = KeyContext::new_with_defaults();
 552        dispatch_context.add("KeymapEditor");
 553        dispatch_context.add("menu");
 554
 555        dispatch_context
 556    }
 557
 558    fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
 559        let index = usize::min(index, self.matches.len().saturating_sub(1));
 560        self.table_interaction_state.update(cx, |this, _cx| {
 561            this.scroll_handle.scroll_to_item(index, strategy);
 562        });
 563    }
 564
 565    fn focus_search(
 566        &mut self,
 567        _: &search::FocusSearch,
 568        window: &mut Window,
 569        cx: &mut Context<Self>,
 570    ) {
 571        if !self
 572            .filter_editor
 573            .focus_handle(cx)
 574            .contains_focused(window, cx)
 575        {
 576            window.focus(&self.filter_editor.focus_handle(cx));
 577        } else {
 578            self.filter_editor.update(cx, |editor, cx| {
 579                editor.select_all(&Default::default(), window, cx);
 580            });
 581        }
 582        self.selected_index.take();
 583    }
 584
 585    fn selected_keybind_idx(&self) -> Option<usize> {
 586        self.selected_index
 587            .and_then(|match_index| self.matches.get(match_index))
 588            .map(|r#match| r#match.candidate_id)
 589    }
 590
 591    fn selected_binding(&self) -> Option<&ProcessedKeybinding> {
 592        self.selected_keybind_idx()
 593            .and_then(|keybind_index| self.keybindings.get(keybind_index))
 594    }
 595
 596    fn select_index(&mut self, index: usize, cx: &mut Context<Self>) {
 597        if self.selected_index != Some(index) {
 598            self.selected_index = Some(index);
 599            cx.notify();
 600        }
 601    }
 602
 603    fn create_context_menu(
 604        &mut self,
 605        position: Point<Pixels>,
 606        window: &mut Window,
 607        cx: &mut Context<Self>,
 608    ) {
 609        self.context_menu = self.selected_binding().map(|selected_binding| {
 610            let selected_binding_has_no_context = selected_binding
 611                .context
 612                .as_ref()
 613                .and_then(KeybindContextString::local)
 614                .is_none();
 615
 616            let selected_binding_is_unbound = selected_binding.ui_key_binding.is_none();
 617
 618            let context_menu = ContextMenu::build(window, cx, |menu, _window, _cx| {
 619                menu.action_disabled_when(
 620                    selected_binding_is_unbound,
 621                    "Edit",
 622                    Box::new(EditBinding),
 623                )
 624                .action("Create", Box::new(CreateBinding))
 625                .action("Copy action", Box::new(CopyAction))
 626                .action_disabled_when(
 627                    selected_binding_has_no_context,
 628                    "Copy Context",
 629                    Box::new(CopyContext),
 630                )
 631            });
 632
 633            let context_menu_handle = context_menu.focus_handle(cx);
 634            window.defer(cx, move |window, _cx| window.focus(&context_menu_handle));
 635            let subscription = cx.subscribe_in(
 636                &context_menu,
 637                window,
 638                |this, _, _: &DismissEvent, window, cx| {
 639                    this.dismiss_context_menu(window, cx);
 640                },
 641            );
 642            (context_menu, position, subscription)
 643        });
 644
 645        cx.notify();
 646    }
 647
 648    fn dismiss_context_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 649        self.context_menu.take();
 650        window.focus(&self.focus_handle);
 651        cx.notify();
 652    }
 653
 654    fn context_menu_deployed(&self) -> bool {
 655        self.context_menu.is_some()
 656    }
 657
 658    fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
 659        if let Some(selected) = self.selected_index {
 660            let selected = selected + 1;
 661            if selected >= self.matches.len() {
 662                self.select_last(&Default::default(), window, cx);
 663            } else {
 664                self.selected_index = Some(selected);
 665                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
 666                cx.notify();
 667            }
 668        } else {
 669            self.select_first(&Default::default(), window, cx);
 670        }
 671    }
 672
 673    fn select_previous(
 674        &mut self,
 675        _: &menu::SelectPrevious,
 676        window: &mut Window,
 677        cx: &mut Context<Self>,
 678    ) {
 679        if let Some(selected) = self.selected_index {
 680            if selected == 0 {
 681                return;
 682            }
 683
 684            let selected = selected - 1;
 685
 686            if selected >= self.matches.len() {
 687                self.select_last(&Default::default(), window, cx);
 688            } else {
 689                self.selected_index = Some(selected);
 690                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
 691                cx.notify();
 692            }
 693        } else {
 694            self.select_last(&Default::default(), window, cx);
 695        }
 696    }
 697
 698    fn select_first(
 699        &mut self,
 700        _: &menu::SelectFirst,
 701        _window: &mut Window,
 702        cx: &mut Context<Self>,
 703    ) {
 704        if self.matches.get(0).is_some() {
 705            self.selected_index = Some(0);
 706            self.scroll_to_item(0, ScrollStrategy::Center, cx);
 707            cx.notify();
 708        }
 709    }
 710
 711    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
 712        if self.matches.last().is_some() {
 713            let index = self.matches.len() - 1;
 714            self.selected_index = Some(index);
 715            self.scroll_to_item(index, ScrollStrategy::Center, cx);
 716            cx.notify();
 717        }
 718    }
 719
 720    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
 721        self.open_edit_keybinding_modal(false, window, cx);
 722    }
 723
 724    fn open_edit_keybinding_modal(
 725        &mut self,
 726        create: bool,
 727        window: &mut Window,
 728        cx: &mut Context<Self>,
 729    ) {
 730        let Some((keybind_idx, keybind)) = self
 731            .selected_keybind_idx()
 732            .zip(self.selected_binding().cloned())
 733        else {
 734            return;
 735        };
 736        let keymap_editor = cx.entity();
 737        self.workspace
 738            .update(cx, |workspace, cx| {
 739                let fs = workspace.app_state().fs.clone();
 740                let workspace_weak = cx.weak_entity();
 741                workspace.toggle_modal(window, cx, |window, cx| {
 742                    let modal = KeybindingEditorModal::new(
 743                        create,
 744                        keybind,
 745                        keybind_idx,
 746                        keymap_editor,
 747                        workspace_weak,
 748                        fs,
 749                        window,
 750                        cx,
 751                    );
 752                    window.focus(&modal.focus_handle(cx));
 753                    modal
 754                });
 755            })
 756            .log_err();
 757    }
 758
 759    fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
 760        self.open_edit_keybinding_modal(false, window, cx);
 761    }
 762
 763    fn create_binding(&mut self, _: &CreateBinding, window: &mut Window, cx: &mut Context<Self>) {
 764        self.open_edit_keybinding_modal(true, window, cx);
 765    }
 766
 767    fn delete_binding(&mut self, _: &DeleteBinding, window: &mut Window, cx: &mut Context<Self>) {
 768        let Some(to_remove) = self.selected_binding().cloned() else {
 769            return;
 770        };
 771        let Ok(fs) = self
 772            .workspace
 773            .read_with(cx, |workspace, _| workspace.app_state().fs.clone())
 774        else {
 775            return;
 776        };
 777        let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
 778        cx.spawn(async move |_, _| remove_keybinding(to_remove, &fs, tab_size).await)
 779            .detach_and_notify_err(window, cx);
 780    }
 781
 782    fn copy_context_to_clipboard(
 783        &mut self,
 784        _: &CopyContext,
 785        _window: &mut Window,
 786        cx: &mut Context<Self>,
 787    ) {
 788        let context = self
 789            .selected_binding()
 790            .and_then(|binding| binding.context.as_ref())
 791            .and_then(KeybindContextString::local_str)
 792            .map(|context| context.to_string());
 793        let Some(context) = context else {
 794            return;
 795        };
 796        cx.write_to_clipboard(gpui::ClipboardItem::new_string(context.clone()));
 797    }
 798
 799    fn copy_action_to_clipboard(
 800        &mut self,
 801        _: &CopyAction,
 802        _window: &mut Window,
 803        cx: &mut Context<Self>,
 804    ) {
 805        let action = self
 806            .selected_binding()
 807            .map(|binding| binding.action_name.to_string());
 808        let Some(action) = action else {
 809            return;
 810        };
 811        cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
 812    }
 813
 814    fn toggle_conflict_filter(
 815        &mut self,
 816        _: &ToggleConflictFilter,
 817        _: &mut Window,
 818        cx: &mut Context<Self>,
 819    ) {
 820        self.filter_state = self.filter_state.invert();
 821        self.update_matches(cx);
 822    }
 823
 824    fn toggle_keystroke_search(
 825        &mut self,
 826        _: &ToggleKeystrokeSearch,
 827        window: &mut Window,
 828        cx: &mut Context<Self>,
 829    ) {
 830        self.search_mode = self.search_mode.invert();
 831        self.update_matches(cx);
 832
 833        match self.search_mode {
 834            SearchMode::KeyStroke => {
 835                window.focus(&self.keystroke_editor.focus_handle(cx));
 836            }
 837            SearchMode::Normal => {}
 838        }
 839    }
 840}
 841
 842#[derive(Clone)]
 843struct ProcessedKeybinding {
 844    keystroke_text: SharedString,
 845    ui_key_binding: Option<ui::KeyBinding>,
 846    action_name: SharedString,
 847    action_input: Option<SyntaxHighlightedText>,
 848    action_docs: Option<&'static str>,
 849    action_schema: Option<schemars::Schema>,
 850    context: Option<KeybindContextString>,
 851    source: Option<(KeybindSource, SharedString)>,
 852}
 853
 854impl ProcessedKeybinding {
 855    fn get_action_mapping(&self) -> ActionMapping {
 856        (
 857            self.keystroke_text.clone(),
 858            self.context
 859                .as_ref()
 860                .and_then(|context| context.local())
 861                .cloned(),
 862        )
 863    }
 864}
 865
 866#[derive(Clone, Debug, IntoElement, PartialEq, Eq, Hash)]
 867enum KeybindContextString {
 868    Global,
 869    Local(SharedString, Arc<Language>),
 870}
 871
 872impl KeybindContextString {
 873    const GLOBAL: SharedString = SharedString::new_static("<global>");
 874
 875    pub fn local(&self) -> Option<&SharedString> {
 876        match self {
 877            KeybindContextString::Global => None,
 878            KeybindContextString::Local(name, _) => Some(name),
 879        }
 880    }
 881
 882    pub fn local_str(&self) -> Option<&str> {
 883        match self {
 884            KeybindContextString::Global => None,
 885            KeybindContextString::Local(name, _) => Some(name),
 886        }
 887    }
 888}
 889
 890impl RenderOnce for KeybindContextString {
 891    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 892        match self {
 893            KeybindContextString::Global => {
 894                muted_styled_text(KeybindContextString::GLOBAL.clone(), cx).into_any_element()
 895            }
 896            KeybindContextString::Local(name, language) => {
 897                SyntaxHighlightedText::new(name, language).into_any_element()
 898            }
 899        }
 900    }
 901}
 902
 903fn muted_styled_text(text: SharedString, cx: &App) -> StyledText {
 904    let len = text.len();
 905    StyledText::new(text).with_highlights([(
 906        0..len,
 907        gpui::HighlightStyle::color(cx.theme().colors().text_muted),
 908    )])
 909}
 910
 911impl Item for KeymapEditor {
 912    type Event = ();
 913
 914    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
 915        "Keymap Editor".into()
 916    }
 917}
 918
 919impl Render for KeymapEditor {
 920    fn render(&mut self, window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
 921        let row_count = self.matches.len();
 922        let theme = cx.theme();
 923
 924        v_flex()
 925            .id("keymap-editor")
 926            .track_focus(&self.focus_handle)
 927            .key_context(self.dispatch_context(window, cx))
 928            .on_action(cx.listener(Self::select_next))
 929            .on_action(cx.listener(Self::select_previous))
 930            .on_action(cx.listener(Self::select_first))
 931            .on_action(cx.listener(Self::select_last))
 932            .on_action(cx.listener(Self::focus_search))
 933            .on_action(cx.listener(Self::confirm))
 934            .on_action(cx.listener(Self::edit_binding))
 935            .on_action(cx.listener(Self::create_binding))
 936            .on_action(cx.listener(Self::delete_binding))
 937            .on_action(cx.listener(Self::copy_action_to_clipboard))
 938            .on_action(cx.listener(Self::copy_context_to_clipboard))
 939            .on_action(cx.listener(Self::toggle_conflict_filter))
 940            .on_action(cx.listener(Self::toggle_keystroke_search))
 941            .size_full()
 942            .p_2()
 943            .gap_1()
 944            .bg(theme.colors().editor_background)
 945            .child(
 946                h_flex()
 947                    .p_2()
 948                    .gap_1()
 949                    .key_context({
 950                        let mut context = KeyContext::new_with_defaults();
 951                        context.add("BufferSearchBar");
 952                        context
 953                    })
 954                    .child(
 955                        div()
 956                            .size_full()
 957                            .h_8()
 958                            .pl_2()
 959                            .pr_1()
 960                            .py_1()
 961                            .border_1()
 962                            .border_color(theme.colors().border)
 963                            .rounded_lg()
 964                            .child(self.filter_editor.clone()),
 965                    )
 966                    .child(
 967                        // TODO: Ask Mikyala if there's a way to get have items be aligned by horizontally
 968                        // without embedding a h_flex in another h_flex
 969                        h_flex()
 970                            .when(self.keybinding_conflict_state.any_conflicts(), |this| {
 971                                this.child(
 972                                    IconButton::new("KeymapEditorConflictIcon", IconName::Warning)
 973                                        .tooltip({
 974                                            let filter_state = self.filter_state;
 975
 976                                            move |window, cx| {
 977                                                Tooltip::for_action(
 978                                                    match filter_state {
 979                                                        FilterState::All => "Show conflicts",
 980                                                        FilterState::Conflicts => "Hide conflicts",
 981                                                    },
 982                                                    &ToggleConflictFilter,
 983                                                    window,
 984                                                    cx,
 985                                                )
 986                                            }
 987                                        })
 988                                        .selected_icon_color(Color::Error)
 989                                        .toggle_state(matches!(
 990                                            self.filter_state,
 991                                            FilterState::Conflicts
 992                                        ))
 993                                        .on_click(|_, window, cx| {
 994                                            window.dispatch_action(
 995                                                ToggleConflictFilter.boxed_clone(),
 996                                                cx,
 997                                            );
 998                                        }),
 999                                )
1000                            })
1001                            .child(
1002                                IconButton::new("KeymapEditorToggleFiltersIcon", IconName::Filter)
1003                                    .tooltip(|window, cx| {
1004                                        Tooltip::for_action(
1005                                            "Toggle Keystroke Search",
1006                                            &ToggleKeystrokeSearch,
1007                                            window,
1008                                            cx,
1009                                        )
1010                                    })
1011                                    .toggle_state(matches!(self.search_mode, SearchMode::KeyStroke))
1012                                    .on_click(|_, window, cx| {
1013                                        window.dispatch_action(
1014                                            ToggleKeystrokeSearch.boxed_clone(),
1015                                            cx,
1016                                        );
1017                                    }),
1018                            ),
1019                    ),
1020            )
1021            .when(matches!(self.search_mode, SearchMode::KeyStroke), |this| {
1022                this.child(
1023                    div()
1024                        .child(self.keystroke_editor.clone())
1025                        .border_1()
1026                        .border_color(theme.colors().border)
1027                        .rounded_lg(),
1028                )
1029            })
1030            .child(
1031                Table::new()
1032                    .interactable(&self.table_interaction_state)
1033                    .striped()
1034                    .column_widths([rems(16.), rems(16.), rems(16.), rems(32.), rems(8.)])
1035                    .header(["Action", "Arguments", "Keystrokes", "Context", "Source"])
1036                    .uniform_list(
1037                        "keymap-editor-table",
1038                        row_count,
1039                        cx.processor(move |this, range: Range<usize>, _window, cx| {
1040                            let context_menu_deployed = this.context_menu_deployed();
1041                            range
1042                                .filter_map(|index| {
1043                                    let candidate_id = this.matches.get(index)?.candidate_id;
1044                                    let binding = &this.keybindings[candidate_id];
1045
1046                                    let action = div()
1047                                        .child(binding.action_name.clone())
1048                                        .id(("keymap action", index))
1049                                        .when(!context_menu_deployed, |this| {
1050                                            this.tooltip({
1051                                                let action_name = binding.action_name.clone();
1052                                                let action_docs = binding.action_docs;
1053                                                move |_, cx| {
1054                                                    let action_tooltip = Tooltip::new(
1055                                                        command_palette::humanize_action_name(
1056                                                            &action_name,
1057                                                        ),
1058                                                    );
1059                                                    let action_tooltip = match action_docs {
1060                                                        Some(docs) => action_tooltip.meta(docs),
1061                                                        None => action_tooltip,
1062                                                    };
1063                                                    cx.new(|_| action_tooltip).into()
1064                                                }
1065                                            })
1066                                        })
1067                                        .into_any_element();
1068                                    let keystrokes = binding.ui_key_binding.clone().map_or(
1069                                        binding.keystroke_text.clone().into_any_element(),
1070                                        IntoElement::into_any_element,
1071                                    );
1072                                    let action_input = match binding.action_input.clone() {
1073                                        Some(input) => input.into_any_element(),
1074                                        None => {
1075                                            if binding.action_schema.is_some() {
1076                                                muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
1077                                                    .into_any_element()
1078                                            } else {
1079                                                gpui::Empty.into_any_element()
1080                                            }
1081                                        }
1082                                    };
1083                                    let context = binding.context.clone().map_or(
1084                                        gpui::Empty.into_any_element(),
1085                                        |context| {
1086                                            let is_local = context.local().is_some();
1087
1088                                            div()
1089                                                .id(("keymap context", index))
1090                                                .child(context.clone())
1091                                                .when(is_local && !context_menu_deployed, |this| {
1092                                                    this.tooltip(Tooltip::element({
1093                                                        move |_, _| {
1094                                                            context.clone().into_any_element()
1095                                                        }
1096                                                    }))
1097                                                })
1098                                                .into_any_element()
1099                                        },
1100                                    );
1101                                    let source = binding
1102                                        .source
1103                                        .clone()
1104                                        .map(|(_source, name)| name)
1105                                        .unwrap_or_default()
1106                                        .into_any_element();
1107                                    Some([action, action_input, keystrokes, context, source])
1108                                })
1109                                .collect()
1110                        }),
1111                    )
1112                    .map_row(
1113                        cx.processor(|this, (row_index, row): (usize, Div), _window, cx| {
1114                            let is_conflict = this
1115                                .matches
1116                                .get(row_index)
1117                                .map(|candidate| candidate.candidate_id)
1118                                .is_some_and(|id| this.keybinding_conflict_state.has_conflict(&id));
1119                            let is_selected = this.selected_index == Some(row_index);
1120
1121                            let row = row
1122                                .id(("keymap-table-row", row_index))
1123                                .on_any_mouse_down(cx.listener(
1124                                    move |this,
1125                                          mouse_down_event: &gpui::MouseDownEvent,
1126                                          window,
1127                                          cx| {
1128                                        match mouse_down_event.button {
1129                                            MouseButton::Left => {
1130                                                this.select_index(row_index, cx);
1131                                            }
1132
1133                                            MouseButton::Right => {
1134                                                this.select_index(row_index, cx);
1135                                                this.create_context_menu(
1136                                                    mouse_down_event.position,
1137                                                    window,
1138                                                    cx,
1139                                                );
1140                                            }
1141                                            _ => {}
1142                                        }
1143                                    },
1144                                ))
1145                                .on_click(cx.listener(
1146                                    move |this, event: &ClickEvent, window, cx| {
1147                                        if event.up.click_count == 2 {
1148                                            this.open_edit_keybinding_modal(false, window, cx);
1149                                        }
1150                                    },
1151                                ))
1152                                .border_2()
1153                                .when(is_conflict, |row| {
1154                                    row.bg(cx.theme().status().error_background)
1155                                })
1156                                .when(is_selected, |row| {
1157                                    row.border_color(cx.theme().colors().panel_focused_border)
1158                                });
1159
1160                            row.into_any_element()
1161                        }),
1162                    ),
1163            )
1164            .on_scroll_wheel(cx.listener(|this, _, _, cx| {
1165                this.context_menu.take();
1166                cx.notify();
1167            }))
1168            .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1169                deferred(
1170                    anchored()
1171                        .position(*position)
1172                        .anchor(gpui::Corner::TopLeft)
1173                        .child(menu.clone()),
1174                )
1175                .with_priority(1)
1176            }))
1177    }
1178}
1179
1180#[derive(Debug, Clone, IntoElement)]
1181struct SyntaxHighlightedText {
1182    text: SharedString,
1183    language: Arc<Language>,
1184}
1185
1186impl SyntaxHighlightedText {
1187    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
1188        Self {
1189            text: text.into(),
1190            language,
1191        }
1192    }
1193}
1194
1195impl RenderOnce for SyntaxHighlightedText {
1196    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
1197        let text_style = window.text_style();
1198        let syntax_theme = cx.theme().syntax();
1199
1200        let text = self.text.clone();
1201
1202        let highlights = self
1203            .language
1204            .highlight_text(&text.as_ref().into(), 0..text.len());
1205        let mut runs = Vec::with_capacity(highlights.len());
1206        let mut offset = 0;
1207
1208        for (highlight_range, highlight_id) in highlights {
1209            // Add un-highlighted text before the current highlight
1210            if highlight_range.start > offset {
1211                runs.push(text_style.to_run(highlight_range.start - offset));
1212            }
1213
1214            let mut run_style = text_style.clone();
1215            if let Some(highlight_style) = highlight_id.style(syntax_theme) {
1216                run_style = run_style.highlight(highlight_style);
1217            }
1218            // add the highlighted range
1219            runs.push(run_style.to_run(highlight_range.len()));
1220            offset = highlight_range.end;
1221        }
1222
1223        // Add any remaining un-highlighted text
1224        if offset < text.len() {
1225            runs.push(text_style.to_run(text.len() - offset));
1226        }
1227
1228        return StyledText::new(text).with_runs(runs);
1229    }
1230}
1231
1232#[derive(PartialEq)]
1233enum InputError {
1234    Warning(SharedString),
1235    Error(SharedString),
1236}
1237
1238impl InputError {
1239    fn warning(message: impl Into<SharedString>) -> Self {
1240        Self::Warning(message.into())
1241    }
1242
1243    fn error(message: impl Into<SharedString>) -> Self {
1244        Self::Error(message.into())
1245    }
1246
1247    fn content(&self) -> &SharedString {
1248        match self {
1249            InputError::Warning(content) | InputError::Error(content) => content,
1250        }
1251    }
1252
1253    fn is_warning(&self) -> bool {
1254        matches!(self, InputError::Warning(_))
1255    }
1256}
1257
1258struct KeybindingEditorModal {
1259    creating: bool,
1260    editing_keybind: ProcessedKeybinding,
1261    editing_keybind_idx: usize,
1262    keybind_editor: Entity<KeystrokeInput>,
1263    context_editor: Entity<Editor>,
1264    input_editor: Option<Entity<Editor>>,
1265    fs: Arc<dyn Fs>,
1266    error: Option<InputError>,
1267    keymap_editor: Entity<KeymapEditor>,
1268}
1269
1270impl ModalView for KeybindingEditorModal {}
1271
1272impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
1273
1274impl Focusable for KeybindingEditorModal {
1275    fn focus_handle(&self, cx: &App) -> FocusHandle {
1276        self.keybind_editor.focus_handle(cx)
1277    }
1278}
1279
1280impl KeybindingEditorModal {
1281    pub fn new(
1282        create: bool,
1283        editing_keybind: ProcessedKeybinding,
1284        editing_keybind_idx: usize,
1285        keymap_editor: Entity<KeymapEditor>,
1286        workspace: WeakEntity<Workspace>,
1287        fs: Arc<dyn Fs>,
1288        window: &mut Window,
1289        cx: &mut App,
1290    ) -> Self {
1291        let keybind_editor = cx.new(|cx| KeystrokeInput::new(window, cx));
1292
1293        let context_editor = cx.new(|cx| {
1294            let mut editor = Editor::single_line(window, cx);
1295
1296            if let Some(context) = editing_keybind
1297                .context
1298                .as_ref()
1299                .and_then(KeybindContextString::local)
1300            {
1301                editor.set_text(context.clone(), window, cx);
1302            } else {
1303                editor.set_placeholder_text("Keybinding context", cx);
1304            }
1305
1306            cx.spawn(async |editor, cx| {
1307                let contexts = cx
1308                    .background_spawn(async { collect_contexts_from_assets() })
1309                    .await;
1310
1311                editor
1312                    .update(cx, |editor, _cx| {
1313                        editor.set_completion_provider(Some(std::rc::Rc::new(
1314                            KeyContextCompletionProvider { contexts },
1315                        )));
1316                    })
1317                    .context("Failed to load completions for keybinding context")
1318            })
1319            .detach_and_log_err(cx);
1320
1321            editor
1322        });
1323
1324        let input_editor = editing_keybind.action_schema.clone().map(|_schema| {
1325            cx.new(|cx| {
1326                let mut editor = Editor::auto_height_unbounded(1, window, cx);
1327                if let Some(input) = editing_keybind.action_input.clone() {
1328                    editor.set_text(input.text, window, cx);
1329                } else {
1330                    // TODO: default value from schema?
1331                    editor.set_placeholder_text("Action input", cx);
1332                }
1333                cx.spawn(async |editor, cx| {
1334                    let json_language = load_json_language(workspace, cx).await;
1335                    editor
1336                        .update(cx, |editor, cx| {
1337                            if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
1338                                buffer.update(cx, |buffer, cx| {
1339                                    buffer.set_language(Some(json_language), cx)
1340                                });
1341                            }
1342                        })
1343                        .context("Failed to load JSON language for editing keybinding action input")
1344                })
1345                .detach_and_log_err(cx);
1346                editor
1347            })
1348        });
1349
1350        Self {
1351            creating: create,
1352            editing_keybind,
1353            editing_keybind_idx,
1354            fs,
1355            keybind_editor,
1356            context_editor,
1357            input_editor,
1358            error: None,
1359            keymap_editor,
1360        }
1361    }
1362
1363    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
1364        if self
1365            .error
1366            .as_ref()
1367            .is_some_and(|old_error| old_error.is_warning() && *old_error == error)
1368        {
1369            false
1370        } else {
1371            self.error = Some(error);
1372            cx.notify();
1373            true
1374        }
1375    }
1376
1377    fn save(&mut self, cx: &mut Context<Self>) {
1378        let existing_keybind = self.editing_keybind.clone();
1379        let fs = self.fs.clone();
1380        let new_keystrokes = self
1381            .keybind_editor
1382            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
1383        if new_keystrokes.is_empty() {
1384            self.set_error(InputError::error("Keystrokes cannot be empty"), cx);
1385            return;
1386        }
1387        let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
1388        let new_context = self
1389            .context_editor
1390            .read_with(cx, |editor, cx| editor.text(cx));
1391        let new_context = new_context.is_empty().not().then_some(new_context);
1392        let new_context_err = new_context.as_deref().and_then(|context| {
1393            gpui::KeyBindingContextPredicate::parse(context)
1394                .context("Failed to parse key context")
1395                .err()
1396        });
1397        if let Some(err) = new_context_err {
1398            // TODO: store and display as separate error
1399            // TODO: also, should be validating on keystroke
1400            self.set_error(InputError::error(err.to_string()), cx);
1401            return;
1402        }
1403
1404        let action_mapping: ActionMapping = (
1405            ui::text_for_keystrokes(&new_keystrokes, cx).into(),
1406            new_context
1407                .as_ref()
1408                .map(Into::into)
1409                .or_else(|| existing_keybind.get_action_mapping().1),
1410        );
1411
1412        if let Some(conflicting_indices) = self
1413            .keymap_editor
1414            .read(cx)
1415            .keybinding_conflict_state
1416            .conflicting_indices_for_mapping(action_mapping, self.editing_keybind_idx)
1417        {
1418            let first_conflicting_index = conflicting_indices[0];
1419            let conflicting_action_name = self
1420                .keymap_editor
1421                .read(cx)
1422                .keybindings
1423                .get(first_conflicting_index)
1424                .map(|keybind| keybind.action_name.clone());
1425
1426            let warning_message = match conflicting_action_name {
1427                Some(name) => {
1428                    let confliction_action_amount = conflicting_indices.len() - 1;
1429                    if confliction_action_amount > 0 {
1430                        format!(
1431                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
1432                            name, confliction_action_amount
1433                        )
1434                    } else {
1435                        format!("Your keybind would conflict with the \"{}\" action", name)
1436                    }
1437                }
1438                None => {
1439                    log::info!(
1440                        "Could not find action in keybindings with index {}",
1441                        first_conflicting_index
1442                    );
1443                    "Your keybind would conflict with other actions".to_string()
1444                }
1445            };
1446
1447            if self.set_error(InputError::warning(warning_message), cx) {
1448                return;
1449            }
1450        }
1451
1452        let create = self.creating;
1453
1454        cx.spawn(async move |this, cx| {
1455            if let Err(err) = save_keybinding_update(
1456                create,
1457                existing_keybind,
1458                &new_keystrokes,
1459                new_context.as_deref(),
1460                &fs,
1461                tab_size,
1462            )
1463            .await
1464            {
1465                this.update(cx, |this, cx| {
1466                    this.set_error(InputError::error(err.to_string()), cx);
1467                })
1468                .log_err();
1469            } else {
1470                this.update(cx, |_this, cx| {
1471                    cx.emit(DismissEvent);
1472                })
1473                .ok();
1474            }
1475        })
1476        .detach();
1477    }
1478}
1479
1480impl Render for KeybindingEditorModal {
1481    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1482        let theme = cx.theme().colors();
1483        let input_base = || {
1484            div()
1485                .w_full()
1486                .py_2()
1487                .px_3()
1488                .min_h_8()
1489                .rounded_md()
1490                .bg(theme.editor_background)
1491                .border_1()
1492                .border_color(theme.border_variant)
1493        };
1494
1495        v_flex()
1496            .w(rems(34.))
1497            .elevation_3(cx)
1498            .child(
1499                v_flex()
1500                    .p_3()
1501                    .child(Label::new("Edit Keystroke"))
1502                    .child(
1503                        Label::new("Input the desired keystroke for the selected action.")
1504                            .color(Color::Muted)
1505                            .mb_2(),
1506                    )
1507                    .child(self.keybind_editor.clone()),
1508            )
1509            .when_some(self.input_editor.clone(), |this, editor| {
1510                this.child(
1511                    v_flex()
1512                        .p_3()
1513                        .pt_0()
1514                        .child(Label::new("Edit Input"))
1515                        .child(
1516                            Label::new("Input the desired input to the binding.")
1517                                .color(Color::Muted)
1518                                .mb_2(),
1519                        )
1520                        .child(input_base().child(editor)),
1521                )
1522            })
1523            .child(
1524                v_flex()
1525                    .p_3()
1526                    .pt_0()
1527                    .child(Label::new("Edit Context"))
1528                    .child(
1529                        Label::new("Input the desired context for the binding.")
1530                            .color(Color::Muted)
1531                            .mb_2(),
1532                    )
1533                    .child(input_base().child(self.context_editor.clone())),
1534            )
1535            .when_some(self.error.as_ref(), |this, error| {
1536                this.child(
1537                    div().p_2().child(
1538                        Banner::new()
1539                            .map(|banner| match error {
1540                                InputError::Error(_) => banner.severity(ui::Severity::Error),
1541                                InputError::Warning(_) => banner.severity(ui::Severity::Warning),
1542                            })
1543                            // For some reason, the div overflows its container to the
1544                            // right. The padding accounts for that.
1545                            .child(div().size_full().pr_2().child(Label::new(error.content()))),
1546                    ),
1547                )
1548            })
1549            .child(
1550                h_flex()
1551                    .p_2()
1552                    .w_full()
1553                    .gap_1()
1554                    .justify_end()
1555                    .border_t_1()
1556                    .border_color(theme.border_variant)
1557                    .child(
1558                        Button::new("cancel", "Cancel")
1559                            .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
1560                    )
1561                    .child(
1562                        Button::new("save-btn", "Save").on_click(
1563                            cx.listener(|this, _event, _window, cx| Self::save(this, cx)),
1564                        ),
1565                    ),
1566            )
1567    }
1568}
1569
1570struct KeyContextCompletionProvider {
1571    contexts: Vec<SharedString>,
1572}
1573
1574impl CompletionProvider for KeyContextCompletionProvider {
1575    fn completions(
1576        &self,
1577        _excerpt_id: editor::ExcerptId,
1578        buffer: &Entity<language::Buffer>,
1579        buffer_position: language::Anchor,
1580        _trigger: editor::CompletionContext,
1581        _window: &mut Window,
1582        cx: &mut Context<Editor>,
1583    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
1584        let buffer = buffer.read(cx);
1585        let mut count_back = 0;
1586        for char in buffer.reversed_chars_at(buffer_position) {
1587            if char.is_ascii_alphanumeric() || char == '_' {
1588                count_back += 1;
1589            } else {
1590                break;
1591            }
1592        }
1593        let start_anchor = buffer.anchor_before(
1594            buffer_position
1595                .to_offset(&buffer)
1596                .saturating_sub(count_back),
1597        );
1598        let replace_range = start_anchor..buffer_position;
1599        gpui::Task::ready(Ok(vec![project::CompletionResponse {
1600            completions: self
1601                .contexts
1602                .iter()
1603                .map(|context| project::Completion {
1604                    replace_range: replace_range.clone(),
1605                    label: language::CodeLabel::plain(context.to_string(), None),
1606                    new_text: context.to_string(),
1607                    documentation: None,
1608                    source: project::CompletionSource::Custom,
1609                    icon_path: None,
1610                    insert_text_mode: None,
1611                    confirm: None,
1612                })
1613                .collect(),
1614            is_incomplete: false,
1615        }]))
1616    }
1617
1618    fn is_completion_trigger(
1619        &self,
1620        _buffer: &Entity<language::Buffer>,
1621        _position: language::Anchor,
1622        text: &str,
1623        _trigger_in_words: bool,
1624        _menu_is_open: bool,
1625        _cx: &mut Context<Editor>,
1626    ) -> bool {
1627        text.chars().last().map_or(false, |last_char| {
1628            last_char.is_ascii_alphanumeric() || last_char == '_'
1629        })
1630    }
1631}
1632
1633async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
1634    let json_language_task = workspace
1635        .read_with(cx, |workspace, cx| {
1636            workspace
1637                .project()
1638                .read(cx)
1639                .languages()
1640                .language_for_name("JSON")
1641        })
1642        .context("Failed to load JSON language")
1643        .log_err();
1644    let json_language = match json_language_task {
1645        Some(task) => task.await.context("Failed to load JSON language").log_err(),
1646        None => None,
1647    };
1648    return json_language.unwrap_or_else(|| {
1649        Arc::new(Language::new(
1650            LanguageConfig {
1651                name: "JSON".into(),
1652                ..Default::default()
1653            },
1654            Some(tree_sitter_json::LANGUAGE.into()),
1655        ))
1656    });
1657}
1658
1659async fn load_rust_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
1660    let rust_language_task = workspace
1661        .read_with(cx, |workspace, cx| {
1662            workspace
1663                .project()
1664                .read(cx)
1665                .languages()
1666                .language_for_name("Rust")
1667        })
1668        .context("Failed to load Rust language")
1669        .log_err();
1670    let rust_language = match rust_language_task {
1671        Some(task) => task.await.context("Failed to load Rust language").log_err(),
1672        None => None,
1673    };
1674    return rust_language.unwrap_or_else(|| {
1675        Arc::new(Language::new(
1676            LanguageConfig {
1677                name: "Rust".into(),
1678                ..Default::default()
1679            },
1680            Some(tree_sitter_rust::LANGUAGE.into()),
1681        ))
1682    });
1683}
1684
1685async fn save_keybinding_update(
1686    create: bool,
1687    existing: ProcessedKeybinding,
1688    new_keystrokes: &[Keystroke],
1689    new_context: Option<&str>,
1690    fs: &Arc<dyn Fs>,
1691    tab_size: usize,
1692) -> anyhow::Result<()> {
1693    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
1694        .await
1695        .context("Failed to load keymap file")?;
1696
1697    let existing_keystrokes = existing
1698        .ui_key_binding
1699        .as_ref()
1700        .map(|keybinding| keybinding.keystrokes.as_slice())
1701        .unwrap_or_default();
1702
1703    let existing_context = existing
1704        .context
1705        .as_ref()
1706        .and_then(KeybindContextString::local_str);
1707
1708    let input = existing
1709        .action_input
1710        .as_ref()
1711        .map(|input| input.text.as_ref());
1712
1713    let operation = if !create {
1714        settings::KeybindUpdateOperation::Replace {
1715            target: settings::KeybindUpdateTarget {
1716                context: existing_context,
1717                keystrokes: existing_keystrokes,
1718                action_name: &existing.action_name,
1719                use_key_equivalents: false,
1720                input,
1721            },
1722            target_keybind_source: existing
1723                .source
1724                .map(|(source, _name)| source)
1725                .unwrap_or(KeybindSource::User),
1726            source: settings::KeybindUpdateTarget {
1727                context: new_context,
1728                keystrokes: new_keystrokes,
1729                action_name: &existing.action_name,
1730                use_key_equivalents: false,
1731                input,
1732            },
1733        }
1734    } else {
1735        settings::KeybindUpdateOperation::Add(settings::KeybindUpdateTarget {
1736            context: new_context,
1737            keystrokes: new_keystrokes,
1738            action_name: &existing.action_name,
1739            use_key_equivalents: false,
1740            input,
1741        })
1742    };
1743    let updated_keymap_contents =
1744        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
1745            .context("Failed to update keybinding")?;
1746    fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
1747        .await
1748        .context("Failed to write keymap file")?;
1749    Ok(())
1750}
1751
1752async fn remove_keybinding(
1753    existing: ProcessedKeybinding,
1754    fs: &Arc<dyn Fs>,
1755    tab_size: usize,
1756) -> anyhow::Result<()> {
1757    let Some(ui_key_binding) = existing.ui_key_binding else {
1758        anyhow::bail!("Cannot remove a keybinding that does not exist");
1759    };
1760    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
1761        .await
1762        .context("Failed to load keymap file")?;
1763
1764    let operation = settings::KeybindUpdateOperation::Remove {
1765        target: settings::KeybindUpdateTarget {
1766            context: existing
1767                .context
1768                .as_ref()
1769                .and_then(KeybindContextString::local_str),
1770            keystrokes: &ui_key_binding.keystrokes,
1771            action_name: &existing.action_name,
1772            use_key_equivalents: false,
1773            input: existing
1774                .action_input
1775                .as_ref()
1776                .map(|input| input.text.as_ref()),
1777        },
1778        target_keybind_source: existing
1779            .source
1780            .map(|(source, _name)| source)
1781            .unwrap_or(KeybindSource::User),
1782    };
1783
1784    let updated_keymap_contents =
1785        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
1786            .context("Failed to update keybinding")?;
1787    fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
1788        .await
1789        .context("Failed to write keymap file")?;
1790    Ok(())
1791}
1792
1793struct KeystrokeInput {
1794    keystrokes: Vec<Keystroke>,
1795    highlight_on_focus: bool,
1796    focus_handle: FocusHandle,
1797    intercept_subscription: Option<Subscription>,
1798    _focus_subscriptions: [Subscription; 2],
1799}
1800
1801impl KeystrokeInput {
1802    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
1803        let focus_handle = cx.focus_handle();
1804        let _focus_subscriptions = [
1805            cx.on_focus_in(&focus_handle, window, Self::on_focus_in),
1806            cx.on_focus_out(&focus_handle, window, Self::on_focus_out),
1807        ];
1808        Self {
1809            keystrokes: Vec::new(),
1810            highlight_on_focus: true,
1811            focus_handle,
1812            intercept_subscription: None,
1813            _focus_subscriptions,
1814        }
1815    }
1816
1817    fn on_modifiers_changed(
1818        &mut self,
1819        event: &ModifiersChangedEvent,
1820        _window: &mut Window,
1821        cx: &mut Context<Self>,
1822    ) {
1823        if let Some(last) = self.keystrokes.last_mut()
1824            && last.key.is_empty()
1825        {
1826            if !event.modifiers.modified() {
1827                self.keystrokes.pop();
1828                cx.emit(());
1829            } else {
1830                last.modifiers = event.modifiers;
1831            }
1832        } else {
1833            self.keystrokes.push(Keystroke {
1834                modifiers: event.modifiers,
1835                key: "".to_string(),
1836                key_char: None,
1837            });
1838            cx.emit(());
1839        }
1840        cx.stop_propagation();
1841        cx.notify();
1842    }
1843
1844    fn handle_keystroke(&mut self, keystroke: &Keystroke, cx: &mut Context<Self>) {
1845        if let Some(last) = self.keystrokes.last_mut()
1846            && last.key.is_empty()
1847        {
1848            *last = keystroke.clone();
1849        } else if Some(keystroke) != self.keystrokes.last() {
1850            self.keystrokes.push(keystroke.clone());
1851        }
1852        cx.emit(());
1853        cx.stop_propagation();
1854        cx.notify();
1855    }
1856
1857    fn on_key_up(
1858        &mut self,
1859        event: &gpui::KeyUpEvent,
1860        _window: &mut Window,
1861        cx: &mut Context<Self>,
1862    ) {
1863        if let Some(last) = self.keystrokes.last_mut()
1864            && !last.key.is_empty()
1865            && last.modifiers == event.keystroke.modifiers
1866        {
1867            cx.emit(());
1868            self.keystrokes.push(Keystroke {
1869                modifiers: event.keystroke.modifiers,
1870                key: "".to_string(),
1871                key_char: None,
1872            });
1873        }
1874        cx.stop_propagation();
1875        cx.notify();
1876    }
1877
1878    fn on_focus_in(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
1879        if self.intercept_subscription.is_none() {
1880            let listener = cx.listener(|this, event: &gpui::KeystrokeEvent, _window, cx| {
1881                this.handle_keystroke(&event.keystroke, cx);
1882            });
1883            self.intercept_subscription = Some(cx.intercept_keystrokes(listener))
1884        }
1885    }
1886
1887    fn on_focus_out(
1888        &mut self,
1889        _event: gpui::FocusOutEvent,
1890        _window: &mut Window,
1891        _cx: &mut Context<Self>,
1892    ) {
1893        self.intercept_subscription.take();
1894    }
1895
1896    fn keystrokes(&self) -> &[Keystroke] {
1897        if self
1898            .keystrokes
1899            .last()
1900            .map_or(false, |last| last.key.is_empty())
1901        {
1902            return &self.keystrokes[..self.keystrokes.len() - 1];
1903        }
1904        return &self.keystrokes;
1905    }
1906}
1907
1908impl EventEmitter<()> for KeystrokeInput {}
1909
1910impl Focusable for KeystrokeInput {
1911    fn focus_handle(&self, _cx: &App) -> FocusHandle {
1912        self.focus_handle.clone()
1913    }
1914}
1915
1916impl Render for KeystrokeInput {
1917    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1918        let colors = cx.theme().colors();
1919        let is_focused = self.focus_handle.is_focused(window);
1920
1921        return h_flex()
1922            .id("keybinding_input")
1923            .track_focus(&self.focus_handle)
1924            .on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
1925            .on_key_up(cx.listener(Self::on_key_up))
1926            .when(self.highlight_on_focus, |this| {
1927                this.focus(|mut style| {
1928                    style.border_color = Some(colors.border_focused);
1929                    style
1930                })
1931            })
1932            .py_2()
1933            .px_3()
1934            .gap_2()
1935            .min_h_8()
1936            .w_full()
1937            .flex_1()
1938            .justify_between()
1939            .rounded_md()
1940            .overflow_hidden()
1941            .bg(colors.editor_background)
1942            .border_1()
1943            .border_color(colors.border_variant)
1944            .child(
1945                h_flex()
1946                    .w_full()
1947                    .min_w_0()
1948                    .justify_center()
1949                    .flex_wrap()
1950                    .gap(ui::DynamicSpacing::Base04.rems(cx))
1951                    .children(self.keystrokes.iter().map(|keystroke| {
1952                        h_flex().children(ui::render_keystroke(
1953                            keystroke,
1954                            None,
1955                            Some(rems(0.875).into()),
1956                            ui::PlatformStyle::platform(),
1957                            false,
1958                        ))
1959                    })),
1960            )
1961            .child(
1962                h_flex()
1963                    .gap_0p5()
1964                    .flex_none()
1965                    .child(
1966                        IconButton::new("backspace-btn", IconName::Delete)
1967                            .tooltip(Tooltip::text("Delete Keystroke"))
1968                            .when(!is_focused, |this| this.icon_color(Color::Muted))
1969                            .on_click(cx.listener(|this, _event, _window, cx| {
1970                                this.keystrokes.pop();
1971                                cx.emit(());
1972                                cx.notify();
1973                            })),
1974                    )
1975                    .child(
1976                        IconButton::new("clear-btn", IconName::Eraser)
1977                            .tooltip(Tooltip::text("Clear Keystrokes"))
1978                            .when(!is_focused, |this| this.icon_color(Color::Muted))
1979                            .on_click(cx.listener(|this, _event, _window, cx| {
1980                                this.keystrokes.clear();
1981                                cx.emit(());
1982                                cx.notify();
1983                            })),
1984                    ),
1985            );
1986    }
1987}
1988
1989fn collect_contexts_from_assets() -> Vec<SharedString> {
1990    let mut keymap_assets = vec![
1991        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
1992        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
1993    ];
1994    keymap_assets.extend(
1995        BaseKeymap::OPTIONS
1996            .iter()
1997            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
1998            .map(util::asset_str::<SettingsAssets>),
1999    );
2000
2001    let mut contexts = HashSet::default();
2002
2003    for keymap_asset in keymap_assets {
2004        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
2005            continue;
2006        };
2007
2008        for section in keymap.sections() {
2009            let context_expr = &section.context;
2010            let mut queue = Vec::new();
2011            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
2012                continue;
2013            };
2014
2015            queue.push(root_context);
2016            while let Some(context) = queue.pop() {
2017                match context {
2018                    gpui::KeyBindingContextPredicate::Identifier(ident) => {
2019                        contexts.insert(ident);
2020                    }
2021                    gpui::KeyBindingContextPredicate::Equal(ident_a, ident_b) => {
2022                        contexts.insert(ident_a);
2023                        contexts.insert(ident_b);
2024                    }
2025                    gpui::KeyBindingContextPredicate::NotEqual(ident_a, ident_b) => {
2026                        contexts.insert(ident_a);
2027                        contexts.insert(ident_b);
2028                    }
2029                    gpui::KeyBindingContextPredicate::Child(ctx_a, ctx_b) => {
2030                        queue.push(*ctx_a);
2031                        queue.push(*ctx_b);
2032                    }
2033                    gpui::KeyBindingContextPredicate::Not(ctx) => {
2034                        queue.push(*ctx);
2035                    }
2036                    gpui::KeyBindingContextPredicate::And(ctx_a, ctx_b) => {
2037                        queue.push(*ctx_a);
2038                        queue.push(*ctx_b);
2039                    }
2040                    gpui::KeyBindingContextPredicate::Or(ctx_a, ctx_b) => {
2041                        queue.push(*ctx_a);
2042                        queue.push(*ctx_b);
2043                    }
2044                }
2045            }
2046        }
2047    }
2048
2049    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
2050    contexts.sort();
2051
2052    return contexts;
2053}
2054
2055impl SerializableItem for KeymapEditor {
2056    fn serialized_item_kind() -> &'static str {
2057        "KeymapEditor"
2058    }
2059
2060    fn cleanup(
2061        workspace_id: workspace::WorkspaceId,
2062        alive_items: Vec<workspace::ItemId>,
2063        _window: &mut Window,
2064        cx: &mut App,
2065    ) -> gpui::Task<gpui::Result<()>> {
2066        workspace::delete_unloaded_items(
2067            alive_items,
2068            workspace_id,
2069            "keybinding_editors",
2070            &KEYBINDING_EDITORS,
2071            cx,
2072        )
2073    }
2074
2075    fn deserialize(
2076        _project: Entity<project::Project>,
2077        workspace: WeakEntity<Workspace>,
2078        workspace_id: workspace::WorkspaceId,
2079        item_id: workspace::ItemId,
2080        window: &mut Window,
2081        cx: &mut App,
2082    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
2083        window.spawn(cx, async move |cx| {
2084            if KEYBINDING_EDITORS
2085                .get_keybinding_editor(item_id, workspace_id)?
2086                .is_some()
2087            {
2088                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
2089            } else {
2090                Err(anyhow!("No keybinding editor to deserialize"))
2091            }
2092        })
2093    }
2094
2095    fn serialize(
2096        &mut self,
2097        workspace: &mut Workspace,
2098        item_id: workspace::ItemId,
2099        _closing: bool,
2100        _window: &mut Window,
2101        cx: &mut ui::Context<Self>,
2102    ) -> Option<gpui::Task<gpui::Result<()>>> {
2103        let workspace_id = workspace.database_id()?;
2104        Some(cx.background_spawn(async move {
2105            KEYBINDING_EDITORS
2106                .save_keybinding_editor(item_id, workspace_id)
2107                .await
2108        }))
2109    }
2110
2111    fn should_serialize(&self, _event: &Self::Event) -> bool {
2112        false
2113    }
2114}
2115
2116mod persistence {
2117    use db::{define_connection, query, sqlez_macros::sql};
2118    use workspace::WorkspaceDb;
2119
2120    define_connection! {
2121        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
2122            &[sql!(
2123                CREATE TABLE keybinding_editors (
2124                    workspace_id INTEGER,
2125                    item_id INTEGER UNIQUE,
2126
2127                    PRIMARY KEY(workspace_id, item_id),
2128                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
2129                    ON DELETE CASCADE
2130                ) STRICT;
2131            )];
2132    }
2133
2134    impl KeybindingEditorDb {
2135        query! {
2136            pub async fn save_keybinding_editor(
2137                item_id: workspace::ItemId,
2138                workspace_id: workspace::WorkspaceId
2139            ) -> Result<()> {
2140                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
2141                VALUES (?, ?)
2142            }
2143        }
2144
2145        query! {
2146            pub fn get_keybinding_editor(
2147                item_id: workspace::ItemId,
2148                workspace_id: workspace::WorkspaceId
2149            ) -> Result<Option<workspace::ItemId>> {
2150                SELECT item_id
2151                FROM keybinding_editors
2152                WHERE item_id = ? AND workspace_id = ?
2153            }
2154        }
2155    }
2156}