Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/languages/src/css/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
6 changes: 6 additions & 0 deletions crates/languages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod package_json;
mod python;
mod rust;
mod tailwind;
mod tailwindcss;
mod typescript;
mod vtsls;
mod yaml;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
194 changes: 194 additions & 0 deletions crates/languages/src/tailwindcss.rs
Original file line number Diff line number Diff line change
@@ -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()
}