keymap_file.rs

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