keybindings.rs

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