language: Allow support for ESLint working directories (#43677)
Closes #9648 #9755 Release Notes: - Added way to configure ESLint's working directories in settings. For example: `{"lsp":{"eslint":{"settings":{"workingDirectories":["./client","./server"]}}}}` --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
co-authored by
Piotr Osiewicz
parent
478bcea6d3
commit
7c967b8d1a
Generated
+1
@@ -9015,6 +9015,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"collections",
|
||||
"futures 0.3.31",
|
||||
"globset",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"itertools 0.14.0",
|
||||
|
||||
@@ -36,7 +36,9 @@ use http_client::HttpClient;
|
||||
pub use language_registry::{
|
||||
LanguageName, LanguageServerStatusUpdate, LoadedLanguage, ServerHealth,
|
||||
};
|
||||
use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions};
|
||||
use lsp::{
|
||||
CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions, Uri,
|
||||
};
|
||||
pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery};
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
@@ -406,6 +408,7 @@ pub trait LspAdapter: 'static + Send + Sync + DynLspInstaller {
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
Ok(serde_json::json!({}))
|
||||
|
||||
@@ -14,7 +14,7 @@ use language::{
|
||||
};
|
||||
use lsp::{
|
||||
CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName,
|
||||
LanguageServerSelector,
|
||||
LanguageServerSelector, Uri,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
@@ -282,6 +282,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
|
||||
@@ -42,6 +42,7 @@ async-trait.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
globset.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
json_schema_store.workspace = true
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use gpui::AsyncApp;
|
||||
use language::{LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde_json::json;
|
||||
@@ -142,6 +142,7 @@ impl LspAdapter for CssLspAdapter {
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut default_config = json!({
|
||||
|
||||
@@ -0,0 +1,772 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use gpui::AsyncApp;
|
||||
use http_client::{
|
||||
github::{AssetKind, GitHubLspBinaryVersion, build_asset_url},
|
||||
github_download::download_server_binary,
|
||||
};
|
||||
use language::{LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::lsp_store::language_server_settings_for;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use settings::SettingsLocation;
|
||||
use smol::{fs, stream::StreamExt};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::merge_json_value_into;
|
||||
use util::{fs::remove_matching, rel_path::RelPath};
|
||||
|
||||
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",
|
||||
];
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
impl LspInstaller for EsLintLspAdapter {
|
||||
type BinaryVersion = GitHubLspBinaryVersion;
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_delegate: &dyn LspAdapterDelegate,
|
||||
_: bool,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<GitHubLspBinaryVersion> {
|
||||
let url = build_asset_url(
|
||||
"zed-industries/vscode-eslint",
|
||||
Self::CURRENT_VERSION_TAG_NAME,
|
||||
Self::GITHUB_ASSET_KIND,
|
||||
)?;
|
||||
|
||||
Ok(GitHubLspBinaryVersion {
|
||||
name: Self::CURRENT_VERSION.into(),
|
||||
digest: None,
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: GitHubLspBinaryVersion,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
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, |_| true).await;
|
||||
|
||||
download_server_binary(
|
||||
&*delegate.http_client(),
|
||||
&version.url,
|
||||
None,
|
||||
&destination_path,
|
||||
Self::GITHUB_ASSET_KIND,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
requested_uri: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let worktree_root = delegate.worktree_root_path();
|
||||
let use_flat_config = Self::FLAT_CONFIG_FILE_NAMES
|
||||
.iter()
|
||||
.any(|file| worktree_root.join(file).is_file());
|
||||
|
||||
let mut default_workspace_configuration = json!({
|
||||
"validate": "on",
|
||||
"rulesCustomizations": [],
|
||||
"run": "onType",
|
||||
"nodePath": null,
|
||||
"workingDirectory": {
|
||||
"mode": "auto"
|
||||
},
|
||||
"workspaceFolder": {
|
||||
"uri": worktree_root,
|
||||
"name": worktree_root.file_name()
|
||||
.unwrap_or(worktree_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 file_path = requested_uri
|
||||
.as_ref()
|
||||
.and_then(|uri| {
|
||||
(uri.scheme() == "file")
|
||||
.then(|| uri.to_file_path().ok())
|
||||
.flatten()
|
||||
})
|
||||
.and_then(|abs_path| {
|
||||
abs_path
|
||||
.strip_prefix(&worktree_root)
|
||||
.ok()
|
||||
.map(ToOwned::to_owned)
|
||||
});
|
||||
let file_path = file_path
|
||||
.and_then(|p| RelPath::unix(&p).ok().map(ToOwned::to_owned))
|
||||
.unwrap_or_else(|| RelPath::empty().to_owned());
|
||||
let override_options = cx.update(|cx| {
|
||||
language_server_settings_for(
|
||||
SettingsLocation {
|
||||
worktree_id: delegate.worktree_id(),
|
||||
path: &file_path,
|
||||
},
|
||||
&Self::SERVER_NAME,
|
||||
cx,
|
||||
)
|
||||
.and_then(|s| s.settings.clone())
|
||||
})?;
|
||||
|
||||
if let Some(override_options) = override_options {
|
||||
let working_directories = override_options.get("workingDirectories").and_then(|wd| {
|
||||
serde_json::from_value::<WorkingDirectories>(wd.clone())
|
||||
.ok()
|
||||
.and_then(|wd| wd.0)
|
||||
});
|
||||
|
||||
merge_json_value_into(override_options, &mut default_workspace_configuration);
|
||||
|
||||
let working_directory = working_directories
|
||||
.zip(requested_uri)
|
||||
.and_then(|(wd, uri)| {
|
||||
determine_working_directory(uri, wd, worktree_root.to_owned())
|
||||
});
|
||||
|
||||
if let Some(working_directory) = working_directory
|
||||
&& let Some(wd) = default_workspace_configuration.get_mut("workingDirectory")
|
||||
{
|
||||
*wd = serde_json::to_value(working_directory)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(json!({
|
||||
"": default_workspace_configuration
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> LanguageServerName {
|
||||
Self::SERVER_NAME
|
||||
}
|
||||
}
|
||||
|
||||
/// On Windows, converts Unix-style separators (/) to Windows-style (\).
|
||||
/// On Unix, returns the path unchanged
|
||||
fn normalize_path_separators(path: &str) -> String {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
path.replace('/', "\\")
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
path.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_working_directory(
|
||||
uri: Uri,
|
||||
working_directories: Vec<WorkingDirectory>,
|
||||
workspace_folder_path: PathBuf,
|
||||
) -> Option<ResultWorkingDirectory> {
|
||||
let mut working_directory = None;
|
||||
|
||||
for item in working_directories {
|
||||
let mut directory: Option<String> = None;
|
||||
let mut pattern: Option<String> = None;
|
||||
let mut no_cwd = false;
|
||||
match item {
|
||||
WorkingDirectory::String(contents) => {
|
||||
directory = Some(normalize_path_separators(&contents));
|
||||
}
|
||||
WorkingDirectory::LegacyDirectoryItem(legacy_directory_item) => {
|
||||
directory = Some(normalize_path_separators(&legacy_directory_item.directory));
|
||||
no_cwd = !legacy_directory_item.change_process_cwd;
|
||||
}
|
||||
WorkingDirectory::DirectoryItem(directory_item) => {
|
||||
directory = Some(normalize_path_separators(&directory_item.directory));
|
||||
if let Some(not_cwd) = directory_item.not_cwd {
|
||||
no_cwd = not_cwd;
|
||||
}
|
||||
}
|
||||
WorkingDirectory::PatternItem(pattern_item) => {
|
||||
pattern = Some(normalize_path_separators(&pattern_item.pattern));
|
||||
if let Some(not_cwd) = pattern_item.not_cwd {
|
||||
no_cwd = not_cwd;
|
||||
}
|
||||
}
|
||||
WorkingDirectory::ModeItem(mode_item) => {
|
||||
working_directory = Some(ResultWorkingDirectory::ModeItem(mode_item));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut item_value: Option<String> = None;
|
||||
if directory.is_some() || pattern.is_some() {
|
||||
let file_path: Option<PathBuf> = (uri.scheme() == "file")
|
||||
.then(|| uri.to_file_path().ok())
|
||||
.flatten();
|
||||
if let Some(file_path) = file_path {
|
||||
if let Some(mut directory) = directory {
|
||||
if Path::new(&directory).is_relative() {
|
||||
directory = workspace_folder_path
|
||||
.join(directory)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
}
|
||||
if !directory.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
directory.push(std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
if file_path.starts_with(&directory) {
|
||||
item_value = Some(directory);
|
||||
}
|
||||
} else if let Some(mut pattern) = pattern
|
||||
&& !pattern.is_empty()
|
||||
{
|
||||
if Path::new(&pattern).is_relative() {
|
||||
pattern = workspace_folder_path
|
||||
.join(pattern)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
}
|
||||
if !pattern.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
pattern.push(std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
if let Some(matched) = match_glob_pattern(&pattern, &file_path) {
|
||||
item_value = Some(matched);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(item_value) = item_value {
|
||||
if working_directory
|
||||
.as_ref()
|
||||
.is_none_or(|wd| matches!(wd, ResultWorkingDirectory::ModeItem(_)))
|
||||
{
|
||||
working_directory = Some(ResultWorkingDirectory::DirectoryItem(DirectoryItem {
|
||||
directory: item_value,
|
||||
not_cwd: Some(no_cwd),
|
||||
}));
|
||||
} else if let Some(ResultWorkingDirectory::DirectoryItem(item)) = &mut working_directory
|
||||
&& item.directory.len() < item_value.len()
|
||||
{
|
||||
item.directory = item_value;
|
||||
item.not_cwd = Some(no_cwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
working_directory
|
||||
}
|
||||
|
||||
fn match_glob_pattern(pattern: &str, file_path: &Path) -> Option<String> {
|
||||
use globset::GlobBuilder;
|
||||
|
||||
let glob = GlobBuilder::new(pattern)
|
||||
.literal_separator(true)
|
||||
.build()
|
||||
.ok()?
|
||||
.compile_matcher();
|
||||
|
||||
let mut current = file_path.to_path_buf();
|
||||
let mut matched: Option<String> = None;
|
||||
|
||||
while let Some(parent) = current.parent() {
|
||||
let mut prefix = parent.to_string_lossy().to_string();
|
||||
if !prefix.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
prefix.push(std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
if glob.is_match(&prefix) {
|
||||
matched = Some(prefix);
|
||||
}
|
||||
current = parent.to_path_buf();
|
||||
}
|
||||
|
||||
matched
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct LegacyDirectoryItem {
|
||||
directory: String,
|
||||
#[serde(rename = "changeProcessCWD")]
|
||||
change_process_cwd: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct DirectoryItem {
|
||||
directory: String,
|
||||
#[serde(rename = "!cwd")]
|
||||
not_cwd: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct PatternItem {
|
||||
pattern: String,
|
||||
#[serde(rename = "!cwd")]
|
||||
not_cwd: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ModeItem {
|
||||
mode: ModeEnum,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum ModeEnum {
|
||||
Auto,
|
||||
Location,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum WorkingDirectory {
|
||||
String(String),
|
||||
LegacyDirectoryItem(LegacyDirectoryItem),
|
||||
DirectoryItem(DirectoryItem),
|
||||
PatternItem(PatternItem),
|
||||
ModeItem(ModeItem),
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct WorkingDirectories(Option<Vec<WorkingDirectory>>);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum ResultWorkingDirectory {
|
||||
ModeItem(ModeItem),
|
||||
DirectoryItem(DirectoryItem),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod glob_patterns {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_match_glob_pattern() {
|
||||
let pattern = unix_path_to_platform("/test/*/");
|
||||
let file_path = PathBuf::from(unix_path_to_platform("/test/foo/bar/file.txt"));
|
||||
let matched = match_glob_pattern(&pattern, &file_path);
|
||||
assert_eq!(matched, Some(unix_path_to_platform("/test/foo/")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_glob_pattern_globstar() {
|
||||
let pattern = unix_path_to_platform("/workspace/**/src/");
|
||||
let file_path = PathBuf::from(unix_path_to_platform(
|
||||
"/workspace/packages/core/src/index.ts",
|
||||
));
|
||||
let matched = match_glob_pattern(&pattern, &file_path);
|
||||
assert_eq!(
|
||||
matched,
|
||||
Some(unix_path_to_platform("/workspace/packages/core/src/"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_glob_pattern_no_match() {
|
||||
let pattern = unix_path_to_platform("/other/*/");
|
||||
let file_path = PathBuf::from(unix_path_to_platform("/test/foo/bar/file.txt"));
|
||||
let matched = match_glob_pattern(&pattern, &file_path);
|
||||
assert_eq!(matched, None);
|
||||
}
|
||||
}
|
||||
|
||||
mod unix_style_paths {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_string() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::String("packages/foo".to_string())];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_absolute_path() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::String(unix_path_to_platform(
|
||||
"/workspace/packages/foo",
|
||||
))];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_directory_item() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::DirectoryItem(DirectoryItem {
|
||||
directory: "packages/foo".to_string(),
|
||||
not_cwd: Some(true),
|
||||
})];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_legacy_item() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories =
|
||||
vec![WorkingDirectory::LegacyDirectoryItem(LegacyDirectoryItem {
|
||||
directory: "packages/foo".to_string(),
|
||||
change_process_cwd: false,
|
||||
})];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_pattern_item() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::PatternItem(PatternItem {
|
||||
pattern: "packages/*/".to_string(),
|
||||
not_cwd: Some(false),
|
||||
})];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_multiple_patterns() {
|
||||
let uri = make_uri("/workspace/apps/web/src/file.ts");
|
||||
let working_directories = vec![
|
||||
WorkingDirectory::PatternItem(PatternItem {
|
||||
pattern: "packages/*/".to_string(),
|
||||
not_cwd: None,
|
||||
}),
|
||||
WorkingDirectory::PatternItem(PatternItem {
|
||||
pattern: "apps/*/".to_string(),
|
||||
not_cwd: None,
|
||||
}),
|
||||
];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/apps/web/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows_style_paths {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_string() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::String("packages\\foo".to_string())];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_absolute_path() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::String(
|
||||
unix_path_to_platform("/workspace/packages/foo").replace('/', "\\"),
|
||||
)];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_directory_item() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::DirectoryItem(DirectoryItem {
|
||||
directory: "packages\\foo".to_string(),
|
||||
not_cwd: Some(true),
|
||||
})];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_legacy_item() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories =
|
||||
vec![WorkingDirectory::LegacyDirectoryItem(LegacyDirectoryItem {
|
||||
directory: "packages\\foo".to_string(),
|
||||
change_process_cwd: false,
|
||||
})];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_pattern_item() {
|
||||
let uri = make_uri("/workspace/packages/foo/src/file.ts");
|
||||
let working_directories = vec![WorkingDirectory::PatternItem(PatternItem {
|
||||
pattern: "packages\\*\\".to_string(),
|
||||
not_cwd: Some(false),
|
||||
})];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/packages/foo/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_working_directory_multiple_patterns() {
|
||||
let uri = make_uri("/workspace/apps/web/src/file.ts");
|
||||
let working_directories = vec![
|
||||
WorkingDirectory::PatternItem(PatternItem {
|
||||
pattern: "packages\\*\\".to_string(),
|
||||
not_cwd: None,
|
||||
}),
|
||||
WorkingDirectory::PatternItem(PatternItem {
|
||||
pattern: "apps\\*\\".to_string(),
|
||||
not_cwd: None,
|
||||
}),
|
||||
];
|
||||
let workspace_folder = PathBuf::from(unix_path_to_platform("/workspace"));
|
||||
|
||||
let result = determine_working_directory(uri, working_directories, workspace_folder);
|
||||
assert_directory_result(
|
||||
result,
|
||||
&unix_path_to_platform("/workspace/apps/web/"),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Unix-style path to a platform-specific path.
|
||||
/// On Windows, converts "/workspace/foo/bar" to "C:\workspace\foo\bar"
|
||||
/// On Unix, returns the path unchanged.
|
||||
fn unix_path_to_platform(path: &str) -> String {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if path.starts_with('/') {
|
||||
format!("C:{}", path.replace('/', "\\"))
|
||||
} else {
|
||||
path.replace('/', "\\")
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
path.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn make_uri(path: &str) -> Uri {
|
||||
let platform_path = unix_path_to_platform(path);
|
||||
Uri::from_file_path(&platform_path).unwrap()
|
||||
}
|
||||
|
||||
fn assert_directory_result(
|
||||
result: Option<ResultWorkingDirectory>,
|
||||
expected_directory: &str,
|
||||
expected_not_cwd: bool,
|
||||
) {
|
||||
match result {
|
||||
Some(ResultWorkingDirectory::DirectoryItem(item)) => {
|
||||
assert_eq!(item.directory, expected_directory);
|
||||
assert_eq!(item.not_cwd, Some(expected_not_cwd));
|
||||
}
|
||||
other => panic!("Expected DirectoryItem, got {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use language::{
|
||||
ContextProvider, LanguageName, LocalFile as _, LspAdapter, LspAdapterDelegate, LspInstaller,
|
||||
Toolchain,
|
||||
};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde_json::{Value, json};
|
||||
@@ -251,6 +251,7 @@ impl LspAdapter for JsonLspAdapter {
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let mut config = cx.update(|cx| {
|
||||
|
||||
@@ -21,6 +21,7 @@ mod bash;
|
||||
mod c;
|
||||
mod cpp;
|
||||
mod css;
|
||||
mod eslint;
|
||||
mod go;
|
||||
mod json;
|
||||
mod package_json;
|
||||
@@ -84,7 +85,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
|
||||
|
||||
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(JsonTaskProvider);
|
||||
|
||||
@@ -10,8 +10,8 @@ use language::{ContextLocation, LanguageToolchainStore, LspInstaller};
|
||||
use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
|
||||
use language::{Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata};
|
||||
use lsp::LanguageServerBinary;
|
||||
use lsp::LanguageServerName;
|
||||
use lsp::{LanguageServerBinary, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
use pet_core::Configuration;
|
||||
use pet_core::os_environment::Environment;
|
||||
@@ -206,6 +206,7 @@ impl LspAdapter for TyLspAdapter {
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchain: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let mut ret = cx
|
||||
@@ -517,6 +518,7 @@ impl LspAdapter for PyrightLspAdapter {
|
||||
self: Arc<Self>,
|
||||
adapter: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchain: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
cx.update(move |cx| {
|
||||
@@ -1593,6 +1595,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
self: Arc<Self>,
|
||||
adapter: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchain: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
cx.update(move |cx| {
|
||||
@@ -1884,6 +1887,7 @@ impl LspAdapter for BasedPyrightLspAdapter {
|
||||
self: Arc<Self>,
|
||||
adapter: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchain: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
cx.update(move |cx| {
|
||||
|
||||
@@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::AsyncApp;
|
||||
use language::{LanguageName, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde_json::{Value, json};
|
||||
@@ -154,6 +154,7 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let mut tailwind_user_settings = cx.update(|cx| {
|
||||
|
||||
@@ -4,18 +4,16 @@ use chrono::{DateTime, Local};
|
||||
use collections::HashMap;
|
||||
use futures::future::join_all;
|
||||
use gpui::{App, AppContext, AsyncApp, Task};
|
||||
use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url};
|
||||
use http_client::github_download::download_server_binary;
|
||||
use itertools::Itertools as _;
|
||||
use language::{
|
||||
ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, LspAdapter,
|
||||
LspAdapterDelegate, LspInstaller, Toolchain,
|
||||
};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
use project::{Fs, lsp_store::language_server_settings};
|
||||
use serde_json::{Value, json};
|
||||
use smol::{fs, lock::RwLock, stream::StreamExt};
|
||||
use smol::lock::RwLock;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::OsString,
|
||||
@@ -23,8 +21,8 @@ use std::{
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use task::{TaskTemplate, TaskTemplates, VariableName};
|
||||
use util::{ResultExt, fs::remove_matching, maybe};
|
||||
use util::{merge_json_value_into, rel_path::RelPath};
|
||||
use util::rel_path::RelPath;
|
||||
use util::{ResultExt, maybe};
|
||||
|
||||
use crate::{PackageJson, PackageJsonData};
|
||||
|
||||
@@ -589,14 +587,6 @@ fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
fn eslint_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![
|
||||
"--max-old-space-size=8192".into(),
|
||||
server_path.into(),
|
||||
"--stdio".into(),
|
||||
]
|
||||
}
|
||||
|
||||
fn replace_test_name_parameters(test_name: &str) -> String {
|
||||
static PATTERN: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"(\$([A-Za-z0-9_\.]+|[\#])|%[psdifjo#\$%])").unwrap());
|
||||
@@ -811,9 +801,9 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let override_options = cx.update(|cx| {
|
||||
@@ -866,226 +856,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))
|
||||
}
|
||||
}
|
||||
|
||||
impl LspInstaller for EsLintLspAdapter {
|
||||
type BinaryVersion = GitHubLspBinaryVersion;
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_delegate: &dyn LspAdapterDelegate,
|
||||
_: bool,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<GitHubLspBinaryVersion> {
|
||||
let url = build_asset_url(
|
||||
"zed-industries/vscode-eslint",
|
||||
Self::CURRENT_VERSION_TAG_NAME,
|
||||
Self::GITHUB_ASSET_KIND,
|
||||
)?;
|
||||
|
||||
Ok(GitHubLspBinaryVersion {
|
||||
name: Self::CURRENT_VERSION.into(),
|
||||
digest: None,
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: GitHubLspBinaryVersion,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
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, |_| true).await;
|
||||
|
||||
download_server_binary(
|
||||
&*delegate.http_client(),
|
||||
&version.url,
|
||||
None,
|
||||
&destination_path,
|
||||
Self::GITHUB_ASSET_KIND,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[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 std::path::Path;
|
||||
|
||||
@@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::AsyncApp;
|
||||
use language::{LanguageName, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
use project::{Fs, lsp_store::language_server_settings};
|
||||
use regex::Regex;
|
||||
@@ -228,6 +228,7 @@ impl LspAdapter for VtslsLspAdapter {
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let tsdk_path = self.tsdk_path(delegate).await;
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::AsyncApp;
|
||||
use language::{
|
||||
LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain, language_settings::AllLanguageSettings,
|
||||
};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde_json::Value;
|
||||
@@ -132,9 +132,9 @@ impl LspAdapter for YamlLspAdapter {
|
||||
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Option<Toolchain>,
|
||||
_: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let location = SettingsLocation {
|
||||
|
||||
@@ -436,6 +436,7 @@ impl LocalLspStore {
|
||||
adapter.adapter.clone(),
|
||||
&delegate,
|
||||
toolchain,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
@@ -720,25 +721,42 @@ impl LocalLspStore {
|
||||
)
|
||||
})?
|
||||
.context("Expected the LSP store to be in a local mode")?;
|
||||
let workspace_config = Self::workspace_configuration_for_adapter(
|
||||
adapter.clone(),
|
||||
&delegate,
|
||||
toolchain_for_id,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut scope_uri_to_workspace_config = BTreeMap::new();
|
||||
for item in ¶ms.items {
|
||||
let scope_uri = item.scope_uri.clone();
|
||||
let std::collections::btree_map::Entry::Vacant(new_scope_uri) =
|
||||
scope_uri_to_workspace_config.entry(scope_uri.clone())
|
||||
else {
|
||||
// We've already queried workspace configuration of this URI.
|
||||
continue;
|
||||
};
|
||||
let workspace_config = Self::workspace_configuration_for_adapter(
|
||||
adapter.clone(),
|
||||
&delegate,
|
||||
toolchain_for_id.clone(),
|
||||
scope_uri,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
new_scope_uri.insert(workspace_config);
|
||||
}
|
||||
|
||||
Ok(params
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
.filter_map(|item| {
|
||||
let workspace_config =
|
||||
scope_uri_to_workspace_config.get(&item.scope_uri)?;
|
||||
if let Some(section) = &item.section {
|
||||
workspace_config
|
||||
.get(section)
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
Some(
|
||||
workspace_config
|
||||
.get(section)
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
)
|
||||
} else {
|
||||
workspace_config.clone()
|
||||
Some(workspace_config.clone())
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
@@ -3527,11 +3545,12 @@ impl LocalLspStore {
|
||||
adapter: Arc<dyn LspAdapter>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchain: Option<Toolchain>,
|
||||
requested_uri: Option<Uri>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut workspace_config = adapter
|
||||
.clone()
|
||||
.workspace_configuration(delegate, toolchain, cx)
|
||||
.workspace_configuration(delegate, toolchain, requested_uri, cx)
|
||||
.await?;
|
||||
|
||||
for other_adapter in delegate.registered_lsp_adapters() {
|
||||
@@ -7963,6 +7982,7 @@ impl LspStore {
|
||||
adapter.adapter.clone(),
|
||||
&delegate,
|
||||
toolchain,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
@@ -13619,7 +13639,7 @@ pub fn language_server_settings<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn language_server_settings_for<'a>(
|
||||
pub fn language_server_settings_for<'a>(
|
||||
location: SettingsLocation<'a>,
|
||||
language: &LanguageServerName,
|
||||
cx: &'a App,
|
||||
|
||||
Reference in New Issue
Block a user