keybindings.rs

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