keybindings.rs

   1use std::{ops::Range, sync::Arc};
   2
   3use anyhow::{Context as _, anyhow};
   4use collections::HashSet;
   5use editor::{Editor, EditorEvent};
   6use feature_flags::FeatureFlagViewExt;
   7use fs::Fs;
   8use fuzzy::{StringMatch, StringMatchCandidate};
   9use gpui::{
  10    AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
  11    FontWeight, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText,
  12    Subscription, WeakEntity, actions, div,
  13};
  14use language::{Language, LanguageConfig};
  15use settings::KeybindSource;
  16
  17use util::ResultExt;
  18
  19use ui::{
  20    ActiveTheme as _, App, BorrowAppContext, ParentElement as _, Render, SharedString, Styled as _,
  21    Window, prelude::*,
  22};
  23use workspace::{Item, ModalView, SerializableItem, Workspace, register_serializable_item};
  24
  25use crate::{
  26    SettingsUiFeatureFlag,
  27    keybindings::persistence::KEYBINDING_EDITORS,
  28    ui_components::table::{Table, TableInteractionState},
  29};
  30
  31actions!(zed, [OpenKeymapEditor]);
  32
  33pub fn init(cx: &mut App) {
  34    let keymap_event_channel = KeymapEventChannel::new();
  35    cx.set_global(keymap_event_channel);
  36
  37    cx.on_action(|_: &OpenKeymapEditor, cx| {
  38        workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
  39            let existing = workspace
  40                .active_pane()
  41                .read(cx)
  42                .items()
  43                .find_map(|item| item.downcast::<KeymapEditor>());
  44
  45            if let Some(existing) = existing {
  46                workspace.activate_item(&existing, true, true, window, cx);
  47            } else {
  48                let keymap_editor =
  49                    cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
  50                workspace.add_item_to_active_pane(Box::new(keymap_editor), None, true, window, cx);
  51            }
  52        });
  53    });
  54
  55    cx.observe_new(|_workspace: &mut Workspace, window, cx| {
  56        let Some(window) = window else { return };
  57
  58        let keymap_ui_actions = [std::any::TypeId::of::<OpenKeymapEditor>()];
  59
  60        command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
  61            filter.hide_action_types(&keymap_ui_actions);
  62        });
  63
  64        cx.observe_flag::<SettingsUiFeatureFlag, _>(
  65            window,
  66            move |is_enabled, _workspace, _, cx| {
  67                if is_enabled {
  68                    command_palette_hooks::CommandPaletteFilter::update_global(
  69                        cx,
  70                        |filter, _cx| {
  71                            filter.show_action_types(keymap_ui_actions.iter());
  72                        },
  73                    );
  74                } else {
  75                    command_palette_hooks::CommandPaletteFilter::update_global(
  76                        cx,
  77                        |filter, _cx| {
  78                            filter.hide_action_types(&keymap_ui_actions);
  79                        },
  80                    );
  81                }
  82            },
  83        )
  84        .detach();
  85    })
  86    .detach();
  87
  88    register_serializable_item::<KeymapEditor>(cx);
  89}
  90
  91pub struct KeymapEventChannel {}
  92
  93impl Global for KeymapEventChannel {}
  94
  95impl KeymapEventChannel {
  96    fn new() -> Self {
  97        Self {}
  98    }
  99
 100    pub fn trigger_keymap_changed(cx: &mut App) {
 101        let Some(_event_channel) = cx.try_global::<Self>() else {
 102            // don't panic if no global defined. This usually happens in tests
 103            return;
 104        };
 105        cx.update_global(|_event_channel: &mut Self, _| {
 106            /* triggers observers in KeymapEditors */
 107        });
 108    }
 109}
 110
 111struct KeymapEditor {
 112    workspace: WeakEntity<Workspace>,
 113    focus_handle: FocusHandle,
 114    _keymap_subscription: Subscription,
 115    keybindings: Vec<ProcessedKeybinding>,
 116    // corresponds 1 to 1 with keybindings
 117    string_match_candidates: Arc<Vec<StringMatchCandidate>>,
 118    matches: Vec<StringMatch>,
 119    table_interaction_state: Entity<TableInteractionState>,
 120    filter_editor: Entity<Editor>,
 121    selected_index: Option<usize>,
 122}
 123
 124impl EventEmitter<()> for KeymapEditor {}
 125
 126impl Focusable for KeymapEditor {
 127    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 128        return self.filter_editor.focus_handle(cx);
 129    }
 130}
 131
 132impl KeymapEditor {
 133    fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
 134        let focus_handle = cx.focus_handle();
 135
 136        let _keymap_subscription =
 137            cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
 138        let table_interaction_state = TableInteractionState::new(window, cx);
 139
 140        let filter_editor = cx.new(|cx| {
 141            let mut editor = Editor::single_line(window, cx);
 142            editor.set_placeholder_text("Filter action names...", cx);
 143            editor
 144        });
 145
 146        cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
 147            if !matches!(e, EditorEvent::BufferEdited) {
 148                return;
 149            }
 150
 151            this.update_matches(cx);
 152        })
 153        .detach();
 154
 155        let mut this = Self {
 156            workspace,
 157            keybindings: vec![],
 158            string_match_candidates: Arc::new(vec![]),
 159            matches: vec![],
 160            focus_handle: focus_handle.clone(),
 161            _keymap_subscription,
 162            table_interaction_state,
 163            filter_editor,
 164            selected_index: None,
 165        };
 166
 167        this.update_keybindings(cx);
 168
 169        this
 170    }
 171
 172    fn current_query(&self, cx: &mut Context<Self>) -> String {
 173        self.filter_editor.read(cx).text(cx)
 174    }
 175
 176    fn update_matches(&self, cx: &mut Context<Self>) {
 177        let query = self.current_query(cx);
 178
 179        cx.spawn(async move |this, cx| Self::process_query(this, query, cx).await)
 180            .detach();
 181    }
 182
 183    async fn process_query(
 184        this: WeakEntity<Self>,
 185        query: String,
 186        cx: &mut AsyncApp,
 187    ) -> Result<(), db::anyhow::Error> {
 188        let query = command_palette::normalize_action_query(&query);
 189        let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
 190            (this.string_match_candidates.clone(), this.keybindings.len())
 191        })?;
 192        let executor = cx.background_executor().clone();
 193        let matches = fuzzy::match_strings(
 194            &string_match_candidates,
 195            &query,
 196            true,
 197            true,
 198            keybind_count,
 199            &Default::default(),
 200            executor,
 201        )
 202        .await;
 203        this.update(cx, |this, cx| {
 204            this.selected_index.take();
 205            this.scroll_to_item(0, ScrollStrategy::Top, cx);
 206            this.matches = matches;
 207            cx.notify();
 208        })
 209    }
 210
 211    fn process_bindings(
 212        json_language: Arc<Language>,
 213        cx: &mut Context<Self>,
 214    ) -> (Vec<ProcessedKeybinding>, Vec<StringMatchCandidate>) {
 215        let key_bindings_ptr = cx.key_bindings();
 216        let lock = key_bindings_ptr.borrow();
 217        let key_bindings = lock.bindings();
 218        let mut unmapped_action_names = HashSet::from_iter(cx.all_action_names());
 219
 220        let mut processed_bindings = Vec::new();
 221        let mut string_match_candidates = Vec::new();
 222
 223        for key_binding in key_bindings {
 224            let source = key_binding.meta().map(settings::KeybindSource::from_meta);
 225
 226            let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
 227            let ui_key_binding = Some(
 228                ui::KeyBinding::new(key_binding.clone(), cx)
 229                    .vim_mode(source == Some(settings::KeybindSource::Vim)),
 230            );
 231
 232            let context = key_binding
 233                .predicate()
 234                .map(|predicate| predicate.to_string())
 235                .unwrap_or_else(|| "<global>".to_string());
 236
 237            let source = source.map(|source| (source, source.name().into()));
 238
 239            let action_name = key_binding.action().name();
 240            unmapped_action_names.remove(&action_name);
 241            let action_input = key_binding
 242                .action_input()
 243                .map(|input| TextWithSyntaxHighlighting::new(input, json_language.clone()));
 244
 245            let index = processed_bindings.len();
 246            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
 247            processed_bindings.push(ProcessedKeybinding {
 248                keystroke_text: keystroke_text.into(),
 249                ui_key_binding,
 250                action: action_name.into(),
 251                action_input,
 252                context: context.into(),
 253                source,
 254            });
 255            string_match_candidates.push(string_match_candidate);
 256        }
 257
 258        let empty = SharedString::new_static("");
 259        for action_name in unmapped_action_names.into_iter() {
 260            let index = processed_bindings.len();
 261            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
 262            processed_bindings.push(ProcessedKeybinding {
 263                keystroke_text: empty.clone(),
 264                ui_key_binding: None,
 265                action: (*action_name).into(),
 266                action_input: None,
 267                context: empty.clone(),
 268                source: None,
 269            });
 270            string_match_candidates.push(string_match_candidate);
 271        }
 272
 273        (processed_bindings, string_match_candidates)
 274    }
 275
 276    fn update_keybindings(&mut self, cx: &mut Context<KeymapEditor>) {
 277        let workspace = self.workspace.clone();
 278        cx.spawn(async move |this, cx| {
 279            let json_language = Self::load_json_language(workspace, cx).await;
 280            let query = this.update(cx, |this, cx| {
 281                let (key_bindings, string_match_candidates) =
 282                    Self::process_bindings(json_language.clone(), cx);
 283                this.keybindings = key_bindings;
 284                this.string_match_candidates = Arc::new(string_match_candidates);
 285                this.matches = this
 286                    .string_match_candidates
 287                    .iter()
 288                    .enumerate()
 289                    .map(|(ix, candidate)| StringMatch {
 290                        candidate_id: ix,
 291                        score: 0.0,
 292                        positions: vec![],
 293                        string: candidate.string.clone(),
 294                    })
 295                    .collect();
 296                this.current_query(cx)
 297            })?;
 298            // calls cx.notify
 299            Self::process_query(this, query, cx).await
 300        })
 301        .detach_and_log_err(cx);
 302    }
 303
 304    async fn load_json_language(
 305        workspace: WeakEntity<Workspace>,
 306        cx: &mut AsyncApp,
 307    ) -> Arc<Language> {
 308        let json_language_task = workspace
 309            .read_with(cx, |workspace, cx| {
 310                workspace
 311                    .project()
 312                    .read(cx)
 313                    .languages()
 314                    .language_for_name("JSON")
 315            })
 316            .context("Failed to load JSON language")
 317            .log_err();
 318        let json_language = match json_language_task {
 319            Some(task) => task.await.context("Failed to load JSON language").log_err(),
 320            None => None,
 321        };
 322        return json_language.unwrap_or_else(|| {
 323            Arc::new(Language::new(
 324                LanguageConfig {
 325                    name: "JSON".into(),
 326                    ..Default::default()
 327                },
 328                Some(tree_sitter_json::LANGUAGE.into()),
 329            ))
 330        });
 331    }
 332
 333    fn dispatch_context(&self, _window: &Window, _cx: &Context<Self>) -> KeyContext {
 334        let mut dispatch_context = KeyContext::new_with_defaults();
 335        dispatch_context.add("KeymapEditor");
 336        dispatch_context.add("menu");
 337
 338        dispatch_context
 339    }
 340
 341    fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
 342        let index = usize::min(index, self.matches.len().saturating_sub(1));
 343        self.table_interaction_state.update(cx, |this, _cx| {
 344            this.scroll_handle.scroll_to_item(index, strategy);
 345        });
 346    }
 347
 348    fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
 349        if let Some(selected) = self.selected_index {
 350            let selected = selected + 1;
 351            if selected >= self.matches.len() {
 352                self.select_last(&Default::default(), window, cx);
 353            } else {
 354                self.selected_index = Some(selected);
 355                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
 356                cx.notify();
 357            }
 358        } else {
 359            self.select_first(&Default::default(), window, cx);
 360        }
 361    }
 362
 363    fn select_previous(
 364        &mut self,
 365        _: &menu::SelectPrevious,
 366        window: &mut Window,
 367        cx: &mut Context<Self>,
 368    ) {
 369        if let Some(selected) = self.selected_index {
 370            if selected == 0 {
 371                return;
 372            }
 373
 374            let selected = selected - 1;
 375
 376            if selected >= self.matches.len() {
 377                self.select_last(&Default::default(), window, cx);
 378            } else {
 379                self.selected_index = Some(selected);
 380                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
 381                cx.notify();
 382            }
 383        } else {
 384            self.select_last(&Default::default(), window, cx);
 385        }
 386    }
 387
 388    fn select_first(
 389        &mut self,
 390        _: &menu::SelectFirst,
 391        _window: &mut Window,
 392        cx: &mut Context<Self>,
 393    ) {
 394        if self.matches.get(0).is_some() {
 395            self.selected_index = Some(0);
 396            self.scroll_to_item(0, ScrollStrategy::Center, cx);
 397            cx.notify();
 398        }
 399    }
 400
 401    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
 402        if self.matches.last().is_some() {
 403            let index = self.matches.len() - 1;
 404            self.selected_index = Some(index);
 405            self.scroll_to_item(index, ScrollStrategy::Center, cx);
 406            cx.notify();
 407        }
 408    }
 409
 410    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
 411        let Some(index) = self.selected_index else {
 412            return;
 413        };
 414        let keybind = self.keybindings[self.matches[index].candidate_id].clone();
 415
 416        self.edit_keybinding(keybind, window, cx);
 417    }
 418
 419    fn edit_keybinding(
 420        &mut self,
 421        keybind: ProcessedKeybinding,
 422        window: &mut Window,
 423        cx: &mut Context<Self>,
 424    ) {
 425        self.workspace
 426            .update(cx, |workspace, cx| {
 427                let fs = workspace.app_state().fs.clone();
 428                workspace.toggle_modal(window, cx, |window, cx| {
 429                    let modal = KeybindingEditorModal::new(keybind, fs, window, cx);
 430                    window.focus(&modal.focus_handle(cx));
 431                    modal
 432                });
 433            })
 434            .log_err();
 435    }
 436
 437    fn focus_search(
 438        &mut self,
 439        _: &search::FocusSearch,
 440        window: &mut Window,
 441        cx: &mut Context<Self>,
 442    ) {
 443        if !self
 444            .filter_editor
 445            .focus_handle(cx)
 446            .contains_focused(window, cx)
 447        {
 448            window.focus(&self.filter_editor.focus_handle(cx));
 449        } else {
 450            self.filter_editor.update(cx, |editor, cx| {
 451                editor.select_all(&Default::default(), window, cx);
 452            });
 453        }
 454        self.selected_index.take();
 455    }
 456}
 457
 458#[derive(Clone)]
 459struct ProcessedKeybinding {
 460    keystroke_text: SharedString,
 461    ui_key_binding: Option<ui::KeyBinding>,
 462    action: SharedString,
 463    action_input: Option<TextWithSyntaxHighlighting>,
 464    context: SharedString,
 465    source: Option<(KeybindSource, SharedString)>,
 466}
 467
 468impl Item for KeymapEditor {
 469    type Event = ();
 470
 471    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
 472        "Keymap Editor".into()
 473    }
 474}
 475
 476impl Render for KeymapEditor {
 477    fn render(&mut self, window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
 478        let row_count = self.matches.len();
 479        let theme = cx.theme();
 480
 481        div()
 482            .key_context(self.dispatch_context(window, cx))
 483            .on_action(cx.listener(Self::select_next))
 484            .on_action(cx.listener(Self::select_previous))
 485            .on_action(cx.listener(Self::select_first))
 486            .on_action(cx.listener(Self::select_last))
 487            .on_action(cx.listener(Self::focus_search))
 488            .on_action(cx.listener(Self::confirm))
 489            .size_full()
 490            .bg(theme.colors().editor_background)
 491            .id("keymap-editor")
 492            .track_focus(&self.focus_handle)
 493            .px_4()
 494            .v_flex()
 495            .pb_4()
 496            .child(
 497                h_flex()
 498                    .key_context({
 499                        let mut context = KeyContext::new_with_defaults();
 500                        context.add("BufferSearchBar");
 501                        context
 502                    })
 503                    .w_full()
 504                    .h_12()
 505                    .px_4()
 506                    .my_4()
 507                    .border_2()
 508                    .border_color(theme.colors().border)
 509                    .child(self.filter_editor.clone()),
 510            )
 511            .child(
 512                Table::new()
 513                    .interactable(&self.table_interaction_state)
 514                    .striped()
 515                    .column_widths([rems(16.), rems(16.), rems(16.), rems(32.), rems(8.)])
 516                    .header(["Action", "Arguments", "Keystrokes", "Context", "Source"])
 517                    .selected_item_index(self.selected_index)
 518                    .on_click_row(cx.processor(|this, row_index, _window, _cx| {
 519                        this.selected_index = Some(row_index);
 520                    }))
 521                    .uniform_list(
 522                        "keymap-editor-table",
 523                        row_count,
 524                        cx.processor(move |this, range: Range<usize>, _window, _cx| {
 525                            range
 526                                .filter_map(|index| {
 527                                    let candidate_id = this.matches.get(index)?.candidate_id;
 528                                    let binding = &this.keybindings[candidate_id];
 529
 530                                    let action = binding.action.clone().into_any_element();
 531                                    let keystrokes = binding.ui_key_binding.clone().map_or(
 532                                        binding.keystroke_text.clone().into_any_element(),
 533                                        IntoElement::into_any_element,
 534                                    );
 535                                    let action_input = binding
 536                                        .action_input
 537                                        .clone()
 538                                        .map_or(gpui::Empty.into_any_element(), |input| {
 539                                            input.into_any_element()
 540                                        });
 541                                    let context = binding.context.clone().into_any_element();
 542                                    let source = binding
 543                                        .source
 544                                        .clone()
 545                                        .map(|(_source, name)| name)
 546                                        .unwrap_or_default()
 547                                        .into_any_element();
 548                                    Some([action, action_input, keystrokes, context, source])
 549                                })
 550                                .collect()
 551                        }),
 552                    ),
 553            )
 554    }
 555}
 556
 557#[derive(Debug, Clone, IntoElement)]
 558struct TextWithSyntaxHighlighting {
 559    text: SharedString,
 560    language: Arc<Language>,
 561}
 562
 563impl TextWithSyntaxHighlighting {
 564    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
 565        Self {
 566            text: text.into(),
 567            language,
 568        }
 569    }
 570}
 571
 572impl RenderOnce for TextWithSyntaxHighlighting {
 573    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
 574        let text_style = window.text_style();
 575        let syntax_theme = cx.theme().syntax();
 576
 577        let text = self.text.clone();
 578
 579        let highlights = self
 580            .language
 581            .highlight_text(&text.as_ref().into(), 0..text.len());
 582        let mut runs = Vec::with_capacity(highlights.len());
 583        let mut offset = 0;
 584
 585        for (highlight_range, highlight_id) in highlights {
 586            // Add un-highlighted text before the current highlight
 587            if highlight_range.start > offset {
 588                runs.push(text_style.to_run(highlight_range.start - offset));
 589            }
 590
 591            let mut run_style = text_style.clone();
 592            if let Some(highlight_style) = highlight_id.style(syntax_theme) {
 593                run_style = run_style.highlight(highlight_style);
 594            }
 595            // add the highlighted range
 596            runs.push(run_style.to_run(highlight_range.len()));
 597            offset = highlight_range.end;
 598        }
 599
 600        // Add any remaining un-highlighted text
 601        if offset < text.len() {
 602            runs.push(text_style.to_run(text.len() - offset));
 603        }
 604
 605        return StyledText::new(text).with_runs(runs);
 606    }
 607}
 608
 609struct KeybindingEditorModal {
 610    editing_keybind: ProcessedKeybinding,
 611    keybind_editor: Entity<KeybindInput>,
 612    fs: Arc<dyn Fs>,
 613    error: Option<String>,
 614}
 615
 616impl ModalView for KeybindingEditorModal {}
 617
 618impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
 619
 620impl Focusable for KeybindingEditorModal {
 621    fn focus_handle(&self, cx: &App) -> FocusHandle {
 622        self.keybind_editor.focus_handle(cx)
 623    }
 624}
 625
 626impl KeybindingEditorModal {
 627    pub fn new(
 628        editing_keybind: ProcessedKeybinding,
 629        fs: Arc<dyn Fs>,
 630        _window: &mut Window,
 631        cx: &mut App,
 632    ) -> Self {
 633        let keybind_editor = cx.new(KeybindInput::new);
 634        Self {
 635            editing_keybind,
 636            fs,
 637            keybind_editor,
 638            error: None,
 639        }
 640    }
 641}
 642
 643impl Render for KeybindingEditorModal {
 644    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 645        let theme = cx.theme().colors();
 646        return v_flex()
 647            .gap_4()
 648            .w(rems(36.))
 649            .child(
 650                v_flex()
 651                    .items_center()
 652                    .text_center()
 653                    .bg(theme.background)
 654                    .border_color(theme.border)
 655                    .border_2()
 656                    .px_4()
 657                    .py_2()
 658                    .w_full()
 659                    .child(
 660                        div()
 661                            .text_lg()
 662                            .font_weight(FontWeight::BOLD)
 663                            .child("Input desired keybinding, then hit save"),
 664                    )
 665                    .child(
 666                        h_flex()
 667                            .w_full()
 668                            .child(self.keybind_editor.clone())
 669                            .child(
 670                                IconButton::new("backspace-btn", ui::IconName::Backspace).on_click(
 671                                    cx.listener(|this, _event, _window, cx| {
 672                                        this.keybind_editor.update(cx, |editor, cx| {
 673                                            editor.keystrokes.pop();
 674                                            cx.notify();
 675                                        })
 676                                    }),
 677                                ),
 678                            )
 679                            .child(IconButton::new("clear-btn", ui::IconName::Eraser).on_click(
 680                                cx.listener(|this, _event, _window, cx| {
 681                                    this.keybind_editor.update(cx, |editor, cx| {
 682                                        editor.keystrokes.clear();
 683                                        cx.notify();
 684                                    })
 685                                }),
 686                            )),
 687                    )
 688                    .child(
 689                        h_flex().w_full().items_center().justify_center().child(
 690                            Button::new("save-btn", "Save")
 691                                .label_size(LabelSize::Large)
 692                                .on_click(cx.listener(|this, _event, _window, cx| {
 693                                    let existing_keybind = this.editing_keybind.clone();
 694                                    let fs = this.fs.clone();
 695                                    let new_keystrokes = this
 696                                        .keybind_editor
 697                                        .read_with(cx, |editor, _| editor.keystrokes.clone());
 698                                    if new_keystrokes.is_empty() {
 699                                        this.error = Some("Keystrokes cannot be empty".to_string());
 700                                        cx.notify();
 701                                        return;
 702                                    }
 703                                    let tab_size =
 704                                        cx.global::<settings::SettingsStore>().json_tab_size();
 705                                    cx.spawn(async move |this, cx| {
 706                                        if let Err(err) = save_keybinding_update(
 707                                            existing_keybind,
 708                                            &new_keystrokes,
 709                                            &fs,
 710                                            tab_size,
 711                                        )
 712                                        .await
 713                                        {
 714                                            this.update(cx, |this, cx| {
 715                                                this.error = Some(err);
 716                                                cx.notify();
 717                                            })
 718                                            .log_err();
 719                                        }
 720                                    })
 721                                    .detach();
 722                                })),
 723                        ),
 724                    ),
 725            )
 726            .when_some(self.error.clone(), |this, error| {
 727                this.child(
 728                    div()
 729                        .bg(theme.background)
 730                        .border_color(theme.border)
 731                        .border_2()
 732                        .rounded_md()
 733                        .child(error),
 734                )
 735            });
 736    }
 737}
 738
 739async fn save_keybinding_update(
 740    existing: ProcessedKeybinding,
 741    new_keystrokes: &[Keystroke],
 742    fs: &Arc<dyn Fs>,
 743    tab_size: usize,
 744) -> Result<(), String> {
 745    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
 746        .await
 747        .map_err(|err| format!("Failed to load keymap file: {}", err))?;
 748    let existing_keystrokes = existing
 749        .ui_key_binding
 750        .as_ref()
 751        .map(|keybinding| keybinding.key_binding.keystrokes())
 752        .unwrap_or_default();
 753    let operation = if existing.ui_key_binding.is_some() {
 754        settings::KeybindUpdateOperation::Replace {
 755            target: settings::KeybindUpdateTarget {
 756                context: Some(existing.context.as_ref()).filter(|context| !context.is_empty()),
 757                keystrokes: existing_keystrokes,
 758                action_name: &existing.action,
 759                use_key_equivalents: false,
 760                input: existing
 761                    .action_input
 762                    .as_ref()
 763                    .map(|input| input.text.as_ref()),
 764            },
 765            target_source: existing
 766                .source
 767                .map(|(source, _name)| source)
 768                .unwrap_or(KeybindSource::User),
 769            source: settings::KeybindUpdateTarget {
 770                context: Some(existing.context.as_ref()).filter(|context| !context.is_empty()),
 771                keystrokes: new_keystrokes,
 772                action_name: &existing.action,
 773                use_key_equivalents: false,
 774                input: existing
 775                    .action_input
 776                    .as_ref()
 777                    .map(|input| input.text.as_ref()),
 778            },
 779        }
 780    } else {
 781        return Err(
 782            "Not Implemented: Creating new bindings from unbound actions is not supported yet."
 783                .to_string(),
 784        );
 785    };
 786    let updated_keymap_contents =
 787        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
 788            .map_err(|err| format!("Failed to update keybinding: {}", err))?;
 789    fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
 790        .await
 791        .map_err(|err| format!("Failed to write keymap file: {}", err))?;
 792    Ok(())
 793}
 794
 795struct KeybindInput {
 796    keystrokes: Vec<Keystroke>,
 797    focus_handle: FocusHandle,
 798}
 799
 800impl KeybindInput {
 801    fn new(cx: &mut Context<Self>) -> Self {
 802        let focus_handle = cx.focus_handle();
 803        Self {
 804            keystrokes: Vec::new(),
 805            focus_handle,
 806        }
 807    }
 808
 809    fn on_modifiers_changed(
 810        &mut self,
 811        event: &ModifiersChangedEvent,
 812        _window: &mut Window,
 813        cx: &mut Context<Self>,
 814    ) {
 815        if let Some(last) = self.keystrokes.last_mut()
 816            && last.key.is_empty()
 817        {
 818            if !event.modifiers.modified() {
 819                self.keystrokes.pop();
 820            } else {
 821                last.modifiers = event.modifiers;
 822            }
 823        } else {
 824            self.keystrokes.push(Keystroke {
 825                modifiers: event.modifiers,
 826                key: "".to_string(),
 827                key_char: None,
 828            });
 829        }
 830        cx.stop_propagation();
 831        cx.notify();
 832    }
 833
 834    fn on_key_down(
 835        &mut self,
 836        event: &gpui::KeyDownEvent,
 837        _window: &mut Window,
 838        cx: &mut Context<Self>,
 839    ) {
 840        if event.is_held {
 841            return;
 842        }
 843        if let Some(last) = self.keystrokes.last_mut()
 844            && last.key.is_empty()
 845        {
 846            *last = event.keystroke.clone();
 847        } else {
 848            self.keystrokes.push(event.keystroke.clone());
 849        }
 850        cx.stop_propagation();
 851        cx.notify();
 852    }
 853
 854    fn on_key_up(
 855        &mut self,
 856        event: &gpui::KeyUpEvent,
 857        _window: &mut Window,
 858        cx: &mut Context<Self>,
 859    ) {
 860        if let Some(last) = self.keystrokes.last_mut()
 861            && !last.key.is_empty()
 862            && last.modifiers == event.keystroke.modifiers
 863        {
 864            self.keystrokes.push(Keystroke {
 865                modifiers: event.keystroke.modifiers,
 866                key: "".to_string(),
 867                key_char: None,
 868            });
 869        }
 870        cx.stop_propagation();
 871        cx.notify();
 872    }
 873}
 874
 875impl Focusable for KeybindInput {
 876    fn focus_handle(&self, _cx: &App) -> FocusHandle {
 877        self.focus_handle.clone()
 878    }
 879}
 880
 881impl Render for KeybindInput {
 882    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 883        let colors = cx.theme().colors();
 884        return div()
 885            .track_focus(&self.focus_handle)
 886            .on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
 887            .on_key_down(cx.listener(Self::on_key_down))
 888            .on_key_up(cx.listener(Self::on_key_up))
 889            .focus(|mut style| {
 890                style.border_color = Some(colors.border_focused);
 891                style
 892            })
 893            .h_12()
 894            .w_full()
 895            .bg(colors.editor_background)
 896            .border_2()
 897            .border_color(colors.border)
 898            .p_4()
 899            .flex_row()
 900            .text_center()
 901            .justify_center()
 902            .child(ui::text_for_keystrokes(&self.keystrokes, cx));
 903    }
 904}
 905
 906impl SerializableItem for KeymapEditor {
 907    fn serialized_item_kind() -> &'static str {
 908        "KeymapEditor"
 909    }
 910
 911    fn cleanup(
 912        workspace_id: workspace::WorkspaceId,
 913        alive_items: Vec<workspace::ItemId>,
 914        _window: &mut Window,
 915        cx: &mut App,
 916    ) -> gpui::Task<gpui::Result<()>> {
 917        workspace::delete_unloaded_items(
 918            alive_items,
 919            workspace_id,
 920            "keybinding_editors",
 921            &KEYBINDING_EDITORS,
 922            cx,
 923        )
 924    }
 925
 926    fn deserialize(
 927        _project: Entity<project::Project>,
 928        workspace: WeakEntity<Workspace>,
 929        workspace_id: workspace::WorkspaceId,
 930        item_id: workspace::ItemId,
 931        window: &mut Window,
 932        cx: &mut App,
 933    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
 934        window.spawn(cx, async move |cx| {
 935            if KEYBINDING_EDITORS
 936                .get_keybinding_editor(item_id, workspace_id)?
 937                .is_some()
 938            {
 939                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
 940            } else {
 941                Err(anyhow!("No keybinding editor to deserialize"))
 942            }
 943        })
 944    }
 945
 946    fn serialize(
 947        &mut self,
 948        workspace: &mut Workspace,
 949        item_id: workspace::ItemId,
 950        _closing: bool,
 951        _window: &mut Window,
 952        cx: &mut ui::Context<Self>,
 953    ) -> Option<gpui::Task<gpui::Result<()>>> {
 954        let workspace_id = workspace.database_id()?;
 955        Some(cx.background_spawn(async move {
 956            KEYBINDING_EDITORS
 957                .save_keybinding_editor(item_id, workspace_id)
 958                .await
 959        }))
 960    }
 961
 962    fn should_serialize(&self, _event: &Self::Event) -> bool {
 963        false
 964    }
 965}
 966
 967mod persistence {
 968    use db::{define_connection, query, sqlez_macros::sql};
 969    use workspace::WorkspaceDb;
 970
 971    define_connection! {
 972        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
 973            &[sql!(
 974                CREATE TABLE keybinding_editors (
 975                    workspace_id INTEGER,
 976                    item_id INTEGER UNIQUE,
 977
 978                    PRIMARY KEY(workspace_id, item_id),
 979                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
 980                    ON DELETE CASCADE
 981                ) STRICT;
 982            )];
 983    }
 984
 985    impl KeybindingEditorDb {
 986        query! {
 987            pub async fn save_keybinding_editor(
 988                item_id: workspace::ItemId,
 989                workspace_id: workspace::WorkspaceId
 990            ) -> Result<()> {
 991                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
 992                VALUES (?, ?)
 993            }
 994        }
 995
 996        query! {
 997            pub fn get_keybinding_editor(
 998                item_id: workspace::ItemId,
 999                workspace_id: workspace::WorkspaceId
1000            ) -> Result<Option<workspace::ItemId>> {
1001                SELECT item_id
1002                FROM keybinding_editors
1003                WHERE item_id = ? AND workspace_id = ?
1004            }
1005        }
1006    }
1007}