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