json_language_server_ext.rs

  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}