Allow extensions to define more of the methods in the LspAdapter trait (#9554)

Our goal is to extract Svelte support into an extension, since we've
seen problems with the Tree-sitter Svelte parser crashing due to bugs in
the external scanner. In order to do this, we need a couple more
capabilities in LSP extensions:

* [x] `initialization_options` - programmatically controlling the JSON
initialization params sent to the language server
* [x] `prettier_plugins` - statically specifying a list of prettier
plugins that apply for a given language.
* [x] `npm_install_package`

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
Max Brunsfeld 2024-03-20 12:47:04 -07:00 committed by GitHub
parent 0ce5cdc48f
commit d699b8e104
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 318 additions and 208 deletions

View file

@ -4,8 +4,8 @@ use async_tar::Archive;
use futures::AsyncReadExt;
use semver::Version;
use serde::Deserialize;
use serde_json::Value;
use smol::{fs, io::BufReader, lock::Mutex, process::Command};
use std::io;
use std::process::{Output, Stdio};
use std::{
env::consts,
@ -46,6 +46,12 @@ pub trait NodeRuntime: Send + Sync {
async fn npm_install_packages(&self, directory: &Path, packages: &[(&str, &str)])
-> Result<()>;
async fn npm_package_installed_version(
&self,
local_package_directory: &PathBuf,
name: &str,
) -> Result<Option<String>>;
async fn should_install_npm_package(
&self,
package_name: &str,
@ -60,36 +66,19 @@ pub trait NodeRuntime: Send + Sync {
return true;
}
let package_json_path = local_package_directory.join("package.json");
let mut contents = String::new();
let Some(mut file) = fs::File::open(package_json_path).await.log_err() else {
let Some(installed_version) = self
.npm_package_installed_version(local_package_directory, package_name)
.await
.log_err()
.flatten()
else {
return true;
};
file.read_to_string(&mut contents).await.log_err();
let Some(package_json): Option<Value> = serde_json::from_str(&contents).log_err() else {
let Some(installed_version) = Version::parse(&installed_version).log_err() else {
return true;
};
let installed_version = package_json
.get("dependencies")
.and_then(|deps| deps.get(package_name))
.and_then(|server_name| server_name.as_str());
let Some(installed_version) = installed_version else {
return true;
};
let Some(latest_version) = Version::parse(latest_version).log_err() else {
return true;
};
let installed_version = installed_version.trim_start_matches(|c: char| !c.is_ascii_digit());
let Some(installed_version) = Version::parse(installed_version).log_err() else {
let Some(latest_version) = Version::parse(&latest_version).log_err() else {
return true;
};
@ -281,6 +270,36 @@ impl NodeRuntime for RealNodeRuntime {
.ok_or_else(|| anyhow!("no version found for npm package {}", name))
}
async fn npm_package_installed_version(
&self,
local_package_directory: &PathBuf,
name: &str,
) -> Result<Option<String>> {
let mut package_json_path = local_package_directory.clone();
package_json_path.extend(["node_modules", name, "package.json"]);
let mut file = match fs::File::open(package_json_path).await {
Ok(file) => file,
Err(err) => {
if err.kind() == io::ErrorKind::NotFound {
return Ok(None);
}
Err(err)?
}
};
#[derive(Deserialize)]
struct PackageJson {
version: String,
}
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
let package_json: PackageJson = serde_json::from_str(&contents)?;
Ok(Some(package_json.version))
}
async fn npm_install_packages(
&self,
directory: &Path,
@ -335,6 +354,14 @@ impl NodeRuntime for FakeNodeRuntime {
unreachable!("Should not query npm package '{name}' for latest version")
}
async fn npm_package_installed_version(
&self,
_local_package_directory: &PathBuf,
name: &str,
) -> Result<Option<String>> {
unreachable!("Should not query npm package '{name}' for installed version")
}
async fn npm_install_packages(
&self,
_: &Path,