use super::installation::{latest_github_release, GitHubLspBinaryVersion}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; pub use language::*; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; pub struct ElixirLspAdapter; #[async_trait] impl LspAdapter for ElixirLspAdapter { async fn name(&self) -> LanguageServerName { LanguageServerName("elixir-ls".into()) } async fn fetch_latest_server_version( &self, http: Arc, ) -> Result> { let release = latest_github_release("elixir-lsp/elixir-ls", http).await?; let asset_name = "elixir-ls.zip"; let asset = release .assets .iter() .find(|asset| asset.name == asset_name) .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; let version = GitHubLspBinaryVersion { name: release.name, url: asset.browser_download_url.clone(), }; Ok(Box::new(version) as Box<_>) } async fn fetch_server_binary( &self, version: Box, http: Arc, container_dir: PathBuf, ) -> Result { let version = version.downcast::().unwrap(); let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name)); let version_dir = container_dir.join(format!("elixir-ls_{}", version.name)); let binary_path = version_dir.join("language_server.sh"); if fs::metadata(&binary_path).await.is_err() { let mut response = http .get(&version.url, Default::default(), true) .await .context("error downloading release")?; let mut file = File::create(&zip_path) .await .with_context(|| format!("failed to create file {}", zip_path.display()))?; if !response.status().is_success() { Err(anyhow!( "download failed with status {}", response.status().to_string() ))?; } futures::io::copy(response.body_mut(), &mut file).await?; fs::create_dir_all(&version_dir) .await .with_context(|| format!("failed to create directory {}", version_dir.display()))?; let unzip_status = smol::process::Command::new("unzip") .arg(&zip_path) .arg("-d") .arg(&version_dir) .output() .await? .status; if !unzip_status.success() { Err(anyhow!("failed to unzip clangd archive"))?; } 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() != version_dir { if let Ok(metadata) = fs::metadata(&entry_path).await { if metadata.is_file() { fs::remove_file(&entry_path).await.log_err(); } else { fs::remove_dir_all(&entry_path).await.log_err(); } } } } } } } Ok(binary_path) } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { (|| async move { let mut last = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { last = Some(entry?.path()); } last.ok_or_else(|| anyhow!("no cached binary")) })() .await .log_err() } }