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