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}