keymap_file.rs

  1use crate::parse_json_with_comments;
  2use anyhow::{Context, Result};
  3use assets::Assets;
  4use collections::BTreeMap;
  5use gpui::{keymap::Binding, MutableAppContext};
  6use schemars::{
  7    gen::{SchemaGenerator, SchemaSettings},
  8    schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
  9    JsonSchema,
 10};
 11use serde::Deserialize;
 12use serde_json::{value::RawValue, Value};
 13
 14#[derive(Deserialize, Default, Clone, JsonSchema)]
 15#[serde(transparent)]
 16pub struct KeymapFileContent(Vec<KeymapBlock>);
 17
 18#[derive(Deserialize, Default, Clone, JsonSchema)]
 19pub struct KeymapBlock {
 20    #[serde(default)]
 21    context: Option<String>,
 22    bindings: BTreeMap<String, KeymapAction>,
 23}
 24
 25#[derive(Deserialize, Default, Clone)]
 26#[serde(transparent)]
 27pub struct KeymapAction(Box<RawValue>);
 28
 29impl JsonSchema for KeymapAction {
 30    fn schema_name() -> String {
 31        "KeymapAction".into()
 32    }
 33
 34    fn json_schema(_: &mut SchemaGenerator) -> Schema {
 35        Schema::Bool(true)
 36    }
 37}
 38
 39#[derive(Deserialize)]
 40struct ActionWithData(Box<str>, Box<RawValue>);
 41
 42impl KeymapFileContent {
 43    pub fn load_defaults(cx: &mut MutableAppContext) {
 44        for path in ["keymaps/default.json", "keymaps/vim.json"] {
 45            Self::load(path, cx).unwrap();
 46        }
 47    }
 48
 49    pub fn load(asset_path: &str, cx: &mut MutableAppContext) -> Result<()> {
 50        let content = Assets::get(asset_path).unwrap().data;
 51        let content_str = std::str::from_utf8(content.as_ref()).unwrap();
 52        parse_json_with_comments::<Self>(content_str)?.add_to_cx(cx)
 53    }
 54
 55    pub fn add_to_cx(self, cx: &mut MutableAppContext) -> Result<()> {
 56        for KeymapBlock { context, bindings } in self.0 {
 57            let bindings = bindings
 58                .into_iter()
 59                .map(|(keystroke, action)| {
 60                    let action = action.0.get();
 61
 62                    // This is a workaround for a limitation in serde: serde-rs/json#497
 63                    // We want to deserialize the action data as a `RawValue` so that we can
 64                    // deserialize the action itself dynamically directly from the JSON
 65                    // string. But `RawValue` currently does not work inside of an untagged enum.
 66                    let action = if action.starts_with('[') {
 67                        let ActionWithData(name, data) = serde_json::from_str(action)?;
 68                        cx.deserialize_action(&name, Some(data.get()))
 69                    } else {
 70                        let name = serde_json::from_str(action)?;
 71                        cx.deserialize_action(name, None)
 72                    }
 73                    .with_context(|| {
 74                        format!(
 75                            "invalid binding value for keystroke {keystroke}, context {context:?}"
 76                        )
 77                    })?;
 78                    Binding::load(&keystroke, action, context.as_deref())
 79                })
 80                .collect::<Result<Vec<_>>>()?;
 81
 82            cx.add_bindings(bindings);
 83        }
 84        Ok(())
 85    }
 86}
 87
 88pub fn keymap_file_json_schema(action_names: &[&'static str]) -> serde_json::Value {
 89    let mut root_schema = SchemaSettings::draft07()
 90        .with(|settings| settings.option_add_null_type = false)
 91        .into_generator()
 92        .into_root_schema_for::<KeymapFileContent>();
 93
 94    let action_schema = Schema::Object(SchemaObject {
 95        subschemas: Some(Box::new(SubschemaValidation {
 96            one_of: Some(vec![
 97                Schema::Object(SchemaObject {
 98                    instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
 99                    enum_values: Some(
100                        action_names
101                            .iter()
102                            .map(|name| Value::String(name.to_string()))
103                            .collect(),
104                    ),
105                    ..Default::default()
106                }),
107                Schema::Object(SchemaObject {
108                    instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
109                    ..Default::default()
110                }),
111            ]),
112            ..Default::default()
113        })),
114        ..Default::default()
115    });
116
117    root_schema
118        .definitions
119        .insert("KeymapAction".to_owned(), action_schema);
120
121    serde_json::to_value(root_schema).unwrap()
122}