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