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