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    pub fn generate_json_schema<'a>(
 535        mut generator: schemars::SchemaGenerator,
 536        action_schemas: Vec<(&'a str, Option<schemars::Schema>)>,
 537        action_documentation: &HashMap<&'a str, &'a str>,
 538        deprecations: &HashMap<&'a str, &'a str>,
 539        deprecation_messages: &HashMap<&'a str, &'a 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                "anyOf": 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        // When replacing a non-user binding's keystroke, we need to also suppress the old
 705        // default so it doesn't continue showing under the old keystroke.
 706        let mut old_keystroke_suppression: Option<(Option<String>, String)> = None;
 707
 708        match operation {
 709            // if trying to replace a keybinding that is not user-defined, treat it as an add operation
 710            KeybindUpdateOperation::Replace {
 711                target_keybind_source: target_source,
 712                source,
 713                target,
 714            } if target_source != KeybindSource::User => {
 715                if target.keystrokes_unparsed() != source.keystrokes_unparsed() {
 716                    old_keystroke_suppression = Some((
 717                        target.context.map(String::from),
 718                        target.keystrokes_unparsed(),
 719                    ));
 720                }
 721                operation = KeybindUpdateOperation::Add {
 722                    source,
 723                    from: Some(target),
 724                };
 725            }
 726            // if trying to remove a keybinding that is not user-defined, treat it as creating a binding
 727            // that binds it to `zed::NoAction`
 728            KeybindUpdateOperation::Remove {
 729                target,
 730                target_keybind_source,
 731            } if target_keybind_source != KeybindSource::User => {
 732                let mut source = target.clone();
 733                source.action_name = gpui::NoAction.name();
 734                source.action_arguments.take();
 735                operation = KeybindUpdateOperation::Add {
 736                    source,
 737                    from: Some(target),
 738                };
 739            }
 740            _ => {}
 741        }
 742
 743        // Sanity check that keymap contents are valid, even though we only use it for Replace.
 744        // We don't want to modify the file if it's invalid.
 745        let keymap = Self::parse(&keymap_contents).context("Failed to parse keymap")?;
 746
 747        if let KeybindUpdateOperation::Remove { target, .. } = operation {
 748            let target_action_value = target
 749                .action_value()
 750                .context("Failed to generate target action JSON value")?;
 751            let Some((index, keystrokes_str)) =
 752                find_binding(&keymap, &target, &target_action_value, keyboard_mapper)
 753            else {
 754                anyhow::bail!("Failed to find keybinding to remove");
 755            };
 756            let is_only_binding = keymap.0[index]
 757                .bindings
 758                .as_ref()
 759                .is_none_or(|bindings| bindings.len() == 1);
 760            let key_path: &[&str] = if is_only_binding {
 761                &[]
 762            } else {
 763                &["bindings", keystrokes_str]
 764            };
 765            let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 766                &keymap_contents,
 767                key_path,
 768                None,
 769                None,
 770                index,
 771                tab_size,
 772            );
 773            keymap_contents.replace_range(replace_range, &replace_value);
 774            return Ok(keymap_contents);
 775        }
 776
 777        if let KeybindUpdateOperation::Replace { source, target, .. } = operation {
 778            let target_action_value = target
 779                .action_value()
 780                .context("Failed to generate target action JSON value")?;
 781            let source_action_value = source
 782                .action_value()
 783                .context("Failed to generate source action JSON value")?;
 784
 785            if let Some((index, keystrokes_str)) =
 786                find_binding(&keymap, &target, &target_action_value, keyboard_mapper)
 787            {
 788                if target.context == source.context {
 789                    // if we are only changing the keybinding (common case)
 790                    // not the context, etc. Then just update the binding in place
 791
 792                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 793                        &keymap_contents,
 794                        &["bindings", keystrokes_str],
 795                        Some(&source_action_value),
 796                        Some(&source.keystrokes_unparsed()),
 797                        index,
 798                        tab_size,
 799                    );
 800                    keymap_contents.replace_range(replace_range, &replace_value);
 801
 802                    return Ok(keymap_contents);
 803                } else if keymap.0[index]
 804                    .bindings
 805                    .as_ref()
 806                    .is_none_or(|bindings| bindings.len() == 1)
 807                {
 808                    // if we are replacing the only binding in the section,
 809                    // just update the section in place, updating the context
 810                    // and the binding
 811
 812                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 813                        &keymap_contents,
 814                        &["bindings", keystrokes_str],
 815                        Some(&source_action_value),
 816                        Some(&source.keystrokes_unparsed()),
 817                        index,
 818                        tab_size,
 819                    );
 820                    keymap_contents.replace_range(replace_range, &replace_value);
 821
 822                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 823                        &keymap_contents,
 824                        &["context"],
 825                        source.context.map(Into::into).as_ref(),
 826                        None,
 827                        index,
 828                        tab_size,
 829                    );
 830                    keymap_contents.replace_range(replace_range, &replace_value);
 831                    return Ok(keymap_contents);
 832                } else {
 833                    // if we are replacing one of multiple bindings in a section
 834                    // with a context change, remove the existing binding from the
 835                    // section, then treat this operation as an add operation of the
 836                    // new binding with the updated context.
 837
 838                    let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
 839                        &keymap_contents,
 840                        &["bindings", keystrokes_str],
 841                        None,
 842                        None,
 843                        index,
 844                        tab_size,
 845                    );
 846                    keymap_contents.replace_range(replace_range, &replace_value);
 847                    operation = KeybindUpdateOperation::Add {
 848                        source,
 849                        from: Some(target),
 850                    };
 851                }
 852            } else {
 853                log::warn!(
 854                    "Failed to find keybinding to update `{:?} -> {}` creating new binding for `{:?} -> {}` instead",
 855                    target.keystrokes,
 856                    target_action_value,
 857                    source.keystrokes,
 858                    source_action_value,
 859                );
 860                operation = KeybindUpdateOperation::Add {
 861                    source,
 862                    from: Some(target),
 863                };
 864            }
 865        }
 866
 867        if let KeybindUpdateOperation::Add {
 868            source: keybinding,
 869            from,
 870        } = operation
 871        {
 872            let mut value = serde_json::Map::with_capacity(4);
 873            if let Some(context) = keybinding.context {
 874                value.insert("context".to_string(), context.into());
 875            }
 876            let use_key_equivalents = from.and_then(|from| {
 877                let action_value = from.action_value().context("Failed to serialize action value. `use_key_equivalents` on new keybinding may be incorrect.").log_err()?;
 878                let (index, _) = find_binding(&keymap, &from, &action_value, keyboard_mapper)?;
 879                Some(keymap.0[index].use_key_equivalents)
 880            }).unwrap_or(false);
 881            if use_key_equivalents {
 882                value.insert("use_key_equivalents".to_string(), true.into());
 883            }
 884
 885            value.insert("bindings".to_string(), {
 886                let mut bindings = serde_json::Map::new();
 887                let action = keybinding.action_value()?;
 888                bindings.insert(keybinding.keystrokes_unparsed(), action);
 889                bindings.into()
 890            });
 891
 892            let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
 893                &keymap_contents,
 894                &value.into(),
 895                tab_size,
 896            );
 897            keymap_contents.replace_range(replace_range, &replace_value);
 898        }
 899
 900        // If we converted a Replace to Add because the target was a non-user binding,
 901        // and the keystroke changed, suppress the old default keystroke with a NoAction
 902        // binding so it doesn't continue appearing under the old keystroke.
 903        if let Some((context, old_keystrokes)) = old_keystroke_suppression {
 904            let mut value = serde_json::Map::with_capacity(2);
 905            if let Some(context) = context {
 906                value.insert("context".to_string(), context.into());
 907            }
 908            value.insert("bindings".to_string(), {
 909                let mut bindings = serde_json::Map::new();
 910                bindings.insert(old_keystrokes, Value::Null);
 911                bindings.into()
 912            });
 913            let (replace_range, replace_value) = append_top_level_array_value_in_json_text(
 914                &keymap_contents,
 915                &value.into(),
 916                tab_size,
 917            );
 918            keymap_contents.replace_range(replace_range, &replace_value);
 919        }
 920
 921        return Ok(keymap_contents);
 922
 923        fn find_binding<'a, 'b>(
 924            keymap: &'b KeymapFile,
 925            target: &KeybindUpdateTarget<'a>,
 926            target_action_value: &Value,
 927            keyboard_mapper: &dyn gpui::PlatformKeyboardMapper,
 928        ) -> Option<(usize, &'b str)> {
 929            let target_context_parsed =
 930                KeyBindingContextPredicate::parse(target.context.unwrap_or("")).ok();
 931            for (index, section) in keymap.sections().enumerate() {
 932                let section_context_parsed =
 933                    KeyBindingContextPredicate::parse(&section.context).ok();
 934                if section_context_parsed != target_context_parsed {
 935                    continue;
 936                }
 937                let Some(bindings) = &section.bindings else {
 938                    continue;
 939                };
 940                for (keystrokes_str, action) in bindings {
 941                    let Ok(keystrokes) = keystrokes_str
 942                        .split_whitespace()
 943                        .map(|source| {
 944                            let keystroke = Keystroke::parse(source)?;
 945                            Ok(KeybindingKeystroke::new_with_mapper(
 946                                keystroke,
 947                                false,
 948                                keyboard_mapper,
 949                            ))
 950                        })
 951                        .collect::<Result<Vec<_>, InvalidKeystrokeError>>()
 952                    else {
 953                        continue;
 954                    };
 955                    if keystrokes.len() != target.keystrokes.len()
 956                        || !keystrokes
 957                            .iter()
 958                            .zip(target.keystrokes)
 959                            .all(|(a, b)| a.inner().should_match(b))
 960                    {
 961                        continue;
 962                    }
 963                    if &action.0 != target_action_value {
 964                        continue;
 965                    }
 966                    return Some((index, keystrokes_str));
 967                }
 968            }
 969            None
 970        }
 971    }
 972}
 973
 974#[derive(Clone, Debug)]
 975pub enum KeybindUpdateOperation<'a> {
 976    Replace {
 977        /// Describes the keybind to create
 978        source: KeybindUpdateTarget<'a>,
 979        /// Describes the keybind to remove
 980        target: KeybindUpdateTarget<'a>,
 981        target_keybind_source: KeybindSource,
 982    },
 983    Add {
 984        source: KeybindUpdateTarget<'a>,
 985        from: Option<KeybindUpdateTarget<'a>>,
 986    },
 987    Remove {
 988        target: KeybindUpdateTarget<'a>,
 989        target_keybind_source: KeybindSource,
 990    },
 991}
 992
 993impl KeybindUpdateOperation<'_> {
 994    pub fn generate_telemetry(
 995        &self,
 996    ) -> (
 997        // The keybind that is created
 998        String,
 999        // The keybinding that was removed
1000        String,
1001        // The source of the keybinding
1002        String,
1003    ) {
1004        let (new_binding, removed_binding, source) = match &self {
1005            KeybindUpdateOperation::Replace {
1006                source,
1007                target,
1008                target_keybind_source,
1009            } => (Some(source), Some(target), Some(*target_keybind_source)),
1010            KeybindUpdateOperation::Add { source, .. } => (Some(source), None, None),
1011            KeybindUpdateOperation::Remove {
1012                target,
1013                target_keybind_source,
1014            } => (None, Some(target), Some(*target_keybind_source)),
1015        };
1016
1017        let new_binding = new_binding
1018            .map(KeybindUpdateTarget::telemetry_string)
1019            .unwrap_or("null".to_owned());
1020        let removed_binding = removed_binding
1021            .map(KeybindUpdateTarget::telemetry_string)
1022            .unwrap_or("null".to_owned());
1023
1024        let source = source
1025            .as_ref()
1026            .map(KeybindSource::name)
1027            .map(ToOwned::to_owned)
1028            .unwrap_or("null".to_owned());
1029
1030        (new_binding, removed_binding, source)
1031    }
1032}
1033
1034impl<'a> KeybindUpdateOperation<'a> {
1035    pub fn add(source: KeybindUpdateTarget<'a>) -> Self {
1036        Self::Add { source, from: None }
1037    }
1038}
1039
1040#[derive(Debug, Clone)]
1041pub struct KeybindUpdateTarget<'a> {
1042    pub context: Option<&'a str>,
1043    pub keystrokes: &'a [KeybindingKeystroke],
1044    pub action_name: &'a str,
1045    pub action_arguments: Option<&'a str>,
1046}
1047
1048impl<'a> KeybindUpdateTarget<'a> {
1049    fn action_value(&self) -> Result<Value> {
1050        if self.action_name == gpui::NoAction.name() {
1051            return Ok(Value::Null);
1052        }
1053        let action_name: Value = self.action_name.into();
1054        let value = match self.action_arguments {
1055            Some(args) if !args.is_empty() => {
1056                let args = serde_json::from_str::<Value>(args)
1057                    .context("Failed to parse action arguments as JSON")?;
1058                serde_json::json!([action_name, args])
1059            }
1060            _ => action_name,
1061        };
1062        Ok(value)
1063    }
1064
1065    fn keystrokes_unparsed(&self) -> String {
1066        let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8);
1067        for keystroke in self.keystrokes {
1068            // The reason use `keystroke.unparse()` instead of `keystroke.inner.unparse()`
1069            // here is that, we want the user to use `ctrl-shift-4` instead of `ctrl-$`
1070            // by default on Windows.
1071            keystrokes.push_str(&keystroke.unparse());
1072            keystrokes.push(' ');
1073        }
1074        keystrokes.pop();
1075        keystrokes
1076    }
1077
1078    fn telemetry_string(&self) -> String {
1079        format!(
1080            "action_name: {}, context: {}, action_arguments: {}, keystrokes: {}",
1081            self.action_name,
1082            self.context.unwrap_or("global"),
1083            self.action_arguments.unwrap_or("none"),
1084            self.keystrokes_unparsed()
1085        )
1086    }
1087}
1088
1089#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]
1090pub enum KeybindSource {
1091    User,
1092    Vim,
1093    Base,
1094    #[default]
1095    Default,
1096    Unknown,
1097}
1098
1099impl KeybindSource {
1100    const BASE: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Base as u32);
1101    const DEFAULT: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Default as u32);
1102    const VIM: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::Vim as u32);
1103    const USER: KeyBindingMetaIndex = KeyBindingMetaIndex(KeybindSource::User as u32);
1104
1105    pub fn name(&self) -> &'static str {
1106        match self {
1107            KeybindSource::User => "User",
1108            KeybindSource::Default => "Default",
1109            KeybindSource::Base => "Base",
1110            KeybindSource::Vim => "Vim",
1111            KeybindSource::Unknown => "Unknown",
1112        }
1113    }
1114
1115    pub fn meta(&self) -> KeyBindingMetaIndex {
1116        match self {
1117            KeybindSource::User => Self::USER,
1118            KeybindSource::Default => Self::DEFAULT,
1119            KeybindSource::Base => Self::BASE,
1120            KeybindSource::Vim => Self::VIM,
1121            KeybindSource::Unknown => KeyBindingMetaIndex(*self as u32),
1122        }
1123    }
1124
1125    pub fn from_meta(index: KeyBindingMetaIndex) -> Self {
1126        match index {
1127            Self::USER => KeybindSource::User,
1128            Self::BASE => KeybindSource::Base,
1129            Self::DEFAULT => KeybindSource::Default,
1130            Self::VIM => KeybindSource::Vim,
1131            _ => KeybindSource::Unknown,
1132        }
1133    }
1134}
1135
1136impl From<KeyBindingMetaIndex> for KeybindSource {
1137    fn from(index: KeyBindingMetaIndex) -> Self {
1138        Self::from_meta(index)
1139    }
1140}
1141
1142impl From<KeybindSource> for KeyBindingMetaIndex {
1143    fn from(source: KeybindSource) -> Self {
1144        source.meta()
1145    }
1146}
1147
1148/// Runs a sequence of actions. Does not wait for asynchronous actions to complete before running
1149/// the next action. Currently only works in workspace windows.
1150///
1151/// This action is special-cased in keymap parsing to allow it to access `App` while parsing, so
1152/// that it can parse its input actions.
1153pub struct ActionSequence(pub Vec<Box<dyn Action>>);
1154
1155register_action!(ActionSequence);
1156
1157impl ActionSequence {
1158    fn build_sequence(
1159        value: Value,
1160        cx: &App,
1161    ) -> std::result::Result<Box<dyn Action>, ActionBuildError> {
1162        match value {
1163            Value::Array(values) => {
1164                let actions = values
1165                    .into_iter()
1166                    .enumerate()
1167                    .map(|(index, action)| {
1168                        match KeymapFile::build_keymap_action(&KeymapAction(action), cx) {
1169                            Ok((action, _)) => Ok(action),
1170                            Err(err) => {
1171                                return Err(ActionBuildError::BuildError {
1172                                    name: Self::name_for_type().to_string(),
1173                                    error: anyhow::anyhow!(
1174                                        "error at sequence index {index}: {err}"
1175                                    ),
1176                                });
1177                            }
1178                        }
1179                    })
1180                    .collect::<Result<Vec<_>, _>>()?;
1181                Ok(Box::new(Self(actions)))
1182            }
1183            _ => Err(Self::expected_array_error()),
1184        }
1185    }
1186
1187    fn expected_array_error() -> ActionBuildError {
1188        ActionBuildError::BuildError {
1189            name: Self::name_for_type().to_string(),
1190            error: anyhow::anyhow!("expected array of actions"),
1191        }
1192    }
1193}
1194
1195impl Action for ActionSequence {
1196    fn name(&self) -> &'static str {
1197        Self::name_for_type()
1198    }
1199
1200    fn name_for_type() -> &'static str
1201    where
1202        Self: Sized,
1203    {
1204        "action::Sequence"
1205    }
1206
1207    fn partial_eq(&self, action: &dyn Action) -> bool {
1208        action
1209            .as_any()
1210            .downcast_ref::<Self>()
1211            .map_or(false, |other| {
1212                self.0.len() == other.0.len()
1213                    && self
1214                        .0
1215                        .iter()
1216                        .zip(other.0.iter())
1217                        .all(|(a, b)| a.partial_eq(b.as_ref()))
1218            })
1219    }
1220
1221    fn boxed_clone(&self) -> Box<dyn Action> {
1222        Box::new(ActionSequence(
1223            self.0
1224                .iter()
1225                .map(|action| action.boxed_clone())
1226                .collect::<Vec<_>>(),
1227        ))
1228    }
1229
1230    fn build(_value: Value) -> Result<Box<dyn Action>> {
1231        Err(anyhow::anyhow!(
1232            "{} cannot be built directly",
1233            Self::name_for_type()
1234        ))
1235    }
1236
1237    fn action_json_schema(generator: &mut schemars::SchemaGenerator) -> Option<schemars::Schema> {
1238        let keymap_action_schema = generator.subschema_for::<KeymapAction>();
1239        Some(json_schema!({
1240            "type": "array",
1241            "items": keymap_action_schema
1242        }))
1243    }
1244
1245    fn deprecated_aliases() -> &'static [&'static str] {
1246        &[]
1247    }
1248
1249    fn deprecation_message() -> Option<&'static str> {
1250        None
1251    }
1252
1253    fn documentation() -> Option<&'static str> {
1254        Some(
1255            "Runs a sequence of actions.\n\n\
1256            NOTE: This does **not** wait for asynchronous actions to complete before running the next action.",
1257        )
1258    }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    use gpui::{DummyKeyboardMapper, KeybindingKeystroke, Keystroke};
1264    use unindent::Unindent;
1265
1266    use crate::{
1267        KeybindSource, KeymapFile,
1268        keymap_file::{KeybindUpdateOperation, KeybindUpdateTarget},
1269    };
1270
1271    #[test]
1272    fn can_deserialize_keymap_with_trailing_comma() {
1273        let json = indoc::indoc! {"[
1274              // Standard macOS bindings
1275              {
1276                \"bindings\": {
1277                  \"up\": \"menu::SelectPrevious\",
1278                },
1279              },
1280            ]
1281                  "
1282        };
1283        KeymapFile::parse(json).unwrap();
1284    }
1285
1286    #[track_caller]
1287    fn check_keymap_update(
1288        input: impl ToString,
1289        operation: KeybindUpdateOperation,
1290        expected: impl ToString,
1291    ) {
1292        let result = KeymapFile::update_keybinding(
1293            operation,
1294            input.to_string(),
1295            4,
1296            &gpui::DummyKeyboardMapper,
1297        )
1298        .expect("Update succeeded");
1299        pretty_assertions::assert_eq!(expected.to_string(), result);
1300    }
1301
1302    #[track_caller]
1303    fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
1304        keystrokes
1305            .split(' ')
1306            .map(|s| {
1307                KeybindingKeystroke::new_with_mapper(
1308                    Keystroke::parse(s).expect("Keystrokes valid"),
1309                    false,
1310                    &DummyKeyboardMapper,
1311                )
1312            })
1313            .collect()
1314    }
1315
1316    #[test]
1317    fn keymap_update() {
1318        zlog::init_test();
1319
1320        check_keymap_update(
1321            "[]",
1322            KeybindUpdateOperation::add(KeybindUpdateTarget {
1323                keystrokes: &parse_keystrokes("ctrl-a"),
1324                action_name: "zed::SomeAction",
1325                context: None,
1326                action_arguments: None,
1327            }),
1328            r#"[
1329                {
1330                    "bindings": {
1331                        "ctrl-a": "zed::SomeAction"
1332                    }
1333                }
1334            ]"#
1335            .unindent(),
1336        );
1337
1338        check_keymap_update(
1339            "[]",
1340            KeybindUpdateOperation::add(KeybindUpdateTarget {
1341                keystrokes: &parse_keystrokes("\\ a"),
1342                action_name: "zed::SomeAction",
1343                context: None,
1344                action_arguments: None,
1345            }),
1346            r#"[
1347                {
1348                    "bindings": {
1349                        "\\ a": "zed::SomeAction"
1350                    }
1351                }
1352            ]"#
1353            .unindent(),
1354        );
1355
1356        check_keymap_update(
1357            "[]",
1358            KeybindUpdateOperation::add(KeybindUpdateTarget {
1359                keystrokes: &parse_keystrokes("ctrl-a"),
1360                action_name: "zed::SomeAction",
1361                context: None,
1362                action_arguments: Some(""),
1363            }),
1364            r#"[
1365                {
1366                    "bindings": {
1367                        "ctrl-a": "zed::SomeAction"
1368                    }
1369                }
1370            ]"#
1371            .unindent(),
1372        );
1373
1374        check_keymap_update(
1375            r#"[
1376                {
1377                    "bindings": {
1378                        "ctrl-a": "zed::SomeAction"
1379                    }
1380                }
1381            ]"#
1382            .unindent(),
1383            KeybindUpdateOperation::add(KeybindUpdateTarget {
1384                keystrokes: &parse_keystrokes("ctrl-b"),
1385                action_name: "zed::SomeOtherAction",
1386                context: None,
1387                action_arguments: None,
1388            }),
1389            r#"[
1390                {
1391                    "bindings": {
1392                        "ctrl-a": "zed::SomeAction"
1393                    }
1394                },
1395                {
1396                    "bindings": {
1397                        "ctrl-b": "zed::SomeOtherAction"
1398                    }
1399                }
1400            ]"#
1401            .unindent(),
1402        );
1403
1404        check_keymap_update(
1405            r#"[
1406                {
1407                    "bindings": {
1408                        "ctrl-a": "zed::SomeAction"
1409                    }
1410                }
1411            ]"#
1412            .unindent(),
1413            KeybindUpdateOperation::add(KeybindUpdateTarget {
1414                keystrokes: &parse_keystrokes("ctrl-b"),
1415                action_name: "zed::SomeOtherAction",
1416                context: None,
1417                action_arguments: Some(r#"{"foo": "bar"}"#),
1418            }),
1419            r#"[
1420                {
1421                    "bindings": {
1422                        "ctrl-a": "zed::SomeAction"
1423                    }
1424                },
1425                {
1426                    "bindings": {
1427                        "ctrl-b": [
1428                            "zed::SomeOtherAction",
1429                            {
1430                                "foo": "bar"
1431                            }
1432                        ]
1433                    }
1434                }
1435            ]"#
1436            .unindent(),
1437        );
1438
1439        check_keymap_update(
1440            r#"[
1441                {
1442                    "bindings": {
1443                        "ctrl-a": "zed::SomeAction"
1444                    }
1445                }
1446            ]"#
1447            .unindent(),
1448            KeybindUpdateOperation::add(KeybindUpdateTarget {
1449                keystrokes: &parse_keystrokes("ctrl-b"),
1450                action_name: "zed::SomeOtherAction",
1451                context: Some("Zed > Editor && some_condition = true"),
1452                action_arguments: Some(r#"{"foo": "bar"}"#),
1453            }),
1454            r#"[
1455                {
1456                    "bindings": {
1457                        "ctrl-a": "zed::SomeAction"
1458                    }
1459                },
1460                {
1461                    "context": "Zed > Editor && some_condition = true",
1462                    "bindings": {
1463                        "ctrl-b": [
1464                            "zed::SomeOtherAction",
1465                            {
1466                                "foo": "bar"
1467                            }
1468                        ]
1469                    }
1470                }
1471            ]"#
1472            .unindent(),
1473        );
1474
1475        check_keymap_update(
1476            r#"[
1477                {
1478                    "bindings": {
1479                        "ctrl-a": "zed::SomeAction"
1480                    }
1481                }
1482            ]"#
1483            .unindent(),
1484            KeybindUpdateOperation::Replace {
1485                target: KeybindUpdateTarget {
1486                    keystrokes: &parse_keystrokes("ctrl-a"),
1487                    action_name: "zed::SomeAction",
1488                    context: None,
1489                    action_arguments: None,
1490                },
1491                source: KeybindUpdateTarget {
1492                    keystrokes: &parse_keystrokes("ctrl-b"),
1493                    action_name: "zed::SomeOtherAction",
1494                    context: None,
1495                    action_arguments: Some(r#"{"foo": "bar"}"#),
1496                },
1497                target_keybind_source: KeybindSource::Base,
1498            },
1499            r#"[
1500                {
1501                    "bindings": {
1502                        "ctrl-a": "zed::SomeAction"
1503                    }
1504                },
1505                {
1506                    "bindings": {
1507                        "ctrl-b": [
1508                            "zed::SomeOtherAction",
1509                            {
1510                                "foo": "bar"
1511                            }
1512                        ]
1513                    }
1514                },
1515                {
1516                    "bindings": {
1517                        "ctrl-a": null
1518                    }
1519                }
1520            ]"#
1521            .unindent(),
1522        );
1523
1524        // Replacing a non-user binding without changing the keystroke should
1525        // not produce a NoAction suppression entry.
1526        check_keymap_update(
1527            r#"[
1528                {
1529                    "bindings": {
1530                        "ctrl-a": "zed::SomeAction"
1531                    }
1532                }
1533            ]"#
1534            .unindent(),
1535            KeybindUpdateOperation::Replace {
1536                target: KeybindUpdateTarget {
1537                    keystrokes: &parse_keystrokes("ctrl-a"),
1538                    action_name: "zed::SomeAction",
1539                    context: None,
1540                    action_arguments: None,
1541                },
1542                source: KeybindUpdateTarget {
1543                    keystrokes: &parse_keystrokes("ctrl-a"),
1544                    action_name: "zed::SomeOtherAction",
1545                    context: None,
1546                    action_arguments: None,
1547                },
1548                target_keybind_source: KeybindSource::Base,
1549            },
1550            r#"[
1551                {
1552                    "bindings": {
1553                        "ctrl-a": "zed::SomeAction"
1554                    }
1555                },
1556                {
1557                    "bindings": {
1558                        "ctrl-a": "zed::SomeOtherAction"
1559                    }
1560                }
1561            ]"#
1562            .unindent(),
1563        );
1564
1565        // Replacing a non-user binding with a context and a keystroke change
1566        // should produce a suppression entry that preserves the context.
1567        check_keymap_update(
1568            r#"[
1569                {
1570                    "context": "SomeContext",
1571                    "bindings": {
1572                        "ctrl-a": "zed::SomeAction"
1573                    }
1574                }
1575            ]"#
1576            .unindent(),
1577            KeybindUpdateOperation::Replace {
1578                target: KeybindUpdateTarget {
1579                    keystrokes: &parse_keystrokes("ctrl-a"),
1580                    action_name: "zed::SomeAction",
1581                    context: Some("SomeContext"),
1582                    action_arguments: None,
1583                },
1584                source: KeybindUpdateTarget {
1585                    keystrokes: &parse_keystrokes("ctrl-b"),
1586                    action_name: "zed::SomeOtherAction",
1587                    context: Some("SomeContext"),
1588                    action_arguments: None,
1589                },
1590                target_keybind_source: KeybindSource::Default,
1591            },
1592            r#"[
1593                {
1594                    "context": "SomeContext",
1595                    "bindings": {
1596                        "ctrl-a": "zed::SomeAction"
1597                    }
1598                },
1599                {
1600                    "context": "SomeContext",
1601                    "bindings": {
1602                        "ctrl-b": "zed::SomeOtherAction"
1603                    }
1604                },
1605                {
1606                    "context": "SomeContext",
1607                    "bindings": {
1608                        "ctrl-a": null
1609                    }
1610                }
1611            ]"#
1612            .unindent(),
1613        );
1614
1615        check_keymap_update(
1616            r#"[
1617                {
1618                    "bindings": {
1619                        "a": "zed::SomeAction"
1620                    }
1621                }
1622            ]"#
1623            .unindent(),
1624            KeybindUpdateOperation::Replace {
1625                target: KeybindUpdateTarget {
1626                    keystrokes: &parse_keystrokes("a"),
1627                    action_name: "zed::SomeAction",
1628                    context: None,
1629                    action_arguments: None,
1630                },
1631                source: KeybindUpdateTarget {
1632                    keystrokes: &parse_keystrokes("ctrl-b"),
1633                    action_name: "zed::SomeOtherAction",
1634                    context: None,
1635                    action_arguments: Some(r#"{"foo": "bar"}"#),
1636                },
1637                target_keybind_source: KeybindSource::User,
1638            },
1639            r#"[
1640                {
1641                    "bindings": {
1642                        "ctrl-b": [
1643                            "zed::SomeOtherAction",
1644                            {
1645                                "foo": "bar"
1646                            }
1647                        ]
1648                    }
1649                }
1650            ]"#
1651            .unindent(),
1652        );
1653
1654        check_keymap_update(
1655            r#"[
1656                {
1657                    "bindings": {
1658                        "\\ a": "zed::SomeAction"
1659                    }
1660                }
1661            ]"#
1662            .unindent(),
1663            KeybindUpdateOperation::Replace {
1664                target: KeybindUpdateTarget {
1665                    keystrokes: &parse_keystrokes("\\ a"),
1666                    action_name: "zed::SomeAction",
1667                    context: None,
1668                    action_arguments: None,
1669                },
1670                source: KeybindUpdateTarget {
1671                    keystrokes: &parse_keystrokes("\\ b"),
1672                    action_name: "zed::SomeOtherAction",
1673                    context: None,
1674                    action_arguments: Some(r#"{"foo": "bar"}"#),
1675                },
1676                target_keybind_source: KeybindSource::User,
1677            },
1678            r#"[
1679                {
1680                    "bindings": {
1681                        "\\ b": [
1682                            "zed::SomeOtherAction",
1683                            {
1684                                "foo": "bar"
1685                            }
1686                        ]
1687                    }
1688                }
1689            ]"#
1690            .unindent(),
1691        );
1692
1693        check_keymap_update(
1694            r#"[
1695                {
1696                    "bindings": {
1697                        "\\ a": "zed::SomeAction"
1698                    }
1699                }
1700            ]"#
1701            .unindent(),
1702            KeybindUpdateOperation::Replace {
1703                target: KeybindUpdateTarget {
1704                    keystrokes: &parse_keystrokes("\\ a"),
1705                    action_name: "zed::SomeAction",
1706                    context: None,
1707                    action_arguments: None,
1708                },
1709                source: KeybindUpdateTarget {
1710                    keystrokes: &parse_keystrokes("\\ a"),
1711                    action_name: "zed::SomeAction",
1712                    context: None,
1713                    action_arguments: None,
1714                },
1715                target_keybind_source: KeybindSource::User,
1716            },
1717            r#"[
1718                {
1719                    "bindings": {
1720                        "\\ a": "zed::SomeAction"
1721                    }
1722                }
1723            ]"#
1724            .unindent(),
1725        );
1726
1727        check_keymap_update(
1728            r#"[
1729                {
1730                    "bindings": {
1731                        "ctrl-a": "zed::SomeAction"
1732                    }
1733                }
1734            ]"#
1735            .unindent(),
1736            KeybindUpdateOperation::Replace {
1737                target: KeybindUpdateTarget {
1738                    keystrokes: &parse_keystrokes("ctrl-a"),
1739                    action_name: "zed::SomeNonexistentAction",
1740                    context: None,
1741                    action_arguments: None,
1742                },
1743                source: KeybindUpdateTarget {
1744                    keystrokes: &parse_keystrokes("ctrl-b"),
1745                    action_name: "zed::SomeOtherAction",
1746                    context: None,
1747                    action_arguments: None,
1748                },
1749                target_keybind_source: KeybindSource::User,
1750            },
1751            r#"[
1752                {
1753                    "bindings": {
1754                        "ctrl-a": "zed::SomeAction"
1755                    }
1756                },
1757                {
1758                    "bindings": {
1759                        "ctrl-b": "zed::SomeOtherAction"
1760                    }
1761                }
1762            ]"#
1763            .unindent(),
1764        );
1765
1766        check_keymap_update(
1767            r#"[
1768                {
1769                    "bindings": {
1770                        // some comment
1771                        "ctrl-a": "zed::SomeAction"
1772                        // some other comment
1773                    }
1774                }
1775            ]"#
1776            .unindent(),
1777            KeybindUpdateOperation::Replace {
1778                target: KeybindUpdateTarget {
1779                    keystrokes: &parse_keystrokes("ctrl-a"),
1780                    action_name: "zed::SomeAction",
1781                    context: None,
1782                    action_arguments: None,
1783                },
1784                source: KeybindUpdateTarget {
1785                    keystrokes: &parse_keystrokes("ctrl-b"),
1786                    action_name: "zed::SomeOtherAction",
1787                    context: None,
1788                    action_arguments: Some(r#"{"foo": "bar"}"#),
1789                },
1790                target_keybind_source: KeybindSource::User,
1791            },
1792            r#"[
1793                {
1794                    "bindings": {
1795                        // some comment
1796                        "ctrl-b": [
1797                            "zed::SomeOtherAction",
1798                            {
1799                                "foo": "bar"
1800                            }
1801                        ]
1802                        // some other comment
1803                    }
1804                }
1805            ]"#
1806            .unindent(),
1807        );
1808
1809        check_keymap_update(
1810            r#"[
1811                {
1812                    "context": "SomeContext",
1813                    "bindings": {
1814                        "a": "foo::bar",
1815                        "b": "baz::qux",
1816                    }
1817                }
1818            ]"#
1819            .unindent(),
1820            KeybindUpdateOperation::Replace {
1821                target: KeybindUpdateTarget {
1822                    keystrokes: &parse_keystrokes("a"),
1823                    action_name: "foo::bar",
1824                    context: Some("SomeContext"),
1825                    action_arguments: None,
1826                },
1827                source: KeybindUpdateTarget {
1828                    keystrokes: &parse_keystrokes("c"),
1829                    action_name: "foo::baz",
1830                    context: Some("SomeOtherContext"),
1831                    action_arguments: None,
1832                },
1833                target_keybind_source: KeybindSource::User,
1834            },
1835            r#"[
1836                {
1837                    "context": "SomeContext",
1838                    "bindings": {
1839                        "b": "baz::qux",
1840                    }
1841                },
1842                {
1843                    "context": "SomeOtherContext",
1844                    "bindings": {
1845                        "c": "foo::baz"
1846                    }
1847                }
1848            ]"#
1849            .unindent(),
1850        );
1851
1852        check_keymap_update(
1853            r#"[
1854                {
1855                    "context": "SomeContext",
1856                    "bindings": {
1857                        "a": "foo::bar",
1858                    }
1859                }
1860            ]"#
1861            .unindent(),
1862            KeybindUpdateOperation::Replace {
1863                target: KeybindUpdateTarget {
1864                    keystrokes: &parse_keystrokes("a"),
1865                    action_name: "foo::bar",
1866                    context: Some("SomeContext"),
1867                    action_arguments: None,
1868                },
1869                source: KeybindUpdateTarget {
1870                    keystrokes: &parse_keystrokes("c"),
1871                    action_name: "foo::baz",
1872                    context: Some("SomeOtherContext"),
1873                    action_arguments: None,
1874                },
1875                target_keybind_source: KeybindSource::User,
1876            },
1877            r#"[
1878                {
1879                    "context": "SomeOtherContext",
1880                    "bindings": {
1881                        "c": "foo::baz",
1882                    }
1883                }
1884            ]"#
1885            .unindent(),
1886        );
1887
1888        check_keymap_update(
1889            r#"[
1890                {
1891                    "context": "SomeContext",
1892                    "bindings": {
1893                        "a": "foo::bar",
1894                        "c": "foo::baz",
1895                    }
1896                },
1897            ]"#
1898            .unindent(),
1899            KeybindUpdateOperation::Remove {
1900                target: KeybindUpdateTarget {
1901                    context: Some("SomeContext"),
1902                    keystrokes: &parse_keystrokes("a"),
1903                    action_name: "foo::bar",
1904                    action_arguments: None,
1905                },
1906                target_keybind_source: KeybindSource::User,
1907            },
1908            r#"[
1909                {
1910                    "context": "SomeContext",
1911                    "bindings": {
1912                        "c": "foo::baz",
1913                    }
1914                },
1915            ]"#
1916            .unindent(),
1917        );
1918
1919        check_keymap_update(
1920            r#"[
1921                {
1922                    "context": "SomeContext",
1923                    "bindings": {
1924                        "\\ a": "foo::bar",
1925                        "c": "foo::baz",
1926                    }
1927                },
1928            ]"#
1929            .unindent(),
1930            KeybindUpdateOperation::Remove {
1931                target: KeybindUpdateTarget {
1932                    context: Some("SomeContext"),
1933                    keystrokes: &parse_keystrokes("\\ a"),
1934                    action_name: "foo::bar",
1935                    action_arguments: None,
1936                },
1937                target_keybind_source: KeybindSource::User,
1938            },
1939            r#"[
1940                {
1941                    "context": "SomeContext",
1942                    "bindings": {
1943                        "c": "foo::baz",
1944                    }
1945                },
1946            ]"#
1947            .unindent(),
1948        );
1949
1950        check_keymap_update(
1951            r#"[
1952                {
1953                    "context": "SomeContext",
1954                    "bindings": {
1955                        "a": ["foo::bar", true],
1956                        "c": "foo::baz",
1957                    }
1958                },
1959            ]"#
1960            .unindent(),
1961            KeybindUpdateOperation::Remove {
1962                target: KeybindUpdateTarget {
1963                    context: Some("SomeContext"),
1964                    keystrokes: &parse_keystrokes("a"),
1965                    action_name: "foo::bar",
1966                    action_arguments: Some("true"),
1967                },
1968                target_keybind_source: KeybindSource::User,
1969            },
1970            r#"[
1971                {
1972                    "context": "SomeContext",
1973                    "bindings": {
1974                        "c": "foo::baz",
1975                    }
1976                },
1977            ]"#
1978            .unindent(),
1979        );
1980
1981        check_keymap_update(
1982            r#"[
1983                {
1984                    "context": "SomeContext",
1985                    "bindings": {
1986                        "b": "foo::baz",
1987                    }
1988                },
1989                {
1990                    "context": "SomeContext",
1991                    "bindings": {
1992                        "a": ["foo::bar", true],
1993                    }
1994                },
1995                {
1996                    "context": "SomeContext",
1997                    "bindings": {
1998                        "c": "foo::baz",
1999                    }
2000                },
2001            ]"#
2002            .unindent(),
2003            KeybindUpdateOperation::Remove {
2004                target: KeybindUpdateTarget {
2005                    context: Some("SomeContext"),
2006                    keystrokes: &parse_keystrokes("a"),
2007                    action_name: "foo::bar",
2008                    action_arguments: Some("true"),
2009                },
2010                target_keybind_source: KeybindSource::User,
2011            },
2012            r#"[
2013                {
2014                    "context": "SomeContext",
2015                    "bindings": {
2016                        "b": "foo::baz",
2017                    }
2018                },
2019                {
2020                    "context": "SomeContext",
2021                    "bindings": {
2022                        "c": "foo::baz",
2023                    }
2024                },
2025            ]"#
2026            .unindent(),
2027        );
2028        check_keymap_update(
2029            r#"[
2030                {
2031                    "context": "SomeOtherContext",
2032                    "use_key_equivalents": true,
2033                    "bindings": {
2034                        "b": "foo::bar",
2035                    }
2036                },
2037            ]"#
2038            .unindent(),
2039            KeybindUpdateOperation::Add {
2040                source: KeybindUpdateTarget {
2041                    context: Some("SomeContext"),
2042                    keystrokes: &parse_keystrokes("a"),
2043                    action_name: "foo::baz",
2044                    action_arguments: Some("true"),
2045                },
2046                from: Some(KeybindUpdateTarget {
2047                    context: Some("SomeOtherContext"),
2048                    keystrokes: &parse_keystrokes("b"),
2049                    action_name: "foo::bar",
2050                    action_arguments: None,
2051                }),
2052            },
2053            r#"[
2054                {
2055                    "context": "SomeOtherContext",
2056                    "use_key_equivalents": true,
2057                    "bindings": {
2058                        "b": "foo::bar",
2059                    }
2060                },
2061                {
2062                    "context": "SomeContext",
2063                    "use_key_equivalents": true,
2064                    "bindings": {
2065                        "a": [
2066                            "foo::baz",
2067                            true
2068                        ]
2069                    }
2070                }
2071            ]"#
2072            .unindent(),
2073        );
2074
2075        check_keymap_update(
2076            r#"[
2077                {
2078                    "context": "SomeOtherContext",
2079                    "use_key_equivalents": true,
2080                    "bindings": {
2081                        "b": "foo::bar",
2082                    }
2083                },
2084            ]"#
2085            .unindent(),
2086            KeybindUpdateOperation::Remove {
2087                target: KeybindUpdateTarget {
2088                    context: Some("SomeContext"),
2089                    keystrokes: &parse_keystrokes("a"),
2090                    action_name: "foo::baz",
2091                    action_arguments: Some("true"),
2092                },
2093                target_keybind_source: KeybindSource::Default,
2094            },
2095            r#"[
2096                {
2097                    "context": "SomeOtherContext",
2098                    "use_key_equivalents": true,
2099                    "bindings": {
2100                        "b": "foo::bar",
2101                    }
2102                },
2103                {
2104                    "context": "SomeContext",
2105                    "bindings": {
2106                        "a": null
2107                    }
2108                }
2109            ]"#
2110            .unindent(),
2111        );
2112    }
2113
2114    #[test]
2115    fn test_keymap_remove() {
2116        zlog::init_test();
2117
2118        check_keymap_update(
2119            r#"
2120            [
2121              {
2122                "context": "Editor",
2123                "bindings": {
2124                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
2125                  "cmd-k cmd-l": "editor::ConvertToLowerCase",
2126                  "cmd-[": "pane::GoBack",
2127                }
2128              },
2129            ]
2130            "#,
2131            KeybindUpdateOperation::Remove {
2132                target: KeybindUpdateTarget {
2133                    context: Some("Editor"),
2134                    keystrokes: &parse_keystrokes("cmd-k cmd-l"),
2135                    action_name: "editor::ConvertToLowerCase",
2136                    action_arguments: None,
2137                },
2138                target_keybind_source: KeybindSource::User,
2139            },
2140            r#"
2141            [
2142              {
2143                "context": "Editor",
2144                "bindings": {
2145                  "cmd-k cmd-u": "editor::ConvertToUpperCase",
2146                  "cmd-[": "pane::GoBack",
2147                }
2148              },
2149            ]
2150            "#,
2151        );
2152    }
2153}