keymap_file.rs

   1use anyhow::{Context as _, Result};
   2use collections::{BTreeMap, HashMap, IndexMap};
   3use fs::Fs;
   4use gpui::{
   5    Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE,
   6    KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, KeybindingKeystroke, Keystroke,
   7    NoAction, SharedString, Unbind, generate_list_of_all_registered_actions, register_action,
   8};
   9use schemars::{JsonSchema, json_schema};
  10use serde::Deserialize;
  11use serde_json::{Value, json};
  12use std::borrow::Cow;
  13use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};
  14use util::ResultExt as _;
  15use util::{
  16    asset_str,
  17    markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString},
  18    schemars::AllowTrailingCommas,
  19};
  20
  21use crate::SettingsAssets;
  22use settings_content::{ActionName, ActionWithArguments};
  23use settings_json::{
  24    append_top_level_array_value_in_json_text, parse_json_with_comments,
  25    replace_top_level_array_value_in_json_text,
  26};
  27
  28pub trait KeyBindingValidator: Send + Sync {
  29    fn action_type_id(&self) -> TypeId;
  30    fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>;
  31}
  32
  33pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>);
  34
  35inventory::collect!(KeyBindingValidatorRegistration);
  36
  37pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> =
  38    LazyLock::new(|| {
  39        let mut validators = BTreeMap::new();
  40        for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> {
  41            let validator = validator_registration.0();
  42            validators.insert(validator.action_type_id(), validator);
  43        }
  44        validators
  45    });
  46
  47// Note that the doc comments on these are shown by json-language-server when editing the keymap, so
  48// they should be considered user-facing documentation. Documentation is not handled well with
  49// schemars-0.8 - when there are newlines, it is rendered as plaintext (see
  50// https://github.com/GREsau/schemars/issues/38#issuecomment-2282883519). So for now these docs
  51// avoid newlines.
  52//
  53// TODO: Update to schemars-1.0 once it's released, and add more docs as newlines would be
  54// supported. Tracking issue is https://github.com/GREsau/schemars/issues/112.
  55
  56/// Keymap configuration consisting of sections. Each section may have a context predicate which
  57/// determines whether its bindings are used.
  58#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
  59#[serde(transparent)]
  60pub struct KeymapFile(Vec<KeymapSection>);
  61
  62/// Keymap section which binds keystrokes to actions.
  63#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
  64pub struct KeymapSection {
  65    /// Determines when these bindings are active. When just a name is provided, like `Editor` or
  66    /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`,
  67    /// `X || Y`, `!X` are also supported. Some more complex logic including checking OS and the
  68    /// current file extension are also supported - see [the
  69    /// documentation](https://zed.dev/docs/key-bindings#contexts) for more details.
  70    #[serde(default)]
  71    pub context: String,
  72    /// This option enables specifying keys based on their position on a QWERTY keyboard, by using
  73    /// position-equivalent mappings for some non-QWERTY keyboards. This is currently only supported
  74    /// on macOS. See the documentation for more details.
  75    #[serde(default)]
  76    use_key_equivalents: bool,
  77    /// This keymap section's unbindings, as a JSON object mapping keystrokes to actions. These are
  78    /// parsed before `bindings`, so bindings later in the same section can still take precedence.
  79    #[serde(default)]
  80    unbind: Option<IndexMap<String, UnbindTargetAction>>,
  81    /// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The
  82    /// keystrokes key is a string representing a sequence of keystrokes to type, where the
  83    /// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`,
  84    /// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The
  85    /// order of bindings does matter. When the same keystrokes are bound at the same context depth,
  86    /// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI,
  87    /// the later binding for the same action is preferred.
  88    #[serde(default)]
  89    bindings: Option<IndexMap<String, KeymapAction>>,
  90    #[serde(flatten)]
  91    unrecognized_fields: IndexMap<String, Value>,
  92    // This struct intentionally uses permissive types for its fields, rather than validating during
  93    // deserialization. The purpose of this is to allow loading the portion of the keymap that doesn't
  94    // have errors. The downside of this is that the errors are not reported with line+column info.
  95    // Unfortunately the implementations of the `Spanned` types for preserving this information are
  96    // highly inconvenient (`serde_spanned`) and in some cases don't work at all here
  97    // (`json_spanned_>value`). Serde should really have builtin support for this.
  98}
  99
 100impl KeymapSection {
 101    pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> {
 102        self.bindings.iter().flatten()
 103    }
 104}
 105
 106/// Keymap action as a JSON value, since it can either be null for no action, or the name of the
 107/// action, or an array of the name of the action and the action input.
 108///
 109/// Unlike the other json types involved in keymaps (including actions), this doc-comment will not
 110/// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The
 111/// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.
 112#[derive(Debug, Deserialize, Default, Clone)]
 113#[serde(transparent)]
 114pub struct KeymapAction(Value);
 115
 116impl std::fmt::Display for KeymapAction {
 117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 118        match &self.0 {
 119            Value::String(s) => write!(f, "{}", s),
 120            Value::Array(arr) => {
 121                let strings: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
 122                write!(f, "{}", strings.join(", "))
 123            }
 124            _ => write!(f, "{}", self.0),
 125        }
 126    }
 127}
 128
 129impl JsonSchema for KeymapAction {
 130    /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can
 131    /// reference the keymap action schema.
 132    fn schema_name() -> Cow<'static, str> {
 133        "KeymapAction".into()
 134    }
 135
 136    /// This schema will be replaced with the full action schema in
 137    /// `KeymapFile::generate_json_schema`.
 138    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
 139        json_schema!(true)
 140    }
 141}
 142
 143#[derive(Debug, Deserialize, Default, Clone)]
 144#[serde(transparent)]
 145pub struct UnbindTargetAction(Value);
 146
 147impl JsonSchema for UnbindTargetAction {
 148    fn schema_name() -> Cow<'static, str> {
 149        "UnbindTargetAction".into()
 150    }
 151
 152    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
 153        json_schema!(true)
 154    }
 155}
 156
 157#[derive(Debug)]
 158#[must_use]
 159pub enum KeymapFileLoadResult {
 160    Success {
 161        key_bindings: Vec<KeyBinding>,
 162    },
 163    SomeFailedToLoad {
 164        key_bindings: Vec<KeyBinding>,
 165        error_message: MarkdownString,
 166    },
 167    JsonParseFailure {
 168        error: anyhow::Error,
 169    },
 170}
 171
 172impl KeymapFile {
 173    pub fn parse(content: &str) -> anyhow::Result<Self> {
 174        if content.trim().is_empty() {
 175            return Ok(Self(Vec::new()));
 176        }
 177        parse_json_with_comments::<Self>(content)
 178    }
 179
 180    pub fn load_asset(
 181        asset_path: &str,
 182        source: Option<KeybindSource>,
 183        cx: &App,
 184    ) -> anyhow::Result<Vec<KeyBinding>> {
 185        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
 186            KeymapFileLoadResult::Success { mut key_bindings } => match source {
 187                Some(source) => Ok({
 188                    for key_binding in &mut key_bindings {
 189                        key_binding.set_meta(source.meta());
 190                    }
 191                    key_bindings
 192                }),
 193                None => Ok(key_bindings),
 194            },
 195            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
 196                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
 197            }
 198            KeymapFileLoadResult::JsonParseFailure { error } => {
 199                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
 200            }
 201        }
 202    }
 203
 204    pub fn load_asset_allow_partial_failure(
 205        asset_path: &str,
 206        cx: &App,
 207    ) -> anyhow::Result<Vec<KeyBinding>> {
 208        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
 209            KeymapFileLoadResult::SomeFailedToLoad {
 210                key_bindings,
 211                error_message,
 212                ..
 213            } if key_bindings.is_empty() => {
 214                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
 215            }
 216            KeymapFileLoadResult::Success { key_bindings, .. }
 217            | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
 218            KeymapFileLoadResult::JsonParseFailure { error } => {
 219                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
 220            }
 221        }
 222    }
 223
 224    #[cfg(any(test, feature = "test-support"))]
 225    pub fn load_asset_cached(asset_path: &str, cx: &App) -> anyhow::Result<Vec<KeyBinding>> {
 226        static CACHED: std::sync::OnceLock<KeymapFile> = std::sync::OnceLock::new();
 227        let keymap = CACHED
 228            .get_or_init(|| Self::parse(asset_str::<SettingsAssets>(asset_path).as_ref()).unwrap());
 229        match keymap.load_keymap(cx) {
 230            KeymapFileLoadResult::SomeFailedToLoad {
 231                key_bindings,
 232                error_message,
 233                ..
 234            } if key_bindings.is_empty() => {
 235                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}")
 236            }
 237            KeymapFileLoadResult::Success { key_bindings, .. }
 238            | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
 239            KeymapFileLoadResult::JsonParseFailure { error } => {
 240                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
 241            }
 242        }
 243    }
 244
 245    #[cfg(feature = "test-support")]
 246    pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> {
 247        match Self::load(content, cx) {
 248            KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings,
 249            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
 250                panic!("{error_message}");
 251            }
 252            KeymapFileLoadResult::JsonParseFailure { error } => {
 253                panic!("JSON parse error: {error}");
 254            }
 255        }
 256    }
 257
 258    pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult {
 259        let keymap_file = match Self::parse(content) {
 260            Ok(keymap_file) => keymap_file,
 261            Err(error) => {
 262                return KeymapFileLoadResult::JsonParseFailure { error };
 263            }
 264        };
 265        keymap_file.load_keymap(cx)
 266    }
 267
 268    pub fn load_keymap(&self, cx: &App) -> KeymapFileLoadResult {
 269        // Accumulate errors in order to support partial load of user keymap in the presence of
 270        // errors in context and binding parsing.
 271        let mut errors = Vec::new();
 272        let mut key_bindings = Vec::new();
 273
 274        for KeymapSection {
 275            context,
 276            use_key_equivalents,
 277            unbind,
 278            bindings,
 279            unrecognized_fields,
 280        } in self.0.iter()
 281        {
 282            let context_predicate: Option<Rc<KeyBindingContextPredicate>> = if context.is_empty() {
 283                None
 284            } else {
 285                match KeyBindingContextPredicate::parse(context) {
 286                    Ok(context_predicate) => Some(context_predicate.into()),
 287                    Err(err) => {
 288                        // Leading space is to separate from the message indicating which section
 289                        // the error occurred in.
 290                        errors.push((
 291                            context.clone(),
 292                            format!(" Parse error in section `context` field: {}", err),
 293                        ));
 294                        continue;
 295                    }
 296                }
 297            };
 298
 299            let mut section_errors = String::new();
 300
 301            if !unrecognized_fields.is_empty() {
 302                write!(
 303                    section_errors,
 304                    "\n\n - Unrecognized fields: {}",
 305                    MarkdownInlineCode(&format!("{:?}", unrecognized_fields.keys()))
 306                )
 307                .unwrap();
 308            }
 309
 310            if let Some(unbind) = unbind {
 311                for (keystrokes, action) in unbind {
 312                    let result = Self::load_unbinding(
 313                        keystrokes,
 314                        action,
 315                        context_predicate.clone(),
 316                        *use_key_equivalents,
 317                        cx,
 318                    );
 319                    match result {
 320                        Ok(key_binding) => {
 321                            key_bindings.push(key_binding);
 322                        }
 323                        Err(err) => {
 324                            let mut lines = err.lines();
 325                            let mut indented_err = lines.next().unwrap().to_string();
 326                            for line in lines {
 327                                indented_err.push_str("  ");
 328                                indented_err.push_str(line);
 329                                indented_err.push_str("\n");
 330                            }
 331                            write!(
 332                                section_errors,
 333                                "\n\n- In unbind {}, {indented_err}",
 334                                MarkdownInlineCode(&format!("\"{}\"", keystrokes))
 335                            )
 336                            .unwrap();
 337                        }
 338                    }
 339                }
 340            }
 341
 342            if let Some(bindings) = bindings {
 343                for (keystrokes, action) in bindings {
 344                    let result = Self::load_keybinding(
 345                        keystrokes,
 346                        action,
 347                        context_predicate.clone(),
 348                        *use_key_equivalents,
 349                        cx,
 350                    );
 351                    match result {
 352                        Ok(key_binding) => {
 353                            key_bindings.push(key_binding);
 354                        }
 355                        Err(err) => {
 356                            let mut lines = err.lines();
 357                            let mut indented_err = lines.next().unwrap().to_string();
 358                            for line in lines {
 359                                indented_err.push_str("  ");
 360                                indented_err.push_str(line);
 361                                indented_err.push_str("\n");
 362                            }
 363                            write!(
 364                                section_errors,
 365                                "\n\n- In binding {}, {indented_err}",
 366                                MarkdownInlineCode(&format!("\"{}\"", keystrokes))
 367                            )
 368                            .unwrap();
 369                        }
 370                    }
 371                }
 372            }
 373
 374            if !section_errors.is_empty() {
 375                errors.push((context.clone(), section_errors))
 376            }
 377        }
 378
 379        if errors.is_empty() {
 380            KeymapFileLoadResult::Success { key_bindings }
 381        } else {
 382            let mut error_message = "Errors in user keymap file.".to_owned();
 383
 384            for (context, section_errors) in errors {
 385                if context.is_empty() {
 386                    let _ = write!(error_message, "\nIn section without context predicate:");
 387                } else {
 388                    let _ = write!(
 389                        error_message,
 390                        "\nIn section with {}:",
 391                        MarkdownInlineCode(&format!("context = \"{}\"", context))
 392                    );
 393                }
 394                let _ = write!(error_message, "{section_errors}");
 395            }
 396
 397            KeymapFileLoadResult::SomeFailedToLoad {
 398                key_bindings,
 399                error_message: MarkdownString(error_message),
 400            }
 401        }
 402    }
 403
 404    fn load_keybinding(
 405        keystrokes: &str,
 406        action: &KeymapAction,
 407        context: Option<Rc<KeyBindingContextPredicate>>,
 408        use_key_equivalents: bool,
 409        cx: &App,
 410    ) -> std::result::Result<KeyBinding, String> {
 411        Self::load_keybinding_action_value(keystrokes, &action.0, context, use_key_equivalents, cx)
 412    }
 413
 414    fn load_keybinding_action_value(
 415        keystrokes: &str,
 416        action: &Value,
 417        context: Option<Rc<KeyBindingContextPredicate>>,
 418        use_key_equivalents: bool,
 419        cx: &App,
 420    ) -> std::result::Result<KeyBinding, String> {
 421        let (action, action_input_string) = Self::build_keymap_action_value(action, cx)?;
 422
 423        let key_binding = match KeyBinding::load(
 424            keystrokes,
 425            action,
 426            context,
 427            use_key_equivalents,
 428            action_input_string.map(SharedString::from),
 429            cx.keyboard_mapper().as_ref(),
 430        ) {
 431            Ok(key_binding) => key_binding,
 432            Err(InvalidKeystrokeError { keystroke }) => {
 433                return Err(format!(
 434                    "invalid keystroke {}. {}",
 435                    MarkdownInlineCode(&format!("\"{}\"", &keystroke)),
 436                    KEYSTROKE_PARSE_EXPECTED_MESSAGE
 437                ));
 438            }
 439        };
 440
 441        if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) {
 442            match validator.validate(&key_binding) {
 443                Ok(()) => Ok(key_binding),
 444                Err(error) => Err(error.0),
 445            }
 446        } else {
 447            Ok(key_binding)
 448        }
 449    }
 450
 451    fn load_unbinding(
 452        keystrokes: &str,
 453        action: &UnbindTargetAction,
 454        context: Option<Rc<KeyBindingContextPredicate>>,
 455        use_key_equivalents: bool,
 456        cx: &App,
 457    ) -> std::result::Result<KeyBinding, String> {
 458        let key_binding = Self::load_keybinding_action_value(
 459            keystrokes,
 460            &action.0,
 461            context,
 462            use_key_equivalents,
 463            cx,
 464        )?;
 465
 466        if key_binding.action().partial_eq(&NoAction) {
 467            return Err("expected action name string or [name, input] array.".to_string());
 468        }
 469
 470        if key_binding.action().name() == Unbind::name_for_type() {
 471            return Err(format!(
 472                "can't use {} as an unbind target.",
 473                MarkdownInlineCode(&format!("\"{}\"", Unbind::name_for_type()))
 474            ));
 475        }
 476
 477        KeyBinding::load(
 478            keystrokes,
 479            Box::new(Unbind(key_binding.action().name().into())),
 480            key_binding.predicate(),
 481            use_key_equivalents,
 482            key_binding.action_input(),
 483            cx.keyboard_mapper().as_ref(),
 484        )
 485        .map_err(|InvalidKeystrokeError { keystroke }| {
 486            format!(
 487                "invalid keystroke {}. {}",
 488                MarkdownInlineCode(&format!("\"{}\"", &keystroke)),
 489                KEYSTROKE_PARSE_EXPECTED_MESSAGE
 490            )
 491        })
 492    }
 493
 494    pub fn parse_action(
 495        action: &KeymapAction,
 496    ) -> Result<Option<(&String, Option<&Value>)>, String> {
 497        Self::parse_action_value(&action.0)
 498    }
 499
 500    fn parse_action_value(action: &Value) -> Result<Option<(&String, Option<&Value>)>, String> {
 501        let name_and_input = match action {
 502            Value::Array(items) => {
 503                if items.len() != 2 {
 504                    return Err(format!(
 505                        "expected two-element array of `[name, input]`. \
 506                        Instead found {}.",
 507                        MarkdownInlineCode(&action.to_string())
 508                    ));
 509                }
 510                let serde_json::Value::String(ref name) = items[0] else {
 511                    return Err(format!(
 512                        "expected two-element array of `[name, input]`, \
 513                        but the first element is not a string in {}.",
 514                        MarkdownInlineCode(&action.to_string())
 515                    ));
 516                };
 517                Some((name, Some(&items[1])))
 518            }
 519            Value::String(name) => Some((name, None)),
 520            Value::Null => None,
 521            _ => {
 522                return Err(format!(
 523                    "expected two-element array of `[name, input]`. \
 524                    Instead found {}.",
 525                    MarkdownInlineCode(&action.to_string())
 526                ));
 527            }
 528        };
 529        Ok(name_and_input)
 530    }
 531
 532    fn build_keymap_action(
 533        action: &KeymapAction,
 534        cx: &App,
 535    ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> {
 536        Self::build_keymap_action_value(&action.0, cx)
 537    }
 538
 539    fn build_keymap_action_value(
 540        action: &Value,
 541        cx: &App,
 542    ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> {
 543        let (build_result, action_input_string) = match Self::parse_action_value(action)? {
 544            Some((name, action_input)) if name.as_str() == ActionSequence::name_for_type() => {
 545                match action_input {
 546                    Some(action_input) => (
 547                        ActionSequence::build_sequence(action_input.clone(), cx),
 548                        None,
 549                    ),
 550                    None => (Err(ActionSequence::expected_array_error()), None),
 551                }
 552            }
 553            Some((name, Some(action_input))) => {
 554                let action_input_string = action_input.to_string();
 555                (
 556                    cx.build_action(name, Some(action_input.clone())),
 557                    Some(action_input_string),
 558                )
 559            }
 560            Some((name, None)) => (cx.build_action(name, None), None),
 561            None => (Ok(NoAction.boxed_clone()), None),
 562        };
 563
 564        let action = match build_result {
 565            Ok(action) => action,
 566            Err(ActionBuildError::NotFound { name }) => {
 567                return Err(format!(
 568                    "didn't find an action named {}.",
 569                    MarkdownInlineCode(&format!("\"{}\"", &name))
 570                ));
 571            }
 572            Err(ActionBuildError::BuildError { name, error }) => match action_input_string {
 573                Some(action_input_string) => {
 574                    return Err(format!(
 575                        "can't build {} action from input value {}: {}",
 576                        MarkdownInlineCode(&format!("\"{}\"", &name)),
 577                        MarkdownInlineCode(&action_input_string),
 578                        MarkdownEscaped(&error.to_string())
 579                    ));
 580                }
 581                None => {
 582                    return Err(format!(
 583                        "can't build {} action - it requires input data via [name, input]: {}",
 584                        MarkdownInlineCode(&format!("\"{}\"", &name)),
 585                        MarkdownEscaped(&error.to_string())
 586                    ));
 587                }
 588            },
 589        };
 590
 591        Ok((action, action_input_string))
 592    }
 593
 594    /// Creates a JSON schema generator, suitable for generating json schemas
 595    /// for actions
 596    pub fn action_schema_generator() -> schemars::SchemaGenerator {
 597        schemars::generate::SchemaSettings::draft2019_09()
 598            .with_transform(AllowTrailingCommas)
 599            .into_generator()
 600    }
 601
 602    pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
 603        // instead of using DefaultDenyUnknownFields, actions typically use
 604        // `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This
 605        // is because the rest of the keymap will still load in these cases, whereas other settings
 606        // files would not.
 607        let mut generator = Self::action_schema_generator();
 608
 609        let action_schemas = cx.action_schemas(&mut generator);
 610        let action_documentation = cx.action_documentation();
 611        let deprecations = cx.deprecated_actions_to_preferred_actions();
 612        let deprecation_messages = cx.action_deprecation_messages();
 613        KeymapFile::generate_json_schema(
 614            generator,
 615            action_schemas,
 616            action_documentation,
 617            deprecations,
 618            deprecation_messages,
 619        )
 620    }
 621
 622    pub fn generate_json_schema_from_inventory() -> Value {
 623        let mut generator = Self::action_schema_generator();
 624
 625        let mut action_schemas = Vec::new();
 626        let mut documentation = HashMap::default();
 627        let mut deprecations = HashMap::default();
 628        let mut deprecation_messages = HashMap::default();
 629
 630        for action_data in generate_list_of_all_registered_actions() {
 631            let schema = (action_data.json_schema)(&mut generator);
 632            action_schemas.push((action_data.name, schema));
 633
 634            if let Some(doc) = action_data.documentation {
 635                documentation.insert(action_data.name, doc);
 636            }
 637            if let Some(msg) = action_data.deprecation_message {
 638                deprecation_messages.insert(action_data.name, msg);
 639            }
 640            for &alias in action_data.deprecated_aliases {
 641                deprecations.insert(alias, action_data.name);
 642
 643                let alias_schema = (action_data.json_schema)(&mut generator);
 644                action_schemas.push((alias, alias_schema));
 645            }
 646        }
 647
 648        KeymapFile::generate_json_schema(
 649            generator,
 650            action_schemas,
 651            &documentation,
 652            &deprecations,
 653            &deprecation_messages,
 654        )
 655    }
 656
 657    pub fn get_action_schema_by_name(
 658        action_name: &str,
 659        generator: &mut schemars::SchemaGenerator,
 660    ) -> Option<schemars::Schema> {
 661        for action_data in generate_list_of_all_registered_actions() {
 662            if action_data.name == action_name {
 663                return (action_data.json_schema)(generator);
 664            }
 665            for &alias in action_data.deprecated_aliases {
 666                if alias == action_name {
 667                    return (action_data.json_schema)(generator);
 668                }
 669            }
 670        }
 671        None
 672    }
 673
 674    pub fn generate_json_schema<'a>(
 675        mut generator: schemars::SchemaGenerator,
 676        action_schemas: Vec<(&'a str, Option<schemars::Schema>)>,
 677        action_documentation: &HashMap<&'a str, &'a str>,
 678        deprecations: &HashMap<&'a str, &'a str>,
 679        deprecation_messages: &HashMap<&'a str, &'a str>,
 680    ) -> serde_json::Value {
 681        fn add_deprecation(schema: &mut schemars::Schema, message: String) {
 682            schema.insert(
 683                // deprecationMessage is not part of the JSON Schema spec, but
 684                // json-language-server recognizes it.
 685                "deprecationMessage".to_string(),
 686                Value::String(message),
 687            );
 688        }
 689
 690        fn add_deprecation_preferred_name(schema: &mut schemars::Schema, new_name: &str) {
 691            add_deprecation(schema, format!("Deprecated, use {new_name}"));
 692        }
 693
 694        fn add_description(schema: &mut schemars::Schema, description: &str) {
 695            schema.insert(
 696                "description".to_string(),
 697                Value::String(description.to_string()),
 698            );
 699        }
 700
 701        let empty_object = json_schema!({
 702            "type": "object"
 703        });
 704
 705        // This is a workaround for a json-language-server issue where it matches the first
 706        // alternative that matches the value's shape and uses that for documentation.
 707        //
 708        // In the case of the array validations, it would even provide an error saying that the name
 709        // must match the name of the first alternative.
 710        let mut empty_action_name = json_schema!({
 711            "type": "string",
 712            "const": ""
 713        });
 714        let no_action_message = "No action named this.";
 715        add_description(&mut empty_action_name, no_action_message);
 716        add_deprecation(&mut empty_action_name, no_action_message.to_string());
 717        let empty_action_name_with_input = json_schema!({
 718            "type": "array",
 719            "items": [
 720                empty_action_name,
 721                true
 722            ],
 723            "minItems": 2,
 724            "maxItems": 2
 725        });
 726
 727        let mut keymap_deprecations = deprecations.clone();
 728        keymap_deprecations.insert(NoAction.name(), "null");
 729        let action_name_schema = ActionName::build_schema(
 730            action_schemas.iter().map(|(name, _)| *name),
 731            action_documentation,
 732            &keymap_deprecations,
 733            deprecation_messages,
 734        );
 735
 736        let mut action_with_arguments_alternatives = vec![empty_action_name_with_input.clone()];
 737        let mut unbind_target_action_alternatives =
 738            vec![empty_action_name, empty_action_name_with_input];
 739
 740        let mut empty_schema_action_names = vec![];
 741        let mut empty_schema_unbind_target_action_names = vec![];
 742        for (name, action_schema) in action_schemas.into_iter() {
 743            let deprecation = if name == NoAction.name() {
 744                Some("null")
 745            } else {
 746                deprecations.get(name).copied()
 747            };
 748
 749            let include_in_unbind_target_schema =
 750                name != NoAction.name() && name != Unbind::name_for_type();
 751
 752            // Add an alternative for plain action names.
 753            let mut plain_action = json_schema!({
 754                "type": "string",
 755                "const": name
 756            });
 757            if let Some(message) = deprecation_messages.get(name) {
 758                add_deprecation(&mut plain_action, message.to_string());
 759            } else if let Some(new_name) = deprecation {
 760                add_deprecation_preferred_name(&mut plain_action, new_name);
 761            }
 762            let description = action_documentation.get(name);
 763            if let Some(description) = &description {
 764                add_description(&mut plain_action, description);
 765            }
 766            if include_in_unbind_target_schema {
 767                unbind_target_action_alternatives.push(plain_action);
 768            }
 769
 770            // Add an alternative for actions with data specified as a [name, data] array.
 771            //
 772            // When a struct with no deserializable fields is added by deriving `Action`, an empty
 773            // object schema is produced. The action should be invoked without data in this case.
 774            if let Some(schema) = action_schema
 775                && schema != empty_object
 776            {
 777                let mut matches_action_name = json_schema!({
 778                    "const": name
 779                });
 780                if let Some(description) = &description {
 781                    add_description(&mut matches_action_name, description);
 782                }
 783                if let Some(message) = deprecation_messages.get(name) {
 784                    add_deprecation(&mut matches_action_name, message.to_string());
 785                } else if let Some(new_name) = deprecation {
 786                    add_deprecation_preferred_name(&mut matches_action_name, new_name);
 787                }
 788                let action_with_input = json_schema!({
 789                    "type": "array",
 790                    "items": [matches_action_name, schema],
 791                    "minItems": 2,
 792                    "maxItems": 2
 793                });
 794                action_with_arguments_alternatives.push(action_with_input.clone());
 795                if include_in_unbind_target_schema {
 796                    unbind_target_action_alternatives.push(action_with_input);
 797                }
 798            } else {
 799                empty_schema_action_names.push(name);
 800                if include_in_unbind_target_schema {
 801                    empty_schema_unbind_target_action_names.push(name);
 802                }
 803            }
 804        }
 805
 806        if !empty_schema_action_names.is_empty() {
 807            let action_names = json_schema!({ "enum": empty_schema_action_names });
 808            let no_properties_allowed = json_schema!({
 809                "type": "object",
 810                "additionalProperties": false
 811            });
 812            let mut actions_with_empty_input = json_schema!({
 813                "type": "array",
 814                "items": [action_names, no_properties_allowed],
 815                "minItems": 2,
 816                "maxItems": 2
 817            });
 818            add_deprecation(
 819                &mut actions_with_empty_input,
 820                "This action does not take input - just the action name string should be used."
 821                    .to_string(),
 822            );
 823            action_with_arguments_alternatives.push(actions_with_empty_input);
 824        }
 825
 826        if !empty_schema_unbind_target_action_names.is_empty() {
 827            let action_names = json_schema!({ "enum": empty_schema_unbind_target_action_names });
 828            let no_properties_allowed = json_schema!({
 829                "type": "object",
 830                "additionalProperties": false
 831            });
 832            let mut actions_with_empty_input = json_schema!({
 833                "type": "array",
 834                "items": [action_names, no_properties_allowed],
 835                "minItems": 2,
 836                "maxItems": 2
 837            });
 838            add_deprecation(
 839                &mut actions_with_empty_input,
 840                "This action does not take input - just the action name string should be used."
 841                    .to_string(),
 842            );
 843            unbind_target_action_alternatives.push(actions_with_empty_input);
 844        }
 845
 846        generator.definitions_mut().insert(
 847            ActionName::schema_name().to_string(),
 848            action_name_schema.to_value(),
 849        );
 850        generator.definitions_mut().insert(
 851            ActionWithArguments::schema_name().to_string(),
 852            json!({ "anyOf": action_with_arguments_alternatives }),
 853        );
 854
 855        generator.definitions_mut().insert(
 856            KeymapAction::schema_name().to_string(),
 857            json!({ "anyOf": [
 858                { "$ref": format!("#/$defs/{}", ActionName::schema_name().to_string()) },
 859                { "$ref": format!("#/$defs/{}", ActionWithArguments::schema_name().to_string()) },
 860                { "type": "null" }
 861            ] }),
 862        );
 863        generator.definitions_mut().insert(
 864            UnbindTargetAction::schema_name().to_string(),
 865            json!({
 866                "anyOf": unbind_target_action_alternatives
 867            }),
 868        );
 869
 870        generator.root_schema_for::<KeymapFile>().to_value()
 871    }
 872
 873    pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
 874        self.0.iter()
 875    }
 876
 877    pub async fn load_keymap_file(fs: &Arc<dyn Fs>) -> Result<String> {
 878        match fs.load(paths::keymap_file()).await {
 879            result @ Ok(_) => result,
 880            Err(err) => {
 881                if let Some(e) = err.downcast_ref::<std::io::Error>()
 882                    && e.kind() == std::io::ErrorKind::NotFound
 883                {
 884                    return Ok(crate::initial_keymap_content().to_string());
 885                }
 886                Err(err)
 887            }
 888        }
 889    }
 890
 891    pub fn update_keybinding<'a>(
 892        mut operation: KeybindUpdateOperation<'a>,
 893        mut keymap_contents: String,
 894        tab_size: usize,
 895        keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
 896    ) -> Result<String> {
 897        // When replacing or removing a non-user binding, we may need to write an unbind entry
 898        // to suppress the original default binding.
 899        let mut suppression_unbind: Option<KeybindUpdateTarget<'_>> = None;
 900
 901        match &operation {
 902            // if trying to replace a keybinding that is not user-defined, treat it as an add operation
 903            KeybindUpdateOperation::Replace {
 904                target_keybind_source: target_source,
 905                source,
 906                target,
 907            } if *target_source != KeybindSource::User => {
 908                if target.keystrokes_unparsed() != source.keystrokes_unparsed() {
 909                    suppression_unbind = Some(target.clone());
 910                }
 911                operation = KeybindUpdateOperation::Add {
 912                    source: source.clone(),
 913                    from: Some(target.clone()),
 914                };
 915            }
 916            // if trying to remove a keybinding that is not user-defined, treat it as creating an
 917            // unbind entry for the removed action
 918            KeybindUpdateOperation::Remove {
 919                target,
 920                target_keybind_source,
 921            } if *target_keybind_source != KeybindSource::User => {
 922                suppression_unbind = Some(target.clone());
 923            }
 924            _ => {}
 925        }
 926
 927        // Sanity check that keymap contents are valid, even though we only use it for Replace.
 928        // We don't want to modify the file if it's invalid.
 929        let keymap = Self::parse(&keymap_contents).context("Failed to parse keymap")?;
 930
 931        if let KeybindUpdateOperation::Remove {
 932            target,
 933            target_keybind_source,
 934        } = &operation
 935        {
 936            if *target_keybind_source == KeybindSource::User {
 937                let target_action_value = target
 938                    .action_value()
 939                    .context("Failed to generate target action JSON value")?;
 940                let Some(binding_location) =
 941                    find_binding(&keymap, target, &target_action_value, keyboard_mapper)
 942                else {
 943                    anyhow::bail!("Failed to find keybinding to remove");
 944                };
 945                let is_only_binding = binding_location.is_only_entry_in_section(&keymap);
 946                let key_path: &[&str] = if is_only_binding {
 947                    &[]
 948                } else {
 949                    &[
 950                        binding_location.kind.key_path(),
 951                        binding_location.keystrokes_str,
 952                    ]
 953                };
 954                let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 955                    &keymap_contents,
 956                    key_path,
 957                    None,
 958                    None,
 959                    binding_location.index,
 960                    tab_size,
 961                );
 962                keymap_contents.replace_range(replace_range, &replace_value);
 963
 964                return Ok(keymap_contents);
 965            }
 966        }
 967
 968        if let KeybindUpdateOperation::Replace { source, target, .. } = operation {
 969            let target_action_value = target
 970                .action_value()
 971                .context("Failed to generate target action JSON value")?;
 972            let source_action_value = source
 973                .action_value()
 974                .context("Failed to generate source action JSON value")?;
 975
 976            if let Some(binding_location) =
 977                find_binding(&keymap, &target, &target_action_value, keyboard_mapper)
 978            {
 979                if target.context == source.context {
 980                    // if we are only changing the keybinding (common case)
 981                    // not the context, etc. Then just update the binding in place
 982
 983                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 984                        &keymap_contents,
 985                        &[
 986                            binding_location.kind.key_path(),
 987                            binding_location.keystrokes_str,
 988                        ],
 989                        Some(&source_action_value),
 990                        Some(&source.keystrokes_unparsed()),
 991                        binding_location.index,
 992                        tab_size,
 993                    );
 994                    keymap_contents.replace_range(replace_range, &replace_value);
 995
 996                    return Ok(keymap_contents);
 997                } else if binding_location.is_only_entry_in_section(&keymap) {
 998                    // if we are replacing the only binding in the section,
 999                    // just update the section in place, updating the context
1000                    // and the binding
1001
1002                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
1003                        &keymap_contents,
1004                        &[
1005                            binding_location.kind.key_path(),
1006                            binding_location.keystrokes_str,
1007                        ],
1008                        Some(&source_action_value),
1009                        Some(&source.keystrokes_unparsed()),
1010                        binding_location.index,
1011                        tab_size,
1012                    );
1013                    keymap_contents.replace_range(replace_range, &replace_value);
1014
1015                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
1016                        &keymap_contents,
1017                        &["context"],
1018                        source.context.map(Into::into).as_ref(),
1019                        None,
1020                        binding_location.index,
1021                        tab_size,
1022                    );
1023                    keymap_contents.replace_range(replace_range, &replace_value);
1024                    return Ok(keymap_contents);
1025                } else {
1026                    // if we are replacing one of multiple bindings in a section
1027                    // with a context change, remove the existing binding from the
1028                    // section, then treat this operation as an add operation of the
1029                    // new binding with the updated context.
1030
1031                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
1032                        &keymap_contents,
1033                        &[
1034                            binding_location.kind.key_path(),
1035                            binding_location.keystrokes_str,
1036                        ],
1037                        None,
1038                        None,
1039                        binding_location.index,
1040                        tab_size,
1041                    );
1042                    keymap_contents.replace_range(replace_range, &replace_value);
1043                    operation = KeybindUpdateOperation::Add {
1044                        source,
1045                        from: Some(target),
1046                    };
1047                }
1048            } else {
1049                log::warn!(
1050                    "Failed to find keybinding to update `{:?} -> {}` creating new binding for `{:?} -> {}` instead",
1051                    target.keystrokes,
1052                    target_action_value,
1053                    source.keystrokes,
1054                    source_action_value,
1055                );
1056                operation = KeybindUpdateOperation::Add {
1057                    source,
1058                    from: Some(target),
1059                };
1060            }
1061        }
1062
1063        if let KeybindUpdateOperation::Add {
1064            source: keybinding,
1065            from,
1066        } = operation
1067        {
1068            let mut value = serde_json::Map::with_capacity(4);
1069            if let Some(context) = keybinding.context {
1070                value.insert("context".to_string(), context.into());
1071            }
1072            let use_key_equivalents = from.and_then(|from| {
1073                let action_value = from.action_value().context("Failed to serialize action value. `use_key_equivalents` on new keybinding may be incorrect.").log_err()?;
1074                let binding_location =
1075                    find_binding(&keymap, &from, &action_value, keyboard_mapper)?;
1076                Some(keymap.0[binding_location.index].use_key_equivalents)
1077            }).unwrap_or(false);
1078            if use_key_equivalents {
1079                value.insert("use_key_equivalents".to_string(), true.into());
1080            }
1081
1082            value.insert("bindings".to_string(), {
1083                let mut bindings = serde_json::Map::new();
1084                let action = keybinding.action_value()?;
1085                bindings.insert(keybinding.keystrokes_unparsed(), action);
1086                bindings.into()
1087            });
1088
1089            let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
1090                &keymap_contents,
1091                &value.into(),
1092                tab_size,
1093            );
1094            keymap_contents.replace_range(replace_range, &replace_value);
1095        }
1096
1097        if let Some(suppression_unbind) = suppression_unbind {
1098            let mut value = serde_json::Map::with_capacity(2);
1099            if let Some(context) = suppression_unbind.context {
1100                value.insert("context".to_string(), context.into());
1101            }
1102            value.insert("unbind".to_string(), {
1103                let mut unbind = serde_json::Map::new();
1104                unbind.insert(
1105                    suppression_unbind.keystrokes_unparsed(),
1106                    suppression_unbind.action_value()?,
1107                );
1108                unbind.into()
1109            });
1110            let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
1111                &keymap_contents,
1112                &value.into(),
1113                tab_size,
1114            );
1115            keymap_contents.replace_range(replace_range, &replace_value);
1116        }
1117
1118        return Ok(keymap_contents);
1119
1120        fn find_binding<'a, 'b>(
1121            keymap: &'b KeymapFile,
1122            target: &KeybindUpdateTarget<'a>,
1123            target_action_value: &Value,
1124            keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
1125        ) -> Option<BindingLocation<'b>> {
1126            let target_context_parsed =
1127                KeyBindingContextPredicate::parse(target.context.unwrap_or("")).ok();
1128            for (index, section) in keymap.sections().enumerate() {
1129                let section_context_parsed =
1130                    KeyBindingContextPredicate::parse(&section.context).ok();
1131                if section_context_parsed != target_context_parsed {
1132                    continue;
1133                }
1134
1135                if let Some(binding_location) = find_binding_in_entries(
1136                    section.bindings.as_ref(),
1137                    BindingKind::Binding,
1138                    index,
1139                    target,
1140                    target_action_value,
1141                    keyboard_mapper,
1142                    |action| &action.0,
1143                ) {
1144                    return Some(binding_location);
1145                }
1146
1147                if let Some(binding_location) = find_binding_in_entries(
1148                    section.unbind.as_ref(),
1149                    BindingKind::Unbind,
1150                    index,
1151                    target,
1152                    target_action_value,
1153                    keyboard_mapper,
1154                    |action| &action.0,
1155                ) {
1156                    return Some(binding_location);
1157                }
1158            }
1159            None
1160        }
1161
1162        fn find_binding_in_entries<'a, 'b, T>(
1163            entries: Option<&'b IndexMap<String, T>>,
1164            kind: BindingKind,
1165            index: usize,
1166            target: &KeybindUpdateTarget<'a>,
1167            target_action_value: &Value,
1168            keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
1169            action_value: impl Fn(&T) -> &Value,
1170        ) -> Option<BindingLocation<'b>> {
1171            let entries = entries?;
1172            for (keystrokes_str, action) in entries {
1173                let Ok(keystrokes) = keystrokes_str
1174                    .split_whitespace()
1175                    .map(|source| {
1176                        let keystroke = Keystroke::parse(source)?;
1177                        Ok(KeybindingKeystroke::new_with_mapper(
1178                            keystroke,
1179                            false,
1180                            keyboard_mapper,
1181                        ))
1182                    })
1183                    .collect::<Result<Vec<_>, InvalidKeystrokeError>>()
1184                else {
1185                    continue;
1186                };
1187                if keystrokes.len() != target.keystrokes.len()
1188                    || !keystrokes
1189                        .iter()
1190                        .zip(target.keystrokes)
1191                        .all(|(a, b)| a.inner().should_match(b))
1192                {
1193                    continue;
1194                }
1195                if action_value(action) != target_action_value {
1196                    continue;
1197                }
1198                return Some(BindingLocation {
1199                    index,
1200                    kind,
1201                    keystrokes_str,
1202                });
1203            }
1204            None
1205        }
1206
1207        #[derive(Copy, Clone)]
1208        enum BindingKind {
1209            Binding,
1210            Unbind,
1211        }
1212
1213        impl BindingKind {
1214            fn key_path(self) -> &'static str {
1215                match self {
1216                    Self::Binding => "bindings",
1217                    Self::Unbind => "unbind",
1218                }
1219            }
1220        }
1221
1222        struct BindingLocation<'a> {
1223            index: usize,
1224            kind: BindingKind,
1225            keystrokes_str: &'a str,
1226        }
1227
1228        impl BindingLocation<'_> {
1229            fn is_only_entry_in_section(&self, keymap: &KeymapFile) -> bool {
1230                let section = &keymap.0[self.index];
1231                let binding_count = section.bindings.as_ref().map_or(0, IndexMap::len);
1232                let unbind_count = section.unbind.as_ref().map_or(0, IndexMap::len);
1233                binding_count + unbind_count == 1
1234            }
1235        }
1236    }
1237}
1238
1239#[derive(Clone, Debug)]
1240pub enum KeybindUpdateOperation<'a> {
1241    Replace {
1242        /// Describes the keybind to create
1243        source: KeybindUpdateTarget<'a>,
1244        /// Describes the keybind to remove
1245        target: KeybindUpdateTarget<'a>,
1246        target_keybind_source: KeybindSource,
1247    },
1248    Add {
1249        source: KeybindUpdateTarget<'a>,
1250        from: Option<KeybindUpdateTarget<'a>>,
1251    },
1252    Remove {
1253        target: KeybindUpdateTarget<'a>,
1254        target_keybind_source: KeybindSource,
1255    },
1256}
1257
1258impl KeybindUpdateOperation<'_> {
1259    pub fn generate_telemetry(
1260        &self,
1261    ) -> (
1262        // The keybind that is created
1263        String,
1264        // The keybinding that was removed
1265        String,
1266        // The source of the keybinding
1267        String,
1268    ) {
1269        let (new_binding, removed_binding, source) = match &self {
1270            KeybindUpdateOperation::Replace {
1271                source,
1272                target,
1273                target_keybind_source,
1274            } => (Some(source), Some(target), Some(*target_keybind_source)),
1275            KeybindUpdateOperation::Add { source, .. } => (Some(source), None, None),
1276            KeybindUpdateOperation::Remove {
1277                target,
1278                target_keybind_source,
1279            } => (None, Some(target), Some(*target_keybind_source)),
1280        };
1281
1282        let new_binding = new_binding
1283            .map(KeybindUpdateTarget::telemetry_string)
1284            .unwrap_or("null".to_owned());
1285        let removed_binding = removed_binding
1286            .map(KeybindUpdateTarget::telemetry_string)
1287            .unwrap_or("null".to_owned());
1288
1289        let source = source
1290            .as_ref()
1291            .map(KeybindSource::name)
1292            .map(ToOwned::to_owned)
1293            .unwrap_or("null".to_owned());
1294
1295        (new_binding, removed_binding, source)
1296    }
1297}
1298
1299impl<'a> KeybindUpdateOperation<'a> {
1300    pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
1301        Self::Add { source, from: None }
1302    }
1303}
1304
1305#[derive(Debug, Clone)]
1306pub struct KeybindUpdateTarget<'a> {
1307    pub context: Option<&'a str>,
1308    pub keystrokes: &'a [KeybindingKeystroke],
1309    pub action_name: &'a str,
1310    pub action_arguments: Option<&'a str>,
1311}
1312
1313impl<'a> KeybindUpdateTarget<'a> {
1314    fn action_value(&self) -> Result<Value> {
1315        if self.action_name == gpui::NoAction.name() {
1316            return Ok(Value::Null);
1317        }
1318        let action_name: Value = self.action_name.into();
1319        let value = match self.action_arguments {
1320            Some(args) if !args.is_empty() => {
1321                let args = serde_json::from_str::<Value>(args)
1322                    .context("Failed to parse action arguments as JSON")?;
1323                serde_json::json!([action_name, args])
1324            }
1325            _ => action_name,
1326        };
1327        Ok(value)
1328    }
1329
1330    fn keystrokes_unparsed(&self) -> String {
1331        let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8);
1332        for keystroke in self.keystrokes {
1333            // The reason use `keystroke.unparse()` instead of `keystroke.inner.unparse()`
1334            // here is that, we want the user to use `ctrl-shift-4` instead of `ctrl-$`
1335            // by default on Windows.
1336            keystrokes.push_str(&keystroke.unparse());
1337            keystrokes.push(' ');
1338        }
1339        keystrokes.pop();
1340        keystrokes
1341    }
1342
1343    fn telemetry_string(&self) -> String {
1344        format!(
1345            "action_name: {}, context: {}, action_arguments: {}, keystrokes: {}",
1346            self.action_name,
1347            self.context.unwrap_or("global"),
1348            self.action_arguments.unwrap_or("none"),
1349            self.keystrokes_unparsed()
1350        )
1351    }
1352}
1353
1354#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]
1355pub enum KeybindSource {
1356    User,
1357    Vim,
1358    Base,
1359    #[default]
1360    Default,
1361    Unknown,
1362}
1363
1364impl KeybindSource {
1365    const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Base as u32);
1366    const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Default as u32);
1367    const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Vim as u32);
1368    const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::User as u32);
1369
1370    pub fn name(&self) -> &'static str {
1371        match self {
1372            KeybindSource::User => "User",
1373            KeybindSource::Default => "Default",
1374            KeybindSource::Base => "Base",
1375            KeybindSource::Vim => "Vim",
1376            KeybindSource::Unknown => "Unknown",
1377        }
1378    }
1379
1380    pub fn meta(&self) -> KeyBindingMetaIndex {
1381        match self {
1382            KeybindSource::User => Self::USER,
1383            KeybindSource::Default => Self::DEFAULT,
1384            KeybindSource::Base => Self::BASE,
1385            KeybindSource::Vim => Self::VIM,
1386            KeybindSource::Unknown => KeyBindingMetaIndex(*self as u32),
1387        }
1388    }
1389
1390    pub fn from_meta(index: KeyBindingMetaIndex) -> Self {
1391        match index {
1392            Self::USER => KeybindSource::User,
1393            Self::BASE => KeybindSource::Base,
1394            Self::DEFAULT => KeybindSource::Default,
1395            Self::VIM => KeybindSource::Vim,
1396            _ => KeybindSource::Unknown,
1397        }
1398    }
1399}
1400
1401impl From<KeyBindingMetaIndex> for KeybindSource {
1402    fn from(index: KeyBindingMetaIndex) -> Self {
1403        Self::from_meta(index)
1404    }
1405}
1406
1407impl From<KeybindSource> for KeyBindingMetaIndex {
1408    fn from(source: KeybindSource) -> Self {
1409        source.meta()
1410    }
1411}
1412
1413/// Runs a sequence of actions. Does not wait for asynchronous actions to complete before running
1414/// the next action. Currently only works in workspace windows.
1415///
1416/// This action is special-cased in keymap parsing to allow it to access `App` while parsing, so
1417/// that it can parse its input actions.
1418pub struct ActionSequence(pub Vec<Box<dyn Action>>);
1419
1420register_action!(ActionSequence);
1421
1422impl ActionSequence {
1423    fn build_sequence(
1424        value: Value,
1425        cx: &App,
1426    ) -> std::result::Result<Box<dyn Action>, ActionBuildError> {
1427        match value {
1428            Value::Array(values) => {
1429                let actions = values
1430                    .into_iter()
1431                    .enumerate()
1432                    .map(|(index, action)| {
1433                        match KeymapFile::build_keymap_action(&KeymapAction(action), cx) {
1434                            Ok((action, _)) => Ok(action),
1435                            Err(err) => {
1436                                return Err(ActionBuildError::BuildError {
1437                                    name: Self::name_for_type().to_string(),
1438                                    error: anyhow::anyhow!(
1439                                        "error at sequence index {index}: {err}"
1440                                    ),
1441                                });
1442                            }
1443                        }
1444                    })
1445                    .collect::<Result<Vec<_>, _>>()?;
1446                Ok(Box::new(Self(actions)))
1447            }
1448            _ => Err(Self::expected_array_error()),
1449        }
1450    }
1451
1452    fn expected_array_error() -> ActionBuildError {
1453        ActionBuildError::BuildError {
1454            name: Self::name_for_type().to_string(),
1455            error: anyhow::anyhow!("expected array of actions"),
1456        }
1457    }
1458}
1459
1460impl Action for ActionSequence {
1461    fn name(&self) -> &'static str {
1462        Self::name_for_type()
1463    }
1464
1465    fn name_for_type() -> &'static str
1466    where
1467        Self: Sized,
1468    {
1469        "action::Sequence"
1470    }
1471
1472    fn partial_eq(&self, action: &dyn Action) -> bool {
1473        action
1474            .as_any()
1475            .downcast_ref::<Self>()
1476            .map_or(false, |other| {
1477                self.0.len() == other.0.len()
1478                    && self
1479                        .0
1480                        .iter()
1481                        .zip(other.0.iter())
1482                        .all(|(a, b)| a.partial_eq(b.as_ref()))
1483            })
1484    }
1485
1486    fn boxed_clone(&self) -> Box<dyn Action> {
1487        Box::new(ActionSequence(
1488            self.0
1489                .iter()
1490                .map(|action| action.boxed_clone())
1491                .collect::<Vec<_>>(),
1492        ))
1493    }
1494
1495    fn build(_value: Value) -> Result<Box<dyn Action>> {
1496        Err(anyhow::anyhow!(
1497            "{} cannot be built directly",
1498            Self::name_for_type()
1499        ))
1500    }
1501
1502    fn action_json_schema(generator: &mut schemars::SchemaGenerator) -> Option<schemars::Schema> {
1503        let keymap_action_schema = generator.subschema_for::<KeymapAction>();
1504        Some(json_schema!({
1505            "type": "array",
1506            "items": keymap_action_schema
1507        }))
1508    }
1509
1510    fn deprecated_aliases() -> &'static [&'static str] {
1511        &[]
1512    }
1513
1514    fn deprecation_message() -> Option<&'static str> {
1515        None
1516    }
1517
1518    fn documentation() -> Option<&'static str> {
1519        Some(
1520            "Runs a sequence of actions.\n\n\
1521            NOTE: This does **not** wait for asynchronous actions to complete before running the next action.",
1522        )
1523    }
1524}
1525
1526#[cfg(test)]
1527mod tests {
1528    use gpui::{Action, App, DummyKeyboardMapper, KeybindingKeystroke, Keystroke, Unbind};
1529    use serde_json::Value;
1530    use unindent::Unindent;
1531
1532    use crate::{
1533        KeybindSource, KeymapFile,
1534        keymap_file::{KeybindUpdateOperation, KeybindUpdateTarget},
1535    };
1536
1537    gpui::actions!(test_keymap_file, [StringAction, InputAction]);
1538
1539    #[test]
1540    fn can_deserialize_keymap_with_trailing_comma() {
1541        let json = indoc::indoc! {"[
1542              // Standard macOS bindings
1543              {
1544                \"bindings\": {
1545                  \"up\": \"menu::SelectPrevious\",
1546                },
1547              },
1548            ]
1549                  "
1550        };
1551        KeymapFile::parse(json).unwrap();
1552    }
1553
1554    #[gpui::test]
1555    fn keymap_section_unbinds_are_loaded_before_bindings(cx: &mut App) {
1556        let key_bindings = match KeymapFile::load(
1557            indoc::indoc! {r#"
1558                [
1559                    {
1560                        "unbind": {
1561                            "ctrl-a": "test_keymap_file::StringAction",
1562                            "ctrl-b": ["test_keymap_file::InputAction", {}]
1563                        },
1564                        "bindings": {
1565                            "ctrl-c": "test_keymap_file::StringAction"
1566                        }
1567                    }
1568                ]
1569            "#},
1570            cx,
1571        ) {
1572            crate::keymap_file::KeymapFileLoadResult::Success { key_bindings } => key_bindings,
1573            crate::keymap_file::KeymapFileLoadResult::SomeFailedToLoad {
1574                error_message, ..
1575            } => {
1576                panic!("{error_message}");
1577            }
1578            crate::keymap_file::KeymapFileLoadResult::JsonParseFailure { error } => {
1579                panic!("JSON parse error: {error}");
1580            }
1581        };
1582
1583        assert_eq!(key_bindings.len(), 3);
1584        assert!(
1585            key_bindings[0]
1586                .action()
1587                .partial_eq(&Unbind("test_keymap_file::StringAction".into()))
1588        );
1589        assert_eq!(key_bindings[0].action_input(), None);
1590        assert!(
1591            key_bindings[1]
1592                .action()
1593                .partial_eq(&Unbind("test_keymap_file::InputAction".into()))
1594        );
1595        assert_eq!(
1596            key_bindings[1]
1597                .action_input()
1598                .as_ref()
1599                .map(ToString::to_string),
1600            Some("{}".to_string())
1601        );
1602        assert_eq!(
1603            key_bindings[2].action().name(),
1604            "test_keymap_file::StringAction"
1605        );
1606    }
1607
1608    #[gpui::test]
1609    fn keymap_unbind_loads_valid_target_action_with_input(cx: &mut App) {
1610        let key_bindings = match KeymapFile::load(
1611            indoc::indoc! {r#"
1612                [
1613                    {
1614                        "unbind": {
1615                            "ctrl-a": ["test_keymap_file::InputAction", {}]
1616                        }
1617                    }
1618                ]
1619            "#},
1620            cx,
1621        ) {
1622            crate::keymap_file::KeymapFileLoadResult::Success { key_bindings } => key_bindings,
1623            other => panic!("expected Success, got {other:?}"),
1624        };
1625
1626        assert_eq!(key_bindings.len(), 1);
1627        assert!(
1628            key_bindings[0]
1629                .action()
1630                .partial_eq(&Unbind("test_keymap_file::InputAction".into()))
1631        );
1632        assert_eq!(
1633            key_bindings[0]
1634                .action_input()
1635                .as_ref()
1636                .map(ToString::to_string),
1637            Some("{}".to_string())
1638        );
1639    }
1640
1641    #[gpui::test]
1642    fn keymap_unbind_rejects_null(cx: &mut App) {
1643        match KeymapFile::load(
1644            indoc::indoc! {r#"
1645                [
1646                    {
1647                        "unbind": {
1648                            "ctrl-a": null
1649                        }
1650                    }
1651                ]
1652            "#},
1653            cx,
1654        ) {
1655            crate::keymap_file::KeymapFileLoadResult::SomeFailedToLoad {
1656                key_bindings,
1657                error_message,
1658            } => {
1659                assert!(key_bindings.is_empty());
1660                assert!(
1661                    error_message
1662                        .0
1663                        .contains("expected action name string or [name, input] array.")
1664                );
1665            }
1666            other => panic!("expected SomeFailedToLoad, got {other:?}"),
1667        }
1668    }
1669
1670    #[gpui::test]
1671    fn keymap_unbind_rejects_unbind_action(cx: &mut App) {
1672        match KeymapFile::load(
1673            indoc::indoc! {r#"
1674                [
1675                    {
1676                        "unbind": {
1677                            "ctrl-a": ["zed::Unbind", "test_keymap_file::StringAction"]
1678                        }
1679                    }
1680                ]
1681            "#},
1682            cx,
1683        ) {
1684            crate::keymap_file::KeymapFileLoadResult::SomeFailedToLoad {
1685                key_bindings,
1686                error_message,
1687            } => {
1688                assert!(key_bindings.is_empty());
1689                assert!(
1690                    error_message
1691                        .0
1692                        .contains("can't use `\"zed::Unbind\"` as an unbind target.")
1693                );
1694            }
1695            other => panic!("expected SomeFailedToLoad, got {other:?}"),
1696        }
1697    }
1698
1699    #[test]
1700    fn keymap_schema_for_unbind_excludes_null_and_unbind_action() {
1701        fn schema_allows(schema: &Value, expected: &Value) -> bool {
1702            match schema {
1703                Value::Object(object) => {
1704                    if object.get("const") == Some(expected) {
1705                        return true;
1706                    }
1707                    if object.get("type") == Some(&Value::String("null".to_string()))
1708                        && expected == &Value::Null
1709                    {
1710                        return true;
1711                    }
1712                    object.values().any(|value| schema_allows(value, expected))
1713                }
1714                Value::Array(items) => items.iter().any(|value| schema_allows(value, expected)),
1715                _ => false,
1716            }
1717        }
1718
1719        let schema = KeymapFile::generate_json_schema_from_inventory();
1720        let unbind_schema = schema
1721            .pointer("/$defs/UnbindTargetAction")
1722            .expect("missing UnbindTargetAction schema");
1723
1724        assert!(!schema_allows(unbind_schema, &Value::Null));
1725        assert!(!schema_allows(
1726            unbind_schema,
1727            &Value::String(Unbind::name_for_type().to_string())
1728        ));
1729        assert!(schema_allows(
1730            unbind_schema,
1731            &Value::String("test_keymap_file::StringAction".to_string())
1732        ));
1733        assert!(schema_allows(
1734            unbind_schema,
1735            &Value::String("test_keymap_file::InputAction".to_string())
1736        ));
1737    }
1738
1739    #[track_caller]
1740    fn check_keymap_update(
1741        input: impl ToString,
1742        operation: KeybindUpdateOperation,
1743        expected: impl ToString,
1744    ) {
1745        let result = KeymapFile::update_keybinding(
1746            operation,
1747            input.to_string(),
1748            4,
1749            &gpui::DummyKeyboardMapper,
1750        )
1751        .expect("Update succeeded");
1752        pretty_assertions::assert_eq!(expected.to_string(), result);
1753    }
1754
1755    #[track_caller]
1756    fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
1757        keystrokes
1758            .split(' ')
1759            .map(|s| {
1760                KeybindingKeystroke::new_with_mapper(
1761                    Keystroke::parse(s).expect("Keystrokes valid"),
1762                    false,
1763                    &DummyKeyboardMapper,
1764                )
1765            })
1766            .collect()
1767    }
1768
1769    #[test]
1770    fn keymap_update() {
1771        zlog::init_test();
1772
1773        check_keymap_update(
1774            "[]",
1775            KeybindUpdateOperation::add(KeybindUpdateTarget {
1776                keystrokes: &parse_keystrokes("ctrl-a"),
1777                action_name: "zed::SomeAction",
1778                context: None,
1779                action_arguments: None,
1780            }),
1781            r#"[
1782                {
1783                    "bindings": {
1784                        "ctrl-a": "zed::SomeAction"
1785                    }
1786                }
1787            ]"#
1788            .unindent(),
1789        );
1790
1791        check_keymap_update(
1792            "[]",
1793            KeybindUpdateOperation::add(KeybindUpdateTarget {
1794                keystrokes: &parse_keystrokes("\\ a"),
1795                action_name: "zed::SomeAction",
1796                context: None,
1797                action_arguments: None,
1798            }),
1799            r#"[
1800                {
1801                    "bindings": {
1802                        "\\ a": "zed::SomeAction"
1803                    }
1804                }
1805            ]"#
1806            .unindent(),
1807        );
1808
1809        check_keymap_update(
1810            "[]",
1811            KeybindUpdateOperation::add(KeybindUpdateTarget {
1812                keystrokes: &parse_keystrokes("ctrl-a"),
1813                action_name: "zed::SomeAction",
1814                context: None,
1815                action_arguments: Some(""),
1816            }),
1817            r#"[
1818                {
1819                    "bindings": {
1820                        "ctrl-a": "zed::SomeAction"
1821                    }
1822                }
1823            ]"#
1824            .unindent(),
1825        );
1826
1827        check_keymap_update(
1828            r#"[
1829                {
1830                    "bindings": {
1831                        "ctrl-a": "zed::SomeAction"
1832                    }
1833                }
1834            ]"#
1835            .unindent(),
1836            KeybindUpdateOperation::add(KeybindUpdateTarget {
1837                keystrokes: &parse_keystrokes("ctrl-b"),
1838                action_name: "zed::SomeOtherAction",
1839                context: None,
1840                action_arguments: None,
1841            }),
1842            r#"[
1843                {
1844                    "bindings": {
1845                        "ctrl-a": "zed::SomeAction"
1846                    }
1847                },
1848                {
1849                    "bindings": {
1850                        "ctrl-b": "zed::SomeOtherAction"
1851                    }
1852                }
1853            ]"#
1854            .unindent(),
1855        );
1856
1857        check_keymap_update(
1858            r#"[
1859                {
1860                    "bindings": {
1861                        "ctrl-a": "zed::SomeAction"
1862                    }
1863                }
1864            ]"#
1865            .unindent(),
1866            KeybindUpdateOperation::add(KeybindUpdateTarget {
1867                keystrokes: &parse_keystrokes("ctrl-b"),
1868                action_name: "zed::SomeOtherAction",
1869                context: None,
1870                action_arguments: Some(r#"{"foo": "bar"}"#),
1871            }),
1872            r#"[
1873                {
1874                    "bindings": {
1875                        "ctrl-a": "zed::SomeAction"
1876                    }
1877                },
1878                {
1879                    "bindings": {
1880                        "ctrl-b": [
1881                            "zed::SomeOtherAction",
1882                            {
1883                                "foo": "bar"
1884                            }
1885                        ]
1886                    }
1887                }
1888            ]"#
1889            .unindent(),
1890        );
1891
1892        check_keymap_update(
1893            r#"[
1894                {
1895                    "bindings": {
1896                        "ctrl-a": "zed::SomeAction"
1897                    }
1898                }
1899            ]"#
1900            .unindent(),
1901            KeybindUpdateOperation::add(KeybindUpdateTarget {
1902                keystrokes: &parse_keystrokes("ctrl-b"),
1903                action_name: "zed::SomeOtherAction",
1904                context: Some("Zed > Editor && some_condition = true"),
1905                action_arguments: Some(r#"{"foo": "bar"}"#),
1906            }),
1907            r#"[
1908                {
1909                    "bindings": {
1910                        "ctrl-a": "zed::SomeAction"
1911                    }
1912                },
1913                {
1914                    "context": "Zed > Editor && some_condition = true",
1915                    "bindings": {
1916                        "ctrl-b": [
1917                            "zed::SomeOtherAction",
1918                            {
1919                                "foo": "bar"
1920                            }
1921                        ]
1922                    }
1923                }
1924            ]"#
1925            .unindent(),
1926        );
1927
1928        check_keymap_update(
1929            r#"[
1930                {
1931                    "bindings": {
1932                        "ctrl-a": "zed::SomeAction"
1933                    }
1934                }
1935            ]"#
1936            .unindent(),
1937            KeybindUpdateOperation::Replace {
1938                target: KeybindUpdateTarget {
1939                    keystrokes: &parse_keystrokes("ctrl-a"),
1940                    action_name: "zed::SomeAction",
1941                    context: None,
1942                    action_arguments: None,
1943                },
1944                source: KeybindUpdateTarget {
1945                    keystrokes: &parse_keystrokes("ctrl-b"),
1946                    action_name: "zed::SomeOtherAction",
1947                    context: None,
1948                    action_arguments: Some(r#"{"foo": "bar"}"#),
1949                },
1950                target_keybind_source: KeybindSource::Base,
1951            },
1952            r#"[
1953                {
1954                    "bindings": {
1955                        "ctrl-a": "zed::SomeAction"
1956                    }
1957                },
1958                {
1959                    "bindings": {
1960                        "ctrl-b": [
1961                            "zed::SomeOtherAction",
1962                            {
1963                                "foo": "bar"
1964                            }
1965                        ]
1966                    }
1967                },
1968                {
1969                    "unbind": {
1970                        "ctrl-a": "zed::SomeAction"
1971                    }
1972                }
1973            ]"#
1974            .unindent(),
1975        );
1976
1977        // Replacing a non-user binding without changing the keystroke should
1978        // not produce an unbind suppression entry.
1979        check_keymap_update(
1980            r#"[
1981                {
1982                    "bindings": {
1983                        "ctrl-a": "zed::SomeAction"
1984                    }
1985                }
1986            ]"#
1987            .unindent(),
1988            KeybindUpdateOperation::Replace {
1989                target: KeybindUpdateTarget {
1990                    keystrokes: &parse_keystrokes("ctrl-a"),
1991                    action_name: "zed::SomeAction",
1992                    context: None,
1993                    action_arguments: None,
1994                },
1995                source: KeybindUpdateTarget {
1996                    keystrokes: &parse_keystrokes("ctrl-a"),
1997                    action_name: "zed::SomeOtherAction",
1998                    context: None,
1999                    action_arguments: None,
2000                },
2001                target_keybind_source: KeybindSource::Base,
2002            },
2003            r#"[
2004                {
2005                    "bindings": {
2006                        "ctrl-a": "zed::SomeAction"
2007                    }
2008                },
2009                {
2010                    "bindings": {
2011                        "ctrl-a": "zed::SomeOtherAction"
2012                    }
2013                }
2014            ]"#
2015            .unindent(),
2016        );
2017
2018        // Replacing a non-user binding with a context and a keystroke change
2019        // should produce a suppression entry that preserves the context.
2020        check_keymap_update(
2021            r#"[
2022                {
2023                    "context": "SomeContext",
2024                    "bindings": {
2025                        "ctrl-a": "zed::SomeAction"
2026                    }
2027                }
2028            ]"#
2029            .unindent(),
2030            KeybindUpdateOperation::Replace {
2031                target: KeybindUpdateTarget {
2032                    keystrokes: &parse_keystrokes("ctrl-a"),
2033                    action_name: "zed::SomeAction",
2034                    context: Some("SomeContext"),
2035                    action_arguments: None,
2036                },
2037                source: KeybindUpdateTarget {
2038                    keystrokes: &parse_keystrokes("ctrl-b"),
2039                    action_name: "zed::SomeOtherAction",
2040                    context: Some("SomeContext"),
2041                    action_arguments: None,
2042                },
2043                target_keybind_source: KeybindSource::Default,
2044            },
2045            r#"[
2046                {
2047                    "context": "SomeContext",
2048                    "bindings": {
2049                        "ctrl-a": "zed::SomeAction"
2050                    }
2051                },
2052                {
2053                    "context": "SomeContext",
2054                    "bindings": {
2055                        "ctrl-b": "zed::SomeOtherAction"
2056                    }
2057                },
2058                {
2059                    "context": "SomeContext",
2060                    "unbind": {
2061                        "ctrl-a": "zed::SomeAction"
2062                    }
2063                }
2064            ]"#
2065            .unindent(),
2066        );
2067
2068        check_keymap_update(
2069            r#"[
2070                {
2071                    "bindings": {
2072                        "a": "zed::SomeAction"
2073                    }
2074                }
2075            ]"#
2076            .unindent(),
2077            KeybindUpdateOperation::Replace {
2078                target: KeybindUpdateTarget {
2079                    keystrokes: &parse_keystrokes("a"),
2080                    action_name: "zed::SomeAction",
2081                    context: None,
2082                    action_arguments: None,
2083                },
2084                source: KeybindUpdateTarget {
2085                    keystrokes: &parse_keystrokes("ctrl-b"),
2086                    action_name: "zed::SomeOtherAction",
2087                    context: None,
2088                    action_arguments: Some(r#"{"foo": "bar"}"#),
2089                },
2090                target_keybind_source: KeybindSource::User,
2091            },
2092            r#"[
2093                {
2094                    "bindings": {
2095                        "ctrl-b": [
2096                            "zed::SomeOtherAction",
2097                            {
2098                                "foo": "bar"
2099                            }
2100                        ]
2101                    }
2102                }
2103            ]"#
2104            .unindent(),
2105        );
2106
2107        check_keymap_update(
2108            r#"[
2109                {
2110                    "bindings": {
2111                        "\\ a": "zed::SomeAction"
2112                    }
2113                }
2114            ]"#
2115            .unindent(),
2116            KeybindUpdateOperation::Replace {
2117                target: KeybindUpdateTarget {
2118                    keystrokes: &parse_keystrokes("\\ a"),
2119                    action_name: "zed::SomeAction",
2120                    context: None,
2121                    action_arguments: None,
2122                },
2123                source: KeybindUpdateTarget {
2124                    keystrokes: &parse_keystrokes("\\ b"),
2125                    action_name: "zed::SomeOtherAction",
2126                    context: None,
2127                    action_arguments: Some(r#"{"foo": "bar"}"#),
2128                },
2129                target_keybind_source: KeybindSource::User,
2130            },
2131            r#"[
2132                {
2133                    "bindings": {
2134                        "\\ b": [
2135                            "zed::SomeOtherAction",
2136                            {
2137                                "foo": "bar"
2138                            }
2139                        ]
2140                    }
2141                }
2142            ]"#
2143            .unindent(),
2144        );
2145
2146        check_keymap_update(
2147            r#"[
2148                {
2149                    "bindings": {
2150                        "\\ a": "zed::SomeAction"
2151                    }
2152                }
2153            ]"#
2154            .unindent(),
2155            KeybindUpdateOperation::Replace {
2156                target: KeybindUpdateTarget {
2157                    keystrokes: &parse_keystrokes("\\ a"),
2158                    action_name: "zed::SomeAction",
2159                    context: None,
2160                    action_arguments: None,
2161                },
2162                source: KeybindUpdateTarget {
2163                    keystrokes: &parse_keystrokes("\\ a"),
2164                    action_name: "zed::SomeAction",
2165                    context: None,
2166                    action_arguments: None,
2167                },
2168                target_keybind_source: KeybindSource::User,
2169            },
2170            r#"[
2171                {
2172                    "bindings": {
2173                        "\\ a": "zed::SomeAction"
2174                    }
2175                }
2176            ]"#
2177            .unindent(),
2178        );
2179
2180        check_keymap_update(
2181            r#"[
2182                {
2183                    "bindings": {
2184                        "ctrl-a": "zed::SomeAction"
2185                    }
2186                }
2187            ]"#
2188            .unindent(),
2189            KeybindUpdateOperation::Replace {
2190                target: KeybindUpdateTarget {
2191                    keystrokes: &parse_keystrokes("ctrl-a"),
2192                    action_name: "zed::SomeNonexistentAction",
2193                    context: None,
2194                    action_arguments: None,
2195                },
2196                source: KeybindUpdateTarget {
2197                    keystrokes: &parse_keystrokes("ctrl-b"),
2198                    action_name: "zed::SomeOtherAction",
2199                    context: None,
2200                    action_arguments: None,
2201                },
2202                target_keybind_source: KeybindSource::User,
2203            },
2204            r#"[
2205                {
2206                    "bindings": {
2207                        "ctrl-a": "zed::SomeAction"
2208                    }
2209                },
2210                {
2211                    "bindings": {
2212                        "ctrl-b": "zed::SomeOtherAction"
2213                    }
2214                }
2215            ]"#
2216            .unindent(),
2217        );
2218
2219        check_keymap_update(
2220            r#"[
2221                {
2222                    "bindings": {
2223                        // some comment
2224                        "ctrl-a": "zed::SomeAction"
2225                        // some other comment
2226                    }
2227                }
2228            ]"#
2229            .unindent(),
2230            KeybindUpdateOperation::Replace {
2231                target: KeybindUpdateTarget {
2232                    keystrokes: &parse_keystrokes("ctrl-a"),
2233                    action_name: "zed::SomeAction",
2234                    context: None,
2235                    action_arguments: None,
2236                },
2237                source: KeybindUpdateTarget {
2238                    keystrokes: &parse_keystrokes("ctrl-b"),
2239                    action_name: "zed::SomeOtherAction",
2240                    context: None,
2241                    action_arguments: Some(r#"{"foo": "bar"}"#),
2242                },
2243                target_keybind_source: KeybindSource::User,
2244            },
2245            r#"[
2246                {
2247                    "bindings": {
2248                        // some comment
2249                        "ctrl-b": [
2250                            "zed::SomeOtherAction",
2251                            {
2252                                "foo": "bar"
2253                            }
2254                        ]
2255                        // some other comment
2256                    }
2257                }
2258            ]"#
2259            .unindent(),
2260        );
2261
2262        check_keymap_update(
2263            r#"[
2264                {
2265                    "context": "SomeContext",
2266                    "bindings": {
2267                        "a": "foo::bar",
2268                        "b": "baz::qux",
2269                    }
2270                }
2271            ]"#
2272            .unindent(),
2273            KeybindUpdateOperation::Replace {
2274                target: KeybindUpdateTarget {
2275                    keystrokes: &parse_keystrokes("a"),
2276                    action_name: "foo::bar",
2277                    context: Some("SomeContext"),
2278                    action_arguments: None,
2279                },
2280                source: KeybindUpdateTarget {
2281                    keystrokes: &parse_keystrokes("c"),
2282                    action_name: "foo::baz",
2283                    context: Some("SomeOtherContext"),
2284                    action_arguments: None,
2285                },
2286                target_keybind_source: KeybindSource::User,
2287            },
2288            r#"[
2289                {
2290                    "context": "SomeContext",
2291                    "bindings": {
2292                        "b": "baz::qux",
2293                    }
2294                },
2295                {
2296                    "context": "SomeOtherContext",
2297                    "bindings": {
2298                        "c": "foo::baz"
2299                    }
2300                }
2301            ]"#
2302            .unindent(),
2303        );
2304
2305        check_keymap_update(
2306            r#"[
2307                {
2308                    "context": "SomeContext",
2309                    "bindings": {
2310                        "a": "foo::bar",
2311                    }
2312                }
2313            ]"#
2314            .unindent(),
2315            KeybindUpdateOperation::Replace {
2316                target: KeybindUpdateTarget {
2317                    keystrokes: &parse_keystrokes("a"),
2318                    action_name: "foo::bar",
2319                    context: Some("SomeContext"),
2320                    action_arguments: None,
2321                },
2322                source: KeybindUpdateTarget {
2323                    keystrokes: &parse_keystrokes("c"),
2324                    action_name: "foo::baz",
2325                    context: Some("SomeOtherContext"),
2326                    action_arguments: None,
2327                },
2328                target_keybind_source: KeybindSource::User,
2329            },
2330            r#"[
2331                {
2332                    "context": "SomeOtherContext",
2333                    "bindings": {
2334                        "c": "foo::baz",
2335                    }
2336                }
2337            ]"#
2338            .unindent(),
2339        );
2340
2341        check_keymap_update(
2342            r#"[
2343                {
2344                    "context": "SomeContext",
2345                    "bindings": {
2346                        "a": "foo::bar",
2347                        "c": "foo::baz",
2348                    }
2349                },
2350            ]"#
2351            .unindent(),
2352            KeybindUpdateOperation::Remove {
2353                target: KeybindUpdateTarget {
2354                    context: Some("SomeContext"),
2355                    keystrokes: &parse_keystrokes("a"),
2356                    action_name: "foo::bar",
2357                    action_arguments: None,
2358                },
2359                target_keybind_source: KeybindSource::User,
2360            },
2361            r#"[
2362                {
2363                    "context": "SomeContext",
2364                    "bindings": {
2365                        "c": "foo::baz",
2366                    }
2367                },
2368            ]"#
2369            .unindent(),
2370        );
2371
2372        check_keymap_update(
2373            r#"[
2374                {
2375                    "context": "SomeContext",
2376                    "bindings": {
2377                        "\\ a": "foo::bar",
2378                        "c": "foo::baz",
2379                    }
2380                },
2381            ]"#
2382            .unindent(),
2383            KeybindUpdateOperation::Remove {
2384                target: KeybindUpdateTarget {
2385                    context: Some("SomeContext"),
2386                    keystrokes: &parse_keystrokes("\\ a"),
2387                    action_name: "foo::bar",
2388                    action_arguments: None,
2389                },
2390                target_keybind_source: KeybindSource::User,
2391            },
2392            r#"[
2393                {
2394                    "context": "SomeContext",
2395                    "bindings": {
2396                        "c": "foo::baz",
2397                    }
2398                },
2399            ]"#
2400            .unindent(),
2401        );
2402
2403        check_keymap_update(
2404            r#"[
2405                {
2406                    "context": "SomeContext",
2407                    "bindings": {
2408                        "a": ["foo::bar", true],
2409                        "c": "foo::baz",
2410                    }
2411                },
2412            ]"#
2413            .unindent(),
2414            KeybindUpdateOperation::Remove {
2415                target: KeybindUpdateTarget {
2416                    context: Some("SomeContext"),
2417                    keystrokes: &parse_keystrokes("a"),
2418                    action_name: "foo::bar",
2419                    action_arguments: Some("true"),
2420                },
2421                target_keybind_source: KeybindSource::User,
2422            },
2423            r#"[
2424                {
2425                    "context": "SomeContext",
2426                    "bindings": {
2427                        "c": "foo::baz",
2428                    }
2429                },
2430            ]"#
2431            .unindent(),
2432        );
2433
2434        check_keymap_update(
2435            r#"[
2436                {
2437                    "context": "SomeContext",
2438                    "bindings": {
2439                        "b": "foo::baz",
2440                    }
2441                },
2442                {
2443                    "context": "SomeContext",
2444                    "bindings": {
2445                        "a": ["foo::bar", true],
2446                    }
2447                },
2448                {
2449                    "context": "SomeContext",
2450                    "bindings": {
2451                        "c": "foo::baz",
2452                    }
2453                },
2454            ]"#
2455            .unindent(),
2456            KeybindUpdateOperation::Remove {
2457                target: KeybindUpdateTarget {
2458                    context: Some("SomeContext"),
2459                    keystrokes: &parse_keystrokes("a"),
2460                    action_name: "foo::bar",
2461                    action_arguments: Some("true"),
2462                },
2463                target_keybind_source: KeybindSource::User,
2464            },
2465            r#"[
2466                {
2467                    "context": "SomeContext",
2468                    "bindings": {
2469                        "b": "foo::baz",
2470                    }
2471                },
2472                {
2473                    "context": "SomeContext",
2474                    "bindings": {
2475                        "c": "foo::baz",
2476                    }
2477                },
2478            ]"#
2479            .unindent(),
2480        );
2481        check_keymap_update(
2482            r#"[
2483                {
2484                    "context": "SomeOtherContext",
2485                    "use_key_equivalents": true,
2486                    "bindings": {
2487                        "b": "foo::bar",
2488                    }
2489                },
2490            ]"#
2491            .unindent(),
2492            KeybindUpdateOperation::Add {
2493                source: KeybindUpdateTarget {
2494                    context: Some("SomeContext"),
2495                    keystrokes: &parse_keystrokes("a"),
2496                    action_name: "foo::baz",
2497                    action_arguments: Some("true"),
2498                },
2499                from: Some(KeybindUpdateTarget {
2500                    context: Some("SomeOtherContext"),
2501                    keystrokes: &parse_keystrokes("b"),
2502                    action_name: "foo::bar",
2503                    action_arguments: None,
2504                }),
2505            },
2506            r#"[
2507                {
2508                    "context": "SomeOtherContext",
2509                    "use_key_equivalents": true,
2510                    "bindings": {
2511                        "b": "foo::bar",
2512                    }
2513                },
2514                {
2515                    "context": "SomeContext",
2516                    "use_key_equivalents": true,
2517                    "bindings": {
2518                        "a": [
2519                            "foo::baz",
2520                            true
2521                        ]
2522                    }
2523                }
2524            ]"#
2525            .unindent(),
2526        );
2527
2528        check_keymap_update(
2529            r#"[
2530                {
2531                    "context": "SomeOtherContext",
2532                    "use_key_equivalents": true,
2533                    "bindings": {
2534                        "b": "foo::bar",
2535                    }
2536                },
2537            ]"#
2538            .unindent(),
2539            KeybindUpdateOperation::Remove {
2540                target: KeybindUpdateTarget {
2541                    context: Some("SomeContext"),
2542                    keystrokes: &parse_keystrokes("a"),
2543                    action_name: "foo::baz",
2544                    action_arguments: Some("true"),
2545                },
2546                target_keybind_source: KeybindSource::Default,
2547            },
2548            r#"[
2549                {
2550                    "context": "SomeOtherContext",
2551                    "use_key_equivalents": true,
2552                    "bindings": {
2553                        "b": "foo::bar",
2554                    }
2555                },
2556                {
2557                    "context": "SomeContext",
2558                    "unbind": {
2559                        "a": [
2560                            "foo::baz",
2561                            true
2562                        ]
2563                    }
2564                }
2565            ]"#
2566            .unindent(),
2567        );
2568    }
2569
2570    #[test]
2571    fn test_keymap_remove() {
2572        zlog::init_test();
2573
2574        check_keymap_update(
2575            r#"
2576            [
2577              {
2578                "context": "Editor",
2579                "bindings": {
2580                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
2581                  "cmd-k cmd-l": "editor::ConvertToLowerCase",
2582                  "cmd-[": "pane::GoBack",
2583                }
2584              },
2585            ]
2586            "#,
2587            KeybindUpdateOperation::Remove {
2588                target: KeybindUpdateTarget {
2589                    context: Some("Editor"),
2590                    keystrokes: &parse_keystrokes("cmd-k cmd-l"),
2591                    action_name: "editor::ConvertToLowerCase",
2592                    action_arguments: None,
2593                },
2594                target_keybind_source: KeybindSource::User,
2595            },
2596            r#"
2597            [
2598              {
2599                "context": "Editor",
2600                "bindings": {
2601                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
2602                  "cmd-[": "pane::GoBack",
2603                }
2604              },
2605            ]
2606            "#,
2607        );
2608    }
2609}