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