aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/installation/language_server.ts
blob: a169eae47b3e79ed908e28f57d1a2470cd241f8e (plain)
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 { 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 } from "./interfaces";
import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata";
import { downloadFile } from "./download_file";

export async function downloadLatestLanguageServer(
    {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease
) {
    const { releaseName, downloadUrl } = (await fetchLatestArtifactMetadata(
        repo, artifactFileName
    ))!;

    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, 0o755); // 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 BinarySource.Type.ExplicitPath: {
            if (isBinaryAvailable(langServerSource.path)) {
                return langServerSource.path;
            }

            vscode.window.showErrorMessage(
                `Unable to run ${langServerSource.path} binary. ` +
                `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` +
                "value to `null` or remove it from the settings to use it by default."
            );
            return null;
        }
        case BinarySource.Type.GithubRelease: {
            const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file);

            if (isBinaryAvailable(prebuiltBinaryPath)) {
                return prebuiltBinaryPath;
            }

            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(prebuiltBinaryPath),
                "Downloaded language server binary is not functional"
            );

            vscode.window.showInformationMessage(
                "Rust analyzer language server was successfully installed 🦀"
            );

            return prebuiltBinaryPath;
        }
    }

    function isBinaryAvailable(binaryPath: string) {
        return spawnSync(binaryPath, ["--version"]).status === 0;
    }
}