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