keybindings.rs

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