Provide JSON language server with settings schema

Max Brunsfeld created

Change summary

Cargo.lock                       | 42 ++++++++++++++++++++++++++++++++++
crates/language/src/language.rs  |  8 ++++-
crates/workspace/Cargo.toml      |  1 
crates/workspace/src/settings.rs | 12 +++++++--
crates/zed/src/language.rs       | 11 ++++++++
5 files changed, 69 insertions(+), 5 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1574,6 +1574,12 @@ dependencies = [
  "wio",
 ]
 
+[[package]]
+name = "dyn-clone"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
+
 [[package]]
 name = "easy-parallel"
 version = "3.1.0"
@@ -4139,6 +4145,30 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "schemars"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3"
+dependencies = [
+ "dyn-clone",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn",
+]
+
 [[package]]
 name = "scoped-tls"
 version = "1.0.0"
@@ -4260,6 +4290,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "serde_derive_internals"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "serde_json"
 version = "1.0.64"
@@ -5851,6 +5892,7 @@ dependencies = [
  "parking_lot",
  "postage",
  "project",
+ "schemars",
  "serde",
  "serde_json",
  "smallvec",

crates/language/src/language.rs 🔗

@@ -95,6 +95,8 @@ pub trait LspAdapter: 'static + Send + Sync {
     fn initialization_options(&self) -> Option<Value> {
         None
     }
+
+    fn register_handlers(&self, _: &mut lsp::LanguageServer) {}
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -319,13 +321,15 @@ impl LanguageRegistry {
 
             let server_binary_path = server_binary_path.await?;
             let server_args = adapter.server_args();
-            lsp::LanguageServer::new(
+            let mut server = lsp::LanguageServer::new(
                 &server_binary_path,
                 server_args,
                 &root_path,
                 adapter.initialization_options(),
                 background,
-            )
+            )?;
+            adapter.register_handlers(&mut server);
+            Ok(server)
         }))
     }
 

crates/workspace/Cargo.toml 🔗

@@ -24,6 +24,7 @@ futures = "0.3"
 log = "0.4"
 parking_lot = "0.11.1"
 postage = { version = "0.4.1", features = ["futures-traits"] }
+schemars = "0.8"
 serde = { version = "1", features = ["derive", "rc"] }
 serde_json = { version = "1", features = ["preserve_order"] }
 smallvec = { version = "1.6", features = ["union"] }

crates/workspace/src/settings.rs 🔗

@@ -8,6 +8,7 @@ use language::Language;
 use parking_lot::Mutex;
 use postage::{prelude::Stream, watch};
 use project::Fs;
+use schemars::{schema_for, JsonSchema};
 use serde::Deserialize;
 use std::{collections::HashMap, path::Path, sync::Arc, time::Duration};
 use theme::{Theme, ThemeRegistry};
@@ -24,14 +25,14 @@ pub struct Settings {
     pub theme: Arc<Theme>,
 }
 
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 pub struct LanguageOverride {
     pub tab_size: Option<usize>,
     pub soft_wrap: Option<SoftWrap>,
     pub preferred_line_length: Option<u32>,
 }
 
-#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum SoftWrap {
     None,
@@ -42,7 +43,7 @@ pub enum SoftWrap {
 #[derive(Clone)]
 pub struct SettingsFile(watch::Receiver<SettingsFileContent>);
 
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 struct SettingsFileContent {
     #[serde(default)]
     buffer_font_family: Option<String>,
@@ -94,6 +95,11 @@ impl SettingsFile {
 }
 
 impl Settings {
+    pub fn file_json_schema() -> String {
+        let schema = schema_for!(SettingsFileContent);
+        serde_json::to_string(&schema).unwrap()
+    }
+
     pub fn from_files(
         defaults: Self,
         sources: Vec<SettingsFile>,

crates/zed/src/language.rs 🔗

@@ -530,6 +530,17 @@ impl LspAdapter for JsonLspAdapter {
             "provideFormatter": true
         }))
     }
+
+    fn register_handlers(&self, lsp: &mut lsp::LanguageServer) {
+        lsp.on_custom_request::<Vec<String>, Option<String>, _>("vscode/content", |schema| {
+            if schema.get(0).map(String::as_str) == Some("zed://settings") {
+                Ok(Some(workspace::Settings::file_json_schema()))
+            } else {
+                Ok(None)
+            }
+        })
+        .detach();
+    }
 }
 
 pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry {