diff --git a/Cargo.lock b/Cargo.lock index 13f4164..274d581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "anyhow" version = "1.0.79" @@ -20,6 +26,31 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -122,6 +153,15 @@ dependencies = [ "url", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -174,6 +214,7 @@ name = "rs4lapce" version = "0.1.0" dependencies = [ "anyhow", + "flate2", "lapce-plugin", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index a25eb8b..14b7140 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,13 @@ repository = "https://github.com/TeamHarTex/rs4lapce" [dependencies] anyhow = "1.0.79" +flate2 = "1.0.28" lapce-plugin = "0.1.2" serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +strip = true diff --git a/src/config.rs b/src/config.rs index aa3d04f..e007004 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,14 +1,22 @@ use serde::{Deserialize, Serialize}; +use serde_json::Value; #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct PluginConfiguration { + #[serde(default)] #[serde(rename = "rustAnalyzer")] - pub rust_analyzer: Option, + pub rust_analyzer: Option, + #[serde(default)] + #[serde(rename = "rustAnalyzerBuilds")] + pub rust_analyzer_builds: Option, } -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct RustAnalyzerConfiguration { +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct RustAnalyzerBuildsConfiguration { #[serde(default)] #[serde(rename = "nightly")] pub nightly: bool, } + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct RustAnalyzerConfiguration; diff --git a/src/init.rs b/src/init.rs index 6b713d2..7bc45ac 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,7 +1,11 @@ use crate::config::PluginConfiguration; -use anyhow::Result; -use lapce_plugin::psp_types::lsp_types::{InitializeParams, MessageType}; -use lapce_plugin::PLUGIN_RPC; +use anyhow::{Error, Result}; +use flate2::read::GzDecoder; +use lapce_plugin::psp_types::lsp_types::{DocumentFilter, InitializeParams, MessageType, Url}; +use lapce_plugin::{Http, VoltEnvironment, PLUGIN_RPC}; +use std::fs::File; +use std::path::PathBuf; +use std::{fs, io}; pub(crate) fn initialize_plugin(params: InitializeParams) -> Result<()> { let config = params @@ -9,21 +13,109 @@ pub(crate) fn initialize_plugin(params: InitializeParams) -> Result<()> { .map(|value| serde_json::from_value::(value)) .transpose()?; - let nightly = if let Some(config) = config - && let Some(rust_analyzer) = config.rust_analyzer + let nightly = if let Some(config) = &config + && let Some(rust_analyzer_builds) = &config.rust_analyzer_builds { - rust_analyzer.nightly + rust_analyzer_builds.nightly } else { false }; PLUGIN_RPC.window_show_message( - MessageType::INFO, + MessageType::LOG, format!( "using {} rust-analyzer", if nightly { "nightly" } else { "stable" } ), ); + let architecture = match VoltEnvironment::architecture()?.as_str() { + "aarch64" => "aarch64", + "x86_64" => "x86_64", + unsupported => { + PLUGIN_RPC.window_show_message( + MessageType::ERROR, + format!("unsupported system architecture: {unsupported}"), + ); + + return Err(Error::msg(format!( + "unsupported system architecture: {unsupported}" + ))); + } + }; + + let (target_triple, executable_name) = match VoltEnvironment::operating_system()?.as_str() { + "windows" => ("pc-windows-msvc", "rust-analyzer.exe"), + "macos" => ("apple-darwin", "rust-analyzer"), + // FIXME: unknown-linux-musl exists + "linux" => ("unknown-linux-gnu", "rust-analyzer"), + unsupported => { + PLUGIN_RPC.window_show_message( + MessageType::ERROR, + format!("unsupported operating system: {unsupported}"), + ); + + return Err(Error::msg(format!( + "unsupported operating system: {unsupported}" + ))); + } + }; + + let file_path = PathBuf::from(executable_name); + if !file_path.exists() { + PLUGIN_RPC.window_show_message(MessageType::LOG, String::from("downloading rust-analyzer")); + + if let Err(error) = download_rust_analyzer(nightly, &file_path, architecture, target_triple) + { + PLUGIN_RPC.window_show_message( + MessageType::ERROR, + format!("failed to download rust-analyzer: {error}"), + ); + + return Ok(()); + } + } + + let pwd = VoltEnvironment::uri()?; + let server_path = Url::parse(&pwd)?.join(&file_path.as_os_str().to_string_lossy())?; + + PLUGIN_RPC.start_lsp( + server_path, + Vec::new(), + vec![DocumentFilter { + language: Some(String::from("rust")), + scheme: None, + pattern: None, + }], + config.map(|config| config.rust_analyzer).unwrap_or(None), + ); + + Ok(()) +} + +fn download_rust_analyzer( + nightly: bool, + target: &PathBuf, + architecture: &str, + target_triple: &str, +) -> Result<()> { + let gz_path = PathBuf::from("rust-analyzer.gz"); + + let url = if nightly { + format!("https://github.com/rust-lang/rust-analyzer/releases/download/nightly/rust-analyzer-{architecture}-{target_triple}.gz") + } else { + format!("https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-{architecture}-{target_triple}.gz") + }; + + let mut response = Http::get(&url)?; + let body = response.body_read_all()?; + fs::write(&gz_path, body)?; + + let mut gz = GzDecoder::new(File::open(&gz_path)?); + let mut file = File::create(target)?; + io::copy(&mut gz, &mut file)?; + + fs::remove_file(&gz_path)?; + Ok(()) } diff --git a/volt.toml b/volt.toml index dd5d56f..ba8498f 100644 --- a/volt.toml +++ b/volt.toml @@ -11,6 +11,6 @@ icon = "rust-logo.svg" language = ["rust"] workspace-contains = ["*/Cargo.toml"] -[config."rustAnalyzer.nightly"] +[config."rustAnalyzerBuilds.nightly"] default = false description = "Whether to fetch daily nightly builds of rust-analyzer for usage in the editor."