keybindings.rs

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