keybindings.rs

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