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