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