@@ -9,6 +9,6 @@ brackets = [
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]
-completion_query_characters = ["-"]
+completion_query_characters = ["-", "@"]
block_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 }
-prettier_parser_name = "css"
+prettier_parser_name = "css"
@@ -28,6 +28,7 @@ mod package_json;
mod python;
mod rust;
mod tailwind;
+mod tailwindcss;
mod typescript;
mod vtsls;
mod yaml;
@@ -101,6 +102,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
let rust_context_provider = Arc::new(rust::RustContextProvider);
let rust_lsp_adapter = Arc::new(rust::RustLspAdapter);
let tailwind_adapter = Arc::new(tailwind::TailwindLspAdapter::new(node.clone()));
+ let tailwindcss_adapter = Arc::new(tailwindcss::TailwindCssLspAdapter::new(node.clone()));
let typescript_context = Arc::new(typescript::TypeScriptContextProvider::new(fs.clone()));
let typescript_lsp_adapter = Arc::new(typescript::TypeScriptLspAdapter::new(
node.clone(),
@@ -261,6 +263,10 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
LanguageServerName("tailwindcss-language-server".into()),
tailwind_adapter.clone(),
);
+ languages.register_available_lsp_adapter(
+ LanguageServerName("tailwindcss-intellisense-css".into()),
+ tailwindcss_adapter.clone(),
+ );
languages.register_available_lsp_adapter(
LanguageServerName("eslint".into()),
eslint_adapter.clone(),
@@ -0,0 +1,192 @@
+use anyhow::Result;
+use async_trait::async_trait;
+use gpui::AsyncApp;
+use language::{LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain};
+use lsp::{LanguageServerBinary, LanguageServerName, Uri};
+use node_runtime::{NodeRuntime, VersionStrategy};
+use project::lsp_store::language_server_settings;
+use serde_json::json;
+use std::{
+ ffi::OsString,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::{ResultExt, maybe, merge_json_value_into};
+
+const SERVER_PATH: &str = "node_modules/@tailwindcss/language-server/bin/css-language-server";
+
+fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct TailwindCssLspAdapter {
+ node: NodeRuntime,
+}
+
+// Implements the LSP adapter for the Tailwind CSS LSP fork: https://github.com/zed-industries/zed/pull/39517#issuecomment-3368206678
+impl TailwindCssLspAdapter {
+ const SERVER_NAME: LanguageServerName =
+ LanguageServerName::new_static("tailwindcss-intellisense-css");
+ const PACKAGE_NAME: &str = "@tailwindcss/language-server";
+
+ pub fn new(node: NodeRuntime) -> Self {
+ TailwindCssLspAdapter { node }
+ }
+}
+
+impl LspInstaller for TailwindCssLspAdapter {
+ type BinaryVersion = String;
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<String> {
+ self.node
+ .npm_package_latest_version(Self::PACKAGE_NAME)
+ .await
+ }
+
+ async fn check_if_user_installed(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ _: Option<Toolchain>,
+ _: &AsyncApp,
+ ) -> Option<LanguageServerBinary> {
+ let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
+ let env = delegate.shell_env().await;
+
+ Some(LanguageServerBinary {
+ path,
+ env: Some(env),
+ arguments: vec!["--stdio".into()],
+ })
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ latest_version: String,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let server_path = container_dir.join(SERVER_PATH);
+
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[(Self::PACKAGE_NAME, latest_version.as_str())],
+ )
+ .await?;
+
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ env: None,
+ arguments: server_binary_arguments(&server_path),
+ })
+ }
+
+ async fn check_if_version_installed(
+ &self,
+ version: &String,
+ container_dir: &PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ let server_path = container_dir.join(SERVER_PATH);
+
+ let should_install_language_server = self
+ .node
+ .should_install_npm_package(
+ Self::PACKAGE_NAME,
+ &server_path,
+ container_dir,
+ VersionStrategy::Latest(version),
+ )
+ .await;
+
+ if should_install_language_server {
+ None
+ } else {
+ Some(LanguageServerBinary {
+ path: self.node.binary_path().await.ok()?,
+ env: None,
+ arguments: server_binary_arguments(&server_path),
+ })
+ }
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_server_binary(container_dir, &self.node).await
+ }
+}
+
+#[async_trait(?Send)]
+impl LspAdapter for TailwindCssLspAdapter {
+ fn name(&self) -> LanguageServerName {
+ Self::SERVER_NAME
+ }
+
+ async fn initialization_options(
+ self: Arc<Self>,
+ _: &Arc<dyn LspAdapterDelegate>,
+ ) -> Result<Option<serde_json::Value>> {
+ Ok(Some(json!({
+ "provideFormatter": true
+ })))
+ }
+
+ async fn workspace_configuration(
+ self: Arc<Self>,
+ delegate: &Arc<dyn LspAdapterDelegate>,
+ _: Option<Toolchain>,
+ _: Option<Uri>,
+ cx: &mut AsyncApp,
+ ) -> Result<serde_json::Value> {
+ let mut default_config = json!({
+ "css": {
+ "lint": {}
+ },
+ "less": {
+ "lint": {}
+ },
+ "scss": {
+ "lint": {}
+ }
+ });
+
+ let project_options = cx.update(|cx| {
+ language_server_settings(delegate.as_ref(), &self.name(), cx)
+ .and_then(|s| s.settings.clone())
+ })?;
+
+ if let Some(override_options) = project_options {
+ merge_json_value_into(override_options, &mut default_config);
+ }
+
+ Ok(default_config)
+ }
+}
+
+async fn get_cached_server_binary(
+ container_dir: PathBuf,
+ node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+ maybe!(async {
+ let server_path = container_dir.join(SERVER_PATH);
+ anyhow::ensure!(
+ server_path.exists(),
+ "missing executable in directory {server_path:?}"
+ );
+ Ok(LanguageServerBinary {
+ path: node.binary_path().await?,
+ env: None,
+ arguments: server_binary_arguments(&server_path),
+ })
+ })
+ .await
+ .log_err()
+}