diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index fbc8182d30..3bf57ad75b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -75,9 +75,10 @@ pub trait LspAdapter: 'static + Send + Sync { &self, version: Box, http: Arc, - container_dir: PathBuf, + container_dir: Arc, ) -> BoxFuture<'static, Result>; - fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option>; + fn cached_server_binary(&self, container_dir: Arc) + -> BoxFuture<'static, Option>; fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -362,7 +363,7 @@ async fn get_server_binary_path( download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { - let container_dir = download_dir.join(adapter.name().0.as_ref()); + let container_dir: Arc = download_dir.join(adapter.name().0.as_ref()).into(); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await @@ -399,6 +400,7 @@ async fn fetch_latest_server_binary_path( container_dir: &Path, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { + let container_dir: Arc = container_dir.into(); lsp_binary_statuses_tx .broadcast(( language.clone(), @@ -412,7 +414,7 @@ async fn fetch_latest_server_binary_path( .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) .await?; let path = adapter - .fetch_server_binary(version_info, http_client, container_dir.to_path_buf()) + .fetch_server_binary(version_info, http_client, container_dir.clone()) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) @@ -657,12 +659,12 @@ impl LspAdapter for FakeLspAdapter { &self, _: Box, _: Arc, - _: PathBuf, + _: Arc, ) -> BoxFuture<'static, Result> { unreachable!(); } - fn cached_server_binary(&self, _: PathBuf) -> BoxFuture<'static, Option> { + fn cached_server_binary(&self, _: Arc) -> BoxFuture<'static, Option> { unreachable!(); } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 6fad623858..44c0d85566 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -4,6 +4,7 @@ use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; mod c; +mod go; mod installation; mod json; mod rust; diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index fd92e419a7..74b48ffee8 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -4,7 +4,11 @@ use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; pub use language::*; use smol::fs::{self, File}; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{ + any::Any, + path::{Path, PathBuf}, + sync::Arc, +}; use util::{ResultExt, TryFutureExt}; pub struct CLspAdapter; @@ -39,7 +43,7 @@ impl super::LspAdapter for CLspAdapter { &self, version: Box, http: Arc, - container_dir: PathBuf, + container_dir: Arc, ) -> BoxFuture<'static, Result> { let version = version.downcast::().unwrap(); async move { @@ -88,7 +92,10 @@ impl super::LspAdapter for CLspAdapter { .boxed() } - fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { + fn cached_server_binary( + &self, + container_dir: Arc, + ) -> BoxFuture<'static, Option> { async move { let mut last_clangd_dir = None; let mut entries = fs::read_dir(&container_dir).await?; diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs new file mode 100644 index 0000000000..24f98b706c --- /dev/null +++ b/crates/zed/src/languages/go.rs @@ -0,0 +1,140 @@ +use super::installation::latest_github_release; +use anyhow::{anyhow, Result}; +use client::http::HttpClient; +use futures::{future::BoxFuture, FutureExt, StreamExt}; +pub use language::*; +use lazy_static::lazy_static; +use regex::Regex; +use smol::{fs, process}; +use std::{ + any::Any, + path::{Path, PathBuf}, + str, + sync::Arc, +}; +use util::{ResultExt, TryFutureExt}; + +#[derive(Copy, Clone)] +pub struct GoLspAdapter; + +lazy_static! { + static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap(); +} + +impl super::LspAdapter for GoLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("gopls".into()) + } + + fn fetch_latest_server_version( + &self, + http: Arc, + ) -> BoxFuture<'static, Result>> { + async move { + let release = latest_github_release("golang/tools", http).await?; + let version: Option = release.name.strip_prefix("gopls/v").map(str::to_string); + if version.is_none() { + log::warn!( + "couldn't infer gopls version from github release name '{}'", + release.name + ); + } + Ok(Box::new(version) as Box<_>) + } + .boxed() + } + + fn fetch_server_binary( + &self, + version: Box, + _: Arc, + container_dir: Arc, + ) -> BoxFuture<'static, Result> { + let version = version.downcast::>().unwrap(); + let this = *self; + + async move { + if let Some(version) = *version { + let binary_path = container_dir.join(&format!("gopls_{version}")); + if let Ok(metadata) = fs::metadata(&binary_path).await { + if metadata.is_file() { + if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { + while let Some(entry) = entries.next().await { + if let Some(entry) = entry.log_err() { + let entry_path = entry.path(); + if entry_path.as_path() != binary_path + && entry.file_name() != "gobin" + { + fs::remove_file(&entry_path).await.log_err(); + } + } + } + } + + return Ok(binary_path.to_path_buf()); + } + } + } else if let Some(path) = this.cached_server_binary(container_dir.clone()).await { + return Ok(path.to_path_buf()); + } + + let gobin_dir = container_dir.join("gobin"); + fs::create_dir_all(&gobin_dir).await?; + let install_output = process::Command::new("go") + .env("GO111MODULE", "on") + .env("GOBIN", &gobin_dir) + .args(["install", "golang.org/x/tools/gopls@latest"]) + .output() + .await?; + if !install_output.status.success() { + Err(anyhow!("failed to install gopls"))?; + } + + let installed_binary_path = gobin_dir.join("gopls"); + let version_output = process::Command::new(&installed_binary_path) + .arg("version") + .output() + .await + .map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?; + let version_stdout = str::from_utf8(&version_output.stdout) + .map_err(|_| anyhow!("gopls version produced invalid utf8"))?; + let version = GOPLS_VERSION_REGEX + .shortest_match(version_stdout) + .ok_or_else(|| anyhow!("failed to parse gopls version output"))?; + let binary_path = container_dir.join(&format!("gopls_{version}")); + fs::rename(&installed_binary_path, &binary_path).await?; + + Ok(binary_path.to_path_buf()) + } + .boxed() + } + + fn cached_server_binary( + &self, + container_dir: Arc, + ) -> BoxFuture<'static, Option> { + async move { + let mut last_binary_path = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name.starts_with("gopls_")) + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(path.to_path_buf()) + } else { + Err(anyhow!("no cached binary")) + } + } + .log_err() + .boxed() + } +} diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index dab6922c71..59e0652612 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -5,7 +5,11 @@ use language::{LanguageServerName, LspAdapter}; use serde::Deserialize; use serde_json::json; use smol::fs; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{ + any::Any, + path::{Path, PathBuf}, + sync::Arc, +}; use util::{ResultExt, TryFutureExt}; pub struct JsonLspAdapter; @@ -56,7 +60,7 @@ impl LspAdapter for JsonLspAdapter { &self, version: Box, _: Arc, - container_dir: PathBuf, + container_dir: Arc, ) -> BoxFuture<'static, Result> { let version = version.downcast::().unwrap(); async move { @@ -95,7 +99,10 @@ impl LspAdapter for JsonLspAdapter { .boxed() } - fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { + fn cached_server_binary( + &self, + container_dir: Arc, + ) -> BoxFuture<'static, Option> { async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 6cce615d81..1abeeb097c 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -7,7 +7,14 @@ pub use language::*; use lazy_static::lazy_static; use regex::Regex; use smol::fs::{self, File}; -use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; +use std::{ + any::Any, + borrow::Cow, + env::consts, + path::{Path, PathBuf}, + str, + sync::Arc, +}; use util::{ResultExt, TryFutureExt}; pub struct RustLspAdapter; @@ -42,7 +49,7 @@ impl LspAdapter for RustLspAdapter { &self, version: Box, http: Arc, - container_dir: PathBuf, + container_dir: Arc, ) -> BoxFuture<'static, Result> { async move { let version = version.downcast::().unwrap(); @@ -79,7 +86,10 @@ impl LspAdapter for RustLspAdapter { .boxed() } - fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { + fn cached_server_binary( + &self, + container_dir: Arc, + ) -> BoxFuture<'static, Option> { async move { let mut last = None; let mut entries = fs::read_dir(&container_dir).await?; diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 96682d26d7..7b436ad79f 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -5,7 +5,11 @@ use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; use serde_json::json; use smol::fs; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{ + any::Any, + path::{Path, PathBuf}, + sync::Arc, +}; use util::{ResultExt, TryFutureExt}; pub struct TypeScriptLspAdapter; @@ -45,7 +49,7 @@ impl LspAdapter for TypeScriptLspAdapter { &self, versions: Box, _: Arc, - container_dir: PathBuf, + container_dir: Arc, ) -> BoxFuture<'static, Result> { let versions = versions.downcast::().unwrap(); async move { @@ -88,7 +92,10 @@ impl LspAdapter for TypeScriptLspAdapter { .boxed() } - fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option> { + fn cached_server_binary( + &self, + container_dir: Arc, + ) -> BoxFuture<'static, Option> { async move { let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?;