Add YAML LSP initialization_options (#17479)

Peter Tripp created

Makes YAML language server configurable under `lsp.yaml-language-server`:
- Add support for `initialization_options` 
- Add support for custom `bin` specification

Change summary

crates/languages/src/yaml.rs | 58 +++++++++++++++++++++++++++++++------
docs/src/languages/yaml.md   | 55 ++++++++++++++++++++++++++++++++++++
2 files changed, 103 insertions(+), 10 deletions(-)

Detailed changes

crates/languages/src/yaml.rs 🔗

@@ -7,6 +7,7 @@ use language::{
 };
 use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
+use project::project_settings::ProjectSettings;
 use serde_json::Value;
 use settings::{Settings, SettingsLocation};
 use smol::fs;
@@ -16,7 +17,7 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use util::{maybe, ResultExt};
+use util::{maybe, merge_json_value_into, ResultExt};
 
 const SERVER_PATH: &str = "node_modules/yaml-language-server/bin/yaml-language-server";
 
@@ -29,6 +30,7 @@ pub struct YamlLspAdapter {
 }
 
 impl YamlLspAdapter {
+    const SERVER_NAME: &'static str = "yaml-language-server";
     pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         YamlLspAdapter { node }
     }
@@ -37,7 +39,40 @@ impl YamlLspAdapter {
 #[async_trait(?Send)]
 impl LspAdapter for YamlLspAdapter {
     fn name(&self) -> LanguageServerName {
-        LanguageServerName("yaml-language-server".into())
+        LanguageServerName(Self::SERVER_NAME.into())
+    }
+
+    async fn check_if_user_installed(
+        &self,
+        _delegate: &dyn LspAdapterDelegate,
+        cx: &AsyncAppContext,
+    ) -> Option<LanguageServerBinary> {
+        let configured_binary = cx
+            .update(|cx| {
+                ProjectSettings::get_global(cx)
+                    .lsp
+                    .get(Self::SERVER_NAME)
+                    .and_then(|s| s.binary.clone())
+            })
+            .ok()??;
+
+        let path = if let Some(configured_path) = configured_binary.path.map(PathBuf::from) {
+            configured_path
+        } else {
+            self.node.binary_path().await.ok()?
+        };
+
+        let arguments = configured_binary
+            .arguments
+            .unwrap_or_default()
+            .iter()
+            .map(|arg| arg.into())
+            .collect();
+        Some(LanguageServerBinary {
+            path,
+            arguments,
+            env: None,
+        })
     }
 
     async fn fetch_latest_server_version(
@@ -109,15 +144,18 @@ impl LspAdapter for YamlLspAdapter {
                 .language(Some("YAML"))
                 .tab_size
         })?;
+        let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}});
 
-        Ok(serde_json::json!({
-            "yaml": {
-                "keyOrdering": false
-            },
-            "[yaml]": {
-                "editor.tabSize": tab_size
-            }
-        }))
+        let project_options = cx.update(|cx| {
+            ProjectSettings::get_global(cx)
+                .lsp
+                .get(Self::SERVER_NAME)
+                .and_then(|s| s.initialization_options.clone())
+        })?;
+        if let Some(override_options) = project_options {
+            merge_json_value_into(override_options, &mut options);
+        }
+        Ok(options)
     }
 }
 

docs/src/languages/yaml.md 🔗

@@ -4,3 +4,58 @@ YAML support is available natively in Zed.
 
 - Tree Sitter: [zed-industries/tree-sitter-yaml](https://github.com/zed-industries/tree-sitter-yaml)
 - Language Server: [redhat-developer/yaml-language-server](https://github.com/redhat-developer/yaml-language-server)
+
+## Configuration
+
+You can configure various [yaml-language-server settings](https://github.com/redhat-developer/yaml-language-server?tab=readme-ov-file#language-server-settings) by adding them to your Zed settings.json in a `yaml-language-server` block under the `lsp` key. For example:
+
+```json
+  "lsp": {
+    "yaml-language-server": {
+      "initialization_options": {
+        "yaml": {
+          "keyOrdering": true,
+          "format": {
+            "singleQuote": true
+          },
+          "schemas": {
+              "http://json.schemastore.org/composer": ["/*"],
+              "../relative/path/schema.json": ["/config*.yaml"]
+          }
+        }
+      }
+    }
+  }
+```
+
+Note, settings keys must be nested, so `yaml.keyOrdering` becomes `{"yaml": { "keyOrdering": true }}`.
+
+## Schemas
+
+By default yaml-language-server will attempt to determine the correct schema for a given yaml file and retrieve the appropriate JSON Schema from [Json Schema Store].
+
+You can override this by [using an inlined schema] reference via a modeline comment at the top of your yaml file:
+
+```yaml
+# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
+name: Issue Assignment
+on:
+  issues:
+    types: [oppened]
+```
+
+You can disable this functionality entirely if desired:
+
+```json
+  "lsp": {
+    "yaml-language-server": {
+      "initialization_options": {
+        "yaml": {
+          "schemaStore": {
+            "enable": false
+          }
+        }
+      }
+    }
+  }
+```