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