keybindings.rs

   1use std::{
   2    ops::{Not, Range},
   3    sync::Arc,
   4};
   5
   6use anyhow::{Context as _, anyhow};
   7use collections::HashSet;
   8use editor::{CompletionProvider, Editor, EditorEvent};
   9use feature_flags::FeatureFlagViewExt;
  10use fs::Fs;
  11use fuzzy::{StringMatch, StringMatchCandidate};
  12use gpui::{
  13    AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
  14    Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText, Subscription,
  15    WeakEntity, actions, div, transparent_black,
  16};
  17use language::{Language, LanguageConfig, ToOffset as _};
  18use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets};
  19
  20use util::ResultExt;
  21
  22use ui::{
  23    ActiveTheme as _, App, BorrowAppContext, ContextMenu, ParentElement as _, Render, SharedString,
  24    Styled as _, Tooltip, Window, prelude::*, right_click_menu,
  25};
  26use workspace::{Item, ModalView, SerializableItem, Workspace, register_serializable_item};
  27
  28use crate::{
  29    SettingsUiFeatureFlag,
  30    keybindings::persistence::KEYBINDING_EDITORS,
  31    ui_components::table::{Table, TableInteractionState},
  32};
  33
  34actions!(
  35    zed,
  36    [
  37        /// Opens the keymap editor.
  38        OpenKeymapEditor
  39    ]
  40);
  41
  42const KEYMAP_EDITOR_NAMESPACE: &'static str = "keymap_editor";
  43actions!(
  44    keymap_editor,
  45    [
  46        /// Edits the selected key binding.
  47        EditBinding,
  48        /// Copies the action name to clipboard.
  49        CopyAction,
  50        /// Copies the context predicate to clipboard.
  51        CopyContext
  52    ]
  53);
  54
  55pub fn init(cx: &mut App) {
  56    let keymap_event_channel = KeymapEventChannel::new();
  57    cx.set_global(keymap_event_channel);
  58
  59    cx.on_action(|_: &OpenKeymapEditor, cx| {
  60        workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
  61            let existing = workspace
  62                .active_pane()
  63                .read(cx)
  64                .items()
  65                .find_map(|item| item.downcast::<KeymapEditor>());
  66
  67            if let Some(existing) = existing {
  68                workspace.activate_item(&existing, true, true, window, cx);
  69            } else {
  70                let keymap_editor =
  71                    cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
  72                workspace.add_item_to_active_pane(Box::new(keymap_editor), None, true, window, cx);
  73            }
  74        });
  75    });
  76
  77    cx.observe_new(|_workspace: &mut Workspace, window, cx| {
  78        let Some(window) = window else { return };
  79
  80        let keymap_ui_actions = [std::any::TypeId::of::<OpenKeymapEditor>()];
  81
  82        command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
  83            filter.hide_action_types(&keymap_ui_actions);
  84            filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
  85        });
  86
  87        cx.observe_flag::<SettingsUiFeatureFlag, _>(
  88            window,
  89            move |is_enabled, _workspace, _, cx| {
  90                if is_enabled {
  91                    command_palette_hooks::CommandPaletteFilter::update_global(
  92                        cx,
  93                        |filter, _cx| {
  94                            filter.show_action_types(keymap_ui_actions.iter());
  95                            filter.show_namespace(KEYMAP_EDITOR_NAMESPACE);
  96                        },
  97                    );
  98                } else {
  99                    command_palette_hooks::CommandPaletteFilter::update_global(
 100                        cx,
 101                        |filter, _cx| {
 102                            filter.hide_action_types(&keymap_ui_actions);
 103                            filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
 104                        },
 105                    );
 106                }
 107            },
 108        )
 109        .detach();
 110    })
 111    .detach();
 112
 113    register_serializable_item::<KeymapEditor>(cx);
 114}
 115
 116pub struct KeymapEventChannel {}
 117
 118impl Global for KeymapEventChannel {}
 119
 120impl KeymapEventChannel {
 121    fn new() -> Self {
 122        Self {}
 123    }
 124
 125    pub fn trigger_keymap_changed(cx: &mut App) {
 126        let Some(_event_channel) = cx.try_global::<Self>() else {
 127            // don't panic if no global defined. This usually happens in tests
 128            return;
 129        };
 130        cx.update_global(|_event_channel: &mut Self, _| {
 131            /* triggers observers in KeymapEditors */
 132        });
 133    }
 134}
 135
 136struct KeymapEditor {
 137    workspace: WeakEntity<Workspace>,
 138    focus_handle: FocusHandle,
 139    _keymap_subscription: Subscription,
 140    keybindings: Vec<ProcessedKeybinding>,
 141    // corresponds 1 to 1 with keybindings
 142    string_match_candidates: Arc<Vec<StringMatchCandidate>>,
 143    matches: Vec<StringMatch>,
 144    table_interaction_state: Entity<TableInteractionState>,
 145    filter_editor: Entity<Editor>,
 146    selected_index: Option<usize>,
 147}
 148
 149impl EventEmitter<()> for KeymapEditor {}
 150
 151impl Focusable for KeymapEditor {
 152    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 153        return self.filter_editor.focus_handle(cx);
 154    }
 155}
 156
 157impl KeymapEditor {
 158    fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
 159        let focus_handle = cx.focus_handle();
 160
 161        let _keymap_subscription =
 162            cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
 163        let table_interaction_state = TableInteractionState::new(window, cx);
 164
 165        let filter_editor = cx.new(|cx| {
 166            let mut editor = Editor::single_line(window, cx);
 167            editor.set_placeholder_text("Filter action names…", cx);
 168            editor
 169        });
 170
 171        cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
 172            if !matches!(e, EditorEvent::BufferEdited) {
 173                return;
 174            }
 175
 176            this.update_matches(cx);
 177        })
 178        .detach();
 179
 180        let mut this = Self {
 181            workspace,
 182            keybindings: vec![],
 183            string_match_candidates: Arc::new(vec![]),
 184            matches: vec![],
 185            focus_handle: focus_handle.clone(),
 186            _keymap_subscription,
 187            table_interaction_state,
 188            filter_editor,
 189            selected_index: None,
 190        };
 191
 192        this.update_keybindings(cx);
 193
 194        this
 195    }
 196
 197    fn current_query(&self, cx: &mut Context<Self>) -> String {
 198        self.filter_editor.read(cx).text(cx)
 199    }
 200
 201    fn update_matches(&self, cx: &mut Context<Self>) {
 202        let query = self.current_query(cx);
 203
 204        cx.spawn(async move |this, cx| Self::process_query(this, query, cx).await)
 205            .detach();
 206    }
 207
 208    async fn process_query(
 209        this: WeakEntity<Self>,
 210        query: String,
 211        cx: &mut AsyncApp,
 212    ) -> anyhow::Result<()> {
 213        let query = command_palette::normalize_action_query(&query);
 214        let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
 215            (this.string_match_candidates.clone(), this.keybindings.len())
 216        })?;
 217        let executor = cx.background_executor().clone();
 218        let mut matches = fuzzy::match_strings(
 219            &string_match_candidates,
 220            &query,
 221            true,
 222            true,
 223            keybind_count,
 224            &Default::default(),
 225            executor,
 226        )
 227        .await;
 228        this.update(cx, |this, cx| {
 229            if query.is_empty() {
 230                // apply default sort
 231                // sorts by source precedence, and alphabetically by action name within each source
 232                matches.sort_by_key(|match_item| {
 233                    let keybind = &this.keybindings[match_item.candidate_id];
 234                    let source = keybind.source.as_ref().map(|s| s.0);
 235                    use KeybindSource::*;
 236                    let source_precedence = match source {
 237                        Some(User) => 0,
 238                        Some(Vim) => 1,
 239                        Some(Base) => 2,
 240                        Some(Default) => 3,
 241                        None => 4,
 242                    };
 243                    return (source_precedence, keybind.action.as_ref());
 244                });
 245            }
 246            this.selected_index.take();
 247            this.scroll_to_item(0, ScrollStrategy::Top, cx);
 248            this.matches = matches;
 249            cx.notify();
 250        })
 251    }
 252
 253    fn process_bindings(
 254        json_language: Arc<Language>,
 255        rust_language: Arc<Language>,
 256        cx: &mut App,
 257    ) -> (Vec<ProcessedKeybinding>, Vec<StringMatchCandidate>) {
 258        let key_bindings_ptr = cx.key_bindings();
 259        let lock = key_bindings_ptr.borrow();
 260        let key_bindings = lock.bindings();
 261        let mut unmapped_action_names =
 262            HashSet::from_iter(cx.all_action_names().into_iter().copied());
 263        let action_documentation = cx.action_documentation();
 264
 265        let mut processed_bindings = Vec::new();
 266        let mut string_match_candidates = Vec::new();
 267
 268        for key_binding in key_bindings {
 269            let source = key_binding.meta().map(settings::KeybindSource::from_meta);
 270
 271            let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
 272            let ui_key_binding = Some(
 273                ui::KeyBinding::new_from_gpui(key_binding.clone(), cx)
 274                    .vim_mode(source == Some(settings::KeybindSource::Vim)),
 275            );
 276
 277            let context = key_binding
 278                .predicate()
 279                .map(|predicate| {
 280                    KeybindContextString::Local(predicate.to_string().into(), rust_language.clone())
 281                })
 282                .unwrap_or(KeybindContextString::Global);
 283
 284            let source = source.map(|source| (source, source.name().into()));
 285
 286            let action_name = key_binding.action().name();
 287            unmapped_action_names.remove(&action_name);
 288            let action_input = key_binding
 289                .action_input()
 290                .map(|input| SyntaxHighlightedText::new(input, json_language.clone()));
 291            let action_docs = action_documentation.get(action_name).copied();
 292
 293            let index = processed_bindings.len();
 294            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
 295            processed_bindings.push(ProcessedKeybinding {
 296                keystroke_text: keystroke_text.into(),
 297                ui_key_binding,
 298                action: action_name.into(),
 299                action_input,
 300                action_docs,
 301                context: Some(context),
 302                source,
 303            });
 304            string_match_candidates.push(string_match_candidate);
 305        }
 306
 307        let empty = SharedString::new_static("");
 308        for action_name in unmapped_action_names.into_iter() {
 309            let index = processed_bindings.len();
 310            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
 311            processed_bindings.push(ProcessedKeybinding {
 312                keystroke_text: empty.clone(),
 313                ui_key_binding: None,
 314                action: action_name.into(),
 315                action_input: None,
 316                action_docs: action_documentation.get(action_name).copied(),
 317                context: None,
 318                source: None,
 319            });
 320            string_match_candidates.push(string_match_candidate);
 321        }
 322
 323        (processed_bindings, string_match_candidates)
 324    }
 325
 326    fn update_keybindings(&mut self, cx: &mut Context<KeymapEditor>) {
 327        let workspace = self.workspace.clone();
 328        cx.spawn(async move |this, cx| {
 329            let json_language = Self::load_json_language(workspace.clone(), cx).await;
 330            let rust_language = Self::load_rust_language(workspace.clone(), cx).await;
 331
 332            let query = this.update(cx, |this, cx| {
 333                let (key_bindings, string_match_candidates) =
 334                    Self::process_bindings(json_language, rust_language, cx);
 335                this.keybindings = key_bindings;
 336                this.string_match_candidates = Arc::new(string_match_candidates);
 337                this.matches = this
 338                    .string_match_candidates
 339                    .iter()
 340                    .enumerate()
 341                    .map(|(ix, candidate)| StringMatch {
 342                        candidate_id: ix,
 343                        score: 0.0,
 344                        positions: vec![],
 345                        string: candidate.string.clone(),
 346                    })
 347                    .collect();
 348                this.current_query(cx)
 349            })?;
 350            // calls cx.notify
 351            Self::process_query(this, query, cx).await
 352        })
 353        .detach_and_log_err(cx);
 354    }
 355
 356    async fn load_json_language(
 357        workspace: WeakEntity<Workspace>,
 358        cx: &mut AsyncApp,
 359    ) -> Arc<Language> {
 360        let json_language_task = workspace
 361            .read_with(cx, |workspace, cx| {
 362                workspace
 363                    .project()
 364                    .read(cx)
 365                    .languages()
 366                    .language_for_name("JSON")
 367            })
 368            .context("Failed to load JSON language")
 369            .log_err();
 370        let json_language = match json_language_task {
 371            Some(task) => task.await.context("Failed to load JSON language").log_err(),
 372            None => None,
 373        };
 374        return json_language.unwrap_or_else(|| {
 375            Arc::new(Language::new(
 376                LanguageConfig {
 377                    name: "JSON".into(),
 378                    ..Default::default()
 379                },
 380                Some(tree_sitter_json::LANGUAGE.into()),
 381            ))
 382        });
 383    }
 384
 385    async fn load_rust_language(
 386        workspace: WeakEntity<Workspace>,
 387        cx: &mut AsyncApp,
 388    ) -> Arc<Language> {
 389        let rust_language_task = workspace
 390            .read_with(cx, |workspace, cx| {
 391                workspace
 392                    .project()
 393                    .read(cx)
 394                    .languages()
 395                    .language_for_name("Rust")
 396            })
 397            .context("Failed to load Rust language")
 398            .log_err();
 399        let rust_language = match rust_language_task {
 400            Some(task) => task.await.context("Failed to load Rust language").log_err(),
 401            None => None,
 402        };
 403        return rust_language.unwrap_or_else(|| {
 404            Arc::new(Language::new(
 405                LanguageConfig {
 406                    name: "Rust".into(),
 407                    ..Default::default()
 408                },
 409                Some(tree_sitter_rust::LANGUAGE.into()),
 410            ))
 411        });
 412    }
 413
 414    fn dispatch_context(&self, _window: &Window, _cx: &Context<Self>) -> KeyContext {
 415        let mut dispatch_context = KeyContext::new_with_defaults();
 416        dispatch_context.add("KeymapEditor");
 417        dispatch_context.add("menu");
 418
 419        dispatch_context
 420    }
 421
 422    fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
 423        let index = usize::min(index, self.matches.len().saturating_sub(1));
 424        self.table_interaction_state.update(cx, |this, _cx| {
 425            this.scroll_handle.scroll_to_item(index, strategy);
 426        });
 427    }
 428
 429    fn focus_search(
 430        &mut self,
 431        _: &search::FocusSearch,
 432        window: &mut Window,
 433        cx: &mut Context<Self>,
 434    ) {
 435        if !self
 436            .filter_editor
 437            .focus_handle(cx)
 438            .contains_focused(window, cx)
 439        {
 440            window.focus(&self.filter_editor.focus_handle(cx));
 441        } else {
 442            self.filter_editor.update(cx, |editor, cx| {
 443                editor.select_all(&Default::default(), window, cx);
 444            });
 445        }
 446        self.selected_index.take();
 447    }
 448
 449    fn selected_binding(&self) -> Option<&ProcessedKeybinding> {
 450        self.selected_index
 451            .and_then(|match_index| self.matches.get(match_index))
 452            .map(|r#match| r#match.candidate_id)
 453            .and_then(|keybind_index| self.keybindings.get(keybind_index))
 454    }
 455
 456    fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
 457        if let Some(selected) = self.selected_index {
 458            let selected = selected + 1;
 459            if selected >= self.matches.len() {
 460                self.select_last(&Default::default(), window, cx);
 461            } else {
 462                self.selected_index = Some(selected);
 463                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
 464                cx.notify();
 465            }
 466        } else {
 467            self.select_first(&Default::default(), window, cx);
 468        }
 469    }
 470
 471    fn select_previous(
 472        &mut self,
 473        _: &menu::SelectPrevious,
 474        window: &mut Window,
 475        cx: &mut Context<Self>,
 476    ) {
 477        if let Some(selected) = self.selected_index {
 478            if selected == 0 {
 479                return;
 480            }
 481
 482            let selected = selected - 1;
 483
 484            if selected >= self.matches.len() {
 485                self.select_last(&Default::default(), window, cx);
 486            } else {
 487                self.selected_index = Some(selected);
 488                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
 489                cx.notify();
 490            }
 491        } else {
 492            self.select_last(&Default::default(), window, cx);
 493        }
 494    }
 495
 496    fn select_first(
 497        &mut self,
 498        _: &menu::SelectFirst,
 499        _window: &mut Window,
 500        cx: &mut Context<Self>,
 501    ) {
 502        if self.matches.get(0).is_some() {
 503            self.selected_index = Some(0);
 504            self.scroll_to_item(0, ScrollStrategy::Center, cx);
 505            cx.notify();
 506        }
 507    }
 508
 509    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
 510        if self.matches.last().is_some() {
 511            let index = self.matches.len() - 1;
 512            self.selected_index = Some(index);
 513            self.scroll_to_item(index, ScrollStrategy::Center, cx);
 514            cx.notify();
 515        }
 516    }
 517
 518    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
 519        self.edit_selected_keybinding(window, cx);
 520    }
 521
 522    fn edit_selected_keybinding(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 523        let Some(keybind) = self.selected_binding() else {
 524            return;
 525        };
 526        self.workspace
 527            .update(cx, |workspace, cx| {
 528                let fs = workspace.app_state().fs.clone();
 529                workspace.toggle_modal(window, cx, |window, cx| {
 530                    let modal = KeybindingEditorModal::new(keybind.clone(), fs, window, cx);
 531                    window.focus(&modal.focus_handle(cx));
 532                    modal
 533                });
 534            })
 535            .log_err();
 536    }
 537
 538    fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
 539        self.edit_selected_keybinding(window, cx);
 540    }
 541
 542    fn copy_context_to_clipboard(
 543        &mut self,
 544        _: &CopyContext,
 545        _window: &mut Window,
 546        cx: &mut Context<Self>,
 547    ) {
 548        let context = self
 549            .selected_binding()
 550            .and_then(|binding| binding.context.as_ref())
 551            .and_then(KeybindContextString::local_str)
 552            .map(|context| context.to_string());
 553        let Some(context) = context else {
 554            return;
 555        };
 556        cx.write_to_clipboard(gpui::ClipboardItem::new_string(context.clone()));
 557    }
 558
 559    fn copy_action_to_clipboard(
 560        &mut self,
 561        _: &CopyAction,
 562        _window: &mut Window,
 563        cx: &mut Context<Self>,
 564    ) {
 565        let action = self
 566            .selected_binding()
 567            .map(|binding| binding.action.to_string());
 568        let Some(action) = action else {
 569            return;
 570        };
 571        cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
 572    }
 573}
 574
 575#[derive(Clone)]
 576struct ProcessedKeybinding {
 577    keystroke_text: SharedString,
 578    ui_key_binding: Option<ui::KeyBinding>,
 579    action: SharedString,
 580    action_input: Option<SyntaxHighlightedText>,
 581    action_docs: Option<&'static str>,
 582    context: Option<KeybindContextString>,
 583    source: Option<(KeybindSource, SharedString)>,
 584}
 585
 586#[derive(Clone, Debug, IntoElement)]
 587enum KeybindContextString {
 588    Global,
 589    Local(SharedString, Arc<Language>),
 590}
 591
 592impl KeybindContextString {
 593    const GLOBAL: SharedString = SharedString::new_static("<global>");
 594
 595    pub fn local(&self) -> Option<&SharedString> {
 596        match self {
 597            KeybindContextString::Global => None,
 598            KeybindContextString::Local(name, _) => Some(name),
 599        }
 600    }
 601
 602    pub fn local_str(&self) -> Option<&str> {
 603        match self {
 604            KeybindContextString::Global => None,
 605            KeybindContextString::Local(name, _) => Some(name),
 606        }
 607    }
 608}
 609
 610impl RenderOnce for KeybindContextString {
 611    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
 612        match self {
 613            KeybindContextString::Global => StyledText::new(KeybindContextString::GLOBAL.clone())
 614                .with_highlights([(
 615                    0..KeybindContextString::GLOBAL.len(),
 616                    gpui::HighlightStyle::color(_cx.theme().colors().text_muted),
 617                )])
 618                .into_any_element(),
 619            KeybindContextString::Local(name, language) => {
 620                SyntaxHighlightedText::new(name, language).into_any_element()
 621            }
 622        }
 623    }
 624}
 625
 626impl Item for KeymapEditor {
 627    type Event = ();
 628
 629    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
 630        "Keymap Editor".into()
 631    }
 632}
 633
 634impl Render for KeymapEditor {
 635    fn render(&mut self, window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
 636        let row_count = self.matches.len();
 637        let theme = cx.theme();
 638
 639        v_flex()
 640            .id("keymap-editor")
 641            .track_focus(&self.focus_handle)
 642            .key_context(self.dispatch_context(window, cx))
 643            .on_action(cx.listener(Self::select_next))
 644            .on_action(cx.listener(Self::select_previous))
 645            .on_action(cx.listener(Self::select_first))
 646            .on_action(cx.listener(Self::select_last))
 647            .on_action(cx.listener(Self::focus_search))
 648            .on_action(cx.listener(Self::confirm))
 649            .on_action(cx.listener(Self::edit_binding))
 650            .on_action(cx.listener(Self::copy_action_to_clipboard))
 651            .on_action(cx.listener(Self::copy_context_to_clipboard))
 652            .size_full()
 653            .p_2()
 654            .gap_1()
 655            .bg(theme.colors().editor_background)
 656            .child(
 657                h_flex()
 658                    .key_context({
 659                        let mut context = KeyContext::new_with_defaults();
 660                        context.add("BufferSearchBar");
 661                        context
 662                    })
 663                    .h_8()
 664                    .pl_2()
 665                    .pr_1()
 666                    .py_1()
 667                    .border_1()
 668                    .border_color(theme.colors().border)
 669                    .rounded_lg()
 670                    .child(self.filter_editor.clone()),
 671            )
 672            .child(
 673                Table::new()
 674                    .interactable(&self.table_interaction_state)
 675                    .striped()
 676                    .column_widths([rems(16.), rems(16.), rems(16.), rems(32.), rems(8.)])
 677                    .header(["Action", "Arguments", "Keystrokes", "Context", "Source"])
 678                    .uniform_list(
 679                        "keymap-editor-table",
 680                        row_count,
 681                        cx.processor(move |this, range: Range<usize>, _window, _cx| {
 682                            range
 683                                .filter_map(|index| {
 684                                    let candidate_id = this.matches.get(index)?.candidate_id;
 685                                    let binding = &this.keybindings[candidate_id];
 686
 687                                    let action = div()
 688                                        .child(binding.action.clone())
 689                                        .id(("keymap action", index))
 690                                        .tooltip({
 691                                            let action_name = binding.action.clone();
 692                                            let action_docs = binding.action_docs;
 693                                            move |_, cx| {
 694                                                let action_tooltip = Tooltip::new(
 695                                                    command_palette::humanize_action_name(
 696                                                        &action_name,
 697                                                    ),
 698                                                );
 699                                                let action_tooltip = match action_docs {
 700                                                    Some(docs) => action_tooltip.meta(docs),
 701                                                    None => action_tooltip,
 702                                                };
 703                                                cx.new(|_| action_tooltip).into()
 704                                            }
 705                                        })
 706                                        .into_any_element();
 707                                    let keystrokes = binding.ui_key_binding.clone().map_or(
 708                                        binding.keystroke_text.clone().into_any_element(),
 709                                        IntoElement::into_any_element,
 710                                    );
 711                                    let action_input = binding
 712                                        .action_input
 713                                        .clone()
 714                                        .map_or(gpui::Empty.into_any_element(), |input| {
 715                                            input.into_any_element()
 716                                        });
 717                                    let context = binding
 718                                        .context
 719                                        .clone()
 720                                        .map_or(gpui::Empty.into_any_element(), |context| {
 721                                            context.into_any_element()
 722                                        });
 723                                    let source = binding
 724                                        .source
 725                                        .clone()
 726                                        .map(|(_source, name)| name)
 727                                        .unwrap_or_default()
 728                                        .into_any_element();
 729                                    Some([action, action_input, keystrokes, context, source])
 730                                })
 731                                .collect()
 732                        }),
 733                    )
 734                    .map_row(
 735                        cx.processor(|this, (row_index, row): (usize, Div), _window, cx| {
 736                            let is_selected = this.selected_index == Some(row_index);
 737                            let row = row
 738                                .id(("keymap-table-row", row_index))
 739                                .on_click(cx.listener(move |this, _event, _window, _cx| {
 740                                    this.selected_index = Some(row_index);
 741                                }))
 742                                .border_2()
 743                                .border_color(transparent_black())
 744                                .when(is_selected, |row| {
 745                                    row.border_color(cx.theme().colors().panel_focused_border)
 746                                });
 747
 748                            right_click_menu(("keymap-table-row-menu", row_index))
 749                                .trigger({
 750                                    let this = cx.weak_entity();
 751                                    move |is_menu_open: bool, _window, cx| {
 752                                        if is_menu_open {
 753                                            this.update(cx, |this, cx| {
 754                                                if this.selected_index != Some(row_index) {
 755                                                    this.selected_index = Some(row_index);
 756                                                    cx.notify();
 757                                                }
 758                                            })
 759                                            .ok();
 760                                        }
 761                                        row
 762                                    }
 763                                })
 764                                .menu({
 765                                    let this = cx.weak_entity();
 766                                    move |window, cx| build_keybind_context_menu(&this, window, cx)
 767                                })
 768                                .into_any_element()
 769                        }),
 770                    ),
 771            )
 772    }
 773}
 774
 775#[derive(Debug, Clone, IntoElement)]
 776struct SyntaxHighlightedText {
 777    text: SharedString,
 778    language: Arc<Language>,
 779}
 780
 781impl SyntaxHighlightedText {
 782    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
 783        Self {
 784            text: text.into(),
 785            language,
 786        }
 787    }
 788}
 789
 790impl RenderOnce for SyntaxHighlightedText {
 791    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
 792        let text_style = window.text_style();
 793        let syntax_theme = cx.theme().syntax();
 794
 795        let text = self.text.clone();
 796
 797        let highlights = self
 798            .language
 799            .highlight_text(&text.as_ref().into(), 0..text.len());
 800        let mut runs = Vec::with_capacity(highlights.len());
 801        let mut offset = 0;
 802
 803        for (highlight_range, highlight_id) in highlights {
 804            // Add un-highlighted text before the current highlight
 805            if highlight_range.start > offset {
 806                runs.push(text_style.to_run(highlight_range.start - offset));
 807            }
 808
 809            let mut run_style = text_style.clone();
 810            if let Some(highlight_style) = highlight_id.style(syntax_theme) {
 811                run_style = run_style.highlight(highlight_style);
 812            }
 813            // add the highlighted range
 814            runs.push(run_style.to_run(highlight_range.len()));
 815            offset = highlight_range.end;
 816        }
 817
 818        // Add any remaining un-highlighted text
 819        if offset < text.len() {
 820            runs.push(text_style.to_run(text.len() - offset));
 821        }
 822
 823        return StyledText::new(text).with_runs(runs);
 824    }
 825}
 826
 827struct KeybindingEditorModal {
 828    editing_keybind: ProcessedKeybinding,
 829    keybind_editor: Entity<KeystrokeInput>,
 830    context_editor: Entity<Editor>,
 831    fs: Arc<dyn Fs>,
 832    error: Option<String>,
 833}
 834
 835impl ModalView for KeybindingEditorModal {}
 836
 837impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
 838
 839impl Focusable for KeybindingEditorModal {
 840    fn focus_handle(&self, cx: &App) -> FocusHandle {
 841        self.keybind_editor.focus_handle(cx)
 842    }
 843}
 844
 845impl KeybindingEditorModal {
 846    pub fn new(
 847        editing_keybind: ProcessedKeybinding,
 848        fs: Arc<dyn Fs>,
 849        window: &mut Window,
 850        cx: &mut App,
 851    ) -> Self {
 852        let keybind_editor = cx.new(KeystrokeInput::new);
 853
 854        let context_editor = cx.new(|cx| {
 855            let mut editor = Editor::single_line(window, cx);
 856
 857            if let Some(context) = editing_keybind
 858                .context
 859                .as_ref()
 860                .and_then(KeybindContextString::local)
 861            {
 862                editor.set_text(context.clone(), window, cx);
 863            } else {
 864                editor.set_placeholder_text("Keybinding context", cx);
 865            }
 866
 867            cx.spawn(async |editor, cx| {
 868                let contexts = cx
 869                    .background_spawn(async { collect_contexts_from_assets() })
 870                    .await;
 871
 872                editor
 873                    .update(cx, |editor, _cx| {
 874                        editor.set_completion_provider(Some(std::rc::Rc::new(
 875                            KeyContextCompletionProvider { contexts },
 876                        )));
 877                    })
 878                    .context("Failed to load completions for keybinding context")
 879            })
 880            .detach_and_log_err(cx);
 881
 882            editor
 883        });
 884        Self {
 885            editing_keybind,
 886            fs,
 887            keybind_editor,
 888            context_editor,
 889            error: None,
 890        }
 891    }
 892
 893    fn save(&mut self, cx: &mut Context<Self>) {
 894        let existing_keybind = self.editing_keybind.clone();
 895        let fs = self.fs.clone();
 896        let new_keystrokes = self
 897            .keybind_editor
 898            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
 899        if new_keystrokes.is_empty() {
 900            self.error = Some("Keystrokes cannot be empty".to_string());
 901            cx.notify();
 902            return;
 903        }
 904        let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
 905        let new_context = self
 906            .context_editor
 907            .read_with(cx, |editor, cx| editor.text(cx));
 908        let new_context = new_context.is_empty().not().then_some(new_context);
 909        let new_context_err = new_context.as_deref().and_then(|context| {
 910            gpui::KeyBindingContextPredicate::parse(context)
 911                .context("Failed to parse key context")
 912                .err()
 913        });
 914        if let Some(err) = new_context_err {
 915            // TODO: store and display as separate error
 916            // TODO: also, should be validating on keystroke
 917            self.error = Some(err.to_string());
 918            cx.notify();
 919            return;
 920        }
 921
 922        cx.spawn(async move |this, cx| {
 923            if let Err(err) = save_keybinding_update(
 924                existing_keybind,
 925                &new_keystrokes,
 926                new_context.as_deref(),
 927                &fs,
 928                tab_size,
 929            )
 930            .await
 931            {
 932                this.update(cx, |this, cx| {
 933                    this.error = Some(err.to_string());
 934                    cx.notify();
 935                })
 936                .log_err();
 937            } else {
 938                this.update(cx, |_this, cx| {
 939                    cx.emit(DismissEvent);
 940                })
 941                .ok();
 942            }
 943        })
 944        .detach();
 945    }
 946}
 947
 948impl Render for KeybindingEditorModal {
 949    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 950        let theme = cx.theme().colors();
 951
 952        return v_flex()
 953            .w(rems(34.))
 954            .elevation_3(cx)
 955            .child(
 956                v_flex()
 957                    .p_3()
 958                    .gap_2()
 959                    .child(
 960                        v_flex().child(Label::new("Edit Keystroke")).child(
 961                            Label::new("Input the desired keystroke for the selected action.")
 962                                .color(Color::Muted),
 963                        ),
 964                    )
 965                    .child(self.keybind_editor.clone()),
 966            )
 967            .child(
 968                v_flex()
 969                    .p_3()
 970                    .gap_3()
 971                    .child(
 972                        v_flex().child(Label::new("Edit Keystroke")).child(
 973                            Label::new("Input the desired keystroke for the selected action.")
 974                                .color(Color::Muted),
 975                        ),
 976                    )
 977                    .child(
 978                        div()
 979                            .w_full()
 980                            .border_color(cx.theme().colors().border_variant)
 981                            .border_1()
 982                            .py_2()
 983                            .px_3()
 984                            .min_h_8()
 985                            .rounded_md()
 986                            .bg(theme.editor_background)
 987                            .child(self.context_editor.clone()),
 988                    ),
 989            )
 990            .child(
 991                h_flex()
 992                    .p_2()
 993                    .w_full()
 994                    .gap_1()
 995                    .justify_end()
 996                    .border_t_1()
 997                    .border_color(cx.theme().colors().border_variant)
 998                    .child(
 999                        Button::new("cancel", "Cancel")
1000                            .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
1001                    )
1002                    .child(
1003                        Button::new("save-btn", "Save").on_click(
1004                            cx.listener(|this, _event, _window, cx| Self::save(this, cx)),
1005                        ),
1006                    ),
1007            )
1008            .when_some(self.error.clone(), |this, error| {
1009                this.child(
1010                    div()
1011                        .bg(theme.background)
1012                        .border_color(theme.border)
1013                        .border_2()
1014                        .rounded_md()
1015                        .child(error),
1016                )
1017            });
1018    }
1019}
1020
1021struct KeyContextCompletionProvider {
1022    contexts: Vec<SharedString>,
1023}
1024
1025impl CompletionProvider for KeyContextCompletionProvider {
1026    fn completions(
1027        &self,
1028        _excerpt_id: editor::ExcerptId,
1029        buffer: &Entity<language::Buffer>,
1030        buffer_position: language::Anchor,
1031        _trigger: editor::CompletionContext,
1032        _window: &mut Window,
1033        cx: &mut Context<Editor>,
1034    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
1035        let buffer = buffer.read(cx);
1036        let mut count_back = 0;
1037        for char in buffer.reversed_chars_at(buffer_position) {
1038            if char.is_ascii_alphanumeric() || char == '_' {
1039                count_back += 1;
1040            } else {
1041                break;
1042            }
1043        }
1044        let start_anchor = buffer.anchor_before(
1045            buffer_position
1046                .to_offset(&buffer)
1047                .saturating_sub(count_back),
1048        );
1049        let replace_range = start_anchor..buffer_position;
1050        gpui::Task::ready(Ok(vec![project::CompletionResponse {
1051            completions: self
1052                .contexts
1053                .iter()
1054                .map(|context| project::Completion {
1055                    replace_range: replace_range.clone(),
1056                    label: language::CodeLabel::plain(context.to_string(), None),
1057                    new_text: context.to_string(),
1058                    documentation: None,
1059                    source: project::CompletionSource::Custom,
1060                    icon_path: None,
1061                    insert_text_mode: None,
1062                    confirm: None,
1063                })
1064                .collect(),
1065            is_incomplete: false,
1066        }]))
1067    }
1068
1069    fn is_completion_trigger(
1070        &self,
1071        _buffer: &Entity<language::Buffer>,
1072        _position: language::Anchor,
1073        text: &str,
1074        _trigger_in_words: bool,
1075        _menu_is_open: bool,
1076        _cx: &mut Context<Editor>,
1077    ) -> bool {
1078        text.chars().last().map_or(false, |last_char| {
1079            last_char.is_ascii_alphanumeric() || last_char == '_'
1080        })
1081    }
1082}
1083
1084async fn save_keybinding_update(
1085    existing: ProcessedKeybinding,
1086    new_keystrokes: &[Keystroke],
1087    new_context: Option<&str>,
1088    fs: &Arc<dyn Fs>,
1089    tab_size: usize,
1090) -> anyhow::Result<()> {
1091    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
1092        .await
1093        .context("Failed to load keymap file")?;
1094
1095    let existing_keystrokes = existing
1096        .ui_key_binding
1097        .as_ref()
1098        .map(|keybinding| keybinding.keystrokes.as_slice())
1099        .unwrap_or_default();
1100
1101    let existing_context = existing
1102        .context
1103        .as_ref()
1104        .and_then(KeybindContextString::local_str);
1105
1106    let input = existing
1107        .action_input
1108        .as_ref()
1109        .map(|input| input.text.as_ref());
1110
1111    let operation = if existing.ui_key_binding.is_some() {
1112        settings::KeybindUpdateOperation::Replace {
1113            target: settings::KeybindUpdateTarget {
1114                context: existing_context,
1115                keystrokes: existing_keystrokes,
1116                action_name: &existing.action,
1117                use_key_equivalents: false,
1118                input,
1119            },
1120            target_keybind_source: existing
1121                .source
1122                .map(|(source, _name)| source)
1123                .unwrap_or(KeybindSource::User),
1124            source: settings::KeybindUpdateTarget {
1125                context: new_context,
1126                keystrokes: new_keystrokes,
1127                action_name: &existing.action,
1128                use_key_equivalents: false,
1129                input,
1130            },
1131        }
1132    } else {
1133        anyhow::bail!("Adding new bindings not implemented yet");
1134    };
1135    let updated_keymap_contents =
1136        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
1137            .context("Failed to update keybinding")?;
1138    fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
1139        .await
1140        .context("Failed to write keymap file")?;
1141    Ok(())
1142}
1143
1144struct KeystrokeInput {
1145    keystrokes: Vec<Keystroke>,
1146    focus_handle: FocusHandle,
1147}
1148
1149impl KeystrokeInput {
1150    fn new(cx: &mut Context<Self>) -> Self {
1151        let focus_handle = cx.focus_handle();
1152        Self {
1153            keystrokes: Vec::new(),
1154            focus_handle,
1155        }
1156    }
1157
1158    fn on_modifiers_changed(
1159        &mut self,
1160        event: &ModifiersChangedEvent,
1161        _window: &mut Window,
1162        cx: &mut Context<Self>,
1163    ) {
1164        if let Some(last) = self.keystrokes.last_mut()
1165            && last.key.is_empty()
1166        {
1167            if !event.modifiers.modified() {
1168                self.keystrokes.pop();
1169            } else {
1170                last.modifiers = event.modifiers;
1171            }
1172        } else {
1173            self.keystrokes.push(Keystroke {
1174                modifiers: event.modifiers,
1175                key: "".to_string(),
1176                key_char: None,
1177            });
1178        }
1179        cx.stop_propagation();
1180        cx.notify();
1181    }
1182
1183    fn on_key_down(
1184        &mut self,
1185        event: &gpui::KeyDownEvent,
1186        _window: &mut Window,
1187        cx: &mut Context<Self>,
1188    ) {
1189        if event.is_held {
1190            return;
1191        }
1192        if let Some(last) = self.keystrokes.last_mut()
1193            && last.key.is_empty()
1194        {
1195            *last = event.keystroke.clone();
1196        } else {
1197            self.keystrokes.push(event.keystroke.clone());
1198        }
1199        cx.stop_propagation();
1200        cx.notify();
1201    }
1202
1203    fn on_key_up(
1204        &mut self,
1205        event: &gpui::KeyUpEvent,
1206        _window: &mut Window,
1207        cx: &mut Context<Self>,
1208    ) {
1209        if let Some(last) = self.keystrokes.last_mut()
1210            && !last.key.is_empty()
1211            && last.modifiers == event.keystroke.modifiers
1212        {
1213            self.keystrokes.push(Keystroke {
1214                modifiers: event.keystroke.modifiers,
1215                key: "".to_string(),
1216                key_char: None,
1217            });
1218        }
1219        cx.stop_propagation();
1220        cx.notify();
1221    }
1222
1223    fn keystrokes(&self) -> &[Keystroke] {
1224        if self
1225            .keystrokes
1226            .last()
1227            .map_or(false, |last| last.key.is_empty())
1228        {
1229            return &self.keystrokes[..self.keystrokes.len() - 1];
1230        }
1231        return &self.keystrokes;
1232    }
1233}
1234
1235impl Focusable for KeystrokeInput {
1236    fn focus_handle(&self, _cx: &App) -> FocusHandle {
1237        self.focus_handle.clone()
1238    }
1239}
1240
1241impl Render for KeystrokeInput {
1242    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1243        let colors = cx.theme().colors();
1244
1245        return h_flex()
1246            .id("keybinding_input")
1247            .track_focus(&self.focus_handle)
1248            .on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
1249            .on_key_down(cx.listener(Self::on_key_down))
1250            .on_key_up(cx.listener(Self::on_key_up))
1251            .focus(|mut style| {
1252                style.border_color = Some(colors.border_focused);
1253                style
1254            })
1255            .py_2()
1256            .px_3()
1257            .gap_2()
1258            .min_h_8()
1259            .w_full()
1260            .justify_between()
1261            .bg(colors.editor_background)
1262            .border_1()
1263            .rounded_md()
1264            .flex_1()
1265            .overflow_hidden()
1266            .child(
1267                h_flex()
1268                    .w_full()
1269                    .min_w_0()
1270                    .justify_center()
1271                    .flex_wrap()
1272                    .gap(ui::DynamicSpacing::Base04.rems(cx))
1273                    .children(self.keystrokes.iter().map(|keystroke| {
1274                        h_flex().children(ui::render_keystroke(
1275                            keystroke,
1276                            None,
1277                            Some(rems(0.875).into()),
1278                            ui::PlatformStyle::platform(),
1279                            false,
1280                        ))
1281                    })),
1282            )
1283            .child(
1284                h_flex()
1285                    .gap_0p5()
1286                    .flex_none()
1287                    .child(
1288                        IconButton::new("backspace-btn", IconName::Delete)
1289                            .tooltip(Tooltip::text("Delete Keystroke"))
1290                            .on_click(cx.listener(|this, _event, _window, cx| {
1291                                this.keystrokes.pop();
1292                                cx.notify();
1293                            })),
1294                    )
1295                    .child(
1296                        IconButton::new("clear-btn", IconName::Eraser)
1297                            .tooltip(Tooltip::text("Clear Keystrokes"))
1298                            .on_click(cx.listener(|this, _event, _window, cx| {
1299                                this.keystrokes.clear();
1300                                cx.notify();
1301                            })),
1302                    ),
1303            );
1304    }
1305}
1306
1307fn build_keybind_context_menu(
1308    this: &WeakEntity<KeymapEditor>,
1309    window: &mut Window,
1310    cx: &mut App,
1311) -> Entity<ContextMenu> {
1312    ContextMenu::build(window, cx, |menu, _window, cx| {
1313        let Some(this) = this.upgrade() else {
1314            return menu;
1315        };
1316        let selected_binding = this.read_with(cx, |this, _cx| this.selected_binding().cloned());
1317        let Some(selected_binding) = selected_binding else {
1318            return menu;
1319        };
1320
1321        let selected_binding_has_context = selected_binding
1322            .context
1323            .as_ref()
1324            .and_then(KeybindContextString::local)
1325            .is_some();
1326
1327        menu.action("Edit Binding", Box::new(EditBinding))
1328            .action("Copy action", Box::new(CopyAction))
1329            .action_disabled_when(
1330                !selected_binding_has_context,
1331                "Copy Context",
1332                Box::new(CopyContext),
1333            )
1334    })
1335}
1336
1337fn collect_contexts_from_assets() -> Vec<SharedString> {
1338    let mut keymap_assets = vec![
1339        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
1340        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
1341    ];
1342    keymap_assets.extend(
1343        BaseKeymap::OPTIONS
1344            .iter()
1345            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
1346            .map(util::asset_str::<SettingsAssets>),
1347    );
1348
1349    let mut contexts = HashSet::default();
1350
1351    for keymap_asset in keymap_assets {
1352        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
1353            continue;
1354        };
1355
1356        for section in keymap.sections() {
1357            let context_expr = &section.context;
1358            let mut queue = Vec::new();
1359            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
1360                continue;
1361            };
1362
1363            queue.push(root_context);
1364            while let Some(context) = queue.pop() {
1365                match context {
1366                    gpui::KeyBindingContextPredicate::Identifier(ident) => {
1367                        contexts.insert(ident);
1368                    }
1369                    gpui::KeyBindingContextPredicate::Equal(ident_a, ident_b) => {
1370                        contexts.insert(ident_a);
1371                        contexts.insert(ident_b);
1372                    }
1373                    gpui::KeyBindingContextPredicate::NotEqual(ident_a, ident_b) => {
1374                        contexts.insert(ident_a);
1375                        contexts.insert(ident_b);
1376                    }
1377                    gpui::KeyBindingContextPredicate::Child(ctx_a, ctx_b) => {
1378                        queue.push(*ctx_a);
1379                        queue.push(*ctx_b);
1380                    }
1381                    gpui::KeyBindingContextPredicate::Not(ctx) => {
1382                        queue.push(*ctx);
1383                    }
1384                    gpui::KeyBindingContextPredicate::And(ctx_a, ctx_b) => {
1385                        queue.push(*ctx_a);
1386                        queue.push(*ctx_b);
1387                    }
1388                    gpui::KeyBindingContextPredicate::Or(ctx_a, ctx_b) => {
1389                        queue.push(*ctx_a);
1390                        queue.push(*ctx_b);
1391                    }
1392                }
1393            }
1394        }
1395    }
1396
1397    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
1398    contexts.sort();
1399
1400    return contexts;
1401}
1402
1403impl SerializableItem for KeymapEditor {
1404    fn serialized_item_kind() -> &'static str {
1405        "KeymapEditor"
1406    }
1407
1408    fn cleanup(
1409        workspace_id: workspace::WorkspaceId,
1410        alive_items: Vec<workspace::ItemId>,
1411        _window: &mut Window,
1412        cx: &mut App,
1413    ) -> gpui::Task<gpui::Result<()>> {
1414        workspace::delete_unloaded_items(
1415            alive_items,
1416            workspace_id,
1417            "keybinding_editors",
1418            &KEYBINDING_EDITORS,
1419            cx,
1420        )
1421    }
1422
1423    fn deserialize(
1424        _project: Entity<project::Project>,
1425        workspace: WeakEntity<Workspace>,
1426        workspace_id: workspace::WorkspaceId,
1427        item_id: workspace::ItemId,
1428        window: &mut Window,
1429        cx: &mut App,
1430    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
1431        window.spawn(cx, async move |cx| {
1432            if KEYBINDING_EDITORS
1433                .get_keybinding_editor(item_id, workspace_id)?
1434                .is_some()
1435            {
1436                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
1437            } else {
1438                Err(anyhow!("No keybinding editor to deserialize"))
1439            }
1440        })
1441    }
1442
1443    fn serialize(
1444        &mut self,
1445        workspace: &mut Workspace,
1446        item_id: workspace::ItemId,
1447        _closing: bool,
1448        _window: &mut Window,
1449        cx: &mut ui::Context<Self>,
1450    ) -> Option<gpui::Task<gpui::Result<()>>> {
1451        let workspace_id = workspace.database_id()?;
1452        Some(cx.background_spawn(async move {
1453            KEYBINDING_EDITORS
1454                .save_keybinding_editor(item_id, workspace_id)
1455                .await
1456        }))
1457    }
1458
1459    fn should_serialize(&self, _event: &Self::Event) -> bool {
1460        false
1461    }
1462}
1463
1464mod persistence {
1465    use db::{define_connection, query, sqlez_macros::sql};
1466    use workspace::WorkspaceDb;
1467
1468    define_connection! {
1469        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
1470            &[sql!(
1471                CREATE TABLE keybinding_editors (
1472                    workspace_id INTEGER,
1473                    item_id INTEGER UNIQUE,
1474
1475                    PRIMARY KEY(workspace_id, item_id),
1476                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
1477                    ON DELETE CASCADE
1478                ) STRICT;
1479            )];
1480    }
1481
1482    impl KeybindingEditorDb {
1483        query! {
1484            pub async fn save_keybinding_editor(
1485                item_id: workspace::ItemId,
1486                workspace_id: workspace::WorkspaceId
1487            ) -> Result<()> {
1488                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
1489                VALUES (?, ?)
1490            }
1491        }
1492
1493        query! {
1494            pub fn get_keybinding_editor(
1495                item_id: workspace::ItemId,
1496                workspace_id: workspace::WorkspaceId
1497            ) -> Result<Option<workspace::ItemId>> {
1498                SELECT item_id
1499                FROM keybinding_editors
1500                WHERE item_id = ? AND workspace_id = ?
1501            }
1502        }
1503    }
1504}