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 Ok(parse_json_with_comments::<Self>(content_str)?.add(cx)?)
53 }
54
55 pub fn add(self, cx: &mut MutableAppContext) -> Result<()> {
56 for KeymapBlock { context, bindings } in self.0 {
57 cx.add_bindings(
58 bindings
59 .into_iter()
60 .map(|(keystroke, action)| {
61 let action = action.0.get();
62
63 // This is a workaround for a limitation in serde: serde-rs/json#497
64 // We want to deserialize the action data as a `RawValue` so that we can
65 // deserialize the action itself dynamically directly from the JSON
66 // string. But `RawValue` currently does not work inside of an untagged enum.
67 let action = if action.starts_with('[') {
68 let ActionWithData(name, data) = serde_json::from_str(action)?;
69 cx.deserialize_action(&name, Some(data.get()))
70 } else {
71 let name = serde_json::from_str(action)?;
72 cx.deserialize_action(name, None)
73 }
74 .with_context(|| {
75 format!(
76 "invalid binding value for keystroke {keystroke}, context {context:?}"
77 )
78 })?;
79 Binding::load(&keystroke, action, context.as_deref())
80 })
81 .collect::<Result<Vec<_>>>()?,
82 )
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 .into_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}