language: Add support for Tailwind CSS Mode (#45352)
Closes #44904 Adds the Tailwind CSS LSP as suggested here: https://github.com/zed-industries/zed/pull/39517#issuecomment-3368206678. This allows users to use the Tailwind CSS LSP for CSS files, to support Tailwind specific rules. Docs will be added in a separate PR after this is released Release Notes: - Add Tailwind CSS Mode support, for Tailwind specific CSS rules like `@apply`, `@layer`, and `@theme`.
This commit is contained in:
1 parent
5694eb8f43
commit
3d9f35d704
3 files changed
+202
-2
No files matched your search
@@ -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,
|
||||
);
|
||||
languages.register_available_lsp_adapter(
|
||||
LanguageServerName("eslint".into()),
|
||||
eslint_adapter.clone(),
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
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 semver::Version;
|
||||
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 = Version;
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
_: bool,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<Self::BinaryVersion> {
|
||||
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: Self::BinaryVersion,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let latest_version = latest_version.to_string();
|
||||
|
||||
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: &Self::BinaryVersion,
|
||||
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()
|
||||
}
|
||||
Reference in new issue
Block a user