Detailed changes
@@ -75,9 +75,10 @@ pub trait LspAdapter: 'static + Send + Sync {
&self,
version: Box<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
- container_dir: PathBuf,
+ container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>>;
- fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>>;
+ fn cached_server_binary(&self, container_dir: Arc<Path>)
+ -> BoxFuture<'static, Option<PathBuf>>;
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
@@ -362,7 +363,7 @@ async fn get_server_binary_path(
download_dir: Arc<Path>,
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
) -> Result<PathBuf> {
- let container_dir = download_dir.join(adapter.name().0.as_ref());
+ let container_dir: Arc<Path> = 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<Language>, LanguageServerBinaryStatus)>,
) -> Result<PathBuf> {
+ let container_dir: Arc<Path> = 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<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
- _: PathBuf,
+ _: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
unreachable!();
}
- fn cached_server_binary(&self, _: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ fn cached_server_binary(&self, _: Arc<Path>) -> BoxFuture<'static, Option<PathBuf>> {
unreachable!();
}
@@ -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;
@@ -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<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
- container_dir: PathBuf,
+ container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
async move {
@@ -88,7 +92,10 @@ impl super::LspAdapter for CLspAdapter {
.boxed()
}
- fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ fn cached_server_binary(
+ &self,
+ container_dir: Arc<Path>,
+ ) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_clangd_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -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<dyn HttpClient>,
+ ) -> BoxFuture<'static, Result<Box<dyn 'static + Send + Any>>> {
+ async move {
+ let release = latest_github_release("golang/tools", http).await?;
+ let version: Option<String> = 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<dyn 'static + Send + Any>,
+ _: Arc<dyn HttpClient>,
+ container_dir: Arc<Path>,
+ ) -> BoxFuture<'static, Result<PathBuf>> {
+ let version = version.downcast::<Option<String>>().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<Path>,
+ ) -> BoxFuture<'static, Option<PathBuf>> {
+ 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()
+ }
+}
@@ -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<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
- container_dir: PathBuf,
+ container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
let version = version.downcast::<String>().unwrap();
async move {
@@ -95,7 +99,10 @@ impl LspAdapter for JsonLspAdapter {
.boxed()
}
- fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ fn cached_server_binary(
+ &self,
+ container_dir: Arc<Path>,
+ ) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -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<dyn 'static + Send + Any>,
http: Arc<dyn HttpClient>,
- container_dir: PathBuf,
+ container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
async move {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
@@ -79,7 +86,10 @@ impl LspAdapter for RustLspAdapter {
.boxed()
}
- fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ fn cached_server_binary(
+ &self,
+ container_dir: Arc<Path>,
+ ) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last = None;
let mut entries = fs::read_dir(&container_dir).await?;
@@ -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<dyn 'static + Send + Any>,
_: Arc<dyn HttpClient>,
- container_dir: PathBuf,
+ container_dir: Arc<Path>,
) -> BoxFuture<'static, Result<PathBuf>> {
let versions = versions.downcast::<Versions>().unwrap();
async move {
@@ -88,7 +92,10 @@ impl LspAdapter for TypeScriptLspAdapter {
.boxed()
}
- fn cached_server_binary(&self, container_dir: PathBuf) -> BoxFuture<'static, Option<PathBuf>> {
+ fn cached_server_binary(
+ &self,
+ container_dir: Arc<Path>,
+ ) -> BoxFuture<'static, Option<PathBuf>> {
async move {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;