keymap_file.rs

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