diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 71ab3f0959..503fc3bddc 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -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, 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()); diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 0662eddd3f..0b2907772d 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -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> { - Some(vec![ - CodeActionKind::QUICKFIX, - CodeActionKind::new("source.fixAll.eslint"), - ]) - } - - async fn workspace_configuration( - self: Arc, - _: &dyn Fs, - delegate: &Arc, - _: Arc, - cx: &mut AsyncApp, - ) -> Result { - 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> { - 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, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result { - let version = version.downcast::().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 { - 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};