1use crate::{parse_json_with_comments, Settings};
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};
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}