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