1use anyhow::{Context, Result};
2use gpui::{App, AsyncApp, Entity, Global, WeakEntity};
3use lsp::LanguageServer;
4
5use crate::LspStore;
6
7const LOGGER: zlog::Logger = zlog::scoped!("json-schema");
8
9/// https://github.com/Microsoft/vscode/blob/main/extensions/json-language-features/server/README.md#schema-content-request
10///
11/// Represents a "JSON language server-specific, non-standardized, extension to the LSP" with which the vscode-json-language-server
12/// can request the contents of a schema that is associated with a uri scheme it does not support.
13/// In our case, we provide the uris for actions on server startup under the `zed://schemas/action/{normalize_action_name}` scheme.
14/// 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
15struct SchemaContentRequest {}
16
17impl lsp::request::Request for SchemaContentRequest {
18 type Params = Vec<String>;
19
20 type Result = String;
21
22 const METHOD: &'static str = "vscode/content";
23}
24
25type SchemaRequestHandler = fn(Entity<LspStore>, String, &mut AsyncApp) -> Result<String>;
26pub struct SchemaHandlingImpl(SchemaRequestHandler);
27
28impl Global for SchemaHandlingImpl {}
29
30pub fn register_schema_handler(handler: SchemaRequestHandler, cx: &mut App) {
31 debug_assert!(
32 !cx.has_global::<SchemaHandlingImpl>(),
33 "SchemaHandlingImpl already registered"
34 );
35 cx.set_global(SchemaHandlingImpl(handler));
36}
37
38struct SchemaContentsChanged {}
39
40impl lsp::notification::Notification for SchemaContentsChanged {
41 const METHOD: &'static str = "json/schemaContent";
42 type Params = String;
43}
44
45pub fn notify_schema_changed(lsp_store: Entity<LspStore>, uri: &String, cx: &App) {
46 zlog::trace!(LOGGER => "Notifying schema changed for URI: {:?}", uri);
47 let servers = lsp_store.read_with(cx, |lsp_store, _| {
48 let mut servers = Vec::new();
49 let Some(local) = lsp_store.as_local() else {
50 return servers;
51 };
52
53 for states in local.language_servers.values() {
54 let json_server = match states {
55 super::LanguageServerState::Running {
56 adapter, server, ..
57 } if adapter.adapter.is_primary_zed_json_schema_adapter() => server.clone(),
58 _ => continue,
59 };
60
61 servers.push(json_server);
62 }
63 servers
64 });
65 for server in servers {
66 zlog::trace!(LOGGER => "Notifying server {:?} of schema change for URI: {:?}", server.server_id(), &uri);
67 // TODO: handle errors
68 server.notify::<SchemaContentsChanged>(uri).ok();
69 }
70}
71
72pub fn register_requests(lsp_store: WeakEntity<LspStore>, language_server: &LanguageServer) {
73 language_server
74 .on_request::<SchemaContentRequest, _, _>(move |params, cx| {
75 let handler = cx.try_read_global::<SchemaHandlingImpl, _>(|handler, _| {
76 handler.0
77 });
78 let mut cx = cx.clone();
79 let uri = params.clone().pop();
80 let lsp_store = lsp_store.clone();
81 let resolution = async move {
82 let lsp_store = lsp_store.upgrade().context("LSP store has been dropped")?;
83 let uri = uri.context("No URI")?;
84 let handle_schema_request = handler.context("No schema handler registered")?;
85 handle_schema_request(lsp_store, uri, &mut cx)
86 };
87 async move {
88 zlog::trace!(LOGGER => "Handling schema request for {:?}", ¶ms);
89 let result = resolution.await;
90 match &result {
91 Ok(content) => {zlog::trace!(LOGGER => "Schema request resolved with {}B schema", content.len());},
92 Err(err) => {zlog::warn!(LOGGER => "Schema request failed: {}", err);},
93 }
94 result
95 }
96 })
97 .detach();
98}