aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/installation/language_server.ts
blob: 3510f9178f83f74f6f9656c7dd386d3407dbef85 (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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import * as vscode from "vscode";
import * as path from "path";
import { strict as assert } from "assert";
import { promises as fs } from "fs";
import { promises as dns } from "dns";
import { spawnSync } from "child_process";
import { throttle } from "throttle-debounce";

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);

    console.time("Downloading ra_lsp_server");
    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, throttle(
                200,
                /* noTrailing: */ true,
                (readBytes, totalBytes) => {
                    const newPercentage = (readBytes / totalBytes) * 100;
                    progress.report({
                        message: newPercentage.toFixed(0) + "%",
                        increment: newPercentage - lastPrecentage
                    });

                    lastPrecentage = newPercentage;
                })
            );
        }
    );
    console.timeEnd("Downloading ra_lsp_server");

    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) {
                vscode.window.showErrorMessage(
                    `Failed to download language server from ${langServerSource.repo.name} ` +
                    `GitHub repository: ${err.message}`
                );

                dns.resolve('example.com').then(
                    addrs => console.log("DNS resolution for example.com was successful", addrs),
                    err => {
                        console.error(
                            "DNS resolution for example.com failed, " +
                            "there might be an issue with Internet availability"
                        );
                        console.error(err);
                    }
                );

                return null;
            }

            if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false,
                `Downloaded language server binary is not functional.` +
                `Downloaded from: ${JSON.stringify(langServerSource)}`
            );


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

            return prebuiltBinaryPath;
        }
    }

    function isBinaryAvailable(binaryPath: string) {
        const res = spawnSync(binaryPath, ["--version"]);

        // ACHTUNG! `res` type declaration is inherently wrong, see
        // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221

        console.log("Checked binary availablity via --version", res);
        console.log(binaryPath, "--version output:", res.output?.map(String));

        return res.status === 0;
    }
}