From 1bdb78a89f6618527f2dc23f8f76e83ee77e3ea5 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 7 Feb 2020 00:09:13 +0200 Subject: vscode: add FIXME about language client shared resource protection --- editors/code/src/ctx.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index aa75943bf..a4dcc3037 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -11,6 +11,9 @@ export class Ctx { // deal with it. // // Ideally, this should be replaced with async getter though. + // FIXME: this actually needs syncronization of some kind (check how + // vscode deals with `deactivate()` call when extension has some work scheduled + // on the event loop to get a better picture of what we can do here) client: lc.LanguageClient | null = null; private extCtx: vscode.ExtensionContext; private onDidRestartHooks: Array<(client: lc.LanguageClient) => void> = []; -- cgit v1.2.3 From 3e0e4e90aeeff25db674f8db562c611bd8016482 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 7 Feb 2020 03:11:24 +0200 Subject: added fetchLatestArtifactMetadata() and downloadFile() functions --- editors/code/package-lock.json | 25 ++++++++++ editors/code/package.json | 6 ++- editors/code/src/github/download_file.ts | 26 ++++++++++ .../src/github/fetch_latest_artifact_metadata.ts | 55 ++++++++++++++++++++++ 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 editors/code/src/github/download_file.ts create mode 100644 editors/code/src/github/fetch_latest_artifact_metadata.ts diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 353af06bf..5c056463e 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -82,6 +82,15 @@ "integrity": "sha512-nf1LMGZvgFX186geVZR1xMZKKblJiRfiASTHw85zED2kI1yDKHDwTKMdkaCbTlXoRKlGKaDfYywt+V0As30q3w==", "dev": true }, + "@types/node-fetch": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.4.tgz", + "integrity": "sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -91,6 +100,12 @@ "@types/node": "*" } }, + "@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==", + "dev": true + }, "@types/vscode": { "version": "1.41.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.41.0.tgz", @@ -536,6 +551,11 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -719,6 +739,11 @@ "has-flag": "^3.0.0" } }, + "throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg==" + }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index 11d37053e..8e23718cd 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -25,18 +25,22 @@ }, "dependencies": { "jsonc-parser": "^2.1.0", + "node-fetch": "^2.6.0", + "throttle-debounce": "^2.1.0", "vscode-languageclient": "^6.1.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@types/node": "^12.12.25", + "@types/node-fetch": "^2.5.4", + "@types/throttle-debounce": "^2.1.0", "@types/vscode": "^1.41.0", "rollup": "^1.31.0", "tslib": "^1.10.0", "tslint": "^5.20.1", - "typescript-formatter": "^7.2.2", "typescript": "^3.7.5", + "typescript-formatter": "^7.2.2", "vsce": "^1.71.0" }, "activationEvents": [ diff --git a/editors/code/src/github/download_file.ts b/editors/code/src/github/download_file.ts new file mode 100644 index 000000000..f40750be9 --- /dev/null +++ b/editors/code/src/github/download_file.ts @@ -0,0 +1,26 @@ +import fetch from "node-fetch"; +import { throttle } from "throttle-debounce"; +import * as fs from "fs"; + +export async function downloadFile( + url: string, + destFilePath: fs.PathLike, + onProgress: (readBytes: number, totalBytes: number) => void +): Promise { + onProgress = throttle(100, /* noTrailing: */ true, onProgress); + + const response = await fetch(url); + + const totalBytes = Number(response.headers.get('content-length')); + let readBytes = 0; + + return new Promise((resolve, reject) => response.body + .on("data", (chunk: Buffer) => { + readBytes += chunk.length; + onProgress(readBytes, totalBytes); + }) + .on("end", resolve) + .on("error", reject) + .pipe(fs.createWriteStream(destFilePath)) + ); +} diff --git a/editors/code/src/github/fetch_latest_artifact_metadata.ts b/editors/code/src/github/fetch_latest_artifact_metadata.ts new file mode 100644 index 000000000..52641ca67 --- /dev/null +++ b/editors/code/src/github/fetch_latest_artifact_metadata.ts @@ -0,0 +1,55 @@ +import fetch from "node-fetch"; + +const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; + +export interface FetchLatestArtifactMetadataOpts { + repoName: string; + repoOwner: string; + artifactFileName: string; +} + +export interface ArtifactMetadata { + releaseName: string; + releaseDate: Date; + downloadUrl: string; +} + +export async function fetchLatestArtifactMetadata( + opts: FetchLatestArtifactMetadataOpts +): Promise { + + const repoOwner = encodeURIComponent(opts.repoOwner); + const repoName = encodeURIComponent(opts.repoName); + + const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; + const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; + + // We skip runtime type checks for simplicity (here we cast from `any` to `Release`) + + const response: GithubRelease = await fetch(requestUrl, { + headers: { Accept: "application/vnd.github.v3+json" } + }) + .then(res => res.json()); + + const artifact = response.assets.find(artifact => artifact.name === opts.artifactFileName); + + return !artifact ? null : { + releaseName: response.name, + releaseDate: new Date(response.published_at), + downloadUrl: artifact.browser_download_url + }; + + // Noise denotes tremendous amount of data that we are not using here + interface GithubRelease { + name: string; + published_at: Date; + assets: Array<{ + browser_download_url: string; + + [noise: string]: unknown; + }>; + + [noise: string]: unknown; + } + +} -- cgit v1.2.3 From 5d88c1db38200896d2e4af7836fec95097adf509 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 8 Feb 2020 04:22:44 +0200 Subject: vscode: amended config to use binary from globalStoragePath, added ui for downloading --- editors/code/package-lock.json | 13 +++ editors/code/package.json | 6 +- editors/code/src/client.ts | 22 +--- editors/code/src/config.ts | 56 ++++++++-- editors/code/src/ctx.ts | 6 +- editors/code/src/github/download_file.ts | 26 ----- .../src/github/fetch_latest_artifact_metadata.ts | 55 ---------- editors/code/src/installation/download_file.ts | 26 +++++ .../installation/fetch_latest_artifact_metadata.ts | 47 ++++++++ editors/code/src/installation/interfaces.ts | 26 +++++ editors/code/src/installation/language_server.ts | 119 +++++++++++++++++++++ editors/code/tsconfig.json | 2 + 12 files changed, 296 insertions(+), 108 deletions(-) delete mode 100644 editors/code/src/github/download_file.ts delete mode 100644 editors/code/src/github/fetch_latest_artifact_metadata.ts create mode 100644 editors/code/src/installation/download_file.ts create mode 100644 editors/code/src/installation/fetch_latest_artifact_metadata.ts create mode 100644 editors/code/src/installation/interfaces.ts create mode 100644 editors/code/src/installation/language_server.ts diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 5c056463e..1b7c8910e 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -753,6 +753,19 @@ "os-tmpdir": "~1.0.1" } }, + "ts-not-nil": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-not-nil/-/ts-not-nil-1.0.1.tgz", + "integrity": "sha512-19+u+3okJddVZlrIdTOdFBaMsHYDInIGDPiujxfRa0RS2Ch5055zVG4GAqa+CZ/Rd1a+7ORSm8O4+2kesPymtw==", + "requires": { + "ts-typedefs": ">=3.2.0" + } + }, + "ts-typedefs": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ts-typedefs/-/ts-typedefs-3.2.0.tgz", + "integrity": "sha512-NglEH2YiY40YxNAvwBISqqXRTKlQq6x+qoCF+tkjPxwrPbrkmq7V3LXavmxrD63fENtMhFkcqgMJtOirtow9iA==" + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index 8e23718cd..c0a62619d 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -27,6 +27,7 @@ "jsonc-parser": "^2.1.0", "node-fetch": "^2.6.0", "throttle-debounce": "^2.1.0", + "ts-not-nil": "^1.0.1", "vscode-languageclient": "^6.1.0" }, "devDependencies": { @@ -173,10 +174,11 @@ }, "rust-analyzer.raLspServerPath": { "type": [ + "null", "string" ], - "default": "ra_lsp_server", - "description": "Path to ra_lsp_server executable" + "default": null, + "description": "Path to ra_lsp_server executable (points to bundled binary by default)" }, "rust-analyzer.excludeGlobs": { "type": "array", diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 7e7e909dd..7639ed44b 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -1,24 +1,18 @@ -import { homedir } from 'os'; import * as lc from 'vscode-languageclient'; -import { spawnSync } from 'child_process'; import { window, workspace } from 'vscode'; import { Config } from './config'; +import { ensureLanguageServerBinary } from './installation/language_server'; -export function createClient(config: Config): lc.LanguageClient { +export async function createClient(config: Config): Promise { // '.' Is the fallback if no folder is open // TODO?: Workspace folders support Uri's (eg: file://test.txt). // It might be a good idea to test if the uri points to a file. const workspaceFolderPath = workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.'; - const raLspServerPath = expandPathResolving(config.raLspServerPath); - if (spawnSync(raLspServerPath, ["--version"]).status !== 0) { - window.showErrorMessage( - `Unable to execute '${raLspServerPath} --version'\n\n` + - `Perhaps it is not in $PATH?\n\n` + - `PATH=${process.env.PATH}\n` - ); - } + const raLspServerPath = await ensureLanguageServerBinary(config.raLspServerSource); + if (!raLspServerPath) return null; + const run: lc.Executable = { command: raLspServerPath, options: { cwd: workspaceFolderPath }, @@ -87,9 +81,3 @@ export function createClient(config: Config): lc.LanguageClient { res.registerProposedFeatures(); return res; } -function expandPathResolving(path: string) { - if (path.startsWith('~/')) { - return path.replace('~', homedir()); - } - return path; -} diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 524620433..aca5dab5a 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -1,4 +1,6 @@ +import * as os from "os"; import * as vscode from 'vscode'; +import { BinarySource, BinarySourceType } from "./installation/interfaces"; const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; @@ -16,10 +18,24 @@ export interface CargoFeatures { } export class Config { + readonly raLspServerGithubArtifactName = { + linux: "ra_lsp_server-linux", + darwin: "ra_lsp_server-mac", + win32: "ra_lsp_server-windows.exe", + aix: null, + android: null, + freebsd: null, + openbsd: null, + sunos: null, + cygwin: null, + netbsd: null, + }[process.platform]; + + raLspServerSource!: null | BinarySource; + highlightingOn = true; rainbowHighlightingOn = false; enableEnhancedTyping = true; - raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; lruCapacity: null | number = null; displayInlayHints = true; maxInlayHintLength: null | number = null; @@ -45,11 +61,20 @@ export class Config { private prevCargoWatchOptions: null | CargoWatchOptions = null; constructor(ctx: vscode.ExtensionContext) { - vscode.workspace.onDidChangeConfiguration(_ => this.refresh(), null, ctx.subscriptions); - this.refresh(); + vscode.workspace.onDidChangeConfiguration(_ => this.refresh(ctx), null, ctx.subscriptions); + this.refresh(ctx); + } + + private static expandPathResolving(path: string) { + if (path.startsWith('~/')) { + return path.replace('~', os.homedir()); + } + return path; } - private refresh() { + // FIXME: revisit the logic for `if (.has(...)) config.get(...)` set default + // values only in one place (i.e. remove default values from non-readonly members declarations) + private refresh(ctx: vscode.ExtensionContext) { const config = vscode.workspace.getConfiguration('rust-analyzer'); let requireReloadMessage = null; @@ -82,9 +107,26 @@ export class Config { this.prevEnhancedTyping = this.enableEnhancedTyping; } - if (config.has('raLspServerPath')) { - this.raLspServerPath = - RA_LSP_DEBUG || (config.get('raLspServerPath') as string); + { + const raLspServerPath = RA_LSP_DEBUG ?? config.get("raLspServerPath"); + if (raLspServerPath) { + this.raLspServerSource = { + type: BinarySourceType.ExplicitPath, + path: Config.expandPathResolving(raLspServerPath) + }; + } else if (this.raLspServerGithubArtifactName) { + this.raLspServerSource = { + type: BinarySourceType.GithubBinary, + dir: ctx.globalStoragePath, + file: this.raLspServerGithubArtifactName, + repo: { + name: "rust-analyzer", + owner: "rust-analyzer", + } + }; + } else { + this.raLspServerSource = null; + } } if (config.has('cargo-watch.enable')) { diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index a4dcc3037..f0e2d72f7 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -29,7 +29,11 @@ export class Ctx { await old.stop(); } this.client = null; - const client = createClient(this.config); + const client = await createClient(this.config); + if (!client) { + throw new Error("Rust Analyzer Language Server is not available"); + } + this.pushCleanup(client.start()); await client.onReady(); diff --git a/editors/code/src/github/download_file.ts b/editors/code/src/github/download_file.ts deleted file mode 100644 index f40750be9..000000000 --- a/editors/code/src/github/download_file.ts +++ /dev/null @@ -1,26 +0,0 @@ -import fetch from "node-fetch"; -import { throttle } from "throttle-debounce"; -import * as fs from "fs"; - -export async function downloadFile( - url: string, - destFilePath: fs.PathLike, - onProgress: (readBytes: number, totalBytes: number) => void -): Promise { - onProgress = throttle(100, /* noTrailing: */ true, onProgress); - - const response = await fetch(url); - - const totalBytes = Number(response.headers.get('content-length')); - let readBytes = 0; - - return new Promise((resolve, reject) => response.body - .on("data", (chunk: Buffer) => { - readBytes += chunk.length; - onProgress(readBytes, totalBytes); - }) - .on("end", resolve) - .on("error", reject) - .pipe(fs.createWriteStream(destFilePath)) - ); -} diff --git a/editors/code/src/github/fetch_latest_artifact_metadata.ts b/editors/code/src/github/fetch_latest_artifact_metadata.ts deleted file mode 100644 index 52641ca67..000000000 --- a/editors/code/src/github/fetch_latest_artifact_metadata.ts +++ /dev/null @@ -1,55 +0,0 @@ -import fetch from "node-fetch"; - -const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; - -export interface FetchLatestArtifactMetadataOpts { - repoName: string; - repoOwner: string; - artifactFileName: string; -} - -export interface ArtifactMetadata { - releaseName: string; - releaseDate: Date; - downloadUrl: string; -} - -export async function fetchLatestArtifactMetadata( - opts: FetchLatestArtifactMetadataOpts -): Promise { - - const repoOwner = encodeURIComponent(opts.repoOwner); - const repoName = encodeURIComponent(opts.repoName); - - const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; - const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; - - // We skip runtime type checks for simplicity (here we cast from `any` to `Release`) - - const response: GithubRelease = await fetch(requestUrl, { - headers: { Accept: "application/vnd.github.v3+json" } - }) - .then(res => res.json()); - - const artifact = response.assets.find(artifact => artifact.name === opts.artifactFileName); - - return !artifact ? null : { - releaseName: response.name, - releaseDate: new Date(response.published_at), - downloadUrl: artifact.browser_download_url - }; - - // Noise denotes tremendous amount of data that we are not using here - interface GithubRelease { - name: string; - published_at: Date; - assets: Array<{ - browser_download_url: string; - - [noise: string]: unknown; - }>; - - [noise: string]: unknown; - } - -} diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts new file mode 100644 index 000000000..7b537e114 --- /dev/null +++ b/editors/code/src/installation/download_file.ts @@ -0,0 +1,26 @@ +import fetch from "node-fetch"; +import { throttle } from "throttle-debounce"; +import * as fs from "fs"; + +export async function downloadFile( + url: string, + destFilePath: fs.PathLike, + onProgress: (readBytes: number, totalBytes: number) => void +): Promise { + onProgress = throttle(1000, /* noTrailing: */ true, onProgress); + + const response = await fetch(url); + + const totalBytes = Number(response.headers.get('content-length')); + let readBytes = 0; + + return new Promise((resolve, reject) => response.body + .on("data", (chunk: Buffer) => { + readBytes += chunk.length; + onProgress(readBytes, totalBytes); + }) + .on("end", resolve) + .on("error", reject) + .pipe(fs.createWriteStream(destFilePath)) + ); +} diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_latest_artifact_metadata.ts new file mode 100644 index 000000000..f07431aac --- /dev/null +++ b/editors/code/src/installation/fetch_latest_artifact_metadata.ts @@ -0,0 +1,47 @@ +import fetch from "node-fetch"; +import { GithubRepo, ArtifactMetadata } from "./interfaces"; + +const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; + +export interface FetchLatestArtifactMetadataOpts { + repo: GithubRepo; + artifactFileName: string; +} + +export async function fetchLatestArtifactMetadata( + opts: FetchLatestArtifactMetadataOpts +): Promise { + + const repoOwner = encodeURIComponent(opts.repo.owner); + const repoName = encodeURIComponent(opts.repo.name); + + const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; + const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; + + // We skip runtime type checks for simplicity (here we cast from `any` to `Release`) + + const response: GithubRelease = await fetch(requestUrl, { + headers: { Accept: "application/vnd.github.v3+json" } + }) + .then(res => res.json()); + + const artifact = response.assets.find(artifact => artifact.name === opts.artifactFileName); + + return !artifact ? null : { + releaseName: response.name, + downloadUrl: artifact.browser_download_url + }; + + // Noise denotes tremendous amount of data that we are not using here + interface GithubRelease { + name: string; + assets: Array<{ + browser_download_url: string; + + [noise: string]: unknown; + }>; + + [noise: string]: unknown; + } + +} diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts new file mode 100644 index 000000000..f54e24e26 --- /dev/null +++ b/editors/code/src/installation/interfaces.ts @@ -0,0 +1,26 @@ +export interface GithubRepo { + name: string; + owner: string; +} + +export interface ArtifactMetadata { + releaseName: string; + downloadUrl: string; +} + + +export enum BinarySourceType { ExplicitPath, GithubBinary } + +export type BinarySource = EplicitPathSource | GithubBinarySource; + +export interface EplicitPathSource { + type: BinarySourceType.ExplicitPath; + path: string; +} + +export interface GithubBinarySource { + type: BinarySourceType.GithubBinary; + repo: GithubRepo; + dir: string; + file: string; +} diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts new file mode 100644 index 000000000..2b3ce6621 --- /dev/null +++ b/editors/code/src/installation/language_server.ts @@ -0,0 +1,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 { + + 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; + } +} diff --git a/editors/code/tsconfig.json b/editors/code/tsconfig.json index e60eb8e5e..0c7702974 100644 --- a/editors/code/tsconfig.json +++ b/editors/code/tsconfig.json @@ -6,6 +6,8 @@ "lib": [ "es2019" ], + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, "sourceMap": true, "rootDir": "src", "strict": true, -- cgit v1.2.3 From f7ef72db64e75dadf6f7cae9d739a0a6e8d8f4b4 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 8 Feb 2020 20:07:00 +0200 Subject: vscode: changed chmod value to 755 as per @lnicola --- editors/code/src/installation/language_server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index 2b3ce6621..8d8c62952 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -12,7 +12,7 @@ import { downloadFile } from "./download_file"; export async function downloadLatestLanguageServer( {file: artifactFileName, dir: installationDir, repo}: GithubBinarySource ) { - const binaryMetadata = await fetchLatestArtifactMetadata({ artifactFileName, repo }); + const binaryMetadata = await fetchLatestArtifactMetadata(repo, artifactFileName); const { releaseName, @@ -20,7 +20,7 @@ export async function downloadLatestLanguageServer( } = unwrapNotNil(binaryMetadata, `Latest GitHub release lacks "${artifactFileName}" file`); await fs.mkdir(installationDir).catch(err => assert.strictEqual( - err && err.code, + err?.code, "EEXIST", `Couldn't create directory "${installationDir}" to download `+ `language server binary: ${err.message}` @@ -48,7 +48,7 @@ export async function downloadLatestLanguageServer( } ); - await fs.chmod(installationPath, 111); // Set xxx permissions + await fs.chmod(installationPath, 755); // Set (rwx, r_x, r_x) permissions } export async function ensureLanguageServerBinary( langServerSource: null | BinarySource -- cgit v1.2.3 From 6ef912f9259a78495fdba6a37bef7d78c4e0a4fd Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 8 Feb 2020 20:18:33 +0200 Subject: vscode: converted fetchLatestArtifactMetadata params to positional, added docs --- .../installation/fetch_latest_artifact_metadata.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_latest_artifact_metadata.ts index f07431aac..cda955af2 100644 --- a/editors/code/src/installation/fetch_latest_artifact_metadata.ts +++ b/editors/code/src/installation/fetch_latest_artifact_metadata.ts @@ -3,29 +3,28 @@ import { GithubRepo, ArtifactMetadata } from "./interfaces"; const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; -export interface FetchLatestArtifactMetadataOpts { - repo: GithubRepo; - artifactFileName: string; -} - +/** + * Fetches the latest release from GitHub `repo` and returns metadata about + * `artifactFileName` shipped with this release or `null` if no such artifact was published. + */ export async function fetchLatestArtifactMetadata( - opts: FetchLatestArtifactMetadataOpts + repo: GithubRepo, artifactFileName: string ): Promise { - const repoOwner = encodeURIComponent(opts.repo.owner); - const repoName = encodeURIComponent(opts.repo.name); + const repoOwner = encodeURIComponent(repo.owner); + const repoName = encodeURIComponent(repo.name); const apiEndpointPath = `/repos/${repoOwner}/${repoName}/releases/latest`; const requestUrl = GITHUB_API_ENDPOINT_URL + apiEndpointPath; - // We skip runtime type checks for simplicity (here we cast from `any` to `Release`) + // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`) const response: GithubRelease = await fetch(requestUrl, { headers: { Accept: "application/vnd.github.v3+json" } }) .then(res => res.json()); - const artifact = response.assets.find(artifact => artifact.name === opts.artifactFileName); + const artifact = response.assets.find(artifact => artifact.name === artifactFileName); return !artifact ? null : { releaseName: response.name, @@ -36,6 +35,7 @@ export async function fetchLatestArtifactMetadata( interface GithubRelease { name: string; assets: Array<{ + name: string; browser_download_url: string; [noise: string]: unknown; -- cgit v1.2.3 From 4e85254444cfaf34fefc253ecd0b43b786e31dd8 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 8 Feb 2020 21:03:27 +0200 Subject: vscode: add docs to installation module interfaces and sanity check to donloadFile() --- editors/code/src/installation/download_file.ts | 10 +++++++- editors/code/src/installation/interfaces.ts | 33 +++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts index 7b537e114..0cc5fc0cb 100644 --- a/editors/code/src/installation/download_file.ts +++ b/editors/code/src/installation/download_file.ts @@ -1,17 +1,25 @@ import fetch from "node-fetch"; import { throttle } from "throttle-debounce"; import * as fs from "fs"; +import { strict as assert } from "assert"; +/** + * Downloads file from `url` and stores it at `destFilePath`. + * `onProgress` callback is periodically called to track the progress of downloading, + * it gets the already read and total amount of bytes to read as its parameters. + */ export async function downloadFile( url: string, destFilePath: fs.PathLike, onProgress: (readBytes: number, totalBytes: number) => void ): Promise { - onProgress = throttle(1000, /* noTrailing: */ true, onProgress); + onProgress = throttle(500, /* noTrailing: */ true, onProgress); const response = await fetch(url); const totalBytes = Number(response.headers.get('content-length')); + assert(!Number.isNaN(totalBytes), "Sanity check of content-length protocol"); + let readBytes = 0; return new Promise((resolve, reject) => response.body diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts index f54e24e26..03eac5b79 100644 --- a/editors/code/src/installation/interfaces.ts +++ b/editors/code/src/installation/interfaces.ts @@ -3,24 +3,51 @@ export interface GithubRepo { owner: string; } +/** + * Metadata about particular artifact retrieved from GitHub releases. + */ export interface ArtifactMetadata { releaseName: string; downloadUrl: string; } - +/** + * Type tag for `BinarySource` discriminated union. + */ export enum BinarySourceType { ExplicitPath, GithubBinary } -export type BinarySource = EplicitPathSource | GithubBinarySource; +/** + * Represents the source of a binary artifact which is either specified by the user + * explicitly, or bundled by this extension from GitHub releases. + */ +export type BinarySource = ExplicitPathSource | GithubBinarySource; + -export interface EplicitPathSource { +export interface ExplicitPathSource { type: BinarySourceType.ExplicitPath; + + /** + * Filesystem path to the binary specified by the user explicitly. + */ path: string; } export interface GithubBinarySource { type: BinarySourceType.GithubBinary; + + /** + * Repository where the binary is stored. + */ repo: GithubRepo; + + /** + * Directory on the filesystem where the bundled binary is stored. + */ dir: string; + + /** + * Name of the binary file. It is stored under the same name on GitHub releases + * and in local `.dir`. + */ file: string; } -- cgit v1.2.3 From 9791b6a8de7149d97cdf9880d86bfc9e640c8297 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 8 Feb 2020 21:24:29 +0200 Subject: vscode: add name to the second unused argument of withProgress() callback --- editors/code/src/installation/language_server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index 8d8c62952..b75d3a00a 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -34,7 +34,7 @@ export async function downloadLatestLanguageServer( cancellable: false, // FIXME: add support for canceling download? title: `Downloading language server ${releaseName}` }, - async (progress, _) => { + async (progress, _cancellationToken) => { let lastPrecentage = 0; await downloadFile(downloadUrl, installationPath, (readBytes, totalBytes) => { const newPercentage = (readBytes / totalBytes) * 100; -- cgit v1.2.3 From bdd88c2fad272f96a8212e5230010f7c02b4d15d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 8 Feb 2020 21:25:03 +0200 Subject: vscode: reduce throttle latency of downloadFile() progress callback for smoother UX --- editors/code/src/installation/download_file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts index 0cc5fc0cb..ec16dcd66 100644 --- a/editors/code/src/installation/download_file.ts +++ b/editors/code/src/installation/download_file.ts @@ -13,7 +13,7 @@ export async function downloadFile( destFilePath: fs.PathLike, onProgress: (readBytes: number, totalBytes: number) => void ): Promise { - onProgress = throttle(500, /* noTrailing: */ true, onProgress); + onProgress = throttle(200, /* noTrailing: */ true, onProgress); const response = await fetch(url); -- cgit v1.2.3 From 539daf4454e3f11424a469e8fba26cacb325176a Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 00:27:04 +0200 Subject: vscode: refactor platform artifact name query to switch statement, move BinarySource union variants into a namespace --- editors/code/src/client.ts | 2 +- editors/code/src/config.ts | 89 ++++++++++++++---------- editors/code/src/installation/interfaces.ts | 58 +++++++-------- editors/code/src/installation/language_server.ts | 10 +-- 4 files changed, 89 insertions(+), 70 deletions(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 7639ed44b..2e3d4aba2 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -10,7 +10,7 @@ export async function createClient(config: Config): Promise("raLspServerPath"); + + if (raLspServerPath) { + return { + type: BinarySource.Type.ExplicitPath, + path: Config.expandPathResolving(raLspServerPath) + }; + } + + const prebuiltBinaryName = Config.prebuiltLangServerFileName(process.platform); + + return !prebuiltBinaryName ? null : { + type: BinarySource.Type.GithubRelease, + dir: ctx.globalStoragePath, + file: prebuiltBinaryName, + repo: { + name: "rust-analyzer", + owner: "rust-analyzer", + } + }; + } + + // FIXME: revisit the logic for `if (.has(...)) config.get(...)` set default // values only in one place (i.e. remove default values from non-readonly members declarations) private refresh(ctx: vscode.ExtensionContext) { @@ -107,27 +144,7 @@ export class Config { this.prevEnhancedTyping = this.enableEnhancedTyping; } - { - const raLspServerPath = RA_LSP_DEBUG ?? config.get("raLspServerPath"); - if (raLspServerPath) { - this.raLspServerSource = { - type: BinarySourceType.ExplicitPath, - path: Config.expandPathResolving(raLspServerPath) - }; - } else if (this.raLspServerGithubArtifactName) { - this.raLspServerSource = { - type: BinarySourceType.GithubBinary, - dir: ctx.globalStoragePath, - file: this.raLspServerGithubArtifactName, - repo: { - name: "rust-analyzer", - owner: "rust-analyzer", - } - }; - } else { - this.raLspServerSource = null; - } - } + this.langServerSource = Config.langServerBinarySource(ctx, config); if (config.has('cargo-watch.enable')) { this.cargoWatchOptions.enable = config.get( diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts index 03eac5b79..8039d0b90 100644 --- a/editors/code/src/installation/interfaces.ts +++ b/editors/code/src/installation/interfaces.ts @@ -11,43 +11,45 @@ export interface ArtifactMetadata { downloadUrl: string; } -/** - * Type tag for `BinarySource` discriminated union. - */ -export enum BinarySourceType { ExplicitPath, GithubBinary } - /** * Represents the source of a binary artifact which is either specified by the user * explicitly, or bundled by this extension from GitHub releases. */ -export type BinarySource = ExplicitPathSource | GithubBinarySource; - - -export interface ExplicitPathSource { - type: BinarySourceType.ExplicitPath; +export type BinarySource = BinarySource.ExplicitPath | BinarySource.GithubRelease; +export namespace BinarySource { /** - * Filesystem path to the binary specified by the user explicitly. + * Type tag for `BinarySource` discriminated union. */ - path: string; -} + export const enum Type { ExplicitPath, GithubRelease } -export interface GithubBinarySource { - type: BinarySourceType.GithubBinary; + export interface ExplicitPath { + type: Type.ExplicitPath; - /** - * Repository where the binary is stored. - */ - repo: GithubRepo; + /** + * Filesystem path to the binary specified by the user explicitly. + */ + path: string; + } - /** - * Directory on the filesystem where the bundled binary is stored. - */ - dir: string; + export interface GithubRelease { + type: Type.GithubRelease; + + /** + * Repository where the binary is stored. + */ + repo: GithubRepo; + + /** + * Directory on the filesystem where the bundled binary is stored. + */ + dir: string; + + /** + * Name of the binary file. It is stored under the same name on GitHub releases + * and in local `.dir`. + */ + file: string; + } - /** - * Name of the binary file. It is stored under the same name on GitHub releases - * and in local `.dir`. - */ - file: string; } diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index b75d3a00a..522d59eb5 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -5,12 +5,12 @@ import * as path from "path"; import { strict as assert } from "assert"; import { promises as fs } from "fs"; -import { BinarySource, BinarySourceType, GithubBinarySource } from "./interfaces"; +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}: GithubBinarySource + {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease ) { const binaryMetadata = await fetchLatestArtifactMetadata(repo, artifactFileName); @@ -67,7 +67,7 @@ export async function ensureLanguageServerBinary( } switch (langServerSource.type) { - case BinarySourceType.ExplicitPath: { + case BinarySource.Type.ExplicitPath: { if (isBinaryAvailable(langServerSource.path)) { return langServerSource.path; } @@ -78,7 +78,7 @@ export async function ensureLanguageServerBinary( ); return null; } - case BinarySourceType.GithubBinary: { + case BinarySource.Type.GithubRelease: { const bundledBinaryPath = path.join(langServerSource.dir, langServerSource.file); if (!isBinaryAvailable(bundledBinaryPath)) { @@ -106,7 +106,7 @@ export async function ensureLanguageServerBinary( ); vscode.window.showInformationMessage( - "Rust analyzer language server was successfully installed" + "Rust analyzer language server was successfully installed 🦀" ); } return bundledBinaryPath; -- cgit v1.2.3 From 8f291c0089700ab17fafddde33dc12515ba45662 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 00:28:32 +0200 Subject: vscode: refactor comment --- editors/code/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index f216ab461..5aed8c9f3 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -78,7 +78,7 @@ export class Config { case "sunos": case "cygwin": case "netbsd": return null; - // The list of platforms is exhaustive see (`NodeJS.Platform` type definition) + // The list of platforms is exhaustive (see `NodeJS.Platform` type definition) } } -- cgit v1.2.3 From fd6a98ef6e8cb3ee4d578bf90ad327df548dd1c5 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 00:42:51 +0200 Subject: vscode: rename raLspServer variable to langServer --- editors/code/src/config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 5aed8c9f3..46394600b 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -86,12 +86,12 @@ export class Config { ctx: vscode.ExtensionContext, config: vscode.WorkspaceConfiguration ): null | BinarySource { - const raLspServerPath = RA_LSP_DEBUG ?? config.get("raLspServerPath"); + const langServerPath = RA_LSP_DEBUG ?? config.get("raLspServerPath"); - if (raLspServerPath) { + if (langServerPath) { return { type: BinarySource.Type.ExplicitPath, - path: Config.expandPathResolving(raLspServerPath) + path: Config.expandPathResolving(langServerPath) }; } -- cgit v1.2.3 From d08ae7e82f9eb4abb92f1274ea361acb21fd7b87 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 00:58:53 +0200 Subject: vscode: minor names and message contents changes --- editors/code/src/ctx.ts | 5 ++++- editors/code/src/installation/language_server.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index f0e2d72f7..70042a479 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -31,7 +31,10 @@ export class Ctx { this.client = null; const client = await createClient(this.config); if (!client) { - throw new Error("Rust Analyzer Language Server is not available"); + throw new Error( + "Rust Analyzer Language Server is not available. " + + "Please, ensure its [proper installation](https://github.com/rust-analyzer/rust-analyzer/tree/master/docs/user#vs-code)." + ); } this.pushCleanup(client.start()); diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index 522d59eb5..d09fc63a7 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -72,19 +72,19 @@ export async function ensureLanguageServerBinary( return langServerSource.path; } vscode.window.showErrorMessage( - `Unable to execute ${'`'}${langServerSource.path} --version${'`'}. ` + - "To use the bundled language server, set `rust-analyzer.raLspServerPath` " + + `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 bundledBinaryPath = path.join(langServerSource.dir, langServerSource.file); + const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file); - if (!isBinaryAvailable(bundledBinaryPath)) { + if (!isBinaryAvailable(prebuiltBinaryPath)) { const userResponse = await vscode.window.showInformationMessage( - `Language server binary for rust-analyzer was not found. ` + - `Do you want to download it now?`, + "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; @@ -101,7 +101,7 @@ export async function ensureLanguageServerBinary( assert( - isBinaryAvailable(bundledBinaryPath), + isBinaryAvailable(prebuiltBinaryPath), "Downloaded language server binary is not functional" ); @@ -109,7 +109,7 @@ export async function ensureLanguageServerBinary( "Rust analyzer language server was successfully installed 🦀" ); } - return bundledBinaryPath; + return prebuiltBinaryPath; } } -- cgit v1.2.3 From a3e3fba7bf9cbaa51d39ecfd2b111472fdbf4cb3 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 12:54:51 +0200 Subject: vscode: fix chmod to octal literal Co-Authored-By: Aleksey Kladov --- editors/code/src/installation/language_server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index d09fc63a7..ea1228e46 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -48,7 +48,7 @@ export async function downloadLatestLanguageServer( } ); - await fs.chmod(installationPath, 755); // Set (rwx, r_x, r_x) permissions + await fs.chmod(installationPath, 0o755); // Set (rwx, r_x, r_x) permissions } export async function ensureLanguageServerBinary( langServerSource: null | BinarySource -- cgit v1.2.3 From 3159e87c49cd43677927ba7baa8f12c1232183a1 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 13:36:36 +0200 Subject: vscode: refactor levels of code nesting and string literals quotes --- editors/code/src/installation/language_server.ts | 56 +++++++++++++----------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index ea1228e46..e571cbc98 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -71,9 +71,10 @@ export async function ensureLanguageServerBinary( 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` " + + `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; @@ -81,34 +82,37 @@ export async function ensureLanguageServerBinary( case BinarySource.Type.GithubRelease: { const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file); - if (!isBinaryAvailable(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" - ); + if (isBinaryAvailable(prebuiltBinaryPath)) { + return prebuiltBinaryPath; + } - vscode.window.showInformationMessage( - "Rust analyzer language server was successfully installed 🦀" + 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; } } -- cgit v1.2.3 From 34241b9af966208d73f37f1cdf7f862f2590d846 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 13:39:54 +0200 Subject: vscode: remove noise data fields declarations as per @matklad --- editors/code/src/installation/fetch_latest_artifact_metadata.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_latest_artifact_metadata.ts index cda955af2..f1019e089 100644 --- a/editors/code/src/installation/fetch_latest_artifact_metadata.ts +++ b/editors/code/src/installation/fetch_latest_artifact_metadata.ts @@ -31,17 +31,12 @@ export async function fetchLatestArtifactMetadata( downloadUrl: artifact.browser_download_url }; - // Noise denotes tremendous amount of data that we are not using here + // We omit declaration of tremendous amount of fields that we are not using here interface GithubRelease { name: string; assets: Array<{ name: string; browser_download_url: string; - - [noise: string]: unknown; }>; - - [noise: string]: unknown; } - } -- cgit v1.2.3 From 7a09274e52dc00f7d4e3a040686aa1eb1e075671 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 13:45:06 +0200 Subject: vscode: refactor inverted ternaries to if statements as per @matklad --- editors/code/src/config.ts | 4 +++- editors/code/src/installation/fetch_latest_artifact_metadata.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 46394600b..d5f3da2ed 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -97,7 +97,9 @@ export class Config { const prebuiltBinaryName = Config.prebuiltLangServerFileName(process.platform); - return !prebuiltBinaryName ? null : { + if (!prebuiltBinaryName) return null; + + return { type: BinarySource.Type.GithubRelease, dir: ctx.globalStoragePath, file: prebuiltBinaryName, diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_latest_artifact_metadata.ts index f1019e089..9141c92ef 100644 --- a/editors/code/src/installation/fetch_latest_artifact_metadata.ts +++ b/editors/code/src/installation/fetch_latest_artifact_metadata.ts @@ -26,7 +26,9 @@ export async function fetchLatestArtifactMetadata( const artifact = response.assets.find(artifact => artifact.name === artifactFileName); - return !artifact ? null : { + if (!artifact) return null; + + return { releaseName: response.name, downloadUrl: artifact.browser_download_url }; -- cgit v1.2.3 From a63659badb75d22ad834b7524e77505790c10dd0 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 13:59:27 +0200 Subject: vscode: replaced unwrapNotNil() with ! as per @matklad --- editors/code/package-lock.json | 13 ------------- editors/code/package.json | 1 - editors/code/src/installation/language_server.ts | 10 +++------- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 1b7c8910e..5c056463e 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -753,19 +753,6 @@ "os-tmpdir": "~1.0.1" } }, - "ts-not-nil": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-not-nil/-/ts-not-nil-1.0.1.tgz", - "integrity": "sha512-19+u+3okJddVZlrIdTOdFBaMsHYDInIGDPiujxfRa0RS2Ch5055zVG4GAqa+CZ/Rd1a+7ORSm8O4+2kesPymtw==", - "requires": { - "ts-typedefs": ">=3.2.0" - } - }, - "ts-typedefs": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ts-typedefs/-/ts-typedefs-3.2.0.tgz", - "integrity": "sha512-NglEH2YiY40YxNAvwBISqqXRTKlQq6x+qoCF+tkjPxwrPbrkmq7V3LXavmxrD63fENtMhFkcqgMJtOirtow9iA==" - }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", diff --git a/editors/code/package.json b/editors/code/package.json index c0a62619d..f687eb8d4 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -27,7 +27,6 @@ "jsonc-parser": "^2.1.0", "node-fetch": "^2.6.0", "throttle-debounce": "^2.1.0", - "ts-not-nil": "^1.0.1", "vscode-languageclient": "^6.1.0" }, "devDependencies": { diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index e571cbc98..a169eae47 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -1,4 +1,3 @@ -import { unwrapNotNil } from "ts-not-nil"; import { spawnSync } from "child_process"; import * as vscode from "vscode"; import * as path from "path"; @@ -12,12 +11,9 @@ import { downloadFile } from "./download_file"; export async function downloadLatestLanguageServer( {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease ) { - const binaryMetadata = await fetchLatestArtifactMetadata(repo, artifactFileName); - - const { - releaseName, - downloadUrl - } = unwrapNotNil(binaryMetadata, `Latest GitHub release lacks "${artifactFileName}" file`); + const { releaseName, downloadUrl } = (await fetchLatestArtifactMetadata( + repo, artifactFileName + ))!; await fs.mkdir(installationDir).catch(err => assert.strictEqual( err?.code, -- cgit v1.2.3 From f3240e22c6de69b408a83b59e85f40fb913acfab Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 14:18:05 +0200 Subject: vscode: move throtting of download progress to call site --- editors/code/src/installation/download_file.ts | 8 +++---- editors/code/src/installation/language_server.ts | 27 ++++++++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts index ec16dcd66..53bf46d78 100644 --- a/editors/code/src/installation/download_file.ts +++ b/editors/code/src/installation/download_file.ts @@ -1,20 +1,18 @@ import fetch from "node-fetch"; -import { throttle } from "throttle-debounce"; import * as fs from "fs"; import { strict as assert } from "assert"; /** * Downloads file from `url` and stores it at `destFilePath`. - * `onProgress` callback is periodically called to track the progress of downloading, - * it gets the already read and total amount of bytes to read as its parameters. + * `onProgress` callback is called on recieveing each chunk of bytes + * to track the progress of downloading, it gets the already read and total + * amount of bytes to read as its parameters. */ export async function downloadFile( url: string, destFilePath: fs.PathLike, onProgress: (readBytes: number, totalBytes: number) => void ): Promise { - onProgress = throttle(200, /* noTrailing: */ true, onProgress); - const response = await fetch(url); const totalBytes = Number(response.headers.get('content-length')); diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index a169eae47..c1f37f978 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -1,8 +1,9 @@ -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 { spawnSync } from "child_process"; +import { throttle } from "throttle-debounce"; import { BinarySource } from "./interfaces"; import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata"; @@ -28,19 +29,23 @@ export async function downloadLatestLanguageServer( { location: vscode.ProgressLocation.Notification, cancellable: false, // FIXME: add support for canceling download? - title: `Downloading language server ${releaseName}` + 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 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; + }) + ); } ); -- cgit v1.2.3 From 7cba77ed4e4207b2e24b8dd57723368c2717bb2a Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 15:01:00 +0200 Subject: vscode: added logging when donloading binaries --- editors/code/src/installation/download_file.ts | 2 ++ .../installation/fetch_latest_artifact_metadata.ts | 2 ++ editors/code/src/installation/language_server.ts | 27 ++++++++++++++++++---- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/download_file.ts index 53bf46d78..b51602ef9 100644 --- a/editors/code/src/installation/download_file.ts +++ b/editors/code/src/installation/download_file.ts @@ -20,6 +20,8 @@ export async function downloadFile( let readBytes = 0; + console.log("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath); + return new Promise((resolve, reject) => response.body .on("data", (chunk: Buffer) => { readBytes += chunk.length; diff --git a/editors/code/src/installation/fetch_latest_artifact_metadata.ts b/editors/code/src/installation/fetch_latest_artifact_metadata.ts index 9141c92ef..7e3700603 100644 --- a/editors/code/src/installation/fetch_latest_artifact_metadata.ts +++ b/editors/code/src/installation/fetch_latest_artifact_metadata.ts @@ -19,6 +19,8 @@ export async function fetchLatestArtifactMetadata( // We skip runtime type checks for simplicity (here we cast from `any` to `GithubRelease`) + console.log("Issuing request for released artifacts metadata to", requestUrl); + const response: GithubRelease = await fetch(requestUrl, { headers: { Accept: "application/vnd.github.v3+json" } }) diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts index c1f37f978..1ce67b8b2 100644 --- a/editors/code/src/installation/language_server.ts +++ b/editors/code/src/installation/language_server.ts @@ -2,6 +2,7 @@ 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"; @@ -25,6 +26,7 @@ export async function downloadLatestLanguageServer( const installationPath = path.join(installationDir, artifactFileName); + console.time("Downloading ra_lsp_server"); await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, @@ -48,6 +50,7 @@ export async function downloadLatestLanguageServer( ); } ); + console.timeEnd("Downloading ra_lsp_server"); await fs.chmod(installationPath, 0o755); // Set (rwx, r_x, r_x) permissions } @@ -101,15 +104,21 @@ export async function ensureLanguageServerBinary( `Failed to download language server from ${langServerSource.repo.name} ` + `GitHub repository: ${err.message}` ); + + await dns.resolve('www.google.com').catch(err => { + console.error("DNS resolution failed, there might be an issue with Internet availability"); + console.error(err); + }); + return null; } - - assert( - isBinaryAvailable(prebuiltBinaryPath), - "Downloaded language server binary is not functional" + 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 🦀" ); @@ -119,6 +128,14 @@ export async function ensureLanguageServerBinary( } function isBinaryAvailable(binaryPath: string) { - return spawnSync(binaryPath, ["--version"]).status === 0; + 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; } } -- cgit v1.2.3 From 6a367886ece53e12d62394fe676c9476b4343933 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 15:51:28 +0200 Subject: vscode: updated docs on prebuilt binaries --- docs/user/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/user/README.md b/docs/user/README.md index 18867cd11..abf7c013b 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -32,6 +32,37 @@ a minimum version of 10 installed. Please refer to You will also need the most recent version of VS Code: we don't try to maintain compatibility with older versions yet. +### Installation from prebuilt binaries + +We ship prebuilt binaries for Linux, Mac and Windows via +[GitHub releases](https://github.com/rust-analyzer/rust-analyzer/releases). +In order to use them you need to install the client VSCode extension. + +Publishing to VSCode marketplace is currently WIP. Thus you need to clone the repository and install **only** the client extension via +``` +$ git clone https://github.com/rust-analyzer/rust-analyzer.git --depth 1 +$ cd rust-analyzer +$ cargo xtask install --client-code +``` +Then open VSCode (or reload the window if it was already running), open some Rust project and you should +see an info message pop-up. + + +Download now message + + +Click `Download now`, wait untill the progress is 100% and you are ready to go. + +For updates you need to remove installed binary +``` +rm -rf ${HOME}/.config/Code/User/globalStorage/matklad.rust-analyzer +``` + +`"Donwload latest language server"` command for VSCode and automatic updates detection is currently WIP. + + +### Installation from sources + The experimental VS Code plugin can then be built and installed by executing the following commands: @@ -47,6 +78,7 @@ doesn't, report bugs! **Note** [#1831](https://github.com/rust-analyzer/rust-analyzer/issues/1831): If you are using the popular [Vim emulation plugin](https://github.com/VSCodeVim/Vim), you will likely need to turn off the `rust-analyzer.enableEnhancedTyping` setting. +(// TODO: This configuration is no longer available, enhanced typing shoud be disabled via removing Enter key binding, [see this issue](https://github.com/rust-analyzer/rust-analyzer/issues/3051)) If you have an unusual setup (for example, `code` is not in the `PATH`), you should adapt these manual installation instructions: -- cgit v1.2.3 From be244ab2fb243e945c7024da06c801fb3774b753 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 15:54:54 +0200 Subject: docs: minor fixes in wordings and punctuation --- docs/user/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/README.md b/docs/user/README.md index abf7c013b..a60990ca8 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -38,7 +38,7 @@ We ship prebuilt binaries for Linux, Mac and Windows via [GitHub releases](https://github.com/rust-analyzer/rust-analyzer/releases). In order to use them you need to install the client VSCode extension. -Publishing to VSCode marketplace is currently WIP. Thus you need to clone the repository and install **only** the client extension via +Publishing to VSCode marketplace is currently WIP. Thus, you need to clone the repository and install **only** the client extension via ``` $ git clone https://github.com/rust-analyzer/rust-analyzer.git --depth 1 $ cd rust-analyzer @@ -63,7 +63,7 @@ rm -rf ${HOME}/.config/Code/User/globalStorage/matklad.rust-analyzer ### Installation from sources -The experimental VS Code plugin can then be built and installed by executing the +The experimental VS Code plugin can be built and installed by executing the following commands: ``` -- cgit v1.2.3 From dfb81a8cd4b9a2efd8151b4ac36105c51df7d683 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 9 Feb 2020 17:04:33 +0200 Subject: docs: fix spelling untill -> until Co-Authored-By: Jonas Platte --- docs/user/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/README.md b/docs/user/README.md index a60990ca8..082dadd76 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -51,7 +51,7 @@ see an info message pop-up. Download now message -Click `Download now`, wait untill the progress is 100% and you are ready to go. +Click `Download now`, wait until the progress is 100% and you are ready to go. For updates you need to remove installed binary ``` -- cgit v1.2.3