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