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}