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