keymap_file.rs

  1use anyhow::{anyhow, Context as _, Result};
  2use collections::{BTreeMap, HashMap, IndexMap};
  3use fs::Fs;
  4use gpui::{
  5    Action, ActionBuildError, App, InvalidKeystrokeError, KeyBinding, KeyBindingContextPredicate,
  6    NoAction, SharedString, KEYSTROKE_PARSE_EXPECTED_MESSAGE,
  7};
  8use migrator::migrate_keymap;
  9use schemars::{
 10    gen::{SchemaGenerator, SchemaSettings},
 11    schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SubschemaValidation},
 12    JsonSchema,
 13};
 14use serde::{Deserialize, Serialize};
 15use serde_json::Value;
 16use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};
 17use util::{asset_str, markdown::MarkdownString};
 18
 19use crate::{settings_store::parse_json_with_comments, SettingsAssets};
 20
 21pub trait KeyBindingValidator: Send + Sync {
 22    fn action_type_id(&self) -> TypeId;
 23    fn validate(&self, binding: &KeyBinding) -> Result<(), MarkdownString>;
 24}
 25
 26pub struct KeyBindingValidatorRegistration(pub fn() -> Box<dyn KeyBindingValidator>);
 27
 28inventory::collect!(KeyBindingValidatorRegistration);
 29
 30pub(crate) static KEY_BINDING_VALIDATORS: LazyLock<BTreeMap<TypeId, Box<dyn KeyBindingValidator>>> =
 31    LazyLock::new(|| {
 32        let mut validators = BTreeMap::new();
 33        for validator_registration in inventory::iter::<KeyBindingValidatorRegistration> {
 34            let validator = validator_registration.0();
 35            validators.insert(validator.action_type_id(), validator);
 36        }
 37        validators
 38    });
 39
 40// Note that the doc comments on these are shown by json-language-server when editing the keymap, so
 41// they should be considered user-facing documentation. Documentation is not handled well with
 42// schemars-0.8 - when there are newlines, it is rendered as plaintext (see
 43// https://github.com/GREsau/schemars/issues/38#issuecomment-2282883519). So for now these docs
 44// avoid newlines.
 45//
 46// TODO: Update to schemars-1.0 once it's released, and add more docs as newlines would be
 47// supported. Tracking issue is https://github.com/GREsau/schemars/issues/112.
 48
 49/// Keymap configuration consisting of sections. Each section may have a context predicate which
 50/// determines whether its bindings are used.
 51#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)]
 52#[serde(transparent)]
 53pub struct KeymapFile(Vec<KeymapSection>);
 54
 55/// Keymap section which binds keystrokes to actions.
 56#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)]
 57pub struct KeymapSection {
 58    /// Determines when these bindings are active. When just a name is provided, like `Editor` or
 59    /// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`,
 60    /// `X || Y`, `!X` are also supported. Some more complex logic including checking OS and the
 61    /// current file extension are also supported - see [the
 62    /// documentation](https://zed.dev/docs/key-bindings#contexts) for more details.
 63    #[serde(default)]
 64    context: String,
 65    /// This option enables specifying keys based on their position on a QWERTY keyboard, by using
 66    /// position-equivalent mappings for some non-QWERTY keyboards. This is currently only supported
 67    /// on macOS. See the documentation for more details.
 68    #[serde(default)]
 69    use_key_equivalents: bool,
 70    /// This keymap section's bindings, as a JSON object mapping keystrokes to actions. The
 71    /// keystrokes key is a string representing a sequence of keystrokes to type, where the
 72    /// keystrokes are separated by whitespace. Each keystroke is a sequence of modifiers (`ctrl`,
 73    /// `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) followed by a key, separated by `-`. The
 74    /// order of bindings does matter. When the same keystrokes are bound at the same context depth,
 75    /// the binding that occurs later in the file is preferred. For displaying keystrokes in the UI,
 76    /// the later binding for the same action is preferred.
 77    #[serde(default)]
 78    bindings: Option<IndexMap<String, KeymapAction>>,
 79    #[serde(flatten)]
 80    unrecognized_fields: IndexMap<String, Value>,
 81    // This struct intentionally uses permissive types for its fields, rather than validating during
 82    // deserialization. The purpose of this is to allow loading the portion of the keymap that doesn't
 83    // have errors. The downside of this is that the errors are not reported with line+column info.
 84    // Unfortunately the implementations of the `Spanned` types for preserving this information are
 85    // highly inconvenient (`serde_spanned`) and in some cases don't work at all here
 86    // (`json_spanned_>value`). Serde should really have builtin support for this.
 87}
 88
 89impl KeymapSection {
 90    pub fn bindings(&self) -> impl DoubleEndedIterator<Item = (&String, &KeymapAction)> {
 91        self.bindings.iter().flatten()
 92    }
 93}
 94
 95/// Keymap action as a JSON value, since it can either be null for no action, or the name of the
 96/// action, or an array of the name of the action and the action input.
 97///
 98/// Unlike the other json types involved in keymaps (including actions), this doc-comment will not
 99/// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The
