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, 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};
  19
  20use crate::SettingsAssets;
  21use settings_json::{
  22    append_top_level_array_value_in_json_text, parse_json_with_comments,
  23    replace_top_level_array_value_in_json_text,
  24};
  25
  26pub trait KeyBindingValidator: Send + Sync {
  27    fn action_type_id(&self) -> TypeId;
  28    fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>;
  29}
  30
  31pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>);
  32
  33inventory::collect!(KeyBindingValidatorRegistration);
  34
  35pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> =
  36    LazyLock::new(|| {
  37        let mut validators = BTreeMap::new();
  38        for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> {
  39            let validator = validator_registration.0();
  40            validators.insert(validator.action_type_id(), validator);
  41        }
  42        validators
  43    });
  44
  45// Note that the doc comments on these are shown by json-language-server when editing the keymap, so
  46// they should be considered user-facing documentation. Documentation is not handled well with
  47// schemars-0.8 - when there are newlines, it is rendered as plaintext (see
  48// https://github.com/GREsau/schemars/issues/38#issuecomment-2282883519). So for now these docs
  49// avoid newlines.
  50//
  51// TODO: Update to schemars-1.0 once it's released, and add more docs as newlines would be
  52// supported. Tracking issue is https://github.com/GREsau/schemars/issues/112.
  53
  54/// Keymap configuration consisting of sections. Each section may have a context predicate which
  55/// determines whether its bindings are used.
  56#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
  57#[serde(transparent)]
  58pub struct KeymapFile(Vec<KeymapSection>);
  59
  60/// Keymap section which binds keystrokes to actions.
  61#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
  62pub struct KeymapSection {
  63    /// Determines when these bindings are active. When just a name is provided, like `Editor` or
  64    /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`,
  65    /// `X || Y`, `!X` are also supported. Some more complex logic including checking OS and the
  66    /// current file extension are also supported - see [the
  67    /// documentation](https://zed.dev/docs/key-bindings#contexts) for more details.
  68    #[serde(default)]
  69    pub context: String,
  70    /// This option enables specifying keys based on their position on a QWERTY keyboard, by using
  71    /// position-equivalent mappings for some non-QWERTY keyboards. This is currently only supported
  72    /// on macOS. See the documentation for more details.
  73    #[serde(default)]
  74    use_key_equivalents: bool,
  75    /// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The
  76    /// keystrokes key is a string representing a sequence of keystrokes to type, where the
  77    /// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`,
  78    /// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The
  79    /// order of bindings does matter. When the same keystrokes are bound at the same context depth,
  80    /// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI,
  81    /// the later binding for the same action is preferred.
  82    #[serde(default)]
  83    bindings: Option<IndexMap<String, KeymapAction>>,
  84    #[serde(flatten)]
  85    unrecognized_fields: IndexMap<String, Value>,
  86    // This struct intentionally uses permissive types for its fields, rather than validating during
  87    // deserialization. The purpose of this is to allow loading the portion of the keymap that doesn't
  88    // have errors. The downside of this is that the errors are not reported with line+column info.
  89    // Unfortunately the implementations of the `Spanned` types for preserving this information are
  90    // highly inconvenient (`serde_spanned`) and in some cases don't work at all here
  91    // (`json_spanned_>value`). Serde should really have builtin support for this.
  92}
  93
  94impl KeymapSection {
  95    pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> {
  96        self.bindings.iter().flatten()
  97    }
  98}
  99
 100/// Keymap action as a JSON value, since it can either be null for no action, or the name of the
 101/// action, or an array of the name of the action and the action input.
 102///
 103/// Unlike the other json types involved in keymaps (including actions), this doc-comment will not
 104/// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The
 105/// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.
 106#[derive(Debug, Deserialize, Default, Clone)]
 107#[serde(transparent)]
 108pub struct KeymapAction(Value);
 109
 110impl std::fmt::Display for KeymapAction {
 111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 112        match &self.0 {
 113            Value::String(s) => write!(f, "{}", s),
 114            Value::Array(arr) => {
 115                let strings: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
 116                write!(f, "{}", strings.join(", "))
 117            }
 118            _ => write!(f, "{}", self.0),
 119        }
 120    }
 121}
 122
 123impl JsonSchema for KeymapAction {
 124    /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can
 125    /// reference the keymap action schema.
 126    fn schema_name() -> Cow<'static, str> {
 127        "KeymapAction".into()
 128    }
 129
 130    /// This schema will be replaced with the full action schema in
 131    /// `KeymapFile::generate_json_schema`.
 132    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
 133        json_schema!(true)
 134    }
 135}
 136
 137#[derive(Debug)]
 138#[must_use]
 139pub enum KeymapFileLoadResult {
 140    Success {
 141        key_bindings: Vec<KeyBinding>,
 142    },
 143    SomeFailedToLoad {
 144        key_bindings: Vec<KeyBinding>,
 145        error_message: MarkdownString,
 146    },
 147    JsonParseFailure {
 148        error: anyhow::Error,
 149    },
 150}
 151
 152impl KeymapFile {
 153    pub fn parse(content: &str) -> anyhow::Result<Self> {
 154        if content.trim().is_empty() {
 155            return Ok(Self(Vec::new()));
 156        }
 157        parse_json_with_comments::<Self>(content)
 158    }
 159
 160    pub fn load_asset(
 161        asset_path: &str,
 162        source: Option<KeybindSource>,
 163        cx: &App,
 164    ) -> anyhow::Result<Vec<KeyBinding>> {
 165        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
 166            KeymapFileLoadResult::Success { mut key_bindings } => match source {
 167                Some(source) => Ok({
 168                    for key_binding in &mut key_bindings {
 169                        key_binding.set_meta(source.meta());
 170                    }
 171                    key_bindings
 172                }),
 173                None => Ok(key_bindings),
 174            },
 175            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
 176                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
 177            }
 178            KeymapFileLoadResult::JsonParseFailure { error } => {
 179                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
 180            }
 181        }
 182    }
 183
 184    pub fn load_asset_allow_partial_failure(
 185        asset_path: &str,
 186        cx: &App,
 187    ) -> anyhow::Result<Vec<KeyBinding>> {
 188        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
 189            KeymapFileLoadResult::SomeFailedToLoad {
 190                key_bindings,
 191                error_message,
 192                ..
 193            } if key_bindings.is_empty() => {
 194                anyhow::bail!("Error loading built-in keymap \"{asset_path}\": {error_message}",)
 195            }
 196            KeymapFileLoadResult::Success { key_bindings, .. }
 197            | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
 198            KeymapFileLoadResult::JsonParseFailure { error } => {
 199                anyhow::bail!("JSON parse error in built-in keymap \"{asset_path}\": {error}")
 200            }
 201        }
 202    }
 203
 204    #[cfg(feature = "test-support")]
 205    pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> {
 206        match Self::load(content, cx) {
 207            KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings,
 208            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
 209                panic!("{error_message}");
 210            }
 211            KeymapFileLoadResult::JsonParseFailure { error } => {
 212                panic!("JSON parse error: {error}");
 213            }
 214        }
 215    }
 216
 217    pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult {
 218        let keymap_file = match Self::parse(content) {
 219            Ok(keymap_file) => keymap_file,
 220            Err(error) => {
 221                return KeymapFileLoadResult::JsonParseFailure { error };
 222            }
 223        };
 224
 225        // Accumulate errors in order to support partial load of user keymap in the presence of
 226        // errors in context and binding parsing.
 227        let mut errors = Vec::new();
 228        let mut key_bindings = Vec::new();
 229
 230        for KeymapSection {
 231            context,
 232            use_key_equivalents,
 233            bindings,
 234            unrecognized_fields,
 235        } in keymap_file.0.iter()
 236        {
 237            let context_predicate: Option<Rc<KeyBindingContextPredicate>> = if context.is_empty() {
 238                None
 239            } else {
 240                match KeyBindingContextPredicate::parse(context) {
 241                    Ok(context_predicate) => Some(context_predicate.into()),
 242                    Err(err) => {
 243                        // Leading space is to separate from the message indicating which section
 244                        // the error occurred in.
 245                        errors.push((
 246                            context,
 247                            format!(" Parse error in section `context` field: {}", err),
 248                        ));
 249                        continue;
 250                    }
 251                }
 252            };
 253
 254            let mut section_errors = String::new();
 255
 256            if !unrecognized_fields.is_empty() {
 257                write!(
 258                    section_errors,
 259                    "\n\n - Unrecognized fields: {}",
 260                    MarkdownInlineCode(&format!("{:?}", unrecognized_fields.keys()))
 261                )
 262                .unwrap();
 263            }
 264
 265            if let Some(bindings) = bindings {
 266                for (keystrokes, action) in bindings {
 267                    let result = Self::load_keybinding(
 268                        keystrokes,
 269                        action,
 270                        context_predicate.clone(),
 271                        *use_key_equivalents,
 272                        cx,
 273                    );
 274                    match result {
 275                        Ok(key_binding) => {
 276                            key_bindings.push(key_binding);
 277                        }
 278                        Err(err) => {
 279                            let mut lines = err.lines();
 280                            let mut indented_err = lines.next().unwrap().to_string();
 281                            for line in lines {
 282                                indented_err.push_str("  ");
 283                                indented_err.push_str(line);
 284                                indented_err.push_str("\n");
 285                            }
 286                            write!(
 287                                section_errors,
 288                                "\n\n- In binding {}, {indented_err}",
 289                                MarkdownInlineCode(&format!("\"{}\"", keystrokes))
 290                            )
 291                            .unwrap();
 292                        }
 293                    }
 294                }
 295            }
 296
 297            if !section_errors.is_empty() {
 298                errors.push((context, section_errors))
 299            }
 300        }
 301
 302        if errors.is_empty() {
 303            KeymapFileLoadResult::Success { key_bindings }
 304        } else {
 305            let mut error_message = "Errors in user keymap file.\n".to_owned();
 306            for (context, section_errors) in errors {
 307                if context.is_empty() {
 308                    let _ = write!(error_message, "\n\nIn section without context predicate:");
 309                } else {
 310                    let _ = write!(
 311                        error_message,
 312                        "\n\nIn section with {}:",
 313                        MarkdownInlineCode(&format!("context = \"{}\"", context))
 314                    );
 315                }
 316                let _ = write!(error_message, "{section_errors}");
 317            }
 318            KeymapFileLoadResult::SomeFailedToLoad {
 319                key_bindings,
 320                error_message: MarkdownString(error_message),
 321            }
 322        }
 323    }
 324
 325    fn load_keybinding(
 326        keystrokes: &str,
 327        action: &KeymapAction,
 328        context: Option<Rc<KeyBindingContextPredicate>>,
 329        use_key_equivalents: bool,
 330        cx: &App,
 331    ) -> std::result::Result<KeyBinding, String> {
 332        let (action, action_input_string) = Self::build_keymap_action(action, cx)?;
 333
 334        let key_binding = match KeyBinding::load(
 335            keystrokes,
 336            action,
 337            context,
 338            use_key_equivalents,
 339            action_input_string.map(SharedString::from),
 340            cx.keyboard_mapper().as_ref(),
 341        ) {
 342            Ok(key_binding) => key_binding,
 343            Err(InvalidKeystrokeError { keystroke }) => {
 344                return Err(format!(
 345                    "invalid keystroke {}. {}",
 346                    MarkdownInlineCode(&format!("\"{}\"", &keystroke)),
 347                    KEYSTROKE_PARSE_EXPECTED_MESSAGE
 348                ));
 349            }
 350        };
 351
 352        if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) {
 353            match validator.validate(&key_binding) {
 354                Ok(()) => Ok(key_binding),
 355                Err(error) => Err(error.0),
 356            }
 357        } else {
 358            Ok(key_binding)
 359        }
 360    }
 361
 362    pub fn parse_action(
 363        action: &KeymapAction,
 364    ) -> Result<Option<(&String, Option<&Value>)>, String> {
 365        let name_and_input = match &action.0 {
 366            Value::Array(items) => {
 367                if items.len() != 2 {
 368                    return Err(format!(
 369                        "expected two-element array of `[name, input]`. \
 370                        Instead found {}.",
 371                        MarkdownInlineCode(&action.0.to_string())
 372                    ));
 373                }
 374                let serde_json::Value::String(ref name) = items[0] else {
 375                    return Err(format!(
 376                        "expected two-element array of `[name, input]`, \
 377                        but the first element is not a string in {}.",
 378                        MarkdownInlineCode(&action.0.to_string())
 379                    ));
 380                };
 381                Some((name, Some(&items[1])))
 382            }
 383            Value::String(name) => Some((name, None)),
 384            Value::Null => None,
 385            _ => {
 386                return Err(format!(
 387                    "expected two-element array of `[name, input]`. \
 388                    Instead found {}.",
 389                    MarkdownInlineCode(&action.0.to_string())
 390                ));
 391            }
 392        };
 393        Ok(name_and_input)
 394    }
 395
 396    fn build_keymap_action(
 397        action: &KeymapAction,
 398        cx: &App,
 399    ) -> std::result::Result<(Box<dyn Action>, Option<String>), String> {
 400        let (build_result, action_input_string) = match Self::parse_action(action)? {
 401            Some((name, action_input)) if name.as_str() == ActionSequence::name_for_type() => {
 402                match action_input {
 403                    Some(action_input) => (
 404                        ActionSequence::build_sequence(action_input.clone(), cx),
 405                        None,
 406                    ),
 407                    None => (Err(ActionSequence::expected_array_error()), None),
 408                }
 409            }
 410            Some((name, Some(action_input))) => {
 411                let action_input_string = action_input.to_string();
 412                (
 413                    cx.build_action(name, Some(action_input.clone())),
 414                    Some(action_input_string),
 415                )
 416            }
 417            Some((name, None)) => (cx.build_action(name, None), None),
 418            None => (Ok(NoAction.boxed_clone()), None),
 419        };
 420
 421        let action = match build_result {
 422            Ok(action) => action,
 423            Err(ActionBuildError::NotFound { name }) => {
 424                return Err(format!(
 425                    "didn't find an action named {}.",
 426                    MarkdownInlineCode(&format!("\"{}\"", &name))
 427                ));
 428            }
 429            Err(ActionBuildError::BuildError { name, error }) => match action_input_string {
 430                Some(action_input_string) => {
 431                    return Err(format!(
 432                        "can't build {} action from input value {}: {}",
 433                        MarkdownInlineCode(&format!("\"{}\"", &name)),
 434                        MarkdownInlineCode(&action_input_string),
 435                        MarkdownEscaped(&error.to_string())
 436                    ));
 437                }
 438                None => {
 439                    return Err(format!(
 440                        "can't build {} action - it requires input data via [name, input]: {}",
 441                        MarkdownInlineCode(&format!("\"{}\"", &name)),
 442                        MarkdownEscaped(&error.to_string())
 443                    ));
 444                }
 445            },
 446        };
 447
 448        Ok((action, action_input_string))
 449    }
 450
 451    /// Creates a JSON schema generator, suitable for generating json schemas
 452    /// for actions
 453    pub fn action_schema_generator() -> schemars::SchemaGenerator {
 454        schemars::generate::SchemaSettings::draft2019_09().into_generator()
 455    }
 456
 457    pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
 458        // instead of using DefaultDenyUnknownFields, actions typically use
 459        // `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This
 460        // is because the rest of the keymap will still load in these cases, whereas other settings
 461        // files would not.
 462        let mut generator = Self::action_schema_generator();
 463
 464        let action_schemas = cx.action_schemas(&mut generator);
 465        let action_documentation = cx.action_documentation();
 466        let deprecations = cx.deprecated_actions_to_preferred_actions();
 467        let deprecation_messages = cx.action_deprecation_messages();
 468        KeymapFile::generate_json_schema(
 469            generator,
 470            action_schemas,
 471            action_documentation,
 472            deprecations,
 473            deprecation_messages,
 474        )
 475    }
 476
 477    fn generate_json_schema(
 478        mut generator: schemars::SchemaGenerator,
 479        action_schemas: Vec<(&'static str, Option<schemars::Schema>)>,
 480        action_documentation: &HashMap<&'static str, &'static str>,
 481        deprecations: &HashMap<&'static str, &'static str>,
 482        deprecation_messages: &HashMap<&'static str, &'static str>,
 483    ) -> serde_json::Value {
 484        fn add_deprecation(schema: &mut schemars::Schema, message: String) {
 485            schema.insert(
 486                // deprecationMessage is not part of the JSON Schema spec, but
 487                // json-language-server recognizes it.
 488                "deprecationMessage".to_string(),
 489                Value::String(message),
 490            );
 491        }
 492
 493        fn add_deprecation_preferred_name(schema: &mut schemars::Schema, new_name: &str) {
 494            add_deprecation(schema, format!("Deprecated, use {new_name}"));
 495        }
 496
 497        fn add_description(schema: &mut schemars::Schema, description: &str) {
 498            schema.insert(
 499                "description".to_string(),
 500                Value::String(description.to_string()),
 501            );
 502        }
 503
 504        let empty_object = json_schema!({
 505            "type": "object"
 506        });
 507
 508        // This is a workaround for a json-language-server issue where it matches the first
 509        // alternative that matches the value's shape and uses that for documentation.
 510        //
 511        // In the case of the array validations, it would even provide an error saying that the name
 512        // must match the name of the first alternative.
 513        let mut empty_action_name = json_schema!({
 514            "type": "string",
 515            "const": ""
 516        });
 517        let no_action_message = "No action named this.";
 518        add_description(&mut empty_action_name, no_action_message);
 519        add_deprecation(&mut empty_action_name, no_action_message.to_string());
 520        let empty_action_name_with_input = json_schema!({
 521            "type": "array",
 522            "items": [
 523                empty_action_name,
 524                true
 525            ],
 526            "minItems": 2,
 527            "maxItems": 2
 528        });
 529        let mut keymap_action_alternatives = vec![empty_action_name, empty_action_name_with_input];
 530
 531        let mut empty_schema_action_names = vec![];
 532        for (name, action_schema) in action_schemas.into_iter() {
 533            let deprecation = if name == NoAction.name() {
 534                Some("null")
 535            } else {
 536                deprecations.get(name).copied()
 537            };
 538
 539            // Add an alternative for plain action names.
 540            let mut plain_action = json_schema!({
 541                "type": "string",
 542                "const": name
 543            });
 544            if let Some(message) = deprecation_messages.get(name) {
 545                add_deprecation(&mut plain_action, message.to_string());
 546            } else if let Some(new_name) = deprecation {
 547                add_deprecation_preferred_name(&mut plain_action, new_name);
 548            }
 549            let description = action_documentation.get(name);
 550            if let Some(description) = &description {
 551                add_description(&mut plain_action, description);
 552            }
 553            keymap_action_alternatives.push(plain_action);
 554
 555            // Add an alternative for actions with data specified as a [name, data] array.
 556            //
 557            // When a struct with no deserializable fields is added by deriving `Action`, an empty
 558            // object schema is produced. The action should be invoked without data in this case.
 559            if let Some(schema) = action_schema
 560                && schema != empty_object
 561            {
 562                let mut matches_action_name = json_schema!({
 563                    "const": name
 564                });
 565                if let Some(description) = &description {
 566                    add_description(&mut matches_action_name, description);
 567                }
 568                if let Some(message) = deprecation_messages.get(name) {
 569                    add_deprecation(&mut matches_action_name, message.to_string());
 570                } else if let Some(new_name) = deprecation {
 571                    add_deprecation_preferred_name(&mut matches_action_name, new_name);
 572                }
 573                let action_with_input = json_schema!({
 574                    "type": "array",
 575                    "items": [matches_action_name, schema],
 576                    "minItems": 2,
 577                    "maxItems": 2
 578                });
 579                keymap_action_alternatives.push(action_with_input);
 580            } else {
 581                empty_schema_action_names.push(name);
 582            }
 583        }
 584
 585        if !empty_schema_action_names.is_empty() {
 586            let action_names = json_schema!({ "enum": empty_schema_action_names });
 587            let no_properties_allowed = json_schema!({
 588                "type": "object",
 589                "additionalProperties": false
 590            });
 591            let mut actions_with_empty_input = json_schema!({
 592                "type": "array",
 593                "items": [action_names, no_properties_allowed],
 594                "minItems": 2,
 595                "maxItems": 2
 596            });
 597            add_deprecation(
 598                &mut actions_with_empty_input,
 599                "This action does not take input - just the action name string should be used."
 600                    .to_string(),
 601            );
 602            keymap_action_alternatives.push(actions_with_empty_input);
 603        }
 604
 605        // Placing null first causes json-language-server to default assuming actions should be
 606        // null, so place it last.
 607        keymap_action_alternatives.push(json_schema!({
 608            "type": "null"
 609        }));
 610
 611        // The `KeymapSection` schema will reference the `KeymapAction` schema by name, so setting
 612        // the definition of `KeymapAction` results in the full action schema being used.
 613        generator.definitions_mut().insert(
 614            KeymapAction::schema_name().to_string(),
 615            json!({
 616                "oneOf": keymap_action_alternatives
 617            }),
 618        );
 619
 620        generator.root_schema_for::<KeymapFile>().to_value()
 621    }
 622
 623    pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
 624        self.0.iter()
 625    }
 626
 627    pub async fn load_keymap_file(fs: &Arc<dyn Fs>) -> Result<String> {
 628        match fs.load(paths::keymap_file()).await {
 629            result @ Ok(_) => result,
 630            Err(err) => {
 631                if let Some(e) = err.downcast_ref::<std::io::Error>()
 632                    && e.kind() == std::io::ErrorKind::NotFound
 633                {
 634                    return Ok(crate::initial_keymap_content().to_string());
 635                }
 636                Err(err)
 637            }
 638        }
 639    }
 640
 641    pub fn update_keybinding<'a>(
 642        mut operation: KeybindUpdateOperation<'a>,
 643        mut keymap_contents: String,
 644        tab_size: usize,
 645        keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
 646    ) -> Result<String> {
 647        match operation {
 648            // if trying to replace a keybinding that is not user-defined, treat it as an add operation
 649            KeybindUpdateOperation::Replace {
 650                target_keybind_source: target_source,
 651                source,
 652                target,
 653            } if target_source != KeybindSource::User => {
 654                operation = KeybindUpdateOperation::Add {
 655                    source,
 656                    from: Some(target),
 657                };
 658            }
 659            // if trying to remove a keybinding that is not user-defined, treat it as creating a binding
 660            // that binds it to `zed::NoAction`
 661            KeybindUpdateOperation::Remove {
 662                target,
 663                target_keybind_source,
 664            } if target_keybind_source != KeybindSource::User => {
 665                let mut source = target.clone();
 666                source.action_name = gpui::NoAction.name();
 667                source.action_arguments.take();
 668                operation = KeybindUpdateOperation::Add {
 669                    source,
 670                    from: Some(target),
 671                };
 672            }
 673            _ => {}
 674        }
 675
 676        // Sanity check that keymap contents are valid, even though we only use it for Replace.
 677        // We don't want to modify the file if it's invalid.
 678        let keymap = Self::parse(&keymap_contents).context("Failed to parse keymap")?;
 679
 680        if let KeybindUpdateOperation::Remove { target, .. } = operation {
 681            let target_action_value = target
 682                .action_value()
 683                .context("Failed to generate target action JSON value")?;
 684            let Some((index, keystrokes_str)) =
 685                find_binding(&keymap, &target, &target_action_value, keyboard_mapper)
 686            else {
 687                anyhow::bail!("Failed to find keybinding to remove");
 688            };
 689            let is_only_binding = keymap.0[index]
 690                .bindings
 691                .as_ref()
 692                .is_none_or(|bindings| bindings.len() == 1);
 693            let key_path: &[&str] = if is_only_binding {
 694                &[]
 695            } else {
 696                &["bindings", keystrokes_str]
 697            };
 698            let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 699                &keymap_contents,
 700                key_path,
 701                None,
 702                None,
 703                index,
 704                tab_size,
 705            );
 706            keymap_contents.replace_range(replace_range, &replace_value);
 707            return Ok(keymap_contents);
 708        }
 709
 710        if let KeybindUpdateOperation::Replace { source, target, .. } = operation {
 711            let target_action_value = target
 712                .action_value()
 713                .context("Failed to generate target action JSON value")?;
 714            let source_action_value = source
 715                .action_value()
 716                .context("Failed to generate source action JSON value")?;
 717
 718            if let Some((index, keystrokes_str)) =
 719                find_binding(&keymap, &target, &target_action_value, keyboard_mapper)
 720            {
 721                if target.context == source.context {
 722                    // if we are only changing the keybinding (common case)
 723                    // not the context, etc. Then just update the binding in place
 724
 725                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 726                        &keymap_contents,
 727                        &["bindings", keystrokes_str],
 728                        Some(&source_action_value),
 729                        Some(&source.keystrokes_unparsed()),
 730                        index,
 731                        tab_size,
 732                    );
 733                    keymap_contents.replace_range(replace_range, &replace_value);
 734
 735                    return Ok(keymap_contents);
 736                } else if keymap.0[index]
 737                    .bindings
 738                    .as_ref()
 739                    .is_none_or(|bindings| bindings.len() == 1)
 740                {
 741                    // if we are replacing the only binding in the section,
 742                    // just update the section in place, updating the context
 743                    // and the binding
 744
 745                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 746                        &keymap_contents,
 747                        &["bindings", keystrokes_str],
 748                        Some(&source_action_value),
 749                        Some(&source.keystrokes_unparsed()),
 750                        index,
 751                        tab_size,
 752                    );
 753                    keymap_contents.replace_range(replace_range, &replace_value);
 754
 755                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 756                        &keymap_contents,
 757                        &["context"],
 758                        source.context.map(Into::into).as_ref(),
 759                        None,
 760                        index,
 761                        tab_size,
 762                    );
 763                    keymap_contents.replace_range(replace_range, &replace_value);
 764                    return Ok(keymap_contents);
 765                } else {
 766                    // if we are replacing one of multiple bindings in a section
 767                    // with a context change, remove the existing binding from the
 768                    // section, then treat this operation as an add operation of the
 769                    // new binding with the updated context.
 770
 771                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 772                        &keymap_contents,
 773                        &["bindings", keystrokes_str],
 774                        None,
 775                        None,
 776                        index,
 777                        tab_size,
 778                    );
 779                    keymap_contents.replace_range(replace_range, &replace_value);
 780                    operation = KeybindUpdateOperation::Add {
 781                        source,
 782                        from: Some(target),
 783                    };
 784                }
 785            } else {
 786                log::warn!(
 787                    "Failed to find keybinding to update `{:?} -> {}` creating new binding for `{:?} -> {}` instead",
 788                    target.keystrokes,
 789                    target_action_value,
 790                    source.keystrokes,
 791                    source_action_value,
 792                );
 793                operation = KeybindUpdateOperation::Add {
 794                    source,
 795                    from: Some(target),
 796                };
 797            }
 798        }
 799
 800        if let KeybindUpdateOperation::Add {
 801            source: keybinding,
 802            from,
 803        } = operation
 804        {
 805            let mut value = serde_json::Map::with_capacity(4);
 806            if let Some(context) = keybinding.context {
 807                value.insert("context".to_string(), context.into());
 808            }
 809            let use_key_equivalents = from.and_then(|from| {
 810                let action_value = from.action_value().context("Failed to serialize action value. `use_key_equivalents` on new keybinding may be incorrect.").log_err()?;
 811                let (index, _) = find_binding(&keymap, &from, &action_value, keyboard_mapper)?;
 812                Some(keymap.0[index].use_key_equivalents)
 813            }).unwrap_or(false);
 814            if use_key_equivalents {
 815                value.insert("use_key_equivalents".to_string(), true.into());
 816            }
 817
 818            value.insert("bindings".to_string(), {
 819                let mut bindings = serde_json::Map::new();
 820                let action = keybinding.action_value()?;
 821                bindings.insert(keybinding.keystrokes_unparsed(), action);
 822                bindings.into()
 823            });
 824
 825            let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
 826                &keymap_contents,
 827                &value.into(),
 828                tab_size,
 829            );
 830            keymap_contents.replace_range(replace_range, &replace_value);
 831        }
 832        return Ok(keymap_contents);
 833
 834        fn find_binding<'a, 'b>(
 835            keymap: &'b KeymapFile,
 836            target: &KeybindUpdateTarget<'a>,
 837            target_action_value: &Value,
 838            keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
 839        ) -> Option<(usize, &'b str)> {
 840            let target_context_parsed =
 841                KeyBindingContextPredicate::parse(target.context.unwrap_or("")).ok();
 842            for (index, section) in keymap.sections().enumerate() {
 843                let section_context_parsed =
 844                    KeyBindingContextPredicate::parse(&section.context).ok();
 845                if section_context_parsed != target_context_parsed {
 846                    continue;
 847                }
 848                let Some(bindings) = &section.bindings else {
 849                    continue;
 850                };
 851                for (keystrokes_str, action) in bindings {
 852                    let Ok(keystrokes) = keystrokes_str
 853                        .split_whitespace()
 854                        .map(|source| {
 855                            let keystroke = Keystroke::parse(source)?;
 856                            Ok(KeybindingKeystroke::new_with_mapper(
 857                                keystroke,
 858                                false,
 859                                keyboard_mapper,
 860                            ))
 861                        })
 862                        .collect::<Result<Vec<_>, InvalidKeystrokeError>>()
 863                    else {
 864                        continue;
 865                    };
 866                    if keystrokes.len() != target.keystrokes.len()
 867                        || !keystrokes
 868                            .iter()
 869                            .zip(target.keystrokes)
 870                            .all(|(a, b)| a.inner().should_match(b))
 871                    {
 872                        continue;
 873                    }
 874                    if &action.0 != target_action_value {
 875                        continue;
 876                    }
 877                    return Some((index, keystrokes_str));
 878                }
 879            }
 880            None
 881        }
 882    }
 883}
 884
 885#[derive(Clone, Debug)]
 886pub enum KeybindUpdateOperation<'a> {
 887    Replace {
 888        /// Describes the keybind to create
 889        source: KeybindUpdateTarget<'a>,
 890        /// Describes the keybind to remove
 891        target: KeybindUpdateTarget<'a>,
 892        target_keybind_source: KeybindSource,
 893    },
 894    Add {
 895        source: KeybindUpdateTarget<'a>,
 896        from: Option<KeybindUpdateTarget<'a>>,
 897    },
 898    Remove {
 899        target: KeybindUpdateTarget<'a>,
 900        target_keybind_source: KeybindSource,
 901    },
 902}
 903
 904impl KeybindUpdateOperation<'_> {
 905    pub fn generate_telemetry(
 906        &self,
 907    ) -> (
 908        // The keybind that is created
 909        String,
 910        // The keybinding that was removed
 911        String,
 912        // The source of the keybinding
 913        String,
 914    ) {
 915        let (new_binding, removed_binding, source) = match &self {
 916            KeybindUpdateOperation::Replace {
 917                source,
 918                target,
 919                target_keybind_source,
 920            } => (Some(source), Some(target), Some(*target_keybind_source)),
 921            KeybindUpdateOperation::Add { source, .. } => (Some(source), None, None),
 922            KeybindUpdateOperation::Remove {
 923                target,
 924                target_keybind_source,
 925            } => (None, Some(target), Some(*target_keybind_source)),
 926        };
 927
 928        let new_binding = new_binding
 929            .map(KeybindUpdateTarget::telemetry_string)
 930            .unwrap_or("null".to_owned());
 931        let removed_binding = removed_binding
 932            .map(KeybindUpdateTarget::telemetry_string)
 933            .unwrap_or("null".to_owned());
 934
 935        let source = source
 936            .as_ref()
 937            .map(KeybindSource::name)
 938            .map(ToOwned::to_owned)
 939            .unwrap_or("null".to_owned());
 940
 941        (new_binding, removed_binding, source)
 942    }
 943}
 944
 945impl<'a> KeybindUpdateOperation<'a> {
 946    pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
 947        Self::Add { source, from: None }
 948    }
 949}
 950
 951#[derive(Debug, Clone)]
 952pub struct KeybindUpdateTarget<'a> {
 953    pub context: Option<&'a str>,
 954    pub keystrokes: &'a [KeybindingKeystroke],
 955    pub action_name: &'a str,
 956    pub action_arguments: Option<&'a str>,
 957}
 958
 959impl<'a> KeybindUpdateTarget<'a> {
 960    fn action_value(&self) -> Result<Value> {
 961        if self.action_name == gpui::NoAction.name() {
 962            return Ok(Value::Null);
 963        }
 964        let action_name: Value = self.action_name.into();
 965        let value = match self.action_arguments {
 966            Some(args) if !args.is_empty() => {
 967                let args = serde_json::from_str::<Value>(args)
 968                    .context("Failed to parse action arguments as JSON")?;
 969                serde_json::json!([action_name, args])
 970            }
 971            _ => action_name,
 972        };
 973        Ok(value)
 974    }
 975
 976    fn keystrokes_unparsed(&self) -> String {
 977        let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8);
 978        for keystroke in self.keystrokes {
 979            // The reason use `keystroke.unparse()` instead of `keystroke.inner.unparse()`
 980            // here is that, we want the user to use `ctrl-shift-4` instead of `ctrl-$`
 981            // by default on Windows.
 982            keystrokes.push_str(&keystroke.unparse());
 983            keystrokes.push(' ');
 984        }
 985        keystrokes.pop();
 986        keystrokes
 987    }
 988
 989    fn telemetry_string(&self) -> String {
 990        format!(
 991            "action_name: {}, context: {}, action_arguments: {}, keystrokes: {}",
 992            self.action_name,
 993            self.context.unwrap_or("global"),
 994            self.action_arguments.unwrap_or("none"),
 995            self.keystrokes_unparsed()
 996        )
 997    }
 998}
 999
