diff --git a/crates/languages/src/css/config.toml b/crates/languages/src/css/config.toml index a2ca96e76d3427c2ff2eb249d9a2f93a68d8f1c0..001aca34d7b68e6bfefda13b49ef9d73bd7f3173 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 8ce234a864085a324adeb93a1005a0ed60b1c2b1..ca59aa7c9d57cb1adceab91a3a3c49f445fde1dc 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 0000000000000000000000000000000000000000..a8596b1d3ba87040365bc9cfa2ddd388336c54ac --- /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() +}