100/// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.
101#[derive(Debug, Deserialize, Default, Clone, Serialize)]
102#[serde(transparent)]
103pub struct KeymapAction(pub(crate) Value);
104
105impl std::fmt::Display for KeymapAction {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match &self.0 {
108            Value::String(s) => write!(f, "{}", s),
109            Value::Array(arr) => {
110                let strings: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
111                write!(f, "{}", strings.join(", "))
112            }
113            _ => write!(f, "{}", self.0),
114        }
115    }
116}
117
118impl JsonSchema for KeymapAction {
119    /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can
120    /// reference the keymap action schema.
121    fn schema_name() -> String {
122        "KeymapAction".into()
123    }
124
125    /// This schema will be replaced with the full action schema in
126    /// `KeymapFile::generate_json_schema`.
127    fn json_schema(_: &mut SchemaGenerator) -> Schema {
128        Schema::Bool(true)
129    }
130}
131
132#[derive(Debug)]
133#[must_use]
134pub enum KeymapFileLoadResult {
135    Success {
136        key_bindings: Vec<KeyBinding>,
137        keymap_file: KeymapFile,
138    },
139    SomeFailedToLoad {
140        key_bindings: Vec<KeyBinding>,
141        keymap_file: KeymapFile,
142        error_message: MarkdownString,
143    },
144    JsonParseFailure {
145        error: anyhow::Error,
146    },
147}
148
149impl KeymapFile {
150    pub fn parse(content: &str) -> anyhow::Result<Self> {
151        parse_json_with_comments::<Self>(content)
152    }
153
154    pub fn load_asset(asset_path: &str, cx: &App) -> anyhow::Result<Vec<KeyBinding>> {
155        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
156            KeymapFileLoadResult::Success { key_bindings, .. } => Ok(key_bindings),
157            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => Err(anyhow!(
158                "Error loading built-in keymap \"{asset_path}\": {error_message}"
159            )),
160            KeymapFileLoadResult::JsonParseFailure { error } => Err(anyhow!(
161                "JSON parse error in built-in keymap \"{asset_path}\": {error}"
162            )),
163        }
164    }
165
166    #[cfg(feature = "test-support")]
167    pub fn load_asset_allow_partial_failure(
168        asset_path: &str,
169        cx: &App,
170    ) -> anyhow::Result<Vec<KeyBinding>> {
171        match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
172            KeymapFileLoadResult::SomeFailedToLoad {
173                key_bindings,
174                error_message,
175                ..
176            } if key_bindings.is_empty() => Err(anyhow!(
177                "Error loading built-in keymap \"{asset_path}\": {error_message}"
178            )),
179            KeymapFileLoadResult::Success { key_bindings, .. }
180            | KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
181            KeymapFileLoadResult::JsonParseFailure { error } => Err(anyhow!(
182                "JSON parse error in built-in keymap \"{asset_path}\": {error}"
183            )),
184        }
185    }
186
187    #[cfg(feature = "test-support")]
188    pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> {
189        match Self::load(content, cx) {
190            KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings,
191            KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
192                panic!("{error_message}");
193            }
194            KeymapFileLoadResult::JsonParseFailure { error } => {
195                panic!("JSON parse error: {error}");
196            }
197        }
198    }
199
200    pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult {
201        let key_equivalents = crate::key_equivalents::get_key_equivalents(&cx.keyboard_layout());
202
203        if content.is_empty() {
204            return KeymapFileLoadResult::Success {
205                key_bindings: Vec::new(),
206                keymap_file: KeymapFile(Vec::new()),
207            };
208        }
209        let keymap_file = match parse_json_with_comments::<Self>(content) {
210            Ok(keymap_file) => keymap_file,
211            Err(error) => {
212                return KeymapFileLoadResult::JsonParseFailure { error };
213            }
214        };
215
216        // Accumulate errors in order to support partial load of user keymap in the presence of
217        // errors in context and binding parsing.
218        let mut errors = Vec::new();
219        let mut key_bindings = Vec::new();
220
221        for KeymapSection {
222            context,
223            use_key_equivalents,
224            bindings,
225            unrecognized_fields,
226        } in keymap_file.0.iter()
227        {
228            let context_predicate: Option<Rc<KeyBindingContextPredicate>> = if context.is_empty() {
229                None
230            } else {
231                match KeyBindingContextPredicate::parse(context) {
232                    Ok(context_predicate) => Some(context_predicate.into()),
233                    Err(err) => {
234                        // Leading space is to separate from the message indicating which section
235                        // the error occurred in.
236                        errors.push((
237                            context,
238                            format!(" Parse error in section `context` field: {}", err),
239                        ));
240                        continue;
241                    }
242                }
243            };
244
245            let key_equivalents = if *use_key_equivalents {
246                key_equivalents.as_ref()
247            } else {
248                None
249            };
250
251            let mut section_errors = String::new();
252
253            if !unrecognized_fields.is_empty() {
254                write!(
255                    section_errors,
256                    "\n\n - Unrecognized fields: {}",
257                    MarkdownString::inline_code(&format!("{:?}", unrecognized_fields.keys()))
258                )
259                .unwrap();
260            }
261
262            if let Some(bindings) = bindings {
263                for (keystrokes, action) in bindings {
264                    let result = Self::load_keybinding(
265                        keystrokes,
266                        action,
267                        context_predicate.clone(),
268                        key_equivalents,
269                        cx,
270                    );
271                    match result {
272                        Ok(key_binding) => {
273                            key_bindings.push(key_binding);
274                        }
275                        Err(err) => {
276                            let mut lines = err.lines();
277                            let mut indented_err = lines.next().unwrap().to_string();
278                            for line in lines {
279                                indented_err.push_str("  ");
280                                indented_err.push_str(line);
281                                indented_err.push_str("\n");
282                            }
283                            write!(
284                                section_errors,
285                                "\n\n- In binding {}, {indented_err}",
286                                inline_code_string(keystrokes),
287                            )
288                            .unwrap();
289                        }
290                    }
291                }
292            }
293
294            if !section_errors.is_empty() {
295                errors.push((context, section_errors))
296            }
297        }
298
299        if errors.is_empty() {
300            KeymapFileLoadResult::Success {
301                key_bindings,
302                keymap_file,
303            }
304        } else {
305            let mut error_message = "Errors in user keymap file.\n".to_owned();
306            for (context, section_errors) in errors {
307                if context.is_empty() {
308                    write!(error_message, "\n\nIn section without context predicate:").unwrap()
309                } else {
310                    write!(
311                        error_message,
312                        "\n\nIn section with {}:",
313                        MarkdownString::inline_code(&format!("context = \"{}\"", context))
314                    )
315                    .unwrap()
316                }
317                write!(error_message, "{section_errors}").unwrap();
318            }
319            KeymapFileLoadResult::SomeFailedToLoad {
320                key_bindings,
321                keymap_file,
322                error_message: MarkdownString(error_message),
323            }
324        }
325    }
326
327    fn load_keybinding(
328        keystrokes: &str,
329        action: &KeymapAction,
330        context: Option<Rc<KeyBindingContextPredicate>>,
331        key_equivalents: Option<&HashMap<char, char>>,
332        cx: &App,
333    ) -> std::result::Result<KeyBinding, String> {
334        let (build_result, action_input_string) = match &action.0 {
335            Value::Array(items) => {
336                if items.len() != 2 {
337                    return Err(format!(
338                        "expected two-element array of `[name, input]`. \
339                        Instead found {}.",
340                        MarkdownString::inline_code(&action.0.to_string())
341                    ));
342                }
343                let serde_json::Value::String(ref name) = items[0] else {
344                    return Err(format!(
345                        "expected two-element array of `[name, input]`, \
346                        but the first element is not a string in {}.",
347                        MarkdownString::inline_code(&action.0.to_string())
348                    ));
349                };
350                let action_input = items[1].clone();
351                let action_input_string = action_input.to_string();
352                (
353                    cx.build_action(&name, Some(action_input)),
354                    Some(action_input_string),
355                )
356            }
357            Value::String(name) => (cx.build_action(&name, None), None),
358            Value::Null => (Ok(NoAction.boxed_clone()), None),
359            _ => {
360                return Err(format!(
361                    "expected two-element array of `[name, input]`. \
362                    Instead found {}.",
363                    MarkdownString::inline_code(&action.0.to_string())
364                ));
365            }
366        };
367
368        let action = match build_result {
369            Ok(action) => action,
370            Err(ActionBuildError::NotFound { name }) => {
371                return Err(format!(
372                    "didn't find an action named {}.",
373                    inline_code_string(&name)
374                ))
375            }
376            Err(ActionBuildError::BuildError { name, error }) => match action_input_string {
377                Some(action_input_string) => {
378                    return Err(format!(
379                        "can't build {} action from input value {}: {}",
380                        inline_code_string(&name),
381                        MarkdownString::inline_code(&action_input_string),
382                        MarkdownString::escape(&error.to_string())
383                    ))
384                }
385                None => {
386                    return Err(format!(
387                        "can't build {} action - it requires input data via [name, input]: {}",
388                        inline_code_string(&name),
389                        MarkdownString::escape(&error.to_string())
390                    ))
391                }
392            },
393        };
394
395        let key_binding = match KeyBinding::load(keystrokes, action, context, key_equivalents) {
396            Ok(key_binding) => key_binding,
397            Err(InvalidKeystrokeError { keystroke }) => {
398                return Err(format!(
399                    "invalid keystroke {}. {}",
400                    inline_code_string(&keystroke),
401                    KEYSTROKE_PARSE_EXPECTED_MESSAGE
402                ))
403            }
404        };
405
406        if let Some(validator) = KEY_BINDING_VALIDATORS.get(&key_binding.action().type_id()) {
407            match validator.validate(&key_binding) {
408                Ok(()) => Ok(key_binding),
409                Err(error) => Err(error.0),
410            }
411        } else {
412            Ok(key_binding)
413        }
414    }
415
416    pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
417        let mut generator = SchemaSettings::draft07()
418            .with(|settings| settings.option_add_null_type = false)
419            .into_generator();
420
421        let action_schemas = cx.action_schemas(&mut generator);
422        let deprecations = cx.action_deprecations();
423        KeymapFile::generate_json_schema(generator, action_schemas, deprecations)
424    }
425
426    fn generate_json_schema(
427        generator: SchemaGenerator,
428        action_schemas: Vec<(SharedString, Option<Schema>)>,
429        deprecations: &HashMap<SharedString, SharedString>,
430    ) -> serde_json::Value {
431        fn set<I, O>(input: I) -> Option<O>
432        where
433            I: Into<O>,
434        {
435            Some(input.into())
436        }
437
438        fn add_deprecation(schema_object: &mut SchemaObject, message: String) {
439            schema_object.extensions.insert(
440                // deprecationMessage is not part of the JSON Schema spec,
441                // but json-language-server recognizes it.
442                "deprecationMessage".to_owned(),
443                Value::String(message),
444            );
445        }
446
447        fn add_deprecation_preferred_name(schema_object: &mut SchemaObject, new_name: &str) {
448            add_deprecation(schema_object, format!("Deprecated, use {new_name}"));
449        }
450
451        fn add_description(schema_object: &mut SchemaObject, description: String) {
452            schema_object
453                .metadata
454                .get_or_insert(Default::default())
455                .description = Some(description);
456        }
457
458        let empty_object: SchemaObject = SchemaObject {
459            instance_type: set(InstanceType::Object),
460            ..Default::default()
461        };
462
463        // This is a workaround for a json-language-server issue where it matches the first
464        // alternative that matches the value's shape and uses that for documentation.
465        //
466        // In the case of the array validations, it would even provide an error saying that the name
467        // must match the name of the first alternative.
468        let mut plain_action = SchemaObject {
469            instance_type: set(InstanceType::String),
470            const_value: Some(Value::String("".to_owned())),
471            ..Default::default()
472        };
473        let no_action_message = "No action named this.";
474        add_description(&mut plain_action, no_action_message.to_owned());
475        add_deprecation(&mut plain_action, no_action_message.to_owned());
476        let mut matches_action_name = SchemaObject {
477            const_value: Some(Value::String("".to_owned())),
478            ..Default::default()
479        };
480        let no_action_message = "No action named this that takes input.";
481        add_description(&mut matches_action_name, no_action_message.to_owned());
482        add_deprecation(&mut matches_action_name, no_action_message.to_owned());
483        let action_with_input = SchemaObject {
484            instance_type: set(InstanceType::Array),
485            array: set(ArrayValidation {
486                items: set(vec![
487                    matches_action_name.into(),
488                    // Accept any value, as we want this to be the preferred match when there is a
489                    // typo in the name.
490                    Schema::Bool(true),
491                ]),
492                min_items: Some(2),
493                max_items: Some(2),
494                ..Default::default()
495            }),
496            ..Default::default()
497        };
498        let mut keymap_action_alternatives = vec![plain_action.into(), action_with_input.into()];
499
500        for (name, action_schema) in action_schemas.iter() {
501            let schema = if let Some(Schema::Object(schema)) = action_schema {
502                Some(schema.clone())
503            } else {
504                None
505            };
506
507            let description = schema.as_ref().and_then(|schema| {
508                schema
509                    .metadata
510                    .as_ref()
511                    .and_then(|metadata| metadata.description.clone())
512            });
513
514            let deprecation = if name == NoAction.name() {
515                Some("null")
516            } else {
517                deprecations.get(name).map(|new_name| new_name.as_ref())
518            };
519
520            // Add an alternative for plain action names.
521            let mut plain_action = SchemaObject {
522                instance_type: set(InstanceType::String),
523                const_value: Some(Value::String(name.to_string())),
524                ..Default::default()
525            };
526            if let Some(new_name) = deprecation {
527                add_deprecation_preferred_name(&mut plain_action, new_name);
528            }
529            if let Some(description) = description.clone() {
530                add_description(&mut plain_action, description);
531            }
532            keymap_action_alternatives.push(plain_action.into());
533
534            // Add an alternative for actions with data specified as a [name, data] array.
535            //
536            // When a struct with no deserializable fields is added with impl_actions! /
537            // impl_actions_as! an empty object schema is produced. The action should be invoked
538            // without data in this case.
539            if let Some(schema) = schema {
540                if schema != empty_object {
541                    let mut matches_action_name = SchemaObject {
542                        const_value: Some(Value::String(name.to_string())),
543                        ..Default::default()
544                    };
545                    if let Some(description) = description.clone() {
546                        add_description(&mut matches_action_name, description.to_string());
547                    }
548                    if let Some(new_name) = deprecation {
549                        add_deprecation_preferred_name(&mut matches_action_name, new_name);
550                    }
551                    let action_with_input = SchemaObject {
552                        instance_type: set(InstanceType::Array),
553                        array: set(ArrayValidation {
554                            items: set(vec![matches_action_name.into(), schema.into()]),
555                            min_items: Some(2),
556                            max_items: Some(2),
557                            ..Default::default()
558                        }),
559                        ..Default::default()
560                    };
561                    keymap_action_alternatives.push(action_with_input.into());
562                }
563            }
564        }
565
566        // Placing null first causes json-language-server to default assuming actions should be
567        // null, so place it last.
568        keymap_action_alternatives.push(
569            SchemaObject {
570                instance_type: set(InstanceType::Null),
571                ..Default::default()
572            }
573            .into(),
574        );
575
576        let action_schema = SchemaObject {
577            subschemas: set(SubschemaValidation {
578                one_of: Some(keymap_action_alternatives),
579                ..Default::default()
580            }),
581            ..Default::default()
582        }
583        .into();
584
585        // The `KeymapSection` schema will reference the `KeymapAction` schema by name, so replacing
586        // the definition of `KeymapAction` results in the full action schema being used.
587        let mut root_schema = generator.into_root_schema_for::<KeymapFile>();
588        root_schema
589            .definitions
590            .insert(KeymapAction::schema_name(), action_schema);
591
592        // This and other json schemas can be viewed via `debug: open language server logs` ->
593        // `json-language-server` -> `Server Info`.
594        serde_json::to_value(root_schema).unwrap()
595    }
596
597    pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
598        self.0.iter()
599    }
600
601    async fn load_keymap_file(fs: &Arc<dyn Fs>) -> Result<String> {
602        match fs.load(paths::keymap_file()).await {
603            result @ Ok(_) => result,
604            Err(err) => {
605                if let Some(e) = err.downcast_ref::<std::io::Error>() {
606                    if e.kind() == std::io::ErrorKind::NotFound {
607                        return Ok(crate::initial_keymap_content().to_string());
608                    }
609                }
610                Err(err)
611            }
612        }
613    }
614
615    pub fn should_migrate_keymap(keymap_file: Self) -> bool {
616        let Ok(old_text) = serde_json::to_string(&keymap_file) else {
617            return false;
618        };
619        migrate_keymap(&old_text).is_some()
620    }
621
622    pub async fn migrate_keymap(fs: Arc<dyn Fs>) -> Result<()> {
623        let old_text = Self::load_keymap_file(&fs).await?;
624        let Some(new_text) = migrate_keymap(&old_text) else {
625            return Ok(());
626        };
627        let keymap_path = paths::keymap_file().as_path();
628        if fs.is_file(keymap_path).await {
629            fs.atomic_write(paths::keymap_backup_file().to_path_buf(), old_text)
630                .await
631                .with_context(|| {
632                    "Failed to create settings backup in home directory".to_string()
633                })?;
634            let resolved_path = fs
635                .canonicalize(keymap_path)
636                .await
637                .with_context(|| format!("Failed to canonicalize keymap path {:?}", keymap_path))?;
638            fs.atomic_write(resolved_path.clone(), new_text)
639                .await
640                .with_context(|| format!("Failed to write keymap to file {:?}", resolved_path))?;
641        } else {
642            fs.atomic_write(keymap_path.to_path_buf(), new_text)
643                .await
644                .with_context(|| format!("Failed to write keymap to file {:?}", keymap_path))?;
645        }
646
647        Ok(())
648    }
649}
650
651// Double quotes a string and wraps it in backticks for markdown inline code..
652fn inline_code_string(text: &str) -> MarkdownString {
653    MarkdownString::inline_code(&format!("\"{}\"", text))
654}
655
656#[cfg(test)]
657mod tests {
658    use super::KeymapFile;
659
660    #[test]
661    fn can_deserialize_keymap_with_trailing_comma() {
662        let json = indoc::indoc! {"[
663              // Standard macOS bindings
664              {
665                \"bindings\": {
666                  \"up\": \"menu::SelectPrev\",
667                },
668              },
669            ]
670                  "
671        };
672        KeymapFile::parse(json).unwrap();
673    }
674}