move eslint server out of ts-server

This commit is contained in:
Smit Barmase
2025-05-24 18:44:14 +05:30
parent 16853acbb1
commit 97e5ebade9
2 changed files with 4 additions and 252 deletions

View File

@@ -15,6 +15,7 @@ pub use language::*;
mod bash;
mod c;
mod css;
mod eslint;
mod go;
mod json;
mod python;
@@ -75,7 +76,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
let c_lsp_adapter = Arc::new(c::CLspAdapter);
let css_lsp_adapter = Arc::new(css::CssLspAdapter::new(node.clone()));
let eslint_adapter = Arc::new(typescript::EsLintLspAdapter::new(node.clone()));
let eslint_adapter = Arc::new(eslint::EsLintLspAdapter::new(node.clone()));
let go_context_provider = Arc::new(go::GoContextProvider);
let go_lsp_adapter = Arc::new(go::GoLspAdapter);
let json_context_provider = Arc::new(json_task_context());

View File

@@ -1,11 +1,8 @@
use anyhow::{Context as _, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use chrono::{DateTime, Local};
use collections::HashMap;
use gpui::{App, AppContext, AsyncApp, Task};
use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url};
use language::{
ContextLocation, ContextProvider, File, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
};
@@ -13,7 +10,7 @@ use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime;
use project::{Fs, lsp_store::language_server_settings};
use serde_json::{Value, json};
use smol::{fs, io::BufReader, lock::RwLock, stream::StreamExt};
use smol::lock::RwLock;
use std::{
any::Any,
borrow::Cow,
@@ -22,9 +19,7 @@ use std::{
sync::Arc,
};
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
use util::archive::extract_zip;
use util::merge_json_value_into;
use util::{ResultExt, fs::remove_matching, maybe};
use util::{ResultExt, maybe};
pub(crate) struct TypeScriptContextProvider {
last_package_json: PackageJsonContents,
@@ -713,250 +708,6 @@ async fn get_cached_ts_server_binary(
.log_err()
}
pub struct EsLintLspAdapter {
node: NodeRuntime,
}
impl EsLintLspAdapter {
const CURRENT_VERSION: &'static str = "2.4.4";
const CURRENT_VERSION_TAG_NAME: &'static str = "release/2.4.4";
#[cfg(not(windows))]
const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;
#[cfg(windows)]
const GITHUB_ASSET_KIND: AssetKind = AssetKind::Zip;
const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("eslint");
const FLAT_CONFIG_FILE_NAMES: &'static [&'static str] = &[
"eslint.config.js",
"eslint.config.mjs",
"eslint.config.cjs",
"eslint.config.ts",
"eslint.config.cts",
"eslint.config.mts",
];
pub fn new(node: NodeRuntime) -> Self {
EsLintLspAdapter { node }
}
fn build_destination_path(container_dir: &Path) -> PathBuf {
container_dir.join(format!("vscode-eslint-{}", Self::CURRENT_VERSION))
}
}
#[async_trait(?Send)]
impl LspAdapter for EsLintLspAdapter {
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
Some(vec![
CodeActionKind::QUICKFIX,
CodeActionKind::new("source.fixAll.eslint"),
])
}
async fn workspace_configuration(
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncApp,
) -> Result<Value> {
let workspace_root = delegate.worktree_root_path();
let use_flat_config = Self::FLAT_CONFIG_FILE_NAMES
.iter()
.any(|file| workspace_root.join(file).is_file());
let mut default_workspace_configuration = json!({
"validate": "on",
"rulesCustomizations": [],
"run": "onType",
"nodePath": null,
"workingDirectory": {
"mode": "auto"
},
"workspaceFolder": {
"uri": workspace_root,
"name": workspace_root.file_name()
.unwrap_or(workspace_root.as_os_str())
.to_string_lossy(),
},
"problems": {},
"codeActionOnSave": {
// We enable this, but without also configuring code_actions_on_format
// in the Zed configuration, it doesn't have an effect.
"enable": true,
},
"codeAction": {
"disableRuleComment": {
"enable": true,
"location": "separateLine",
},
"showDocumentation": {
"enable": true
}
},
"experimental": {
"useFlatConfig": use_flat_config,
},
});
let override_options = cx.update(|cx| {
language_server_settings(delegate.as_ref(), &Self::SERVER_NAME, cx)
.and_then(|s| s.settings.clone())
})?;
if let Some(override_options) = override_options {
merge_json_value_into(override_options, &mut default_workspace_configuration);
}
Ok(json!({
"": default_workspace_configuration
}))
}
fn name(&self) -> LanguageServerName {
Self::SERVER_NAME.clone()
}
async fn fetch_latest_server_version(
&self,
_delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let url = build_asset_url(
"zed-industries/vscode-eslint",
Self::CURRENT_VERSION_TAG_NAME,
Self::GITHUB_ASSET_KIND,
)?;
Ok(Box::new(GitHubLspBinaryVersion {
name: Self::CURRENT_VERSION.into(),
url,
}))
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let destination_path = Self::build_destination_path(&container_dir);
let server_path = destination_path.join(Self::SERVER_PATH);
if fs::metadata(&server_path).await.is_err() {
remove_matching(&container_dir, |entry| entry != destination_path).await;
let mut response = delegate
.http_client()
.get(&version.url, Default::default(), true)
.await
.context("downloading release")?;
match Self::GITHUB_ASSET_KIND {
AssetKind::TarGz => {
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
let archive = Archive::new(decompressed_bytes);
archive.unpack(&destination_path).await.with_context(|| {
format!("extracting {} to {:?}", version.url, destination_path)
})?;
}
AssetKind::Gz => {
let mut decompressed_bytes =
GzipDecoder::new(BufReader::new(response.body_mut()));
let mut file =
fs::File::create(&destination_path).await.with_context(|| {
format!(
"creating a file {:?} for a download from {}",
destination_path, version.url,
)
})?;
futures::io::copy(&mut decompressed_bytes, &mut file)
.await
.with_context(|| {
format!("extracting {} to {:?}", version.url, destination_path)
})?;
}
AssetKind::Zip => {
extract_zip(&destination_path, response.body_mut())
.await
.with_context(|| {
format!("unzipping {} to {:?}", version.url, destination_path)
})?;
}
}
let mut dir = fs::read_dir(&destination_path).await?;
let first = dir.next().await.context("missing first file")??;
let repo_root = destination_path.join("vscode-eslint");
fs::rename(first.path(), &repo_root).await?;
#[cfg(target_os = "windows")]
{
handle_symlink(
repo_root.join("$shared"),
repo_root.join("client").join("src").join("shared"),
)
.await?;
handle_symlink(
repo_root.join("$shared"),
repo_root.join("server").join("src").join("shared"),
)
.await?;
}
self.node
.run_npm_subcommand(&repo_root, "install", &[])
.await?;
self.node
.run_npm_subcommand(&repo_root, "run-script", &["compile"])
.await?;
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: eslint_server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let server_path =
Self::build_destination_path(&container_dir).join(EsLintLspAdapter::SERVER_PATH);
Some(LanguageServerBinary {
path: self.node.binary_path().await.ok()?,
env: None,
arguments: eslint_server_binary_arguments(&server_path),
})
}
}
#[cfg(target_os = "windows")]
async fn handle_symlink(src_dir: PathBuf, dest_dir: PathBuf) -> Result<()> {
anyhow::ensure!(
fs::metadata(&src_dir).await.is_ok(),
"Directory {src_dir:?} is not present"
);
if fs::metadata(&dest_dir).await.is_ok() {
fs::remove_file(&dest_dir).await?;
}
fs::create_dir_all(&dest_dir).await?;
let mut entries = fs::read_dir(&src_dir).await?;
while let Some(entry) = entries.try_next().await? {
let entry_path = entry.path();
let entry_name = entry.file_name();
let dest_path = dest_dir.join(&entry_name);
fs::copy(&entry_path, &dest_path).await?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use gpui::{AppContext as _, TestAppContext};