From 72f57e573a362278923321b4d201e838758d2b0a Mon Sep 17 00:00:00 2001 From: Gaauwe Rombouts Date: Tue, 16 Dec 2025 16:41:22 +0100 Subject: [PATCH 1/6] Add Tailwind CSS Intellisense LSP --- crates/languages/src/css/config.toml | 4 +- crates/languages/src/lib.rs | 6 + crates/languages/src/tailwindcss.rs | 192 +++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 crates/languages/src/tailwindcss.rs diff --git a/crates/languages/src/css/config.toml b/crates/languages/src/css/config.toml index a2ca96e76d3427..001aca34d7b68e 100644 --- a/crates/languages/src/css/config.toml +++ b/crates/languages/src/css/config.toml @@ -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" \ No newline at end of file diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 8ce234a864085a..ca59aa7c9d57cb 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -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, fs: Arc, 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, fs: Arc, 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(), diff --git a/crates/languages/src/tailwindcss.rs b/crates/languages/src/tailwindcss.rs new file mode 100644 index 00000000000000..a8596b1d3ba870 --- /dev/null +++ b/crates/languages/src/tailwindcss.rs @@ -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 { + 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 { + self.node + .npm_package_latest_version(Self::PACKAGE_NAME) + .await + } + + async fn check_if_user_installed( + &self, + delegate: &dyn LspAdapterDelegate, + _: Option, + _: &AsyncApp, + ) -> Option { + 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 { + 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 { + 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 { + 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, + _: &Arc, + ) -> Result> { + Ok(Some(json!({ + "provideFormatter": true + }))) + } + + async fn workspace_configuration( + self: Arc, + delegate: &Arc, + _: Option, + _: Option, + cx: &mut AsyncApp, + ) -> Result { + 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 { + 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() +} From 1e7ccb190eb449185a76c7b4d3c49196847f0611 Mon Sep 17 00:00:00 2001 From: Gaauwe Rombouts Date: Wed, 17 Dec 2025 16:35:52 +0100 Subject: [PATCH 2/6] Add docs for tailwind css mode --- docs/src/languages/tailwindcss.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/src/languages/tailwindcss.md b/docs/src/languages/tailwindcss.md index be9c9437d1382d..42b08aa1eb2b08 100644 --- a/docs/src/languages/tailwindcss.md +++ b/docs/src/languages/tailwindcss.md @@ -37,6 +37,28 @@ Languages which can be used with Tailwind CSS in Zed: - [Svelte](./svelte.md) - [Vue](./vue.md) +### Using Tailwind CSS Mode in CSS Files + +Zed includes support for the Tailwind CSS language mode, which provides full CSS IntelliSense support even when using Tailwind-specific at-rules like `@apply`, `@layer`, and `@screen`. + +To use the Tailwind CSS language mode for CSS files, add the following to `languages` section of your `settings.json`: + +```json [settings] +{ + "languages": { + "CSS": { + "language_servers": [ + "tailwindcss-intellisense-css", + "!vscode-css-language-server", + "..." + ] + } + } +} +``` + +The `tailwindcss-intellisense-css` language server serves as an alternative to the default CSS language server, maintaining all standard CSS IntelliSense features while adding support for Tailwind-specific syntax. + ### Prettier Plugin Zed supports Prettier out of the box, which means that if you have the [Tailwind CSS Prettier plugin](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) installed, adding it to your Prettier configuration will make it work automatically: From da8e48081bd7c0f285cd539b3de7d710efdc6534 Mon Sep 17 00:00:00 2001 From: Gaauwe Rombouts Date: Fri, 19 Dec 2025 13:25:00 +0100 Subject: [PATCH 3/6] Clippy --- crates/languages/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index ca59aa7c9d57cb..f7e100ed750e74 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -265,7 +265,7 @@ pub fn init(languages: Arc, fs: Arc, node: NodeRuntime ); languages.register_available_lsp_adapter( LanguageServerName("tailwindcss-intellisense-css".into()), - tailwindcss_adapter.clone(), + tailwindcss_adapter, ); languages.register_available_lsp_adapter( LanguageServerName("eslint".into()), From 3230f0ed1b96022c2bbb8420bf0aaec42f7810cd Mon Sep 17 00:00:00 2001 From: Gaauwe Rombouts Date: Fri, 19 Dec 2025 13:39:42 +0100 Subject: [PATCH 4/6] Use semver Version instead of String --- crates/languages/src/tailwindcss.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/languages/src/tailwindcss.rs b/crates/languages/src/tailwindcss.rs index a8596b1d3ba870..0ea3c45ffaff39 100644 --- a/crates/languages/src/tailwindcss.rs +++ b/crates/languages/src/tailwindcss.rs @@ -5,6 +5,7 @@ use language::{LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain}; use lsp::{LanguageServerBinary, LanguageServerName, Uri}; use node_runtime::{NodeRuntime, VersionStrategy}; use project::lsp_store::language_server_settings; +use semver::Version; use serde_json::json; use std::{ ffi::OsString, @@ -35,14 +36,14 @@ impl TailwindCssLspAdapter { } impl LspInstaller for TailwindCssLspAdapter { - type BinaryVersion = String; + type BinaryVersion = Version; async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, _: bool, _: &mut AsyncApp, - ) -> Result { + ) -> Result { self.node .npm_package_latest_version(Self::PACKAGE_NAME) .await @@ -66,11 +67,12 @@ impl LspInstaller for TailwindCssLspAdapter { async fn fetch_server_binary( &self, - latest_version: String, + latest_version: Self::BinaryVersion, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { let server_path = container_dir.join(SERVER_PATH); + let latest_version = latest_version.to_string(); self.node .npm_install_packages( @@ -88,7 +90,7 @@ impl LspInstaller for TailwindCssLspAdapter { async fn check_if_version_installed( &self, - version: &String, + version: &Self::BinaryVersion, container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { From 245ef7d1a64a2d11405195e37253c1d2416537df Mon Sep 17 00:00:00 2001 From: Gaauwe Rombouts Date: Fri, 19 Dec 2025 13:43:25 +0100 Subject: [PATCH 5/6] Update deprecated example --- docs/src/languages/tailwindcss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/languages/tailwindcss.md b/docs/src/languages/tailwindcss.md index e9cae27d57f930..d15033ebe061c8 100644 --- a/docs/src/languages/tailwindcss.md +++ b/docs/src/languages/tailwindcss.md @@ -41,7 +41,7 @@ Refer to [the Tailwind CSS language server settings docs](https://github.com/tai ### Using Tailwind CSS Mode in CSS Files -Zed includes support for the Tailwind CSS language mode, which provides full CSS IntelliSense support even when using Tailwind-specific at-rules like `@apply`, `@layer`, and `@screen`. +Zed includes support for the Tailwind CSS language mode, which provides full CSS IntelliSense support even when using Tailwind-specific at-rules like `@apply`, `@layer`, and `@theme`. To use the Tailwind CSS language mode for CSS files, add the following to `languages` section of your `settings.json`: From 9f1dc5e88061a3b393d5903c086866770ecd357d Mon Sep 17 00:00:00 2001 From: Gaauwe Rombouts Date: Mon, 5 Jan 2026 11:13:03 +0100 Subject: [PATCH 6/6] Remove docs for now --- docs/src/languages/tailwindcss.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/docs/src/languages/tailwindcss.md b/docs/src/languages/tailwindcss.md index d15033ebe061c8..457c71f9768610 100644 --- a/docs/src/languages/tailwindcss.md +++ b/docs/src/languages/tailwindcss.md @@ -39,28 +39,6 @@ If by default the language server isn't enough to make Tailwind work for a given Refer to [the Tailwind CSS language server settings docs](https://github.com/tailwindlabs/tailwindcss-intellisense?tab=readme-ov-file#extension-settings) for more information. -### Using Tailwind CSS Mode in CSS Files - -Zed includes support for the Tailwind CSS language mode, which provides full CSS IntelliSense support even when using Tailwind-specific at-rules like `@apply`, `@layer`, and `@theme`. - -To use the Tailwind CSS language mode for CSS files, add the following to `languages` section of your `settings.json`: - -```json [settings] -{ - "languages": { - "CSS": { - "language_servers": [ - "tailwindcss-intellisense-css", - "!vscode-css-language-server", - "..." - ] - } - } -} -``` - -The `tailwindcss-intellisense-css` language server serves as an alternative to the default CSS language server, maintaining all standard CSS IntelliSense features while adding support for Tailwind-specific syntax. - ### Prettier Plugin Zed supports Prettier out of the box, which means that if you have the [Tailwind CSS Prettier plugin](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) installed, adding it to your Prettier configuration will make it work automatically: