mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(tools): install rust-analyzer without rustup (#490)
* fix(tools): install rust-analyzer without rustup * fix(tools): reinstall a managed rust-analyzer with the installer that made it Once rustup appeared, Reinstall ran rustup while the Open VSX copy stayed in use.
This commit is contained in:
+39
-26
@@ -138,7 +138,7 @@ fn bin_dirs_in(root: &Path, platform: Platform) -> Vec<PathBuf> {
|
||||
}
|
||||
|
||||
fn managed_bin_dir(root: &Path, spec: &ToolSpec, platform: Platform) -> Option<PathBuf> {
|
||||
match spec.method {
|
||||
match spec.fallback.unwrap_or(spec.method) {
|
||||
Method::Npm { .. } => Some(root.join("node_modules").join(".bin")),
|
||||
Method::Go { .. } | Method::Gem { .. } | Method::DotnetTool { .. } | Method::Release { .. } => {
|
||||
Some(root.join(spec.name).join("bin"))
|
||||
@@ -520,16 +520,26 @@ impl Tools {
|
||||
parse_version(&out)
|
||||
}
|
||||
|
||||
/// The installer that owns this tool: a managed copy is refreshed and removed by the
|
||||
/// fallback that made it, even if the primary installer has appeared since.
|
||||
fn install_method(&self, spec: &ToolSpec) -> Method {
|
||||
let Some(fallback) = spec.fallback else { return spec.method };
|
||||
let managed = managed_bin_dir(&self.root, spec, self.platform)
|
||||
.and_then(|dir| self.exe_in(&dir, spec.commands[0]))
|
||||
.is_some();
|
||||
if managed || self.method_missing(spec.method).is_some() { fallback } else { spec.method }
|
||||
}
|
||||
|
||||
/// Requirement check for installing `spec` right now.
|
||||
fn unavailable_reason(&self, spec: &ToolSpec) -> Option<String> {
|
||||
self.installer_missing(spec)
|
||||
self.method_missing(self.install_method(spec))
|
||||
.or_else(|| spec.needs.iter().find(|(cmd, _)| self.which(cmd).is_none()).map(|(_, why)| why.to_string()))
|
||||
}
|
||||
|
||||
/// What the install method itself lacks (npm, a build for this platform, …).
|
||||
fn installer_missing(&self, spec: &ToolSpec) -> Option<String> {
|
||||
fn method_missing(&self, method: Method) -> Option<String> {
|
||||
let need = |ok: bool, msg: &str| (!ok).then(|| msg.to_string());
|
||||
match spec.method {
|
||||
match method {
|
||||
Method::Rustup { .. } => need(self.which("rustup").is_some(), "rustup is required (https://rustup.rs)"),
|
||||
Method::Npm { .. } => need(
|
||||
self.which_any(&["npm", "pnpm"]).is_some(),
|
||||
@@ -602,7 +612,7 @@ impl Tools {
|
||||
status,
|
||||
path: path.map(Into::into),
|
||||
version,
|
||||
install_method: Some(spec.method.label().to_string()),
|
||||
install_method: Some(self.install_method(spec).label().to_string()),
|
||||
can_install: unavailable_reason.is_none(),
|
||||
unavailable_reason,
|
||||
can_uninstall,
|
||||
@@ -643,7 +653,16 @@ impl Tools {
|
||||
|
||||
fn detect(&self, spec: &ToolSpec) -> (ToolStatus, Option<PathBuf>, Option<String>) {
|
||||
let main = spec.commands[0];
|
||||
match spec.method {
|
||||
let managed = managed_bin_dir(&self.root, spec, self.platform).and_then(|d| self.exe_in(&d, main));
|
||||
if let Some(path) = managed {
|
||||
let version = match spec.method {
|
||||
Method::Npm { packages } => self.npm_package_version(npm_name(packages[0])),
|
||||
_ => self.manifest(spec.name).and_then(|m| m.version),
|
||||
}
|
||||
.or_else(|| self.query_version(spec, &path));
|
||||
return (ToolStatus::Installed, Some(path), version);
|
||||
}
|
||||
match self.install_method(spec) {
|
||||
Method::Rustup { .. } => {
|
||||
let Some(path) = self.which(main) else { return (ToolStatus::Missing, None, None) };
|
||||
// The rustup proxy exists even without the component; it only answers when installed.
|
||||
@@ -678,24 +697,13 @@ impl Tools {
|
||||
_ => (ToolStatus::Missing, None, None),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let managed = managed_bin_dir(&self.root, spec, self.platform).and_then(|d| self.exe_in(&d, main));
|
||||
if let Some(path) = managed {
|
||||
let version = match spec.method {
|
||||
Method::Npm { packages } => self.npm_package_version(npm_name(packages[0])),
|
||||
_ => self.manifest(spec.name).and_then(|m| m.version),
|
||||
}
|
||||
.or_else(|| self.query_version(spec, &path));
|
||||
return (ToolStatus::Installed, Some(path), version);
|
||||
_ => match self.which(main) {
|
||||
Some(path) => {
|
||||
let version = self.query_version(spec, &path);
|
||||
(ToolStatus::OnPath, Some(path), version)
|
||||
}
|
||||
match self.which(main) {
|
||||
Some(path) => {
|
||||
let version = self.query_version(spec, &path);
|
||||
(ToolStatus::OnPath, Some(path), version)
|
||||
}
|
||||
None => (ToolStatus::Missing, None, None),
|
||||
}
|
||||
}
|
||||
None => (ToolStatus::Missing, None, None),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -769,8 +777,9 @@ impl Tools {
|
||||
let emit = &fan as Emit;
|
||||
let result = (|| {
|
||||
std::fs::create_dir_all(&self.root)?;
|
||||
emit(InstallEvent::Line { text: format!("Installing {} via {}…", spec.display, spec.method.label()) });
|
||||
match spec.method {
|
||||
let method = self.install_method(spec);
|
||||
emit(InstallEvent::Line { text: format!("Installing {} via {}…", spec.display, method.label()) });
|
||||
match method {
|
||||
Method::Rustup { component } => {
|
||||
let mut c = self.command(self.which("rustup").expect("checked"));
|
||||
c.args(["component", "add", component]);
|
||||
@@ -1072,6 +1081,10 @@ impl Tools {
|
||||
}
|
||||
ReleaseSource::OpenVsx { namespace, extension } => {
|
||||
let json = http.get_text(&sources::openvsx_latest_url(namespace, extension), cancel)?;
|
||||
let json = match sources::openvsx_platform_url(&json, assets, self.platform)? {
|
||||
Some(url) => http.get_text(&url, cancel)?,
|
||||
None => json,
|
||||
};
|
||||
sources::openvsx_pick(&json, assets, self.platform)
|
||||
}
|
||||
ReleaseSource::MavenCentral { group, artifact } => {
|
||||
@@ -1172,7 +1185,7 @@ impl Tools {
|
||||
let _guard = InstallGuard::acquire(&format!("uninstall-{name}"))?;
|
||||
let cancel = CancellationToken::new();
|
||||
let quiet = |_: InstallEvent| {};
|
||||
match spec.method {
|
||||
match self.install_method(spec) {
|
||||
Method::Rustup { component } => {
|
||||
let rustup = self.which("rustup").ok_or_else(|| IdeError::unavailable("rustup not found"))?;
|
||||
let mut c = self.command(rustup);
|
||||
|
||||
@@ -82,6 +82,8 @@ pub struct ToolSpec {
|
||||
/// is a plugin bundle loaded by another tool (copied to `bin/` under this name).
|
||||
pub commands: &'static [&'static str],
|
||||
pub method: Method,
|
||||
/// Used when the primary installer is unavailable.
|
||||
pub fallback: Option<Method>,
|
||||
/// Version query: a command (looked up next to the main binary first) and its args.
|
||||
pub version: Option<(&'static str, &'static [&'static str])>,
|
||||
pub homepage: &'static str,
|
||||
@@ -123,6 +125,18 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["rust-analyzer"],
|
||||
method: Method::Rustup { component: "rust-analyzer" },
|
||||
fallback: Some(Method::Release {
|
||||
source: ReleaseSource::OpenVsx { namespace: "rust-lang", extension: "rust-analyzer" },
|
||||
assets: &[
|
||||
rule(Mac, Arm64, "rust-lang.rust-analyzer-*@darwin-arm64.vsix"),
|
||||
rule(Mac, X64, "rust-lang.rust-analyzer-*@darwin-x64.vsix"),
|
||||
rule(Linux, Arm64, "rust-lang.rust-analyzer-*@linux-arm64.vsix"),
|
||||
rule(Linux, X64, "rust-lang.rust-analyzer-*@linux-x64.vsix"),
|
||||
rule(Windows, Arm64, "rust-lang.rust-analyzer-*@win32-arm64.vsix"),
|
||||
rule(Windows, X64, "rust-lang.rust-analyzer-*@win32-x64.vsix"),
|
||||
],
|
||||
locate: &["rust-analyzer"],
|
||||
}),
|
||||
version: ver("rust-analyzer"),
|
||||
homepage: "https://rust-analyzer.github.io",
|
||||
needs: &[],
|
||||
@@ -133,6 +147,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["vtsls"],
|
||||
method: Method::Npm { packages: &["@vtsls/language-server"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/yioneko/vtsls",
|
||||
needs: &[],
|
||||
@@ -143,6 +158,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["typescript-language-server"],
|
||||
method: Method::Npm { packages: &["typescript-language-server", TYPESCRIPT] },
|
||||
fallback: None,
|
||||
version: ver("typescript-language-server"),
|
||||
homepage: "https://github.com/typescript-language-server/typescript-language-server",
|
||||
needs: &[],
|
||||
@@ -153,6 +169,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["pyright-langserver", "pyright"],
|
||||
method: Method::Npm { packages: &["pyright"] },
|
||||
fallback: None,
|
||||
version: ver("pyright"),
|
||||
homepage: "https://github.com/microsoft/pyright",
|
||||
needs: &[],
|
||||
@@ -163,6 +180,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["basedpyright-langserver", "basedpyright"],
|
||||
method: Method::PipVenv { package: "basedpyright" },
|
||||
fallback: None,
|
||||
version: ver("basedpyright"),
|
||||
homepage: "https://docs.basedpyright.com",
|
||||
needs: &[],
|
||||
@@ -173,6 +191,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["ty"],
|
||||
method: Method::PipVenv { package: "ty" },
|
||||
fallback: None,
|
||||
version: ver("ty"),
|
||||
homepage: "https://docs.astral.sh/ty/",
|
||||
needs: &[],
|
||||
@@ -183,6 +202,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["bash-language-server"],
|
||||
method: Method::Npm { packages: &["bash-language-server"] },
|
||||
fallback: None,
|
||||
version: ver("bash-language-server"),
|
||||
homepage: "https://github.com/bash-lsp/bash-language-server",
|
||||
needs: &[],
|
||||
@@ -193,6 +213,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["yaml-language-server"],
|
||||
method: Method::Npm { packages: &["yaml-language-server"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/redhat-developer/yaml-language-server",
|
||||
needs: &[],
|
||||
@@ -203,6 +224,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["vscode-json-language-server"],
|
||||
method: Method::Npm { packages: &["vscode-langservers-extracted"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/hrsh7th/vscode-langservers-extracted",
|
||||
needs: &[],
|
||||
@@ -213,6 +235,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["vscode-css-language-server"],
|
||||
method: Method::Npm { packages: &["vscode-langservers-extracted"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/hrsh7th/vscode-langservers-extracted",
|
||||
needs: &[],
|
||||
@@ -223,6 +246,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["vscode-html-language-server"],
|
||||
method: Method::Npm { packages: &["vscode-langservers-extracted"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/hrsh7th/vscode-langservers-extracted",
|
||||
needs: &[],
|
||||
@@ -233,6 +257,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["docker-langserver"],
|
||||
method: Method::Npm { packages: &["dockerfile-language-server-nodejs"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/rcjsuen/dockerfile-language-server",
|
||||
needs: &[],
|
||||
@@ -243,6 +268,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["svelteserver"],
|
||||
method: Method::Npm { packages: &["svelte-language-server", TYPESCRIPT] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/sveltejs/language-tools",
|
||||
needs: &[],
|
||||
@@ -253,6 +279,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["vue-language-server"],
|
||||
method: Method::Npm { packages: &["@vue/language-server", TYPESCRIPT] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/vuejs/language-tools",
|
||||
needs: &[],
|
||||
@@ -264,6 +291,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
commands: &["ngserver"],
|
||||
// Brings the TypeScript and `@angular/language-service` it loads.
|
||||
method: Method::Npm { packages: &["@angular/language-server"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://angular.dev/tools/language-service",
|
||||
needs: &[],
|
||||
@@ -274,6 +302,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["graphql-lsp"],
|
||||
method: Method::Npm { packages: &["graphql-language-service-cli"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/graphql/graphiql",
|
||||
needs: &[],
|
||||
@@ -284,6 +313,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["intelephense"],
|
||||
method: Method::Npm { packages: &["intelephense"] },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://intelephense.com",
|
||||
needs: &[],
|
||||
@@ -294,6 +324,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["gopls"],
|
||||
method: Method::Go { module: "golang.org/x/tools/gopls@latest" },
|
||||
fallback: None,
|
||||
version: Some(("gopls", &["version"])),
|
||||
homepage: "https://pkg.go.dev/golang.org/x/tools/gopls",
|
||||
needs: &[],
|
||||
@@ -304,6 +335,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["sqls"],
|
||||
method: Method::Go { module: "github.com/sqls-server/sqls@latest" },
|
||||
fallback: None,
|
||||
version: Some(("sqls", &["--version"])),
|
||||
homepage: "https://github.com/sqls-server/sqls",
|
||||
needs: &[],
|
||||
@@ -314,6 +346,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: Debugger,
|
||||
commands: &["dlv"],
|
||||
method: Method::Go { module: "github.com/go-delve/delve/cmd/dlv@latest" },
|
||||
fallback: None,
|
||||
version: Some(("dlv", &["version"])),
|
||||
homepage: "https://github.com/go-delve/delve",
|
||||
needs: &[],
|
||||
@@ -324,6 +357,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: Debugger,
|
||||
commands: &["python3"],
|
||||
method: Method::PipTarget { package: "debugpy", module: "debugpy" },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/microsoft/debugpy",
|
||||
needs: &[],
|
||||
@@ -342,6 +376,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["clangd"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("clangd"),
|
||||
homepage: "https://clangd.llvm.org",
|
||||
needs: &[],
|
||||
@@ -362,6 +397,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["lua-language-server"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("lua-language-server"),
|
||||
homepage: "https://luals.github.io",
|
||||
needs: &[],
|
||||
@@ -381,6 +417,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["marksman"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("marksman"),
|
||||
homepage: "https://github.com/artempyanykh/marksman",
|
||||
needs: &[],
|
||||
@@ -401,6 +438,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["taplo"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("taplo"),
|
||||
homepage: "https://taplo.tamasfe.dev",
|
||||
needs: &[],
|
||||
@@ -421,6 +459,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["zls"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("zls"),
|
||||
homepage: "https://github.com/zigtools/zls",
|
||||
needs: &[],
|
||||
@@ -441,6 +480,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["terraform-ls"],
|
||||
},
|
||||
fallback: None,
|
||||
version: Some(("terraform-ls", &["version"])),
|
||||
homepage: "https://github.com/hashicorp/terraform-ls",
|
||||
needs: &[],
|
||||
@@ -466,6 +506,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["codelldb"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/vadimcn/codelldb",
|
||||
needs: &[],
|
||||
@@ -485,6 +526,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["dapDebugServer.js"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/microsoft/vscode-js-debug",
|
||||
needs: &[],
|
||||
@@ -503,6 +545,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["jdtls", "jdtls.bat"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/eclipse-jdtls/eclipse.jdt.ls",
|
||||
needs: &[],
|
||||
@@ -522,6 +565,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["com.microsoft.java.debug.plugin-*.jar"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/microsoft/java-debug",
|
||||
needs: &[],
|
||||
@@ -541,6 +585,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["lombok.jar"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://projectlombok.org",
|
||||
needs: &[],
|
||||
@@ -565,6 +610,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["kotlin-lsp", "kotlin-lsp.sh", "kotlin-lsp.cmd"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/Kotlin/kotlin-lsp",
|
||||
needs: &[],
|
||||
@@ -581,6 +627,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
assets: ELIXIR_LS_ASSETS,
|
||||
locate: &["language_server.sh", "language_server.bat"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/elixir-lsp/elixir-ls",
|
||||
needs: ELIXIR,
|
||||
@@ -595,6 +642,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
assets: ELIXIR_LS_ASSETS,
|
||||
locate: &["debug_adapter.sh", "debug_adapter.bat"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/elixir-lsp/elixir-ls",
|
||||
needs: ELIXIR,
|
||||
@@ -616,6 +664,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["buf"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("buf"),
|
||||
homepage: "https://buf.build",
|
||||
needs: &[],
|
||||
@@ -636,6 +685,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["clojure-lsp"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://clojure-lsp.io",
|
||||
needs: &[],
|
||||
@@ -657,6 +707,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["texlab"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("texlab"),
|
||||
homepage: "https://github.com/latex-lsp/texlab",
|
||||
needs: &[],
|
||||
@@ -677,6 +728,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["neocmakelsp"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("neocmakelsp"),
|
||||
homepage: "https://github.com/Decodetalkers/neocmakelsp",
|
||||
needs: &[],
|
||||
@@ -697,6 +749,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["lemminx-*"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/eclipse/lemminx",
|
||||
needs: &[],
|
||||
@@ -715,6 +768,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["perlnavigator"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/bscan/PerlNavigator",
|
||||
needs: &[("perl", "Perl is required (https://www.perl.org/get.html)")],
|
||||
@@ -729,6 +783,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
assets: &[any_arch(Mac, "server.zip"), any_arch(Linux, "server.zip"), any_arch(Windows, "server.zip")],
|
||||
locate: &["kotlin-language-server"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/fwcd/kotlin-language-server",
|
||||
needs: JAVA,
|
||||
@@ -743,6 +798,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
assets: &[any_arch(Mac, "adapter.zip"), any_arch(Linux, "adapter.zip"), any_arch(Windows, "adapter.zip")],
|
||||
locate: &["kotlin-debug-adapter"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/fwcd/kotlin-debug-adapter",
|
||||
needs: JAVA,
|
||||
@@ -762,6 +818,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["netcoredbg"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/Samsung/netcoredbg",
|
||||
needs: &[],
|
||||
@@ -780,6 +837,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["bashDebug.js"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/rogalmic/vscode-bash-debug",
|
||||
needs: NODE,
|
||||
@@ -798,6 +856,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
],
|
||||
locate: &["phpDebug.js"],
|
||||
},
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/xdebug/vscode-php-debug",
|
||||
needs: NODE,
|
||||
@@ -808,6 +867,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["ruby-lsp"],
|
||||
method: Method::Gem { gem: "ruby-lsp" },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://shopify.github.io/ruby-lsp",
|
||||
needs: &[],
|
||||
@@ -818,6 +878,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["solargraph"],
|
||||
method: Method::Gem { gem: "solargraph" },
|
||||
fallback: None,
|
||||
version: ver("solargraph"),
|
||||
homepage: "https://solargraph.org",
|
||||
needs: &[],
|
||||
@@ -828,6 +889,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["csharp-ls"],
|
||||
method: Method::DotnetTool { package: "csharp-ls" },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/razzmatazz/csharp-language-server",
|
||||
needs: &[],
|
||||
@@ -838,6 +900,7 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
kind: LanguageServer,
|
||||
commands: &["fsautocomplete"],
|
||||
method: Method::DotnetTool { package: "fsautocomplete" },
|
||||
fallback: None,
|
||||
version: V,
|
||||
homepage: "https://github.com/ionide/FsAutoComplete",
|
||||
needs: &[],
|
||||
|
||||
@@ -177,6 +177,8 @@ pub fn jdtls_download(version: &str, latest_txt: &str) -> IdeResult<Download> {
|
||||
#[derive(Deserialize)]
|
||||
struct VsxExtension {
|
||||
version: String,
|
||||
#[serde(default)]
|
||||
downloads: std::collections::BTreeMap<String, String>,
|
||||
files: VsxFiles,
|
||||
}
|
||||
|
||||
@@ -191,7 +193,22 @@ pub fn openvsx_latest_url(namespace: &str, extension: &str) -> String {
|
||||
format!("https://open-vsx.org/api/{namespace}/{extension}/latest")
|
||||
}
|
||||
|
||||
/// The `.vsix` of an Open VSX `…/latest` response (platform-independent extensions only).
|
||||
/// Select platform metadata when the universal package does not match the asset rules.
|
||||
pub fn openvsx_platform_url(json: &str, rules: &[AssetRule], platform: Platform) -> IdeResult<Option<String>> {
|
||||
let ext: VsxExtension =
|
||||
serde_json::from_str(json).map_err(|e| proto(format!("unexpected Open VSX response: {e}")))?;
|
||||
if select_asset(&[file_name(&ext.files.download)], rules, platform).is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
let names: Vec<&str> = ext.downloads.values().map(|url| file_name(url)).collect();
|
||||
let name = select_asset(&names, rules, platform)
|
||||
.ok_or_else(|| IdeError::unavailable("No Open VSX package for this platform"))?;
|
||||
let url = ext.downloads.values().find(|url| file_name(url) == name).expect("selected download");
|
||||
let (metadata, _) = url.rsplit_once("/file/").ok_or_else(|| proto("unexpected Open VSX download URL"))?;
|
||||
Ok(Some(metadata.to_string()))
|
||||
}
|
||||
|
||||
/// The `.vsix` of an Open VSX `…/latest` response.
|
||||
pub fn openvsx_pick(json: &str, rules: &[AssetRule], platform: Platform) -> IdeResult<Download> {
|
||||
let ext: VsxExtension =
|
||||
serde_json::from_str(json).map_err(|e| proto(format!("unexpected Open VSX response: {e}")))?;
|
||||
@@ -355,6 +372,56 @@ mod tests {
|
||||
assert!(openvsx_pick(&other, rules, LINUX).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openvsx_rust_analyzer_platform_packages() {
|
||||
use crate::platform::{Arch, Os};
|
||||
let crate::registry::Method::Release { assets, .. } = find("rust-analyzer").unwrap().fallback.unwrap() else {
|
||||
panic!("expected release fallback")
|
||||
};
|
||||
let targets = [
|
||||
(Os::Mac, Arch::Arm64, "darwin-arm64"),
|
||||
(Os::Mac, Arch::X64, "darwin-x64"),
|
||||
(Os::Linux, Arch::Arm64, "linux-arm64"),
|
||||
(Os::Linux, Arch::X64, "linux-x64"),
|
||||
(Os::Windows, Arch::Arm64, "win32-arm64"),
|
||||
(Os::Windows, Arch::X64, "win32-x64"),
|
||||
];
|
||||
let base = "https://open-vsx.org/api/rust-lang/rust-analyzer";
|
||||
let downloads: serde_json::Map<String, serde_json::Value> = targets
|
||||
.iter()
|
||||
.map(|(_, _, target)| {
|
||||
(
|
||||
target.to_string(),
|
||||
format!("{base}/{target}/1.0.0/file/rust-lang.rust-analyzer-1.0.0@{target}.vsix").into(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let universal = serde_json::json!({
|
||||
"version": "1.0.0", "downloads": downloads,
|
||||
"files": {"download": format!("{base}/1.0.0/file/rust-lang.rust-analyzer-1.0.0.vsix")}
|
||||
})
|
||||
.to_string();
|
||||
for (os, arch, target) in targets {
|
||||
let platform = Platform { os, arch };
|
||||
assert_eq!(
|
||||
openvsx_platform_url(&universal, assets, platform).unwrap(),
|
||||
Some(format!("{base}/{target}/1.0.0"))
|
||||
);
|
||||
let json = serde_json::json!({
|
||||
"version": "1.0.0",
|
||||
"files": {
|
||||
"download": downloads[target],
|
||||
"sha256": format!("{base}/{target}/1.0.0/file/rust-lang.rust-analyzer-1.0.0@{target}.sha256")
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
let download = openvsx_pick(&json, assets, platform).unwrap();
|
||||
assert!(download.name.ends_with(&format!("@{target}.vsix")));
|
||||
assert!(download.checksum_url.unwrap().ends_with(&format!("@{target}.sha256")));
|
||||
}
|
||||
assert!(openvsx_platform_url(&universal.replace("downloads", "unused"), assets, MAC_ARM).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maven_central() {
|
||||
let xml =
|
||||
|
||||
@@ -65,8 +65,8 @@ fn everything_missing_on_an_empty_system() {
|
||||
let ra = find(&list, "rust-analyzer");
|
||||
assert_eq!(ra.status, ToolStatus::Missing);
|
||||
assert_eq!(ra.languages, vec!["Rust".to_string()]);
|
||||
assert!(!ra.can_install, "rustup is not on the fake PATH");
|
||||
assert!(ra.unavailable_reason.as_deref().unwrap().contains("rustup"));
|
||||
assert!(!ra.can_install, "curl is not on the fake PATH");
|
||||
assert!(ra.unavailable_reason.as_deref().unwrap().contains("curl"));
|
||||
let vtsls = find(&list, "vtsls");
|
||||
assert!(vtsls.languages.iter().any(|l| l == "TypeScript"));
|
||||
assert!(vtsls.unavailable_reason.as_deref().unwrap().contains("npm"));
|
||||
@@ -552,3 +552,74 @@ fn the_installing_request_can_still_cancel_its_shared_install() {
|
||||
assert!(slot.cancel.is_cancelled());
|
||||
release_install_slot("taplo", &slot);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn rust_analyzer_selects_openvsx_without_rustup() {
|
||||
let env = Env::new();
|
||||
env.script(&env.sys, "curl", "exit 0");
|
||||
let tools = env.tools();
|
||||
let spec = registry::find("rust-analyzer").unwrap();
|
||||
assert!(matches!(
|
||||
tools.install_method(spec),
|
||||
Method::Release { source: ReleaseSource::OpenVsx { namespace: "rust-lang", extension: "rust-analyzer" }, .. }
|
||||
));
|
||||
let info = tools.info("rust-analyzer", &langs()).unwrap();
|
||||
assert_eq!(info.status, ToolStatus::Missing);
|
||||
assert_eq!(info.install_method.as_deref(), Some("Open VSX"));
|
||||
assert!(info.can_install);
|
||||
assert!(info.unavailable_reason.is_none());
|
||||
assert!(bin_dirs_in(&env.root, Platform::current()).contains(&env.root.join("rust-analyzer/bin")));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
static RUST_ANALYZER_INSTALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn rust_analyzer_prefers_rustup_and_installs_component() {
|
||||
let _guard = RUST_ANALYZER_INSTALL.lock();
|
||||
let env = Env::new();
|
||||
env.script(&env.sys, "rustup", "test \"$*\" = 'component add rust-analyzer'");
|
||||
env.script(&env.sys, "rust-analyzer", "echo 'rust-analyzer 1.0.0'");
|
||||
let tools = env.tools();
|
||||
assert!(matches!(
|
||||
tools.install_method(registry::find("rust-analyzer").unwrap()),
|
||||
Method::Rustup { component: "rust-analyzer" }
|
||||
));
|
||||
let info = tools.info("rust-analyzer", &langs()).unwrap();
|
||||
assert_eq!(info.install_method.as_deref(), Some("rustup"));
|
||||
assert!(info.can_install, "rustup does not require curl");
|
||||
let mut lines = Vec::new();
|
||||
let emit = parking_lot::Mutex::new(&mut lines);
|
||||
tools
|
||||
.install("rust-analyzer", &langs(), &CancellationToken::new(), &|event| {
|
||||
if let InstallEvent::Line { text } = event {
|
||||
emit.lock().push(text);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
assert!(lines.iter().any(|line| line.contains("via rustup")));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn rust_analyzer_managed_copy_survives_rustup_becoming_available() {
|
||||
let _guard = RUST_ANALYZER_INSTALL.lock();
|
||||
let env = Env::new();
|
||||
let bin = env.root.join("rust-analyzer/bin");
|
||||
env.script(&bin, "rust-analyzer", "echo 'rust-analyzer 1.0.0'");
|
||||
for with_rustup in [false, true] {
|
||||
if with_rustup {
|
||||
env.script(&env.sys, "rustup", "exit 1");
|
||||
env.script(&env.sys, "rust-analyzer", "exit 1");
|
||||
}
|
||||
let info = env.tools().info("rust-analyzer", &langs()).unwrap();
|
||||
assert_eq!(info.status, ToolStatus::Installed);
|
||||
assert_eq!(info.path.as_deref(), Some(bin.join("rust-analyzer").as_path()));
|
||||
assert_eq!(info.install_method.as_deref(), Some("Open VSX"), "Reinstall refreshes the copy in use");
|
||||
assert!(info.can_uninstall);
|
||||
}
|
||||
env.tools().uninstall("rust-analyzer", &langs()).unwrap();
|
||||
assert!(!env.root.join("rust-analyzer").exists());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user