tailwind: Allow Tailwind LS to be used in Scala (#11858)

Thorsten Ball created

This fixes the issue mentioned here:
https://github.com/zed-industries/zed/issues/5830#issuecomment-2111947083

In order for other languages to work, we need to pass the following
settings along to the Tailwind language server.

With the following Zed settings, it then also works for Scala:

```json
{
  "languages": {
    "Scala": {
      "language_servers": ["tailwindcss-language-server"]
    },
  },
  "lsp": {
    "tailwindcss-language-server": {
      "settings": {
        "includeLanguages": {
          "scala": "html"
        },
        "experimental": {
          "classRegex": ["[cls|className]\\s\\:\\=\\s\"([^\"]*)"]
        }
      }
    }
  }
}
```

Release Notes:

- Added ability to configure settings for `tailwindcss-language-server`,
namely the `includeLanguages` and `experimental` objects.

**NOTE**: I have only tested that the language server boots up for Scala
files and that the settings are forwarded correctly. I don't have a
Scala+Tailwind project with which to test that the actual completions
also work.

cc @nguyenyou

Change summary

crates/languages/src/tailwind.rs | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)

Detailed changes

crates/languages/src/tailwind.rs 🔗

@@ -6,7 +6,9 @@ use gpui::AsyncAppContext;
 use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
 use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
+use project::project_settings::ProjectSettings;
 use serde_json::{json, Value};
+use settings::Settings;
 use smol::fs;
 use std::{
     any::Any,
@@ -27,6 +29,8 @@ pub struct TailwindLspAdapter {
 }
 
 impl TailwindLspAdapter {
+    const SERVER_NAME: &'static str = "tailwindcss-language-server";
+
     pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         TailwindLspAdapter { node }
     }
@@ -35,7 +39,7 @@ impl TailwindLspAdapter {
 #[async_trait(?Send)]
 impl LspAdapter for TailwindLspAdapter {
     fn name(&self) -> LanguageServerName {
-        LanguageServerName("tailwindcss-language-server".into())
+        LanguageServerName(Self::SERVER_NAME.into())
     }
 
     async fn fetch_latest_server_version(
@@ -110,11 +114,34 @@ impl LspAdapter for TailwindLspAdapter {
     async fn workspace_configuration(
         self: Arc<Self>,
         _: &Arc<dyn LspAdapterDelegate>,
-        _cx: &mut AsyncAppContext,
+        cx: &mut AsyncAppContext,
     ) -> Result<Value> {
+        let tailwind_user_settings = cx.update(|cx| {
+            ProjectSettings::get_global(cx)
+                .lsp
+                .get(Self::SERVER_NAME)
+                .and_then(|s| s.settings.clone())
+                .unwrap_or_default()
+        })?;
+
+        // We need to set this to null if it's not set, because tailwindcss-languageserver
+        // will check whether it's an object and if it is (even if it's empty) it will
+        // ignore the `userLanguages` from the initialization options.
+        let include_languages = tailwind_user_settings
+            .get("includeLanguages")
+            .cloned()
+            .unwrap_or(Value::Null);
+
+        let experimental = tailwind_user_settings
+            .get("experimental")
+            .cloned()
+            .unwrap_or_else(|| json!([]));
+
         Ok(json!({
             "tailwindCSS": {
                 "emmetCompletions": true,
+                "includeLanguages": include_languages,
+                "experimental": experimental,
             }
         }))
     }