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