keymap_file.rs

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