Detailed changes
@@ -7,7 +7,7 @@ pub mod proto;
mod tests;
use anyhow::{anyhow, Context, Result};
-use client::http::{self, HttpClient};
+use client::http::HttpClient;
use collections::HashMap;
use futures::{
future::{BoxFuture, Shared},
@@ -61,11 +61,6 @@ pub trait ToLspPosition {
fn to_lsp_position(self) -> lsp::Position;
}
-pub struct GitHubLspBinaryVersion {
- pub name: String,
- pub url: http::Url,
-}
-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LanguageServerName(pub Arc<str>);
@@ -1,11 +1,10 @@
-use client::http;
use gpui::Task;
pub use language::*;
use rust_embed::RustEmbed;
-use serde::Deserialize;
use std::{borrow::Cow, str, sync::Arc};
mod c;
+mod installation;
mod json;
mod rust;
mod typescript;
@@ -15,18 +14,6 @@ mod typescript;
#[exclude = "*.rs"]
struct LanguageDir;
-#[derive(Deserialize)]
-struct GithubRelease {
- name: String,
- assets: Vec<GithubReleaseAsset>,
-}
-
-#[derive(Deserialize)]
-struct GithubReleaseAsset {
- name: String,
- browser_download_url: http::Url,
-}
-
pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegistry {
let languages = LanguageRegistry::new(login_shell_env_loaded);
for (name, grammar, lsp_adapter) in [
@@ -1,13 +1,12 @@
+use super::installation::{latest_github_release, GitHubLspBinaryVersion};
use anyhow::{anyhow, Result};
-use client::http::{self, HttpClient, Method};
+use client::http::{HttpClient, Method};
use futures::{future::BoxFuture, FutureExt, StreamExt};
pub use language::*;
use smol::fs::{self, File};
use std::{any::Any, path::PathBuf, sync::Arc};
use util::{ResultExt, TryFutureExt};
-use super::GithubRelease;
-
pub struct CLspAdapter;
impl super::LspAdapter for CLspAdapter {
@@ -20,33 +19,11 @@ impl super::LspAdapter for CLspAdapter {
http: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
async move {
- let release = http
- .send(
- surf::RequestBuilder::new(
- Method::Get,
- http::Url::parse(
- "https://api.github.com/repos/clangd/clangd/releases/latest",
- )
- .unwrap(),
- )
- .middleware(surf::middleware::Redirect::default())
- .build(),
- )
- .await
- .map_err(|err| anyhow!("error fetching latest release: {}", err))?
- .body_json::<GithubRelease>()
- .await
- .map_err(|err| anyhow!("error parsing latest release: {}", err))?;
- let asset_name = format!("clangd-mac-{}.zip", release.name);
- let asset = release
- .assets
- .iter()
- .find(|asset| asset.name == asset_name)
- .ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?;
- Ok(Box::new(GitHubLspBinaryVersion {
- name: release.name,
- url: asset.browser_download_url.clone(),
- }) as Box<_>)
+ let version = latest_github_release("clangd/clangd", http, |release_name| {
+ format!("clangd-mac-{release_name}.zip")
+ })
+ .await?;
+ Ok(Box::new(version) as Box<_>)
}
.boxed()
}
@@ -0,0 +1,111 @@
+use anyhow::{anyhow, Context, Result};
+use client::http::{self, HttpClient, Method};
+use serde::Deserialize;
+use std::{path::Path, sync::Arc};
+
+pub struct GitHubLspBinaryVersion {
+ pub name: String,
+ pub url: http::Url,
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "kebab-case")]
+struct NpmInfo {
+ #[serde(default)]
+ dist_tags: NpmInfoDistTags,
+ versions: Vec<String>,
+}
+
+#[derive(Deserialize, Default)]
+struct NpmInfoDistTags {
+ latest: Option<String>,
+}
+
+#[derive(Deserialize)]
+pub(crate) struct GithubRelease {
+ name: String,
+ assets: Vec<GithubReleaseAsset>,
+}
+
+#[derive(Deserialize)]
+pub(crate) struct GithubReleaseAsset {
+ name: String,
+ browser_download_url: http::Url,
+}
+
+pub async fn npm_package_latest_version(name: &str) -> Result<String> {
+ let output = smol::process::Command::new("npm")
+ .args(["info", name, "--json"])
+ .output()
+ .await?;
+ if !output.status.success() {
+ Err(anyhow!(
+ "failed to execute npm info: {:?}",
+ String::from_utf8_lossy(&output.stderr)
+ ))?;
+ }
+ let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
+ info.dist_tags
+ .latest
+ .or_else(|| info.versions.pop())
+ .ok_or_else(|| anyhow!("no version found for npm package {}", name))
+}
+
+pub async fn npm_install_packages(
+ packages: impl IntoIterator<Item = (&str, &str)>,
+ directory: &Path,
+) -> Result<()> {
+ let output = smol::process::Command::new("npm")
+ .arg("install")
+ .arg("--prefix")
+ .arg(directory)
+ .args(
+ packages
+ .into_iter()
+ .map(|(name, version)| format!("{name}@{version}")),
+ )
+ .output()
+ .await
+ .context("failed to run npm install")?;
+ if !output.status.success() {
+ Err(anyhow!(
+ "failed to execute npm install: {:?}",
+ String::from_utf8_lossy(&output.stderr)
+ ))?;
+ }
+ Ok(())
+}
+
+pub async fn latest_github_release(
+ repo_name_with_owner: &str,
+ http: Arc<dyn HttpClient>,
+ asset_name: impl Fn(&str) -> String,
+) -> Result<GitHubLspBinaryVersion> {
+ let release = http
+ .send(
+ surf::RequestBuilder::new(
+ Method::Get,
+ http::Url::parse(&format!(
+ "https://api.github.com/repos/{repo_name_with_owner}/releases/latest"
+ ))
+ .unwrap(),
+ )
+ .middleware(surf::middleware::Redirect::default())
+ .build(),
+ )
+ .await
+ .map_err(|err| anyhow!("error fetching latest release: {}", err))?
+ .body_json::<GithubRelease>()
+ .await
+ .map_err(|err| anyhow!("error parsing latest release: {}", err))?;
+ let asset_name = asset_name(&release.name);
+ let asset = release
+ .assets
+ .iter()
+ .find(|asset| asset.name == asset_name)
+ .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
+ Ok(GitHubLspBinaryVersion {
+ name: release.name,
+ url: asset.browser_download_url.clone(),
+ })
+}
@@ -1,6 +1,7 @@
+use super::installation::{latest_github_release, GitHubLspBinaryVersion};
use anyhow::{anyhow, Result};
use async_compression::futures::bufread::GzipDecoder;
-use client::http::{self, HttpClient, Method};
+use client::http::{HttpClient, Method};
use futures::{future::BoxFuture, FutureExt, StreamExt};
pub use language::*;
use lazy_static::lazy_static;
@@ -9,8 +10,6 @@ use smol::fs::{self, File};
use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
use util::{ResultExt, TryFutureExt};
-use super::GithubRelease;
-
pub struct RustLspAdapter;
impl LspAdapter for RustLspAdapter {
@@ -23,33 +22,11 @@ impl LspAdapter for RustLspAdapter {
http: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
async move {
- let release = http
- .send(
- surf::RequestBuilder::new(
- Method::Get,
- http::Url::parse(
- "https://api.github.com/repos/rust-analyzer/rust-analyzer/releases/latest",
- )
- .unwrap(),
- )
- .middleware(surf::middleware::Redirect::default())
- .build(),
- )
- .await
- .map_err(|err| anyhow!("error fetching latest release: {}", err))?
- .body_json::<GithubRelease>()
- .await
- .map_err(|err| anyhow!("error parsing latest release: {}", err))?;
- let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH);
- let asset = release
- .assets
- .iter()
- .find(|asset| asset.name == asset_name)
- .ok_or_else(|| anyhow!("no release found matching {:?}", asset_name))?;
- Ok(Box::new(GitHubLspBinaryVersion {
- name: release.name,
- url: asset.browser_download_url.clone(),
- }) as Box<_>)
+ let version = latest_github_release("rust-analyzer/rust-analyzer", http, |_| {
+ format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH)
+ })
+ .await?;
+ Ok(Box::new(version) as Box<_>)
}
.boxed()
}
@@ -1,8 +1,8 @@
+use super::installation::{npm_install_packages, npm_package_latest_version};
use anyhow::{anyhow, Context, Result};
use client::http::HttpClient;
use futures::{future::BoxFuture, FutureExt, StreamExt};
use language::{LanguageServerName, LspAdapter};
-use serde::Deserialize;
use serde_json::json;
use smol::fs;
use std::{any::Any, path::PathBuf, sync::Arc};
@@ -33,37 +33,9 @@ impl LspAdapter for TypeScriptLspAdapter {
_: Arc<dyn HttpClient>,
) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
async move {
- #[derive(Deserialize)]
- struct NpmInfo {
- versions: Vec<String>,
- }
-
- let typescript_output = smol::process::Command::new("npm")
- .args(["info", "typescript", "--json"])
- .output()
- .await?;
- if !typescript_output.status.success() {
- Err(anyhow!("failed to execute npm info"))?;
- }
- let mut typescript_info: NpmInfo = serde_json::from_slice(&typescript_output.stdout)?;
-
- let server_output = smol::process::Command::new("npm")
- .args(["info", "typescript-language-server", "--json"])
- .output()
- .await?;
- if !server_output.status.success() {
- Err(anyhow!("failed to execute npm info"))?;
- }
- let mut server_info: NpmInfo = serde_json::from_slice(&server_output.stdout)?;
-
Ok(Box::new(Versions {
- typescript_version: typescript_info
- .versions
- .pop()
- .ok_or_else(|| anyhow!("no versions found in typescript npm info"))?,
- server_version: server_info.versions.pop().ok_or_else(|| {
- anyhow!("no versions found in typescript language server npm info")
- })?,
+ typescript_version: npm_package_latest_version("typescript").await?,
+ server_version: npm_package_latest_version("typescript-language-server").await?,
}) as Box<_>)
}
.boxed()
@@ -87,20 +59,17 @@ impl LspAdapter for TypeScriptLspAdapter {
let binary_path = version_dir.join(Self::BIN_PATH);
if fs::metadata(&binary_path).await.is_err() {
- let output = smol::process::Command::new("npm")
- .current_dir(&version_dir)
- .arg("install")
- .arg(format!("typescript@{}", versions.typescript_version))
- .arg(format!(
- "typescript-language-server@{}",
- versions.server_version
- ))
- .output()
- .await
- .context("failed to run npm install")?;
- if !output.status.success() {
- Err(anyhow!("failed to install typescript-language-server"))?;
- }
+ npm_install_packages(
+ [
+ ("typescript", versions.typescript_version.as_str()),
+ (
+ "typescript-language-server",
+ &versions.server_version.as_str(),
+ ),
+ ],
+ &version_dir,
+ )
+ .await?;
if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() {
while let Some(entry) = entries.next().await {