aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/installation/language_server.ts
blob: 2b3ce6621bb0655f1785a2abde78de2bfa19fd26 (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 { 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({ artifactFileName, repo });

    const {
        releaseName,
        downloadUrl
    } = unwrapNotNil(binaryMetadata, `Latest GitHub release lacks "${artifactFileName}" file`);

    await fs.mkdir(installationDir).catch(err => assert.strictEqual(
        err && 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, _) => {
            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, 111); // Set xxx 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;
    }
}