Compare commits
5 Commits
inline-ass
...
eslint-wor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be4785fa65 | ||
|
|
4cfd9bddcf | ||
|
|
7372bd0919 | ||
|
|
b3d63324e1 | ||
|
|
97e5ebade9 |
@@ -323,6 +323,8 @@ pub trait LspAdapterDelegate: Send + Sync {
|
||||
fn http_client(&self) -> Arc<dyn HttpClient>;
|
||||
fn worktree_id(&self) -> WorktreeId;
|
||||
fn worktree_root_path(&self) -> &Path;
|
||||
fn subproject_root_path(&self) -> PathBuf;
|
||||
fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool;
|
||||
fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
|
||||
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
|
||||
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
|
||||
@@ -335,6 +337,7 @@ pub trait LspAdapterDelegate: Send + Sync {
|
||||
async fn shell_env(&self) -> HashMap<String, String>;
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String>;
|
||||
async fn try_exec(&self, binary: LanguageServerBinary) -> Result<()>;
|
||||
fn set_workspace_scope(&self, scope_uri: Option<lsp::Url>);
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
||||
327
crates/languages/src/eslint.rs
Normal file
327
crates/languages/src/eslint.rs
Normal file
@@ -0,0 +1,327 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url};
|
||||
use language::{
|
||||
Attach, LanguageToolchainStore, LspAdapter, LspAdapterDelegate, ManifestName, ManifestProvider,
|
||||
ManifestQuery,
|
||||
};
|
||||
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, stream::StreamExt};
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::archive::extract_zip;
|
||||
use util::{fs::remove_matching, merge_json_value_into};
|
||||
|
||||
fn eslint_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![
|
||||
"--max-old-space-size=8192".into(),
|
||||
server_path.into(),
|
||||
"--stdio".into(),
|
||||
]
|
||||
}
|
||||
|
||||
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",
|
||||
];
|
||||
|
||||
const LEGACY_CONFIG_FILE_NAMES: &'static [&'static str] = &[
|
||||
".eslintrc.js",
|
||||
".eslintrc.cjs",
|
||||
".eslintrc.mjs",
|
||||
".eslintrc.yaml",
|
||||
".eslintrc.yml",
|
||||
".eslintrc.json",
|
||||
".eslintrc",
|
||||
];
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EsLintConfigProvider;
|
||||
|
||||
impl ManifestProvider for EsLintConfigProvider {
|
||||
fn name(&self) -> ManifestName {
|
||||
SharedString::new_static("eslint").into()
|
||||
}
|
||||
|
||||
fn search(
|
||||
&self,
|
||||
ManifestQuery {
|
||||
path,
|
||||
depth,
|
||||
delegate,
|
||||
}: ManifestQuery,
|
||||
) -> Option<Arc<Path>> {
|
||||
for path in path.ancestors().take(depth) {
|
||||
for config_file in EsLintLspAdapter::FLAT_CONFIG_FILE_NAMES {
|
||||
let config_path = path.join(config_file);
|
||||
if delegate.exists(&config_path, Some(false)) {
|
||||
return Some(path.into());
|
||||
}
|
||||
}
|
||||
for config_file in EsLintLspAdapter::LEGACY_CONFIG_FILE_NAMES {
|
||||
let config_path = path.join(config_file);
|
||||
if delegate.exists(&config_path, Some(false)) {
|
||||
return Some(path.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for EsLintLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
Self::SERVER_NAME.clone()
|
||||
}
|
||||
|
||||
fn manifest_name(&self) -> Option<ManifestName> {
|
||||
Some(SharedString::new_static("eslint").into())
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
dbg!(&delegate.worktree_root_path());
|
||||
dbg!(&delegate.subproject_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
|
||||
}))
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context as _;
|
||||
use eslint::EsLintConfigProvider;
|
||||
use gpui::{App, UpdateGlobal};
|
||||
use json::json_task_context;
|
||||
use node_runtime::NodeRuntime;
|
||||
@@ -15,6 +16,7 @@ pub use language::*;
|
||||
mod bash;
|
||||
mod c;
|
||||
mod css;
|
||||
mod eslint;
|
||||
mod go;
|
||||
mod json;
|
||||
mod python;
|
||||
@@ -75,7 +77,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());
|
||||
@@ -303,9 +305,10 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
let manifest_providers: [Arc<dyn ManifestProvider>; 2] = [
|
||||
let manifest_providers: [Arc<dyn ManifestProvider>; 3] = [
|
||||
Arc::from(CargoManifestProvider),
|
||||
Arc::from(PyprojectTomlManifestProvider),
|
||||
Arc::from(EsLintConfigProvider),
|
||||
];
|
||||
for provider in manifest_providers {
|
||||
project::ManifestProviders::global(cx).register(provider);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -515,29 +515,34 @@ impl LocalLspStore {
|
||||
let toolchains =
|
||||
this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
|
||||
|
||||
let workspace_config = Self::workspace_configuration_for_adapter(
|
||||
adapter.clone(),
|
||||
fs.as_ref(),
|
||||
&delegate,
|
||||
toolchains.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
let mut results = Vec::new();
|
||||
|
||||
Ok(params
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
if let Some(section) = &item.section {
|
||||
workspace_config
|
||||
.get(section)
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
} else {
|
||||
workspace_config.clone()
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
for item in params.items {
|
||||
let workspace_config =
|
||||
Self::with_workspace_scope(&delegate, item.scope_uri, || {
|
||||
Self::workspace_configuration_for_adapter(
|
||||
adapter.clone(),
|
||||
fs.as_ref(),
|
||||
&delegate,
|
||||
toolchains.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let config_value = if let Some(section) = &item.section {
|
||||
workspace_config
|
||||
.get(section)
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
} else {
|
||||
workspace_config
|
||||
};
|
||||
|
||||
results.push(config_value);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -3400,6 +3405,25 @@ impl LocalLspStore {
|
||||
Ok(Some(initialization_config))
|
||||
}
|
||||
|
||||
async fn with_workspace_scope<F, Fut, T, E>(
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
scope_uri: Option<Url>,
|
||||
f: F,
|
||||
) -> Result<T, E>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: Future<Output = Result<T, E>>,
|
||||
{
|
||||
if let Some(uri) = scope_uri {
|
||||
delegate.set_workspace_scope(Some(uri));
|
||||
let result = f().await;
|
||||
delegate.set_workspace_scope(None);
|
||||
result
|
||||
} else {
|
||||
f().await
|
||||
}
|
||||
}
|
||||
|
||||
async fn workspace_configuration_for_adapter(
|
||||
adapter: Arc<dyn LspAdapter>,
|
||||
fs: &dyn Fs,
|
||||
@@ -10581,6 +10605,7 @@ pub struct LocalLspAdapterDelegate {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
|
||||
workspace_scope: Mutex<Option<lsp::Url>>,
|
||||
}
|
||||
|
||||
impl LocalLspAdapterDelegate {
|
||||
@@ -10604,6 +10629,7 @@ impl LocalLspAdapterDelegate {
|
||||
http_client,
|
||||
language_registry,
|
||||
load_shell_env_task,
|
||||
workspace_scope: Mutex::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10646,6 +10672,14 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
||||
self.worktree.abs_path().as_ref()
|
||||
}
|
||||
|
||||
fn subproject_root_path(&self) -> PathBuf {
|
||||
if let Some(url) = &*self.workspace_scope.lock() {
|
||||
// get_with_adapters
|
||||
} else {
|
||||
self.worktree_root_path().to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
async fn shell_env(&self) -> HashMap<String, String> {
|
||||
let task = self.load_shell_env_task.clone();
|
||||
task.await.unwrap_or_default()
|
||||
@@ -10761,6 +10795,10 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
||||
|
||||
self.fs.load(&abs_path).await
|
||||
}
|
||||
|
||||
fn set_workspace_scope(&self, scope_uri: Option<lsp::Url>) {
|
||||
*self.workspace_scope.lock() = scope_uri;
|
||||
}
|
||||
}
|
||||
|
||||
async fn populate_labels_for_symbols(
|
||||
|
||||
Reference in New Issue
Block a user