json_language_server_ext.rs

  1use anyhow::{Context, Result};
  2use gpui::{App, AsyncApp, Entity, Global, Task, 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) -> Task<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 {NAME} (id {ID:?}) of schema change for URI: {uri:?}",
 67            NAME = server.name(),
 68            ID = server.server_id()
 69        );
 70        if let Err(error) = server.notify::<SchemaContentsChanged>(uri.clone()) {
 71            zlog::error!(
 72                LOGGER => "Failed to notify server {NAME} (id {ID:?}) of schema change for URI {uri:?}: {error:#}",
 73                    NAME = server.name(),
 74                    ID = server.server_id(),
 75            );
 76        }
 77    }
 78}
 79
 80pub fn register_requests(lsp_store: WeakEntity<LspStore>, language_server: &LanguageServer) {
 81    language_server
 82        .on_request::<SchemaContentRequest, _, _>(move |params, cx| {
 83            let handler = cx.try_read_global::<SchemaHandlingImpl, _>(|handler, _| handler.0);
 84            let mut cx = cx.clone();
 85            let uri = params.clone().pop();
 86            let lsp_store = lsp_store.clone();
 87            let resolution = async move {
 88                let lsp_store = lsp_store.upgrade().context("LSP store has been dropped")?;
 89                let uri = uri.context("No URI")?;
 90                let handle_schema_request = handler.context("No schema handler registered")?;
 91                handle_schema_request(lsp_store, uri, &mut cx).await
 92            };
 93            async move {
 94                zlog::trace!(LOGGER => "Handling schema request for {:?}", &params);
 95                let result = resolution.await;
 96                match &result {
 97                    Ok(content) => {zlog::trace!(LOGGER => "Schema request resolved with {}B schema", content.len());},
 98                    Err(err) => {zlog::warn!(LOGGER => "Schema request failed: {}", err);},
 99                }
100                result
101            }
102        })
103        .detach();
104}