1000#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]
1001pub enum KeybindSource {
1002    User,
1003    Vim,
1004    Base,
1005    #[default]
1006    Default,
1007    Unknown,
1008}
1009
1010impl KeybindSource {
1011    const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Base as u32);
1012    const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Default as u32);
1013    const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Vim as u32);
1014    const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::User as u32);
1015
1016    pub fn name(&self) -> &'static str {
1017        match self {
1018            KeybindSource::User => "User",
1019            KeybindSource::Default => "Default",
1020            KeybindSource::Base => "Base",
1021            KeybindSource::Vim => "Vim",
1022            KeybindSource::Unknown => "Unknown",
1023        }
1024    }
1025
1026    pub fn meta(&self) -> KeyBindingMetaIndex {
1027        match self {
1028            KeybindSource::User => Self::USER,
1029            KeybindSource::Default => Self::DEFAULT,
1030            KeybindSource::Base => Self::BASE,
1031            KeybindSource::Vim => Self::VIM,
1032            KeybindSource::Unknown => KeyBindingMetaIndex(*self as u32),
1033        }
1034    }
1035
1036    pub fn from_meta(index: KeyBindingMetaIndex) -> Self {
1037        match index {
1038            Self::USER => KeybindSource::User,
1039            Self::BASE => KeybindSource::Base,
1040            Self::DEFAULT => KeybindSource::Default,
1041            Self::VIM => KeybindSource::Vim,
1042            _ => KeybindSource::Unknown,
1043        }
1044    }
1045}
1046
1047impl From<KeyBindingMetaIndex> for KeybindSource {
1048    fn from(index: KeyBindingMetaIndex) -> Self {
1049        Self::from_meta(index)
1050    }
1051}
1052
1053impl From<KeybindSource> for KeyBindingMetaIndex {
1054    fn from(source: KeybindSource) -> Self {
1055        source.meta()
1056    }
1057}
1058
1059/// Runs a sequence of actions. Does not wait for asynchronous actions to complete before running
1060/// the next action. Currently only works in workspace windows.
1061///
1062/// This action is special-cased in keymap parsing to allow it to access `App` while parsing, so
1063/// that it can parse its input actions.
1064pub struct ActionSequence(pub Vec<Box<dyn Action>>);
1065
1066register_action!(ActionSequence);
1067
1068impl ActionSequence {
1069    fn build_sequence(
1070        value: Value,
1071        cx: &App,
1072    ) -> std::result::Result<Box<dyn Action>, ActionBuildError> {
1073        match value {
1074            Value::Array(values) => {
1075                let actions = values
1076                    .into_iter()
1077                    .enumerate()
1078                    .map(|(index, action)| {
1079                        match KeymapFile::build_keymap_action(&KeymapAction(action), cx) {
1080                            Ok((action, _)) => Ok(action),
1081                            Err(err) => {
1082                                return Err(ActionBuildError::BuildError {
1083                                    name: Self::name_for_type().to_string(),
1084                                    error: anyhow::anyhow!(
1085                                        "error at sequence index {index}: {err}"
1086                                    ),
1087                                });
1088                            }
1089                        }
1090                    })
1091                    .collect::<Result<Vec<_>, _>>()?;
1092                Ok(Box::new(Self(actions)))
1093            }
1094            _ => Err(Self::expected_array_error()),
1095        }
1096    }
1097
1098    fn expected_array_error() -> ActionBuildError {
1099        ActionBuildError::BuildError {
1100            name: Self::name_for_type().to_string(),
1101            error: anyhow::anyhow!("expected array of actions"),
1102        }
1103    }
1104}
1105
1106impl Action for ActionSequence {
1107    fn name(&self) -> &'static str {
1108        Self::name_for_type()
1109    }
1110
1111    fn name_for_type() -> &'static str
1112    where
1113        Self: Sized,
1114    {
1115        "action::Sequence"
1116    }
1117
1118    fn partial_eq(&self, action: &dyn Action) -> bool {
1119        action
1120            .as_any()
1121            .downcast_ref::<Self>()
1122            .map_or(false, |other| {
1123                self.0.len() == other.0.len()
1124                    && self
1125                        .0
1126                        .iter()
1127                        .zip(other.0.iter())
1128                        .all(|(a, b)| a.partial_eq(b.as_ref()))
1129            })
1130    }
1131
1132    fn boxed_clone(&self) -> Box<dyn Action> {
1133        Box::new(ActionSequence(
1134            self.0
1135                .iter()
1136                .map(|action| action.boxed_clone())
1137                .collect::<Vec<_>>(),
1138        ))
1139    }
1140
1141    fn build(_value: Value) -> Result<Box<dyn Action>> {
1142        Err(anyhow::anyhow!(
1143            "{} cannot be built directly",
1144            Self::name_for_type()
1145        ))
1146    }
1147
1148    fn action_json_schema(generator: &mut schemars::SchemaGenerator) -> Option<schemars::Schema> {
1149        let keymap_action_schema = generator.subschema_for::<KeymapAction>();
1150        Some(json_schema!({
1151            "type": "array",
1152            "items": keymap_action_schema
1153        }))
1154    }
1155
1156    fn deprecated_aliases() -> &'static [&'static str] {
1157        &[]
1158    }
1159
1160    fn deprecation_message() -> Option<&'static str> {
1161        None
1162    }
1163
1164    fn documentation() -> Option<&'static str> {
1165        Some(
1166            "Runs a sequence of actions.\n\n\
1167            NOTE: This does **not** wait for asynchronous actions to complete before running the next action.",
1168        )
1169    }
1170}
1171
1172#[cfg(test)]
1173mod tests {
1174    use gpui::{DummyKeyboardMapper, KeybindingKeystroke, Keystroke};
1175    use unindent::Unindent;
1176
1177    use crate::{
1178        KeybindSource, KeymapFile,
1179        keymap_file::{KeybindUpdateOperation, KeybindUpdateTarget},
1180    };
1181
1182    #[test]
1183    fn can_deserialize_keymap_with_trailing_comma() {
1184        let json = indoc::indoc! {"[
1185              // Standard macOS bindings
1186              {
1187                \"bindings\": {
1188                  \"up\": \"menu::SelectPrevious\",
1189                },
1190              },
1191            ]
1192                  "
1193        };
1194        KeymapFile::parse(json).unwrap();
1195    }
1196
1197    #[track_caller]
1198    fn check_keymap_update(
1199        input: impl ToString,
1200        operation: KeybindUpdateOperation,
1201        expected: impl ToString,
1202    ) {
1203        let result = KeymapFile::update_keybinding(
1204            operation,
1205            input.to_string(),
1206            4,
1207            &gpui::DummyKeyboardMapper,
1208        )
1209        .expect("Update succeeded");
1210        pretty_assertions::assert_eq!(expected.to_string(), result);
1211    }
1212
1213    #[track_caller]
1214    fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
1215        keystrokes
1216            .split(' ')
1217            .map(|s| {
1218                KeybindingKeystroke::new_with_mapper(
1219                    Keystroke::parse(s).expect("Keystrokes valid"),
1220                    false,
1221                    &DummyKeyboardMapper,
1222                )
1223            })
1224            .collect()
1225    }
1226
1227    #[test]
1228    fn keymap_update() {
1229        zlog::init_test();
1230
1231        check_keymap_update(
1232            "[]",
1233            KeybindUpdateOperation::add(KeybindUpdateTarget {
1234                keystrokes: &parse_keystrokes("ctrl-a"),
1235                action_name: "zed::SomeAction",
1236                context: None,
1237                action_arguments: None,
1238            }),
1239            r#"[
1240                {
1241                    "bindings": {
1242                        "ctrl-a": "zed::SomeAction"
1243                    }
1244                }
1245            ]"#
1246            .unindent(),
1247        );
1248
1249        check_keymap_update(
1250            "[]",
1251            KeybindUpdateOperation::add(KeybindUpdateTarget {
1252                keystrokes: &parse_keystrokes("\\ a"),
1253                action_name: "zed::SomeAction",
1254                context: None,
1255                action_arguments: None,
1256            }),
1257            r#"[
1258                {
1259                    "bindings": {
1260                        "\\ a": "zed::SomeAction"
1261                    }
1262                }
1263            ]"#
1264            .unindent(),
1265        );
1266
1267        check_keymap_update(
1268            "[]",
1269            KeybindUpdateOperation::add(KeybindUpdateTarget {
1270                keystrokes: &parse_keystrokes("ctrl-a"),
1271                action_name: "zed::SomeAction",
1272                context: None,
1273                action_arguments: Some(""),
1274            }),
1275            r#"[
1276                {
1277                    "bindings": {
1278                        "ctrl-a": "zed::SomeAction"
1279                    }
1280                }
1281            ]"#
1282            .unindent(),
1283        );
1284
1285        check_keymap_update(
1286            r#"[
1287                {
1288                    "bindings": {
1289                        "ctrl-a": "zed::SomeAction"
1290                    }
1291                }
1292            ]"#
1293            .unindent(),
1294            KeybindUpdateOperation::add(KeybindUpdateTarget {
1295                keystrokes: &parse_keystrokes("ctrl-b"),
1296                action_name: "zed::SomeOtherAction",
1297                context: None,
1298                action_arguments: None,
1299            }),
1300            r#"[
1301                {
1302                    "bindings": {
1303                        "ctrl-a": "zed::SomeAction"
1304                    }
1305                },
1306                {
1307                    "bindings": {
1308                        "ctrl-b": "zed::SomeOtherAction"
1309                    }
1310                }
1311            ]"#
1312            .unindent(),
1313        );
1314
1315        check_keymap_update(
1316            r#"[
1317                {
1318                    "bindings": {
1319                        "ctrl-a": "zed::SomeAction"
1320                    }
1321                }
1322            ]"#
1323            .unindent(),
1324            KeybindUpdateOperation::add(KeybindUpdateTarget {
1325                keystrokes: &parse_keystrokes("ctrl-b"),
1326                action_name: "zed::SomeOtherAction",
1327                context: None,
1328                action_arguments: Some(r#"{"foo": "bar"}"#),
1329            }),
1330            r#"[
1331                {
1332                    "bindings": {
1333                        "ctrl-a": "zed::SomeAction"
1334                    }
1335                },
1336                {
1337                    "bindings": {
1338                        "ctrl-b": [
1339                            "zed::SomeOtherAction",
1340                            {
1341                                "foo": "bar"
1342                            }
1343                        ]
1344                    }
1345                }
1346            ]"#
1347            .unindent(),
1348        );
1349
1350        check_keymap_update(
1351            r#"[
1352                {
1353                    "bindings": {
1354                        "ctrl-a": "zed::SomeAction"
1355                    }
1356                }
1357            ]"#
1358            .unindent(),
1359            KeybindUpdateOperation::add(KeybindUpdateTarget {
1360                keystrokes: &parse_keystrokes("ctrl-b"),
1361                action_name: "zed::SomeOtherAction",
1362                context: Some("Zed > Editor && some_condition = true"),
1363                action_arguments: Some(r#"{"foo": "bar"}"#),
1364            }),
1365            r#"[
1366                {
1367                    "bindings": {
1368                        "ctrl-a": "zed::SomeAction"
1369                    }
1370                },
1371                {
1372                    "context": "Zed > Editor && some_condition = true",
1373                    "bindings": {
1374                        "ctrl-b": [
1375                            "zed::SomeOtherAction",
1376                            {
1377                                "foo": "bar"
1378                            }
1379                        ]
1380                    }
1381                }
1382            ]"#
1383            .unindent(),
1384        );
1385
1386        check_keymap_update(
1387            r#"[
1388                {
1389                    "bindings": {
1390                        "ctrl-a": "zed::SomeAction"
1391                    }
1392                }
1393            ]"#
1394            .unindent(),
1395            KeybindUpdateOperation::Replace {
1396                target: KeybindUpdateTarget {
1397                    keystrokes: &parse_keystrokes("ctrl-a"),
1398                    action_name: "zed::SomeAction",
1399                    context: None,
1400                    action_arguments: None,
1401                },
1402                source: KeybindUpdateTarget {
1403                    keystrokes: &parse_keystrokes("ctrl-b"),
1404                    action_name: "zed::SomeOtherAction",
1405                    context: None,
1406                    action_arguments: Some(r#"{"foo": "bar"}"#),
1407                },
1408                target_keybind_source: KeybindSource::Base,
1409            },
1410            r#"[
1411                {
1412                    "bindings": {
1413                        "ctrl-a": "zed::SomeAction"
1414                    }
1415                },
1416                {
1417                    "bindings": {
1418                        "ctrl-b": [
1419                            "zed::SomeOtherAction",
1420                            {
1421                                "foo": "bar"
1422                            }
1423                        ]
1424                    }
1425                }
1426            ]"#
1427            .unindent(),
1428        );
1429
1430        check_keymap_update(
1431            r#"[
1432                {
1433                    "bindings": {
1434                        "a": "zed::SomeAction"
1435                    }
1436                }
1437            ]"#
1438            .unindent(),
1439            KeybindUpdateOperation::Replace {
1440                target: KeybindUpdateTarget {
1441                    keystrokes: &parse_keystrokes("a"),
1442                    action_name: "zed::SomeAction",
1443                    context: None,
1444                    action_arguments: None,
1445                },
1446                source: KeybindUpdateTarget {
1447                    keystrokes: &parse_keystrokes("ctrl-b"),
1448                    action_name: "zed::SomeOtherAction",
1449                    context: None,
1450                    action_arguments: Some(r#"{"foo": "bar"}"#),
1451                },
1452                target_keybind_source: KeybindSource::User,
1453            },
1454            r#"[
1455                {
1456                    "bindings": {
1457                        "ctrl-b": [
1458                            "zed::SomeOtherAction",
1459                            {
1460                                "foo": "bar"
1461                            }
1462                        ]
1463                    }
1464                }
1465            ]"#
1466            .unindent(),
1467        );
1468
1469        check_keymap_update(
1470            r#"[
1471                {
1472                    "bindings": {
1473                        "\\ a": "zed::SomeAction"
1474                    }
1475                }
1476            ]"#
1477            .unindent(),
1478            KeybindUpdateOperation::Replace {
1479                target: KeybindUpdateTarget {
1480                    keystrokes: &parse_keystrokes("\\ a"),
1481                    action_name: "zed::SomeAction",
1482                    context: None,
1483                    action_arguments: None,
1484                },
1485                source: KeybindUpdateTarget {
1486                    keystrokes: &parse_keystrokes("\\ b"),
1487                    action_name: "zed::SomeOtherAction",
1488                    context: None,
1489                    action_arguments: Some(r#"{"foo": "bar"}"#),
1490                },
1491                target_keybind_source: KeybindSource::User,
1492            },
1493            r#"[
1494                {
1495                    "bindings": {
1496                        "\\ b": [
1497                            "zed::SomeOtherAction",
1498                            {
1499                                "foo": "bar"
1500                            }
1501                        ]
1502                    }
1503                }
1504            ]"#
1505            .unindent(),
1506        );
1507
1508        check_keymap_update(
1509            r#"[
1510                {
1511                    "bindings": {
1512                        "\\ a": "zed::SomeAction"
1513                    }
1514                }
1515            ]"#
1516            .unindent(),
1517            KeybindUpdateOperation::Replace {
1518                target: KeybindUpdateTarget {
1519                    keystrokes: &parse_keystrokes("\\ a"),
1520                    action_name: "zed::SomeAction",
1521                    context: None,
1522                    action_arguments: None,
1523                },
1524                source: KeybindUpdateTarget {
1525                    keystrokes: &parse_keystrokes("\\ a"),
1526                    action_name: "zed::SomeAction",
1527                    context: None,
1528                    action_arguments: None,
1529                },
1530                target_keybind_source: KeybindSource::User,
1531            },
1532            r#"[
1533                {
1534                    "bindings": {
1535                        "\\ a": "zed::SomeAction"
1536                    }
1537                }
1538            ]"#
1539            .unindent(),
1540        );
1541
1542        check_keymap_update(
1543            r#"[
1544                {
1545                    "bindings": {
1546                        "ctrl-a": "zed::SomeAction"
1547                    }
1548                }
1549            ]"#
1550            .unindent(),
1551            KeybindUpdateOperation::Replace {
1552                target: KeybindUpdateTarget {
1553                    keystrokes: &parse_keystrokes("ctrl-a"),
1554                    action_name: "zed::SomeNonexistentAction",
1555                    context: None,
1556                    action_arguments: None,
1557                },
1558                source: KeybindUpdateTarget {
1559                    keystrokes: &parse_keystrokes("ctrl-b"),
1560                    action_name: "zed::SomeOtherAction",
1561                    context: None,
1562                    action_arguments: None,
1563                },
1564                target_keybind_source: KeybindSource::User,
1565            },
1566            r#"[
1567                {
1568                    "bindings": {
1569                        "ctrl-a": "zed::SomeAction"
1570                    }
1571                },
1572                {
1573                    "bindings": {
1574                        "ctrl-b": "zed::SomeOtherAction"
1575                    }
1576                }
1577            ]"#
1578            .unindent(),
1579        );
1580
1581        check_keymap_update(
1582            r#"[
1583                {
1584                    "bindings": {
1585                        // some comment
1586                        "ctrl-a": "zed::SomeAction"
1587                        // some other comment
1588                    }
1589                }
1590            ]"#
1591            .unindent(),
1592            KeybindUpdateOperation::Replace {
1593                target: KeybindUpdateTarget {
1594                    keystrokes: &parse_keystrokes("ctrl-a"),
1595                    action_name: "zed::SomeAction",
1596                    context: None,
1597                    action_arguments: None,
1598                },
1599                source: KeybindUpdateTarget {
1600                    keystrokes: &parse_keystrokes("ctrl-b"),
1601                    action_name: "zed::SomeOtherAction",
1602                    context: None,
1603                    action_arguments: Some(r#"{"foo": "bar"}"#),
1604                },
1605                target_keybind_source: KeybindSource::User,
1606            },
1607            r#"[
1608                {
1609                    "bindings": {
1610                        // some comment
1611                        "ctrl-b": [
1612                            "zed::SomeOtherAction",
1613                            {
1614                                "foo": "bar"
1615                            }
1616                        ]
1617                        // some other comment
1618                    }
1619                }
1620            ]"#
1621            .unindent(),
1622        );
1623
1624        check_keymap_update(
1625            r#"[
1626                {
1627                    "context": "SomeContext",
1628                    "bindings": {
1629                        "a": "foo::bar",
1630                        "b": "baz::qux",
1631                    }
1632                }
1633            ]"#
1634            .unindent(),
1635            KeybindUpdateOperation::Replace {
1636                target: KeybindUpdateTarget {
1637                    keystrokes: &parse_keystrokes("a"),
1638                    action_name: "foo::bar",
1639                    context: Some("SomeContext"),
1640                    action_arguments: None,
1641                },
1642                source: KeybindUpdateTarget {
1643                    keystrokes: &parse_keystrokes("c"),
1644                    action_name: "foo::baz",
1645                    context: Some("SomeOtherContext"),
1646                    action_arguments: None,
1647                },
1648                target_keybind_source: KeybindSource::User,
1649            },
1650            r#"[
1651                {
1652                    "context": "SomeContext",
1653                    "bindings": {
1654                        "b": "baz::qux",
1655                    }
1656                },
1657                {
1658                    "context": "SomeOtherContext",
1659                    "bindings": {
1660                        "c": "foo::baz"
1661                    }
1662                }
1663            ]"#
1664            .unindent(),
1665        );
1666
1667        check_keymap_update(
1668            r#"[
1669                {
1670                    "context": "SomeContext",
1671                    "bindings": {
1672                        "a": "foo::bar",
1673                    }
1674                }
1675            ]"#
1676            .unindent(),
1677            KeybindUpdateOperation::Replace {
1678                target: KeybindUpdateTarget {
1679                    keystrokes: &parse_keystrokes("a"),
1680                    action_name: "foo::bar",
1681                    context: Some("SomeContext"),
1682                    action_arguments: None,
1683                },
1684                source: KeybindUpdateTarget {
1685                    keystrokes: &parse_keystrokes("c"),
1686                    action_name: "foo::baz",
1687                    context: Some("SomeOtherContext"),
1688                    action_arguments: None,
1689                },
1690                target_keybind_source: KeybindSource::User,
1691            },
1692            r#"[
1693                {
1694                    "context": "SomeOtherContext",
1695                    "bindings": {
1696                        "c": "foo::baz",
1697                    }
1698                }
1699            ]"#
1700            .unindent(),
1701        );
1702
1703        check_keymap_update(
1704            r#"[
1705                {
1706                    "context": "SomeContext",
1707                    "bindings": {
1708                        "a": "foo::bar",
1709                        "c": "foo::baz",
1710                    }
1711                },
1712            ]"#
1713            .unindent(),
1714            KeybindUpdateOperation::Remove {
1715                target: KeybindUpdateTarget {
1716                    context: Some("SomeContext"),
1717                    keystrokes: &parse_keystrokes("a"),
1718                    action_name: "foo::bar",
1719                    action_arguments: None,
1720                },
1721                target_keybind_source: KeybindSource::User,
1722            },
1723            r#"[
1724                {
1725                    "context": "SomeContext",
1726                    "bindings": {
1727                        "c": "foo::baz",
1728                    }
1729                },
1730            ]"#
1731            .unindent(),
1732        );
1733
1734        check_keymap_update(
1735            r#"[
1736                {
1737                    "context": "SomeContext",
1738                    "bindings": {
1739                        "\\ a": "foo::bar",
1740                        "c": "foo::baz",
1741                    }
1742                },
1743            ]"#
1744            .unindent(),
1745            KeybindUpdateOperation::Remove {
1746                target: KeybindUpdateTarget {
1747                    context: Some("SomeContext"),
1748                    keystrokes: &parse_keystrokes("\\ a"),
1749                    action_name: "foo::bar",
1750                    action_arguments: None,
1751                },
1752                target_keybind_source: KeybindSource::User,
1753            },
1754            r#"[
1755                {
1756                    "context": "SomeContext",
1757                    "bindings": {
1758                        "c": "foo::baz",
1759                    }
1760                },
1761            ]"#
1762            .unindent(),
1763        );
1764
1765        check_keymap_update(
1766            r#"[
1767                {
1768                    "context": "SomeContext",
1769                    "bindings": {
1770                        "a": ["foo::bar", true],
1771                        "c": "foo::baz",
1772                    }
1773                },
1774            ]"#
1775            .unindent(),
1776            KeybindUpdateOperation::Remove {
1777                target: KeybindUpdateTarget {
1778                    context: Some("SomeContext"),
1779                    keystrokes: &parse_keystrokes("a"),
1780                    action_name: "foo::bar",
1781                    action_arguments: Some("true"),
1782                },
1783                target_keybind_source: KeybindSource::User,
1784            },
1785            r#"[
1786                {
1787                    "context": "SomeContext",
1788                    "bindings": {
1789                        "c": "foo::baz",
1790                    }
1791                },
1792            ]"#
1793            .unindent(),
1794        );
1795
1796        check_keymap_update(
1797            r#"[
1798                {
1799                    "context": "SomeContext",
1800                    "bindings": {
1801                        "b": "foo::baz",
1802                    }
1803                },
1804                {
1805                    "context": "SomeContext",
1806                    "bindings": {
1807                        "a": ["foo::bar", true],
1808                    }
1809                },
1810                {
1811                    "context": "SomeContext",
1812                    "bindings": {
1813                        "c": "foo::baz",
1814                    }
1815                },
1816            ]"#
1817            .unindent(),
1818            KeybindUpdateOperation::Remove {
1819                target: KeybindUpdateTarget {
1820                    context: Some("SomeContext"),
1821                    keystrokes: &parse_keystrokes("a"),
1822                    action_name: "foo::bar",
1823                    action_arguments: Some("true"),
1824                },
1825                target_keybind_source: KeybindSource::User,
1826            },
1827            r#"[
1828                {
1829                    "context": "SomeContext",
1830                    "bindings": {
1831                        "b": "foo::baz",
1832                    }
1833                },
1834                {
1835                    "context": "SomeContext",
1836                    "bindings": {
1837                        "c": "foo::baz",
1838                    }
1839                },
1840            ]"#
1841            .unindent(),
1842        );
1843        check_keymap_update(
1844            r#"[
1845                {
1846                    "context": "SomeOtherContext",
1847                    "use_key_equivalents": true,
1848                    "bindings": {
1849                        "b": "foo::bar",
1850                    }
1851                },
1852            ]"#
1853            .unindent(),
1854            KeybindUpdateOperation::Add {
1855                source: KeybindUpdateTarget {
1856                    context: Some("SomeContext"),
1857                    keystrokes: &parse_keystrokes("a"),
1858                    action_name: "foo::baz",
1859                    action_arguments: Some("true"),
1860                },
1861                from: Some(KeybindUpdateTarget {
1862                    context: Some("SomeOtherContext"),
1863                    keystrokes: &parse_keystrokes("b"),
1864                    action_name: "foo::bar",
1865                    action_arguments: None,
1866                }),
1867            },
1868            r#"[
1869                {
1870                    "context": "SomeOtherContext",
1871                    "use_key_equivalents": true,
1872                    "bindings": {
1873                        "b": "foo::bar",
1874                    }
1875                },
1876                {
1877                    "context": "SomeContext",
1878                    "use_key_equivalents": true,
1879                    "bindings": {
1880                        "a": [
1881                            "foo::baz",
1882                            true
1883                        ]
1884                    }
1885                }
1886            ]"#
1887            .unindent(),
1888        );
1889
1890        check_keymap_update(
1891            r#"[
1892                {
1893                    "context": "SomeOtherContext",
1894                    "use_key_equivalents": true,
1895                    "bindings": {
1896                        "b": "foo::bar",
1897                    }
1898                },
1899            ]"#
1900            .unindent(),
1901            KeybindUpdateOperation::Remove {
1902                target: KeybindUpdateTarget {
1903                    context: Some("SomeContext"),
1904                    keystrokes: &parse_keystrokes("a"),
1905                    action_name: "foo::baz",
1906                    action_arguments: Some("true"),
1907                },
1908                target_keybind_source: KeybindSource::Default,
1909            },
1910            r#"[
1911                {
1912                    "context": "SomeOtherContext",
1913                    "use_key_equivalents": true,
1914                    "bindings": {
1915                        "b": "foo::bar",
1916                    }
1917                },
1918                {
1919                    "context": "SomeContext",
1920                    "bindings": {
1921                        "a": null
1922                    }
1923                }
1924            ]"#
1925            .unindent(),
1926        );
1927    }
1928
1929    #[test]
1930    fn test_keymap_remove() {
1931        zlog::init_test();
1932
1933        check_keymap_update(
1934            r#"
1935            [
1936              {
1937                "context": "Editor",
1938                "bindings": {
1939                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
1940                  "cmd-k cmd-l": "editor::ConvertToLowerCase",
1941                  "cmd-[": "pane::GoBack",
1942                }
1943              },
1944            ]
1945            "#,
1946            KeybindUpdateOperation::Remove {
1947                target: KeybindUpdateTarget {
1948                    context: Some("Editor"),
1949                    keystrokes: &parse_keystrokes("cmd-k cmd-l"),
1950                    action_name: "editor::ConvertToLowerCase",
1951                    action_arguments: None,
1952                },
1953                target_keybind_source: KeybindSource::User,
1954            },
1955            r#"
1956            [
1957              {
1958                "context": "Editor",
1959                "bindings": {
1960                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
1961                  "cmd-[": "pane::GoBack",
1962                }
1963              },
1964            ]
1965            "#,
1966        );
1967    }
1968}