diff --git a/assets/settings/default.json b/assets/settings/default.json index 92b70da3ad..b3190df1a4 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -743,6 +743,9 @@ "Elixir": { "language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."] }, + "Erlang": { + "language_servers": ["erlang-ls", "!elp", "..."] + }, "Go": { "code_actions_on_format": { "source.organizeImports": true diff --git a/extensions/erlang/src/language_servers/erlang_language_platform.rs b/extensions/erlang/src/language_servers/erlang_language_platform.rs index d5642f6097..804e44d522 100644 --- a/extensions/erlang/src/language_servers/erlang_language_platform.rs +++ b/extensions/erlang/src/language_servers/erlang_language_platform.rs @@ -1,12 +1,18 @@ +use std::fs; + use zed_extension_api::{self as zed, LanguageServerId, Result}; -pub struct ErlangLanguagePlatform; +pub struct ErlangLanguagePlatform { + cached_binary_path: Option, +} impl ErlangLanguagePlatform { pub const LANGUAGE_SERVER_ID: &'static str = "elp"; pub fn new() -> Self { - Self + Self { + cached_binary_path: None, + } } pub fn language_server_command( @@ -23,11 +29,84 @@ impl ErlangLanguagePlatform { fn language_server_binary_path( &mut self, - _language_server_id: &LanguageServerId, + language_server_id: &LanguageServerId, worktree: &zed::Worktree, ) -> Result { - worktree - .which("elp") - .ok_or_else(|| "elp must be installed and available on your $PATH".to_string()) + if let Some(path) = worktree.which("elp") { + return Ok(path); + } + + if let Some(path) = &self.cached_binary_path { + if fs::metadata(path).map_or(false, |stat| stat.is_file()) { + return Ok(path.clone()); + } + } + + zed::set_language_server_installation_status( + &language_server_id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ); + let release = zed::latest_github_release( + "WhatsApp/erlang-language-platform", + zed::GithubReleaseOptions { + require_assets: true, + pre_release: false, + }, + )?; + + let (platform, arch) = zed::current_platform(); + let asset_name = { + let otp_version = "26.2"; + let (os, os_target) = match platform { + zed::Os::Mac => ("macos", "apple-darwin"), + zed::Os::Linux => ("linux", "unknown-linux-gnu"), + zed::Os::Windows => return Err(format!("unsupported platform: {platform:?}")), + }; + + format!( + "elp-{os}-{arch}-{os_target}-otp-{otp_version}.tar.gz", + arch = match arch { + zed::Architecture::Aarch64 => "aarch64", + zed::Architecture::X8664 => "x86_64", + zed::Architecture::X86 => + return Err(format!("unsupported architecture: {arch:?}")), + }, + ) + }; + + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; + + let version_dir = format!("elp-{}", release.version); + let binary_path = format!("{version_dir}/elp"); + + if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + zed::set_language_server_installation_status( + &language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + zed::download_file( + &asset.download_url, + &version_dir, + zed::DownloadedFileType::GzipTar, + ) + .map_err(|e| format!("failed to download file: {e}"))?; + + let entries = + fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; + for entry in entries { + let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; + if entry.file_name().to_str() != Some(&version_dir) { + fs::remove_dir_all(&entry.path()).ok(); + } + } + } + + self.cached_binary_path = Some(binary_path.clone()); + Ok(binary_path) } }