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