1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
import { unwrapNotNil } from "ts-not-nil";
import { spawnSync } from "child_process";
import * as vscode from "vscode";
import * as path from "path";
import { strict as assert } from "assert";
import { promises as fs } from "fs";
import { BinarySource, BinarySourceType, GithubBinarySource } from "./interfaces";
import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata";
import { downloadFile } from "./download_file";
export async function downloadLatestLanguageServer(
{file: artifactFileName, dir: installationDir, repo}: GithubBinarySource
) {
const binaryMetadata = await fetchLatestArtifactMetadata(repo, artifactFileName);
const {
releaseName,
downloadUrl
} = unwrapNotNil(binaryMetadata, `Latest GitHub release lacks "${artifactFileName}" file`);
await fs.mkdir(installationDir).catch(err => assert.strictEqual(
err?.code,
"EEXIST",
`Couldn't create directory "${installationDir}" to download `+
`language server binary: ${err.message}`
));
const installationPath = path.join(installationDir, artifactFileName);
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
cancellable: false, // FIXME: add support for canceling download?
title: `Downloading language server ${releaseName}`
},
async (progress, _cancellationToken) => {
let lastPrecentage = 0;
await downloadFile(downloadUrl, installationPath, (readBytes, totalBytes) => {
const newPercentage = (readBytes / totalBytes) * 100;
progress.report({
message: newPercentage.toFixed(0) + "%",
increment: newPercentage - lastPrecentage
});
lastPrecentage = newPercentage;
});
}
);
await fs.chmod(installationPath, 755); // Set (rwx, r_x, r_x) permissions
}
export async function ensureLanguageServerBinary(
langServerSource: null | BinarySource
): Promise<null | string> {
if (!langServerSource) {
vscode.window.showErrorMessage(
"Unfortunately we don't ship binaries for your platform yet. " +
"You need to manually clone rust-analyzer repository and " +
"run `cargo xtask install --server` to build the language server from sources. " +
"If you feel that your platform should be supported, please create an issue " +
"about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " +
"will consider it."
);
return null;
}
switch (langServerSource.type) {
case BinarySourceType.ExplicitPath: {
if (isBinaryAvailable(langServerSource.path)) {
return langServerSource.path;
}
vscode.window.showErrorMessage(
`Unable to execute ${'`'}${langServerSource.path} --version${'`'}. ` +
"To use the bundled language server, set `rust-analyzer.raLspServerPath` " +
"value to `null` or remove it from the settings to use it by default."
);
return null;
}
case BinarySourceType.GithubBinary: {
const bundledBinaryPath = path.join(langServerSource.dir, langServerSource.file);
if (!isBinaryAvailable(bundledBinaryPath)) {
const userResponse = await vscode.window.showInformationMessage(
`Language server binary for rust-analyzer was not found. ` +
`Do you want to download it now?`,
"Download now", "Cancel"
);
if (userResponse !== "Download now") return null;
try {
await downloadLatestLanguageServer(langServerSource);
} catch (err) {
await vscode.window.showErrorMessage(
`Failed to download language server from ${langServerSource.repo.name} ` +
`GitHub repository: ${err.message}`
);
return null;
}
assert(
isBinaryAvailable(bundledBinaryPath),
"Downloaded language server binary is not functional"
);
vscode.window.showInformationMessage(
"Rust analyzer language server was successfully installed"
);
}
return bundledBinaryPath;
}
}
function isBinaryAvailable(binaryPath: string) {
return spawnSync(binaryPath, ["--version"]).status === 0;
}
}
|