1use anyhow::Context as _;
2use collections::HashMap;
3use gpui::WeakEntity;
4use lsp::LanguageServer;
5
6use crate::LspStore;
7/// https://github.com/Microsoft/vscode/blob/main/extensions/json-language-features/server/README.md#schema-content-request
8///
9/// Represents a "JSON language server-specific, non-standardized, extension to the LSP" with which the vscode-json-language-server
10/// can request the contents of a schema that is associated with a uri scheme it does not support.
11/// In our case, we provide the uris for actions on server startup under the `zed://schemas/action/{normalize_action_name}` scheme.
12/// We can then respond to this request with the schema content on demand, thereby greatly reducing the total size of the JSON we send to the server on startup
13struct SchemaContentRequest {}
14
15impl lsp::request::Request for SchemaContentRequest {
16 type Params = Vec<String>;
17
18 type Result = String;
19
20 const METHOD: &'static str = "vscode/content";
21}
22
23pub fn register_requests(_lsp_store: WeakEntity<LspStore>, language_server: &LanguageServer) {
24 language_server
25 .on_request::<SchemaContentRequest, _, _>(|params, cx| {
26 // PERF: Use a cache (`OnceLock`?) to avoid recomputing the action schemas
27 let mut generator = settings::KeymapFile::action_schema_generator();
28 let all_schemas = cx.update(|cx| HashMap::from_iter(cx.action_schemas(&mut generator)));
29 async move {
30 let all_schemas = all_schemas?;
31 let Some(uri) = params.get(0) else {
32 anyhow::bail!("No URI");
33 };
34 let normalized_action_name = uri
35 .strip_prefix("zed://schemas/action/")
36 .context("Invalid URI")?;
37 let action_name = denormalize_action_name(normalized_action_name);
38 let schema = root_schema_from_action_schema(
39 all_schemas
40 .get(action_name.as_str())
41 .and_then(Option::as_ref),
42 &mut generator,
43 )
44 .to_value();
45
46 serde_json::to_string(&schema).context("Failed to serialize schema")
47 }
48 })
49 .detach();
50}
51
52pub fn normalize_action_name(action_name: &str) -> String {
53 action_name.replace("::", "__")
54}
55
56pub fn denormalize_action_name(action_name: &str) -> String {
57 action_name.replace("__", "::")
58}
59
60pub fn normalized_action_file_name(action_name: &str) -> String {
61 normalized_action_name_to_file_name(normalize_action_name(action_name))
62}
63
64pub fn normalized_action_name_to_file_name(mut normalized_action_name: String) -> String {
65 normalized_action_name.push_str(".json");
66 normalized_action_name
67}
68
69pub fn url_schema_for_action(action_name: &str) -> serde_json::Value {
70 let normalized_name = normalize_action_name(action_name);
71 let file_name = normalized_action_name_to_file_name(normalized_name.clone());
72 serde_json::json!({
73 "fileMatch": [file_name],
74 "url": format!("zed://schemas/action/{}", normalized_name)
75 })
76}
77
78fn root_schema_from_action_schema(
79 action_schema: Option<&schemars::Schema>,
80 generator: &mut schemars::SchemaGenerator,
81) -> schemars::Schema {
82 let Some(action_schema) = action_schema else {
83 return schemars::json_schema!(false);
84 };
85 let meta_schema = generator
86 .settings()
87 .meta_schema
88 .as_ref()
89 .expect("meta_schema should be present in schemars settings")
90 .to_string();
91 let defs = generator.definitions();
92 let mut schema = schemars::json_schema!({
93 "$schema": meta_schema,
94 "allowTrailingCommas": true,
95 "$defs": defs,
96 });
97 schema
98 .ensure_object()
99 .extend(std::mem::take(action_schema.clone().ensure_object()));
100 schema
101}