diff options
Diffstat (limited to 'editors/code/src/installation')
-rw-r--r-- | editors/code/src/installation/download_artifact.ts | 58 | ||||
-rw-r--r-- | editors/code/src/installation/download_file.ts | 43 | ||||
-rw-r--r-- | editors/code/src/installation/fetch_artifact_release_info.ts (renamed from editors/code/src/installation/fetch_latest_artifact_metadata.ts) | 20 | ||||
-rw-r--r-- | editors/code/src/installation/interfaces.ts | 15 | ||||
-rw-r--r-- | editors/code/src/installation/language_server.ts | 141 | ||||
-rw-r--r-- | editors/code/src/installation/server.ts | 124 |
6 files changed, 239 insertions, 162 deletions
diff --git a/editors/code/src/installation/download_artifact.ts b/editors/code/src/installation/download_artifact.ts new file mode 100644 index 000000000..de655f8f4 --- /dev/null +++ b/editors/code/src/installation/download_artifact.ts | |||
@@ -0,0 +1,58 @@ | |||
1 | import * as vscode from "vscode"; | ||
2 | import * as path from "path"; | ||
3 | import { promises as fs } from "fs"; | ||
4 | import { strict as assert } from "assert"; | ||
5 | |||
6 | import { ArtifactReleaseInfo } from "./interfaces"; | ||
7 | import { downloadFile } from "./download_file"; | ||
8 | import { throttle } from "throttle-debounce"; | ||
9 | |||
10 | /** | ||
11 | * Downloads artifact from given `downloadUrl`. | ||
12 | * Creates `installationDir` if it is not yet created and put the artifact under | ||
13 | * `artifactFileName`. | ||
14 | * Displays info about the download progress in an info message printing the name | ||
15 | * of the artifact as `displayName`. | ||
16 | */ | ||
17 | export async function downloadArtifact( | ||
18 | {downloadUrl, releaseName}: ArtifactReleaseInfo, | ||
19 | artifactFileName: string, | ||
20 | installationDir: string, | ||
21 | displayName: string, | ||
22 | ) { | ||
23 | await fs.mkdir(installationDir).catch(err => assert.strictEqual( | ||
24 | err?.code, | ||
25 | "EEXIST", | ||
26 | `Couldn't create directory "${installationDir}" to download `+ | ||
27 | `${artifactFileName} artifact: ${err.message}` | ||
28 | )); | ||
29 | |||
30 | const installationPath = path.join(installationDir, artifactFileName); | ||
31 | |||
32 | console.time(`Downloading ${artifactFileName}`); | ||
33 | await vscode.window.withProgress( | ||
34 | { | ||
35 | location: vscode.ProgressLocation.Notification, | ||
36 | cancellable: false, // FIXME: add support for canceling download? | ||
37 | title: `Downloading ${displayName} (${releaseName})` | ||
38 | }, | ||
39 | async (progress, _cancellationToken) => { | ||
40 | let lastPrecentage = 0; | ||
41 | const filePermissions = 0o755; // (rwx, r_x, r_x) | ||
42 | await downloadFile(downloadUrl, installationPath, filePermissions, throttle( | ||
43 | 200, | ||
44 | /* noTrailing: */ true, | ||
45 | (readBytes, totalBytes) => { | ||
46 | const newPercentage = (readBytes / totalBytes) * 100; | ||
47 | progress.report({ | ||
48 | message: newPercentage.toFixed(0) + "%", | ||
49 | increment: newPercentage - lastPrecentage | ||
50 | }); | ||
51 | |||
52 | lastPrecentage = newPercentage; | ||
53 | }) | ||
54 | ); | ||
55 | } | ||
56 | ); | ||
57 | console.timeEnd(`Downloading ${artifactFileName}`); | ||
58 | } | ||
diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts index b51602ef9..d154f4816 100644 --- a/editors/code/src/installation/download_file.ts +++ b/editors/code/src/installation/download_file.ts | |||
@@ -1,9 +1,13 @@ | |||
1 | import fetch from "node-fetch"; | 1 | import fetch from "node-fetch"; |
2 | import * as fs from "fs"; | 2 | import * as fs from "fs"; |
3 | import * as stream from "stream"; | ||
4 | import * as util from "util"; | ||
3 | import { strict as assert } from "assert"; | 5 | import { strict as assert } from "assert"; |
4 | 6 | ||
7 | const pipeline = util.promisify(stream.pipeline); | ||
8 | |||
5 | /** | 9 | /** |
6 | * Downloads file from `url` and stores it at `destFilePath`. | 10 | * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. |
7 | * `onProgress` callback is called on recieveing each chunk of bytes | 11 | * `onProgress` callback is called on recieveing each chunk of bytes |
8 | * to track the progress of downloading, it gets the already read and total | 12 | * to track the progress of downloading, it gets the already read and total |
9 | * amount of bytes to read as its parameters. | 13 | * amount of bytes to read as its parameters. |
@@ -11,24 +15,37 @@ import { strict as assert } from "assert"; | |||
11 | export async function downloadFile( | 15 | export async function downloadFile( |
12 | url: string, | 16 | url: string, |
13 | destFilePath: fs.PathLike, | 17 | destFilePath: fs.PathLike, |
18 | destFilePermissions: number, | ||
14 | onProgress: (readBytes: number, totalBytes: number) => void | 19 | onProgress: (readBytes: number, totalBytes: number) => void |
15 | ): Promise<void> { | 20 | ): Promise<void> { |
16 | const response = await fetch(url); | 21 | const res = await fetch(url); |
22 | |||
23 | if (!res.ok) { | ||
24 | console.log("Error", res.status, "while downloading file from", url); | ||
25 | console.dir({ body: await res.text(), headers: res.headers }, { depth: 3 }); | ||
17 | 26 | ||
18 | const totalBytes = Number(response.headers.get('content-length')); | 27 | throw new Error(`Got response ${res.status} when trying to download a file.`); |
28 | } | ||
29 | |||
30 | const totalBytes = Number(res.headers.get('content-length')); | ||
19 | assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); | 31 | assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); |
20 | 32 | ||
33 | console.log("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); | ||
34 | |||
21 | let readBytes = 0; | 35 | let readBytes = 0; |
36 | res.body.on("data", (chunk: Buffer) => { | ||
37 | readBytes += chunk.length; | ||
38 | onProgress(readBytes, totalBytes); | ||
39 | }); | ||
22 | 40 | ||
23 | console.log("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); | 41 | const destFileStream = fs.createWriteStream(destFilePath, { mode: destFilePermissions }); |
42 | |||
43 | await pipeline(res.body, destFileStream); | ||
44 | return new Promise<void>(resolve => { | ||
45 | destFileStream.on("close", resolve); | ||
46 | destFileStream.destroy(); | ||
24 | 47 | ||
25 | return new Promise<void>((resolve, reject) => response.body | 48 | // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 |
26 | .on("data", (chunk: Buffer) => { | 49 | // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 |
27 | readBytes += chunk.length; | 50 | }); |
28 | onProgress(readBytes, totalBytes); | ||
29 | }) | ||
30 | .on("end", resolve) | ||
31 | .on("error", reject) | ||
32 | .pipe(fs.createWriteStream(destFilePath)) | ||
33 | ); | ||
34 | } | 51 | } |
diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_artifact_release_info.ts index 7e3700603..7d497057a 100644 --- a/editors/code/src/installation/fetch_latest_artifact_metadata.ts +++ b/editors/code/src/installation/fetch_artifact_release_info.ts | |||
@@ -1,26 +1,32 @@ | |||
1 | import fetch from "node-fetch"; | 1 | import fetch from "node-fetch"; |
2 | import { GithubRepo, ArtifactMetadata } from "./interfaces"; | 2 | import { GithubRepo, ArtifactReleaseInfo } from "./interfaces"; |
3 | 3 | ||
4 | const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; | 4 | const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; |
5 | 5 | ||
6 | |||
6 | /** | 7 | /** |
7 | * Fetches the latest release from GitHub `repo` and returns metadata about | 8 | * Fetches the release with `releaseTag` (or just latest release when not specified) |
8 | * `artifactFileName` shipped with this release or `null` if no such artifact was published. | 9 | * from GitHub `repo` and returns metadata about `artifactFileName` shipped with |
10 | * this release or `null` if no such artifact was published. | ||
9 | */ | 11 | */ |
10 | export async function fetchLatestArtifactMetadata( | 12 | export async function fetchArtifactReleaseInfo( |
11 | repo: GithubRepo, artifactFileName: string | 13 | repo: GithubRepo, artifactFileName: string, releaseTag?: string |
12 | ): Promise<null | ArtifactMetadata> { | 14 | ): Promise<null | ArtifactReleaseInfo> { |
13 | 15 | ||
14 | const repoOwner = encodeURIComponent(repo.owner); | 16 | const repoOwner = encodeURIComponent(repo.owner); |
15 | const repoName = encodeURIComponent(repo.name); | 17 | const repoName = encodeURIComponent(repo.name); |
16 | 18 | ||
17 | const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; | 19 | const apiEndpointPath = releaseTag |
20 | ? `/repos/${repoOwner}/${repoName}/releases/tags/${releaseTag}` | ||
21 | : `/repos/${repoOwner}/${repoName}/releases/latest`; | ||
22 | |||
18 | const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; | 23 | const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; |
19 | 24 | ||
20 | // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`) | 25 | // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`) |
21 | 26 | ||
22 | console.log("Issuing request for released artifacts metadata to", requestUrl); | 27 | console.log("Issuing request for released artifacts metadata to", requestUrl); |
23 | 28 | ||
29 | // FIXME: handle non-ok response | ||
24 | const response: GithubRelease = await fetch(requestUrl, { | 30 | const response: GithubRelease = await fetch(requestUrl, { |
25 | headers: { Accept: "application/vnd.github.v3+json" } | 31 | headers: { Accept: "application/vnd.github.v3+json" } |
26 | }) | 32 | }) |
diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts index 8039d0b90..e40839e4b 100644 --- a/editors/code/src/installation/interfaces.ts +++ b/editors/code/src/installation/interfaces.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | import * as vscode from "vscode"; | ||
2 | |||
1 | export interface GithubRepo { | 3 | export interface GithubRepo { |
2 | name: string; | 4 | name: string; |
3 | owner: string; | 5 | owner: string; |
@@ -6,7 +8,7 @@ export interface GithubRepo { | |||
6 | /** | 8 | /** |
7 | * Metadata about particular artifact retrieved from GitHub releases. | 9 | * Metadata about particular artifact retrieved from GitHub releases. |
8 | */ | 10 | */ |
9 | export interface ArtifactMetadata { | 11 | export interface ArtifactReleaseInfo { |
10 | releaseName: string; | 12 | releaseName: string; |
11 | downloadUrl: string; | 13 | downloadUrl: string; |
12 | } | 14 | } |
@@ -50,6 +52,17 @@ export namespace BinarySource { | |||
50 | * and in local `.dir`. | 52 | * and in local `.dir`. |
51 | */ | 53 | */ |
52 | file: string; | 54 | file: string; |
55 | |||
56 | /** | ||
57 | * Tag of github release that denotes a version required by this extension. | ||
58 | */ | ||
59 | version: string; | ||
60 | |||
61 | /** | ||
62 | * Object that provides `get()/update()` operations to store metadata | ||
63 | * about the actual binary, e.g. its actual version. | ||
64 | */ | ||
65 | storage: vscode.Memento; | ||
53 | } | 66 | } |
54 | 67 | ||
55 | } | 68 | } |
diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts deleted file mode 100644 index 1ce67b8b2..000000000 --- a/editors/code/src/installation/language_server.ts +++ /dev/null | |||
@@ -1,141 +0,0 @@ | |||
1 | import * as vscode from "vscode"; | ||
2 | import * as path from "path"; | ||
3 | import { strict as assert } from "assert"; | ||
4 | import { promises as fs } from "fs"; | ||
5 | import { promises as dns } from "dns"; | ||
6 | import { spawnSync } from "child_process"; | ||
7 | import { throttle } from "throttle-debounce"; | ||
8 | |||
9 | import { BinarySource } from "./interfaces"; | ||
10 | import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata"; | ||
11 | import { downloadFile } from "./download_file"; | ||
12 | |||
13 | export async function downloadLatestLanguageServer( | ||
14 | {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease | ||
15 | ) { | ||
16 | const { releaseName, downloadUrl } = (await fetchLatestArtifactMetadata( | ||
17 | repo, artifactFileName | ||
18 | ))!; | ||
19 | |||
20 | await fs.mkdir(installationDir).catch(err => assert.strictEqual( | ||
21 | err?.code, | ||
22 | "EEXIST", | ||
23 | `Couldn't create directory "${installationDir}" to download `+ | ||
24 | `language server binary: ${err.message}` | ||
25 | )); | ||
26 | |||
27 | const installationPath = path.join(installationDir, artifactFileName); | ||
28 | |||
29 | console.time("Downloading ra_lsp_server"); | ||
30 | await vscode.window.withProgress( | ||
31 | { | ||
32 | location: vscode.ProgressLocation.Notification, | ||
33 | cancellable: false, // FIXME: add support for canceling download? | ||
34 | title: `Downloading language server (${releaseName})` | ||
35 | }, | ||
36 | async (progress, _cancellationToken) => { | ||
37 | let lastPrecentage = 0; | ||
38 | await downloadFile(downloadUrl, installationPath, throttle( | ||
39 | 200, | ||
40 | /* noTrailing: */ true, | ||
41 | (readBytes, totalBytes) => { | ||
42 | const newPercentage = (readBytes / totalBytes) * 100; | ||
43 | progress.report({ | ||
44 | message: newPercentage.toFixed(0) + "%", | ||
45 | increment: newPercentage - lastPrecentage | ||
46 | }); | ||
47 | |||
48 | lastPrecentage = newPercentage; | ||
49 | }) | ||
50 | ); | ||
51 | } | ||
52 | ); | ||
53 | console.timeEnd("Downloading ra_lsp_server"); | ||
54 | |||
55 | await fs.chmod(installationPath, 0o755); // Set (rwx, r_x, r_x) permissions | ||
56 | } | ||
57 | export async function ensureLanguageServerBinary( | ||
58 | langServerSource: null | BinarySource | ||
59 | ): Promise<null | string> { | ||
60 | |||
61 | if (!langServerSource) { | ||
62 | vscode.window.showErrorMessage( | ||
63 | "Unfortunately we don't ship binaries for your platform yet. " + | ||
64 | "You need to manually clone rust-analyzer repository and " + | ||
65 | "run `cargo xtask install --server` to build the language server from sources. " + | ||
66 | "If you feel that your platform should be supported, please create an issue " + | ||
67 | "about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " + | ||
68 | "will consider it." | ||
69 | ); | ||
70 | return null; | ||
71 | } | ||
72 | |||
73 | switch (langServerSource.type) { | ||
74 | case BinarySource.Type.ExplicitPath: { | ||
75 | if (isBinaryAvailable(langServerSource.path)) { | ||
76 | return langServerSource.path; | ||
77 | } | ||
78 | |||
79 | vscode.window.showErrorMessage( | ||
80 | `Unable to run ${langServerSource.path} binary. ` + | ||
81 | `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + | ||
82 | "value to `null` or remove it from the settings to use it by default." | ||
83 | ); | ||
84 | return null; | ||
85 | } | ||
86 | case BinarySource.Type.GithubRelease: { | ||
87 | const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file); | ||
88 | |||
89 | if (isBinaryAvailable(prebuiltBinaryPath)) { | ||
90 | return prebuiltBinaryPath; | ||
91 | } | ||
92 | |||
93 | const userResponse = await vscode.window.showInformationMessage( | ||
94 | "Language server binary for rust-analyzer was not found. " + | ||
95 | "Do you want to download it now?", | ||
96 | "Download now", "Cancel" | ||
97 | ); | ||
98 | if (userResponse !== "Download now") return null; | ||
99 | |||
100 | try { | ||
101 | await downloadLatestLanguageServer(langServerSource); | ||
102 | } catch (err) { | ||
103 | await vscode.window.showErrorMessage( | ||
104 | `Failed to download language server from ${langServerSource.repo.name} ` + | ||
105 | `GitHub repository: ${err.message}` | ||
106 | ); | ||
107 | |||
108 | await dns.resolve('www.google.com').catch(err => { | ||
109 | console.error("DNS resolution failed, there might be an issue with Internet availability"); | ||
110 | console.error(err); | ||
111 | }); | ||
112 | |||
113 | return null; | ||
114 | } | ||
115 | |||
116 | if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, | ||
117 | `Downloaded language server binary is not functional.` + | ||
118 | `Downloaded from: ${JSON.stringify(langServerSource)}` | ||
119 | ); | ||
120 | |||
121 | |||
122 | vscode.window.showInformationMessage( | ||
123 | "Rust analyzer language server was successfully installed 🦀" | ||
124 | ); | ||
125 | |||
126 | return prebuiltBinaryPath; | ||
127 | } | ||
128 | } | ||
129 | |||
130 | function isBinaryAvailable(binaryPath: string) { | ||
131 | const res = spawnSync(binaryPath, ["--version"]); | ||
132 | |||
133 | // ACHTUNG! `res` type declaration is inherently wrong, see | ||
134 | // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 | ||
135 | |||
136 | console.log("Checked binary availablity via --version", res); | ||
137 | console.log(binaryPath, "--version output:", res.output?.map(String)); | ||
138 | |||
139 | return res.status === 0; | ||
140 | } | ||
141 | } | ||
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts new file mode 100644 index 000000000..80cb719e3 --- /dev/null +++ b/editors/code/src/installation/server.ts | |||
@@ -0,0 +1,124 @@ | |||
1 | import * as vscode from "vscode"; | ||
2 | import * as path from "path"; | ||
3 | import { strict as assert } from "assert"; | ||
4 | import { promises as dns } from "dns"; | ||
5 | import { spawnSync } from "child_process"; | ||
6 | |||
7 | import { BinarySource } from "./interfaces"; | ||
8 | import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; | ||
9 | import { downloadArtifact } from "./download_artifact"; | ||
10 | |||
11 | export async function ensureServerBinary(source: null | BinarySource): Promise<null | string> { | ||
12 | if (!source) { | ||
13 | vscode.window.showErrorMessage( | ||
14 | "Unfortunately we don't ship binaries for your platform yet. " + | ||
15 | "You need to manually clone rust-analyzer repository and " + | ||
16 | "run `cargo xtask install --server` to build the language server from sources. " + | ||
17 | "If you feel that your platform should be supported, please create an issue " + | ||
18 | "about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " + | ||
19 | "will consider it." | ||
20 | ); | ||
21 | return null; | ||
22 | } | ||
23 | |||
24 | switch (source.type) { | ||
25 | case BinarySource.Type.ExplicitPath: { | ||
26 | if (isBinaryAvailable(source.path)) { | ||
27 | return source.path; | ||
28 | } | ||
29 | |||
30 | vscode.window.showErrorMessage( | ||
31 | `Unable to run ${source.path} binary. ` + | ||
32 | `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + | ||
33 | "value to `null` or remove it from the settings to use it by default." | ||
34 | ); | ||
35 | return null; | ||
36 | } | ||
37 | case BinarySource.Type.GithubRelease: { | ||
38 | const prebuiltBinaryPath = path.join(source.dir, source.file); | ||
39 | |||
40 | const installedVersion: null | string = getServerVersion(source.storage); | ||
41 | const requiredVersion: string = source.version; | ||
42 | |||
43 | console.log("Installed version:", installedVersion, "required:", requiredVersion); | ||
44 | |||
45 | if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion == requiredVersion) { | ||
46 | // FIXME: check for new releases and notify the user to update if possible | ||
47 | return prebuiltBinaryPath; | ||
48 | } | ||
49 | |||
50 | const userResponse = await vscode.window.showInformationMessage( | ||
51 | `Language server version ${source.version} for rust-analyzer is not installed. ` + | ||
52 | "Do you want to download it now?", | ||
53 | "Download now", "Cancel" | ||
54 | ); | ||
55 | if (userResponse !== "Download now") return null; | ||
56 | |||
57 | if (!await downloadServer(source)) return null; | ||
58 | |||
59 | return prebuiltBinaryPath; | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | async function downloadServer(source: BinarySource.GithubRelease): Promise<boolean> { | ||
65 | try { | ||
66 | const releaseInfo = (await fetchArtifactReleaseInfo(source.repo, source.file, source.version))!; | ||
67 | |||
68 | await downloadArtifact(releaseInfo, source.file, source.dir, "language server"); | ||
69 | await setServerVersion(source.storage, releaseInfo.releaseName); | ||
70 | } catch (err) { | ||
71 | vscode.window.showErrorMessage( | ||
72 | `Failed to download language server from ${source.repo.name} ` + | ||
73 | `GitHub repository: ${err.message}` | ||
74 | ); | ||
75 | |||
76 | console.error(err); | ||
77 | |||
78 | dns.resolve('example.com').then( | ||
79 | addrs => console.log("DNS resolution for example.com was successful", addrs), | ||
80 | err => { | ||
81 | console.error( | ||
82 | "DNS resolution for example.com failed, " + | ||
83 | "there might be an issue with Internet availability" | ||
84 | ); | ||
85 | console.error(err); | ||
86 | } | ||
87 | ); | ||
88 | return false; | ||
89 | } | ||
90 | |||
91 | if (!isBinaryAvailable(path.join(source.dir, source.file))) assert(false, | ||
92 | `Downloaded language server binary is not functional.` + | ||
93 | `Downloaded from: ${JSON.stringify(source, null, 4)}` | ||
94 | ); | ||
95 | |||
96 | vscode.window.showInformationMessage( | ||
97 | "Rust analyzer language server was successfully installed 🦀" | ||
98 | ); | ||
99 | |||
100 | return true; | ||
101 | } | ||
102 | |||
103 | function isBinaryAvailable(binaryPath: string): boolean { | ||
104 | const res = spawnSync(binaryPath, ["--version"]); | ||
105 | |||
106 | // ACHTUNG! `res` type declaration is inherently wrong, see | ||
107 | // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 | ||
108 | |||
109 | console.log("Checked binary availablity via --version", res); | ||
110 | console.log(binaryPath, "--version output:", res.output?.map(String)); | ||
111 | |||
112 | return res.status === 0; | ||
113 | } | ||
114 | |||
115 | function getServerVersion(storage: vscode.Memento): null | string { | ||
116 | const version = storage.get<null | string>("server-version", null); | ||
117 | console.log("Get server-version:", version); | ||
118 | return version; | ||
119 | } | ||
120 | |||
121 | async function setServerVersion(storage: vscode.Memento, version: string): Promise<void> { | ||
122 | console.log("Set server-version:", version); | ||
123 | await storage.update("server-version", version.toString()); | ||
124 | } | ||