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