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                        h_flex()
 900                            .when(self.keybinding_conflict_state.any_conflicts(), |this| {
 901                                this.child(
 902                                    IconButton::new("KeymapEditorConflictIcon", IconName::Warning)
 903                                        .tooltip({
 904                                            let filter_state = self.filter_state;
 905
 906                                            move |window, cx| {
 907                                                Tooltip::for_action(
 908                                                    match filter_state {
 909                                                        FilterState::All => "Show conflicts",
 910                                                        FilterState::Conflicts => "Hide conflicts",
 911                                                    },
 912                                                    &ToggleConflictFilter,
 913                                                    window,
 914                                                    cx,
 915                                                )
 916                                            }
 917                                        })
 918                                        .selected_icon_color(Color::Error)
 919                                        .toggle_state(matches!(
 920                                            self.filter_state,
 921                                            FilterState::Conflicts
 922                                        ))
 923                                        .on_click(|_, window, cx| {
 924                                            window.dispatch_action(
 925                                                ToggleConflictFilter.boxed_clone(),
 926                                                cx,
 927                                            );
 928                                        }),
 929                                )
 930                            })
 931                            .child(
 932                                IconButton::new("KeymapEditorToggleFiltersIcon", IconName::Filter)
 933                                    .tooltip(|window, cx| {
 934                                        Tooltip::for_action(
 935                                            "Toggle Keystroke Search",
 936                                            &ToggleKeystrokeSearch,
 937                                            window,
 938                                            cx,
 939                                        )
 940                                    })
 941                                    .toggle_state(matches!(self.search_mode, SearchMode::KeyStroke))
 942                                    .on_click(|_, window, cx| {
 943                                        window.dispatch_action(
 944                                            ToggleKeystrokeSearch.boxed_clone(),
 945                                            cx,
 946                                        );
 947                                    }),
 948                            ),
 949                    ),
 950            )
 951            .when(matches!(self.search_mode, SearchMode::KeyStroke), |this| {
 952                this.child(
 953                    div()
 954                        .child(self.keystroke_editor.clone())
 955                        .border_1()
 956                        .border_color(theme.colors().border)
 957                        .rounded_lg(),
 958                )
 959            })
 960            .child(
 961                Table::new()
 962                    .interactable(&self.table_interaction_state)
 963                    .striped()
 964                    .column_widths([rems(16.), rems(16.), rems(16.), rems(32.), rems(8.)])
 965                    .header(["Action", "Arguments", "Keystrokes", "Context", "Source"])
 966                    .uniform_list(
 967                        "keymap-editor-table",
 968                        row_count,
 969                        cx.processor(move |this, range: Range<usize>, _window, cx| {
 970                            range
 971                                .filter_map(|index| {
 972                                    let candidate_id = this.matches.get(index)?.candidate_id;
 973                                    let binding = &this.keybindings[candidate_id];
 974
 975                                    let action = div()
 976                                        .child(binding.action_name.clone())
 977                                        .id(("keymap action", index))
 978                                        .tooltip({
 979                                            let action_name = binding.action_name.clone();
 980                                            let action_docs = binding.action_docs;
 981                                            move |_, cx| {
 982                                                let action_tooltip = Tooltip::new(
 983                                                    command_palette::humanize_action_name(
 984                                                        &action_name,
 985                                                    ),
 986                                                );
 987                                                let action_tooltip = match action_docs {
 988                                                    Some(docs) => action_tooltip.meta(docs),
 989                                                    None => action_tooltip,
 990                                                };
 991                                                cx.new(|_| action_tooltip).into()
 992                                            }
 993                                        })
 994                                        .into_any_element();
 995                                    let keystrokes = binding.ui_key_binding.clone().map_or(
 996                                        binding.keystroke_text.clone().into_any_element(),
 997                                        IntoElement::into_any_element,
 998                                    );
 999                                    let action_input = match binding.action_input.clone() {
1000                                        Some(input) => input.into_any_element(),
1001                                        None => {
1002                                            if binding.action_schema.is_some() {
1003                                                muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
1004                                                    .into_any_element()
1005                                            } else {
1006                                                gpui::Empty.into_any_element()
1007                                            }
1008                                        }
1009                                    };
1010                                    let context = binding
1011                                        .context
1012                                        .clone()
1013                                        .map_or(gpui::Empty.into_any_element(), |context| {
1014                                            context.into_any_element()
1015                                        });
1016                                    let source = binding
1017                                        .source
1018                                        .clone()
1019                                        .map(|(_source, name)| name)
1020                                        .unwrap_or_default()
1021                                        .into_any_element();
1022                                    Some([action, action_input, keystrokes, context, source])
1023                                })
1024                                .collect()
1025                        }),
1026                    )
1027                    .map_row(
1028                        cx.processor(|this, (row_index, row): (usize, Div), _window, cx| {
1029                            let is_conflict = this
1030                                .matches
1031                                .get(row_index)
1032                                .map(|candidate| candidate.candidate_id)
1033                                .is_some_and(|id| this.keybinding_conflict_state.has_conflict(&id));
1034                            let is_selected = this.selected_index == Some(row_index);
1035
1036                            let row = row
1037                                .id(("keymap-table-row", row_index))
1038                                .on_click(cx.listener(
1039                                    move |this, event: &ClickEvent, window, cx| {
1040                                        this.selected_index = Some(row_index);
1041                                        if event.up.click_count == 2 {
1042                                            this.open_edit_keybinding_modal(false, window, cx);
1043                                        }
1044                                    },
1045                                ))
1046                                .border_2()
1047                                .when(is_conflict, |row| {
1048                                    row.bg(cx.theme().status().error_background)
1049                                })
1050                                .when(is_selected, |row| {
1051                                    row.border_color(cx.theme().colors().panel_focused_border)
1052                                });
1053
1054                            right_click_menu(("keymap-table-row-menu", row_index))
1055                                .trigger(move |_, _, _| row)
1056                                .menu({
1057                                    let this = cx.weak_entity();
1058                                    move |window, cx| {
1059                                        build_keybind_context_menu(&this, row_index, window, cx)
1060                                    }
1061                                })
1062                                .into_any_element()
1063                        }),
1064                    ),
1065            )
1066    }
1067}
1068
1069#[derive(Debug, Clone, IntoElement)]
1070struct SyntaxHighlightedText {
1071    text: SharedString,
1072    language: Arc<Language>,
1073}
1074
1075impl SyntaxHighlightedText {
1076    pub fn new(text: impl Into<SharedString>, language: Arc<Language>) -> Self {
1077        Self {
1078            text: text.into(),
1079            language,
1080        }
1081    }
1082}
1083
1084impl RenderOnce for SyntaxHighlightedText {
1085    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
1086        let text_style = window.text_style();
1087        let syntax_theme = cx.theme().syntax();
1088
1089        let text = self.text.clone();
1090
1091        let highlights = self
1092            .language
1093            .highlight_text(&text.as_ref().into(), 0..text.len());
1094        let mut runs = Vec::with_capacity(highlights.len());
1095        let mut offset = 0;
1096
1097        for (highlight_range, highlight_id) in highlights {
1098            // Add un-highlighted text before the current highlight
1099            if highlight_range.start > offset {
1100                runs.push(text_style.to_run(highlight_range.start - offset));
1101            }
1102
1103            let mut run_style = text_style.clone();
1104            if let Some(highlight_style) = highlight_id.style(syntax_theme) {
1105                run_style = run_style.highlight(highlight_style);
1106            }
1107            // add the highlighted range
1108            runs.push(run_style.to_run(highlight_range.len()));
1109            offset = highlight_range.end;
1110        }
1111
1112        // Add any remaining un-highlighted text
1113        if offset < text.len() {
1114            runs.push(text_style.to_run(text.len() - offset));
1115        }
1116
1117        return StyledText::new(text).with_runs(runs);
1118    }
1119}
1120
1121#[derive(PartialEq)]
1122enum InputError {
1123    Warning(SharedString),
1124    Error(SharedString),
1125}
1126
1127impl InputError {
1128    fn warning(message: impl Into<SharedString>) -> Self {
1129        Self::Warning(message.into())
1130    }
1131
1132    fn error(message: impl Into<SharedString>) -> Self {
1133        Self::Error(message.into())
1134    }
1135
1136    fn content(&self) -> &SharedString {
1137        match self {
1138            InputError::Warning(content) | InputError::Error(content) => content,
1139        }
1140    }
1141
1142    fn is_warning(&self) -> bool {
1143        matches!(self, InputError::Warning(_))
1144    }
1145}
1146
1147struct KeybindingEditorModal {
1148    creating: bool,
1149    editing_keybind: ProcessedKeybinding,
1150    editing_keybind_idx: usize,
1151    keybind_editor: Entity<KeystrokeInput>,
1152    context_editor: Entity<Editor>,
1153    input_editor: Option<Entity<Editor>>,
1154    fs: Arc<dyn Fs>,
1155    error: Option<InputError>,
1156    keymap_editor: Entity<KeymapEditor>,
1157}
1158
1159impl ModalView for KeybindingEditorModal {}
1160
1161impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
1162
1163impl Focusable for KeybindingEditorModal {
1164    fn focus_handle(&self, cx: &App) -> FocusHandle {
1165        self.keybind_editor.focus_handle(cx)
1166    }
1167}
1168
1169impl KeybindingEditorModal {
1170    pub fn new(
1171        create: bool,
1172        editing_keybind: ProcessedKeybinding,
1173        editing_keybind_idx: usize,
1174        keymap_editor: Entity<KeymapEditor>,
1175        workspace: WeakEntity<Workspace>,
1176        fs: Arc<dyn Fs>,
1177        window: &mut Window,
1178        cx: &mut App,
1179    ) -> Self {
1180        let keybind_editor = cx.new(|cx| KeystrokeInput::new(window, cx));
1181
1182        let context_editor = cx.new(|cx| {
1183            let mut editor = Editor::single_line(window, cx);
1184
1185            if let Some(context) = editing_keybind
1186                .context
1187                .as_ref()
1188                .and_then(KeybindContextString::local)
1189            {
1190                editor.set_text(context.clone(), window, cx);
1191            } else {
1192                editor.set_placeholder_text("Keybinding context", cx);
1193            }
1194
1195            cx.spawn(async |editor, cx| {
1196                let contexts = cx
1197                    .background_spawn(async { collect_contexts_from_assets() })
1198                    .await;
1199
1200                editor
1201                    .update(cx, |editor, _cx| {
1202                        editor.set_completion_provider(Some(std::rc::Rc::new(
1203                            KeyContextCompletionProvider { contexts },
1204                        )));
1205                    })
1206                    .context("Failed to load completions for keybinding context")
1207            })
1208            .detach_and_log_err(cx);
1209
1210            editor
1211        });
1212
1213        let input_editor = editing_keybind.action_schema.clone().map(|_schema| {
1214            cx.new(|cx| {
1215                let mut editor = Editor::auto_height_unbounded(1, window, cx);
1216                if let Some(input) = editing_keybind.action_input.clone() {
1217                    editor.set_text(input.text, window, cx);
1218                } else {
1219                    // TODO: default value from schema?
1220                    editor.set_placeholder_text("Action input", cx);
1221                }
1222                cx.spawn(async |editor, cx| {
1223                    let json_language = load_json_language(workspace, cx).await;
1224                    editor
1225                        .update(cx, |editor, cx| {
1226                            if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
1227                                buffer.update(cx, |buffer, cx| {
1228                                    buffer.set_language(Some(json_language), cx)
1229                                });
1230                            }
1231                        })
1232                        .context("Failed to load JSON language for editing keybinding action input")
1233                })
1234                .detach_and_log_err(cx);
1235                editor
1236            })
1237        });
1238
1239        Self {
1240            creating: create,
1241            editing_keybind,
1242            editing_keybind_idx,
1243            fs,
1244            keybind_editor,
1245            context_editor,
1246            input_editor,
1247            error: None,
1248            keymap_editor,
1249        }
1250    }
1251
1252    fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
1253        if self
1254            .error
1255            .as_ref()
1256            .is_some_and(|old_error| old_error.is_warning() && *old_error == error)
1257        {
1258            false
1259        } else {
1260            self.error = Some(error);
1261            cx.notify();
1262            true
1263        }
1264    }
1265
1266    fn save(&mut self, cx: &mut Context<Self>) {
1267        let existing_keybind = self.editing_keybind.clone();
1268        let fs = self.fs.clone();
1269        let new_keystrokes = self
1270            .keybind_editor
1271            .read_with(cx, |editor, _| editor.keystrokes().to_vec());
1272        if new_keystrokes.is_empty() {
1273            self.set_error(InputError::error("Keystrokes cannot be empty"), cx);
1274            return;
1275        }
1276        let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
1277        let new_context = self
1278            .context_editor
1279            .read_with(cx, |editor, cx| editor.text(cx));
1280        let new_context = new_context.is_empty().not().then_some(new_context);
1281        let new_context_err = new_context.as_deref().and_then(|context| {
1282            gpui::KeyBindingContextPredicate::parse(context)
1283                .context("Failed to parse key context")
1284                .err()
1285        });
1286        if let Some(err) = new_context_err {
1287            // TODO: store and display as separate error
1288            // TODO: also, should be validating on keystroke
1289            self.set_error(InputError::error(err.to_string()), cx);
1290            return;
1291        }
1292
1293        let action_mapping: ActionMapping = (
1294            ui::text_for_keystrokes(&new_keystrokes, cx).into(),
1295            new_context
1296                .as_ref()
1297                .map(Into::into)
1298                .or_else(|| existing_keybind.get_action_mapping().1),
1299        );
1300
1301        if let Some(conflicting_indices) = self
1302            .keymap_editor
1303            .read(cx)
1304            .keybinding_conflict_state
1305            .conflicting_indices_for_mapping(action_mapping, self.editing_keybind_idx)
1306        {
1307            let first_conflicting_index = conflicting_indices[0];
1308            let conflicting_action_name = self
1309                .keymap_editor
1310                .read(cx)
1311                .keybindings
1312                .get(first_conflicting_index)
1313                .map(|keybind| keybind.action_name.clone());
1314
1315            let warning_message = match conflicting_action_name {
1316                Some(name) => {
1317                    let confliction_action_amount = conflicting_indices.len() - 1;
1318                    if confliction_action_amount > 0 {
1319                        format!(
1320                            "Your keybind would conflict with the \"{}\" action and {} other bindings",
1321                            name, confliction_action_amount
1322                        )
1323                    } else {
1324                        format!("Your keybind would conflict with the \"{}\" action", name)
1325                    }
1326                }
1327                None => {
1328                    log::info!(
1329                        "Could not find action in keybindings with index {}",
1330                        first_conflicting_index
1331                    );
1332                    "Your keybind would conflict with other actions".to_string()
1333                }
1334            };
1335
1336            if self.set_error(InputError::warning(warning_message), cx) {
1337                return;
1338            }
1339        }
1340
1341        let create = self.creating;
1342
1343        cx.spawn(async move |this, cx| {
1344            if let Err(err) = save_keybinding_update(
1345                create,
1346                existing_keybind,
1347                &new_keystrokes,
1348                new_context.as_deref(),
1349                &fs,
1350                tab_size,
1351            )
1352            .await
1353            {
1354                this.update(cx, |this, cx| {
1355                    this.set_error(InputError::error(err.to_string()), cx);
1356                })
1357                .log_err();
1358            } else {
1359                this.update(cx, |_this, cx| {
1360                    cx.emit(DismissEvent);
1361                })
1362                .ok();
1363            }
1364        })
1365        .detach();
1366    }
1367}
1368
1369impl Render for KeybindingEditorModal {
1370    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1371        let theme = cx.theme().colors();
1372        let input_base = || {
1373            div()
1374                .w_full()
1375                .py_2()
1376                .px_3()
1377                .min_h_8()
1378                .rounded_md()
1379                .bg(theme.editor_background)
1380                .border_1()
1381                .border_color(theme.border_variant)
1382        };
1383
1384        v_flex()
1385            .w(rems(34.))
1386            .elevation_3(cx)
1387            .child(
1388                v_flex()
1389                    .p_3()
1390                    .child(Label::new("Edit Keystroke"))
1391                    .child(
1392                        Label::new("Input the desired keystroke for the selected action.")
1393                            .color(Color::Muted)
1394                            .mb_2(),
1395                    )
1396                    .child(self.keybind_editor.clone()),
1397            )
1398            .when_some(self.input_editor.clone(), |this, editor| {
1399                this.child(
1400                    v_flex()
1401                        .p_3()
1402                        .pt_0()
1403                        .child(Label::new("Edit Input"))
1404                        .child(
1405                            Label::new("Input the desired input to the binding.")
1406                                .color(Color::Muted)
1407                                .mb_2(),
1408                        )
1409                        .child(input_base().child(editor)),
1410                )
1411            })
1412            .child(
1413                v_flex()
1414                    .p_3()
1415                    .pt_0()
1416                    .child(Label::new("Edit Context"))
1417                    .child(
1418                        Label::new("Input the desired context for the binding.")
1419                            .color(Color::Muted)
1420                            .mb_2(),
1421                    )
1422                    .child(input_base().child(self.context_editor.clone())),
1423            )
1424            .when_some(self.error.as_ref(), |this, error| {
1425                this.child(
1426                    div().p_2().child(
1427                        Banner::new()
1428                            .map(|banner| match error {
1429                                InputError::Error(_) => banner.severity(ui::Severity::Error),
1430                                InputError::Warning(_) => banner.severity(ui::Severity::Warning),
1431                            })
1432                            // For some reason, the div overflows its container to the
1433                            // right. The padding accounts for that.
1434                            .child(div().size_full().pr_2().child(Label::new(error.content()))),
1435                    ),
1436                )
1437            })
1438            .child(
1439                h_flex()
1440                    .p_2()
1441                    .w_full()
1442                    .gap_1()
1443                    .justify_end()
1444                    .border_t_1()
1445                    .border_color(theme.border_variant)
1446                    .child(
1447                        Button::new("cancel", "Cancel")
1448                            .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
1449                    )
1450                    .child(
1451                        Button::new("save-btn", "Save").on_click(
1452                            cx.listener(|this, _event, _window, cx| Self::save(this, cx)),
1453                        ),
1454                    ),
1455            )
1456    }
1457}
1458
1459struct KeyContextCompletionProvider {
1460    contexts: Vec<SharedString>,
1461}
1462
1463impl CompletionProvider for KeyContextCompletionProvider {
1464    fn completions(
1465        &self,
1466        _excerpt_id: editor::ExcerptId,
1467        buffer: &Entity<language::Buffer>,
1468        buffer_position: language::Anchor,
1469        _trigger: editor::CompletionContext,
1470        _window: &mut Window,
1471        cx: &mut Context<Editor>,
1472    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
1473        let buffer = buffer.read(cx);
1474        let mut count_back = 0;
1475        for char in buffer.reversed_chars_at(buffer_position) {
1476            if char.is_ascii_alphanumeric() || char == '_' {
1477                count_back += 1;
1478            } else {
1479                break;
1480            }
1481        }
1482        let start_anchor = buffer.anchor_before(
1483            buffer_position
1484                .to_offset(&buffer)
1485                .saturating_sub(count_back),
1486        );
1487        let replace_range = start_anchor..buffer_position;
1488        gpui::Task::ready(Ok(vec![project::CompletionResponse {
1489            completions: self
1490                .contexts
1491                .iter()
1492                .map(|context| project::Completion {
1493                    replace_range: replace_range.clone(),
1494                    label: language::CodeLabel::plain(context.to_string(), None),
1495                    new_text: context.to_string(),
1496                    documentation: None,
1497                    source: project::CompletionSource::Custom,
1498                    icon_path: None,
1499                    insert_text_mode: None,
1500                    confirm: None,
1501                })
1502                .collect(),
1503            is_incomplete: false,
1504        }]))
1505    }
1506
1507    fn is_completion_trigger(
1508        &self,
1509        _buffer: &Entity<language::Buffer>,
1510        _position: language::Anchor,
1511        text: &str,
1512        _trigger_in_words: bool,
1513        _menu_is_open: bool,
1514        _cx: &mut Context<Editor>,
1515    ) -> bool {
1516        text.chars().last().map_or(false, |last_char| {
1517            last_char.is_ascii_alphanumeric() || last_char == '_'
1518        })
1519    }
1520}
1521
1522async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
1523    let json_language_task = workspace
1524        .read_with(cx, |workspace, cx| {
1525            workspace
1526                .project()
1527                .read(cx)
1528                .languages()
1529                .language_for_name("JSON")
1530        })
1531        .context("Failed to load JSON language")
1532        .log_err();
1533    let json_language = match json_language_task {
1534        Some(task) => task.await.context("Failed to load JSON language").log_err(),
1535        None => None,
1536    };
1537    return json_language.unwrap_or_else(|| {
1538        Arc::new(Language::new(
1539            LanguageConfig {
1540                name: "JSON".into(),
1541                ..Default::default()
1542            },
1543            Some(tree_sitter_json::LANGUAGE.into()),
1544        ))
1545    });
1546}
1547
1548async fn load_rust_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
1549    let rust_language_task = workspace
1550        .read_with(cx, |workspace, cx| {
1551            workspace
1552                .project()
1553                .read(cx)
1554                .languages()
1555                .language_for_name("Rust")
1556        })
1557        .context("Failed to load Rust language")
1558        .log_err();
1559    let rust_language = match rust_language_task {
1560        Some(task) => task.await.context("Failed to load Rust language").log_err(),
1561        None => None,
1562    };
1563    return rust_language.unwrap_or_else(|| {
1564        Arc::new(Language::new(
1565            LanguageConfig {
1566                name: "Rust".into(),
1567                ..Default::default()
1568            },
1569            Some(tree_sitter_rust::LANGUAGE.into()),
1570        ))
1571    });
1572}
1573
1574async fn save_keybinding_update(
1575    create: bool,
1576    existing: ProcessedKeybinding,
1577    new_keystrokes: &[Keystroke],
1578    new_context: Option<&str>,
1579    fs: &Arc<dyn Fs>,
1580    tab_size: usize,
1581) -> anyhow::Result<()> {
1582    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
1583        .await
1584        .context("Failed to load keymap file")?;
1585
1586    let existing_keystrokes = existing
1587        .ui_key_binding
1588        .as_ref()
1589        .map(|keybinding| keybinding.keystrokes.as_slice())
1590        .unwrap_or_default();
1591
1592    let existing_context = existing
1593        .context
1594        .as_ref()
1595        .and_then(KeybindContextString::local_str);
1596
1597    let input = existing
1598        .action_input
1599        .as_ref()
1600        .map(|input| input.text.as_ref());
1601
1602    let operation = if !create {
1603        settings::KeybindUpdateOperation::Replace {
1604            target: settings::KeybindUpdateTarget {
1605                context: existing_context,
1606                keystrokes: existing_keystrokes,
1607                action_name: &existing.action_name,
1608                use_key_equivalents: false,
1609                input,
1610            },
1611            target_keybind_source: existing
1612                .source
1613                .map(|(source, _name)| source)
1614                .unwrap_or(KeybindSource::User),
1615            source: settings::KeybindUpdateTarget {
1616                context: new_context,
1617                keystrokes: new_keystrokes,
1618                action_name: &existing.action_name,
1619                use_key_equivalents: false,
1620                input,
1621            },
1622        }
1623    } else {
1624        settings::KeybindUpdateOperation::Add(settings::KeybindUpdateTarget {
1625            context: new_context,
1626            keystrokes: new_keystrokes,
1627            action_name: &existing.action_name,
1628            use_key_equivalents: false,
1629            input,
1630        })
1631    };
1632    let updated_keymap_contents =
1633        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
1634            .context("Failed to update keybinding")?;
1635    fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
1636        .await
1637        .context("Failed to write keymap file")?;
1638    Ok(())
1639}
1640
1641async fn remove_keybinding(
1642    existing: ProcessedKeybinding,
1643    fs: &Arc<dyn Fs>,
1644    tab_size: usize,
1645) -> anyhow::Result<()> {
1646    let Some(ui_key_binding) = existing.ui_key_binding else {
1647        anyhow::bail!("Cannot remove a keybinding that does not exist");
1648    };
1649    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
1650        .await
1651        .context("Failed to load keymap file")?;
1652
1653    let operation = settings::KeybindUpdateOperation::Remove {
1654        target: settings::KeybindUpdateTarget {
1655            context: existing
1656                .context
1657                .as_ref()
1658                .and_then(KeybindContextString::local_str),
1659            keystrokes: &ui_key_binding.keystrokes,
1660            action_name: &existing.action_name,
1661            use_key_equivalents: false,
1662            input: existing
1663                .action_input
1664                .as_ref()
1665                .map(|input| input.text.as_ref()),
1666        },
1667        target_keybind_source: existing
1668            .source
1669            .map(|(source, _name)| source)
1670            .unwrap_or(KeybindSource::User),
1671    };
1672
1673    let updated_keymap_contents =
1674        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
1675            .context("Failed to update keybinding")?;
1676    fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
1677        .await
1678        .context("Failed to write keymap file")?;
1679    Ok(())
1680}
1681
1682struct KeystrokeInput {
1683    keystrokes: Vec<Keystroke>,
1684    highlight_on_focus: bool,
1685    focus_handle: FocusHandle,
1686    intercept_subscription: Option<Subscription>,
1687    _focus_subscriptions: [Subscription; 2],
1688}
1689
1690impl KeystrokeInput {
1691    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
1692        let focus_handle = cx.focus_handle();
1693        let _focus_subscriptions = [
1694            cx.on_focus_in(&focus_handle, window, Self::on_focus_in),
1695            cx.on_focus_out(&focus_handle, window, Self::on_focus_out),
1696        ];
1697        Self {
1698            keystrokes: Vec::new(),
1699            highlight_on_focus: true,
1700            focus_handle,
1701            intercept_subscription: None,
1702            _focus_subscriptions,
1703        }
1704    }
1705
1706    fn on_modifiers_changed(
1707        &mut self,
1708        event: &ModifiersChangedEvent,
1709        _window: &mut Window,
1710        cx: &mut Context<Self>,
1711    ) {
1712        if let Some(last) = self.keystrokes.last_mut()
1713            && last.key.is_empty()
1714        {
1715            if !event.modifiers.modified() {
1716                self.keystrokes.pop();
1717                cx.emit(());
1718            } else {
1719                last.modifiers = event.modifiers;
1720            }
1721        } else {
1722            self.keystrokes.push(Keystroke {
1723                modifiers: event.modifiers,
1724                key: "".to_string(),
1725                key_char: None,
1726            });
1727            cx.emit(());
1728        }
1729        cx.stop_propagation();
1730        cx.notify();
1731    }
1732
1733    fn handle_keystroke(&mut self, keystroke: &Keystroke, cx: &mut Context<Self>) {
1734        if let Some(last) = self.keystrokes.last_mut()
1735            && last.key.is_empty()
1736        {
1737            *last = keystroke.clone();
1738        } else if Some(keystroke) != self.keystrokes.last() {
1739            self.keystrokes.push(keystroke.clone());
1740        }
1741        cx.emit(());
1742        cx.stop_propagation();
1743        cx.notify();
1744    }
1745
1746    fn on_key_up(
1747        &mut self,
1748        event: &gpui::KeyUpEvent,
1749        _window: &mut Window,
1750        cx: &mut Context<Self>,
1751    ) {
1752        if let Some(last) = self.keystrokes.last_mut()
1753            && !last.key.is_empty()
1754            && last.modifiers == event.keystroke.modifiers
1755        {
1756            cx.emit(());
1757            self.keystrokes.push(Keystroke {
1758                modifiers: event.keystroke.modifiers,
1759                key: "".to_string(),
1760                key_char: None,
1761            });
1762        }
1763        cx.stop_propagation();
1764        cx.notify();
1765    }
1766
1767    fn on_focus_in(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
1768        if self.intercept_subscription.is_none() {
1769            let listener = cx.listener(|this, event: &gpui::KeystrokeEvent, _window, cx| {
1770                this.handle_keystroke(&event.keystroke, cx);
1771            });
1772            self.intercept_subscription = Some(cx.intercept_keystrokes(listener))
1773        }
1774    }
1775
1776    fn on_focus_out(
1777        &mut self,
1778        _event: gpui::FocusOutEvent,
1779        _window: &mut Window,
1780        _cx: &mut Context<Self>,
1781    ) {
1782        self.intercept_subscription.take();
1783    }
1784
1785    fn keystrokes(&self) -> &[Keystroke] {
1786        if self
1787            .keystrokes
1788            .last()
1789            .map_or(false, |last| last.key.is_empty())
1790        {
1791            return &self.keystrokes[..self.keystrokes.len() - 1];
1792        }
1793        return &self.keystrokes;
1794    }
1795}
1796
1797impl EventEmitter<()> for KeystrokeInput {}
1798
1799impl Focusable for KeystrokeInput {
1800    fn focus_handle(&self, _cx: &App) -> FocusHandle {
1801        self.focus_handle.clone()
1802    }
1803}
1804
1805impl Render for KeystrokeInput {
1806    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1807        let colors = cx.theme().colors();
1808        let is_focused = self.focus_handle.is_focused(window);
1809
1810        return h_flex()
1811            .id("keybinding_input")
1812            .track_focus(&self.focus_handle)
1813            .on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
1814            .on_key_up(cx.listener(Self::on_key_up))
1815            .when(self.highlight_on_focus, |this| {
1816                this.focus(|mut style| {
1817                    style.border_color = Some(colors.border_focused);
1818                    style
1819                })
1820            })
1821            .py_2()
1822            .px_3()
1823            .gap_2()
1824            .min_h_8()
1825            .w_full()
1826            .flex_1()
1827            .justify_between()
1828            .rounded_md()
1829            .overflow_hidden()
1830            .bg(colors.editor_background)
1831            .border_1()
1832            .border_color(colors.border_variant)
1833            .child(
1834                h_flex()
1835                    .w_full()
1836                    .min_w_0()
1837                    .justify_center()
1838                    .flex_wrap()
1839                    .gap(ui::DynamicSpacing::Base04.rems(cx))
1840                    .children(self.keystrokes.iter().map(|keystroke| {
1841                        h_flex().children(ui::render_keystroke(
1842                            keystroke,
1843                            None,
1844                            Some(rems(0.875).into()),
1845                            ui::PlatformStyle::platform(),
1846                            false,
1847                        ))
1848                    })),
1849            )
1850            .child(
1851                h_flex()
1852                    .gap_0p5()
1853                    .flex_none()
1854                    .child(
1855                        IconButton::new("backspace-btn", IconName::Delete)
1856                            .tooltip(Tooltip::text("Delete Keystroke"))
1857                            .when(!is_focused, |this| this.icon_color(Color::Muted))
1858                            .on_click(cx.listener(|this, _event, _window, cx| {
1859                                this.keystrokes.pop();
1860                                cx.emit(());
1861                                cx.notify();
1862                            })),
1863                    )
1864                    .child(
1865                        IconButton::new("clear-btn", IconName::Eraser)
1866                            .tooltip(Tooltip::text("Clear Keystrokes"))
1867                            .when(!is_focused, |this| this.icon_color(Color::Muted))
1868                            .on_click(cx.listener(|this, _event, _window, cx| {
1869                                this.keystrokes.clear();
1870                                cx.emit(());
1871                                cx.notify();
1872                            })),
1873                    ),
1874            );
1875    }
1876}
1877
1878fn build_keybind_context_menu(
1879    this: &WeakEntity<KeymapEditor>,
1880    item_idx: usize,
1881    window: &mut Window,
1882    cx: &mut App,
1883) -> Entity<ContextMenu> {
1884    ContextMenu::build(window, cx, |menu, _window, cx| {
1885        let selected_binding = this
1886            .update(cx, |this, _cx| {
1887                this.selected_index = Some(item_idx);
1888                this.selected_binding().cloned()
1889            })
1890            .ok()
1891            .flatten();
1892
1893        let Some(selected_binding) = selected_binding else {
1894            return menu;
1895        };
1896
1897        let selected_binding_has_no_context = selected_binding
1898            .context
1899            .as_ref()
1900            .and_then(KeybindContextString::local)
1901            .is_none();
1902
1903        let selected_binding_is_unbound_action = selected_binding.ui_key_binding.is_none();
1904
1905        menu.action_disabled_when(
1906            selected_binding_is_unbound_action,
1907            "Edit",
1908            Box::new(EditBinding),
1909        )
1910        .action("Create", Box::new(CreateBinding))
1911        .action_disabled_when(
1912            selected_binding_is_unbound_action,
1913            "Delete",
1914            Box::new(DeleteBinding),
1915        )
1916        .action("Copy action", Box::new(CopyAction))
1917        .action_disabled_when(
1918            selected_binding_has_no_context,
1919            "Copy Context",
1920            Box::new(CopyContext),
1921        )
1922    })
1923}
1924
1925fn collect_contexts_from_assets() -> Vec<SharedString> {
1926    let mut keymap_assets = vec![
1927        util::asset_str::<SettingsAssets>(settings::DEFAULT_KEYMAP_PATH),
1928        util::asset_str::<SettingsAssets>(settings::VIM_KEYMAP_PATH),
1929    ];
1930    keymap_assets.extend(
1931        BaseKeymap::OPTIONS
1932            .iter()
1933            .filter_map(|(_, base_keymap)| base_keymap.asset_path())
1934            .map(util::asset_str::<SettingsAssets>),
1935    );
1936
1937    let mut contexts = HashSet::default();
1938
1939    for keymap_asset in keymap_assets {
1940        let Ok(keymap) = KeymapFile::parse(&keymap_asset) else {
1941            continue;
1942        };
1943
1944        for section in keymap.sections() {
1945            let context_expr = &section.context;
1946            let mut queue = Vec::new();
1947            let Ok(root_context) = gpui::KeyBindingContextPredicate::parse(context_expr) else {
1948                continue;
1949            };
1950
1951            queue.push(root_context);
1952            while let Some(context) = queue.pop() {
1953                match context {
1954                    gpui::KeyBindingContextPredicate::Identifier(ident) => {
1955                        contexts.insert(ident);
1956                    }
1957                    gpui::KeyBindingContextPredicate::Equal(ident_a, ident_b) => {
1958                        contexts.insert(ident_a);
1959                        contexts.insert(ident_b);
1960                    }
1961                    gpui::KeyBindingContextPredicate::NotEqual(ident_a, ident_b) => {
1962                        contexts.insert(ident_a);
1963                        contexts.insert(ident_b);
1964                    }
1965                    gpui::KeyBindingContextPredicate::Child(ctx_a, ctx_b) => {
1966                        queue.push(*ctx_a);
1967                        queue.push(*ctx_b);
1968                    }
1969                    gpui::KeyBindingContextPredicate::Not(ctx) => {
1970                        queue.push(*ctx);
1971                    }
1972                    gpui::KeyBindingContextPredicate::And(ctx_a, ctx_b) => {
1973                        queue.push(*ctx_a);
1974                        queue.push(*ctx_b);
1975                    }
1976                    gpui::KeyBindingContextPredicate::Or(ctx_a, ctx_b) => {
1977                        queue.push(*ctx_a);
1978                        queue.push(*ctx_b);
1979                    }
1980                }
1981            }
1982        }
1983    }
1984
1985    let mut contexts = contexts.into_iter().collect::<Vec<_>>();
1986    contexts.sort();
1987
1988    return contexts;
1989}
1990
1991impl SerializableItem for KeymapEditor {
1992    fn serialized_item_kind() -> &'static str {
1993        "KeymapEditor"
1994    }
1995
1996    fn cleanup(
1997        workspace_id: workspace::WorkspaceId,
1998        alive_items: Vec<workspace::ItemId>,
1999        _window: &mut Window,
2000        cx: &mut App,
2001    ) -> gpui::Task<gpui::Result<()>> {
2002        workspace::delete_unloaded_items(
2003            alive_items,
2004            workspace_id,
2005            "keybinding_editors",
2006            &KEYBINDING_EDITORS,
2007            cx,
2008        )
2009    }
2010
2011    fn deserialize(
2012        _project: Entity<project::Project>,
2013        workspace: WeakEntity<Workspace>,
2014        workspace_id: workspace::WorkspaceId,
2015        item_id: workspace::ItemId,
2016        window: &mut Window,
2017        cx: &mut App,
2018    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
2019        window.spawn(cx, async move |cx| {
2020            if KEYBINDING_EDITORS
2021                .get_keybinding_editor(item_id, workspace_id)?
2022                .is_some()
2023            {
2024                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
2025            } else {
2026                Err(anyhow!("No keybinding editor to deserialize"))
2027            }
2028        })
2029    }
2030
2031    fn serialize(
2032        &mut self,
2033        workspace: &mut Workspace,
2034        item_id: workspace::ItemId,
2035        _closing: bool,
2036        _window: &mut Window,
2037        cx: &mut ui::Context<Self>,
2038    ) -> Option<gpui::Task<gpui::Result<()>>> {
2039        let workspace_id = workspace.database_id()?;
2040        Some(cx.background_spawn(async move {
2041            KEYBINDING_EDITORS
2042                .save_keybinding_editor(item_id, workspace_id)
2043                .await
2044        }))
2045    }
2046
2047    fn should_serialize(&self, _event: &Self::Event) -> bool {
2048        false
2049    }
2050}
2051
2052mod persistence {
2053    use db::{define_connection, query, sqlez_macros::sql};
2054    use workspace::WorkspaceDb;
2055
2056    define_connection! {
2057        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
2058            &[sql!(
2059                CREATE TABLE keybinding_editors (
2060                    workspace_id INTEGER,
2061                    item_id INTEGER UNIQUE,
2062
2063                    PRIMARY KEY(workspace_id, item_id),
2064                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
2065                    ON DELETE CASCADE
2066                ) STRICT;
2067            )];
2068    }
2069
2070    impl KeybindingEditorDb {
2071        query! {
2072            pub async fn save_keybinding_editor(
2073                item_id: workspace::ItemId,
2074                workspace_id: workspace::WorkspaceId
2075            ) -> Result<()> {
2076                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
2077                VALUES (?, ?)
2078            }
2079        }
2080
2081        query! {
2082            pub fn get_keybinding_editor(
2083                item_id: workspace::ItemId,
2084                workspace_id: workspace::WorkspaceId
2085            ) -> Result<Option<workspace::ItemId>> {
2086                SELECT item_id
2087                FROM keybinding_editors
2088                WHERE item_id = ? AND workspace_id = ?
2089            }
2090        }
2091    }
2092}