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