From 7ad15c396286376c4a439b2dec4ec452b5f28dda Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 13 Feb 2020 22:48:20 +0200 Subject: vscode: redesigned config with simplicity and Dart extension config implementation in mind --- editors/code/src/client.ts | 36 +++--- editors/code/src/config.ts | 258 ++++++++++--------------------------- editors/code/src/highlighting.ts | 6 +- editors/code/src/inlay_hints.ts | 6 +- editors/code/src/status_display.ts | 6 +- 5 files changed, 96 insertions(+), 216 deletions(-) (limited to 'editors/code/src') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 2e3d4aba2..a6fb04536 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -1,6 +1,6 @@ import * as lc from 'vscode-languageclient'; +import * as vscode from 'vscode'; -import { window, workspace } from 'vscode'; import { Config } from './config'; import { ensureLanguageServerBinary } from './installation/language_server'; @@ -8,37 +8,39 @@ export async function createClient(config: Config): Promise `${Config.rootSection}.${opt}`); + + private cfg!: vscode.WorkspaceConfiguration; + + private refreshConfig() { + this.cfg = vscode.workspace.getConfiguration(Config.rootSection); + console.log("Using configuration:", this.cfg); + } - highlightingOn = true; - rainbowHighlightingOn = false; - enableEnhancedTyping = true; - lruCapacity: null | number = null; - displayInlayHints = true; - maxInlayHintLength: null | number = null; - excludeGlobs: string[] = []; - useClientWatching = true; - featureFlags: Record = {}; - // for internal use - withSysroot: null | boolean = null; - cargoWatchOptions: CargoWatchOptions = { - enable: true, - arguments: [], - command: '', - allTargets: true, - }; - cargoFeatures: CargoFeatures = { - noDefaultFeatures: false, - allFeatures: true, - features: [], - }; + constructor(private ctx: vscode.ExtensionContext) { + vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions); + this.refreshConfig(); + } + + async onConfigChange(event: vscode.ConfigurationChangeEvent) { + this.refreshConfig(); + + const requiresReloadOpt = Config.requiresReloadOpts.find( + opt => event.affectsConfiguration(opt) + ); - private prevEnhancedTyping: null | boolean = null; - private prevCargoFeatures: null | CargoFeatures = null; - private prevCargoWatchOptions: null | CargoWatchOptions = null; + if (!requiresReloadOpt) return; - constructor(ctx: vscode.ExtensionContext) { - vscode.workspace.onDidChangeConfiguration(_ => this.refresh(ctx), null, ctx.subscriptions); - this.refresh(ctx); + const userResponse = await vscode.window.showInformationMessage( + `Changing "${requiresReloadOpt}" requires a reload`, + "Reload now" + ); + + if (userResponse === "Reload now") { + vscode.commands.executeCommand("workbench.action.reloadWindow"); + } } - private static expandPathResolving(path: string) { - if (path.startsWith('~/')) { - return path.replace('~', os.homedir()); + private static replaceTildeWithHomeDir(path: string) { + if (path.startsWith("~/")) { + return os.homedir() + path.slice("~".length); } return path; } @@ -97,16 +100,13 @@ export class Config { } } - private static langServerBinarySource( - ctx: vscode.ExtensionContext, - config: vscode.WorkspaceConfiguration - ): null | BinarySource { - const langServerPath = RA_LSP_DEBUG ?? config.get("raLspServerPath"); + langServerBinarySource(): null | BinarySource { + const langServerPath = RA_LSP_DEBUG ?? this.cfg.get("raLspServerPath"); if (langServerPath) { return { type: BinarySource.Type.ExplicitPath, - path: Config.expandPathResolving(langServerPath) + path: Config.replaceTildeWithHomeDir(langServerPath) }; } @@ -118,7 +118,7 @@ export class Config { return { type: BinarySource.Type.GithubRelease, - dir: ctx.globalStoragePath, + dir: this.ctx.globalStoragePath, file: prebuiltBinaryName, repo: { name: "rust-analyzer", @@ -127,158 +127,36 @@ export class Config { }; } + // We don't do runtime config validation here for simplicity. More on stackoverflow: + // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension - // 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; - - if (config.has('highlightingOn')) { - this.highlightingOn = config.get('highlightingOn') as boolean; - } - - if (config.has('rainbowHighlightingOn')) { - this.rainbowHighlightingOn = config.get( - 'rainbowHighlightingOn', - ) as boolean; - } - - if (config.has('enableEnhancedTyping')) { - this.enableEnhancedTyping = config.get( - 'enableEnhancedTyping', - ) as boolean; - - if (this.prevEnhancedTyping === null) { - this.prevEnhancedTyping = this.enableEnhancedTyping; - } - } else if (this.prevEnhancedTyping === null) { - this.prevEnhancedTyping = this.enableEnhancedTyping; - } - - if (this.prevEnhancedTyping !== this.enableEnhancedTyping) { - requireReloadMessage = - 'Changing enhanced typing setting requires a reload'; - this.prevEnhancedTyping = this.enableEnhancedTyping; - } - - this.langServerSource = Config.langServerBinarySource(ctx, config); - - if (config.has('cargo-watch.enable')) { - this.cargoWatchOptions.enable = config.get( - 'cargo-watch.enable', - true, - ); - } - - if (config.has('cargo-watch.arguments')) { - this.cargoWatchOptions.arguments = config.get( - 'cargo-watch.arguments', - [], - ); - } - - if (config.has('cargo-watch.command')) { - this.cargoWatchOptions.command = config.get( - 'cargo-watch.command', - '', - ); - } + // FIXME: add codegen for primitive configurations + highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } + rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } + lruCapacity() { return this.cfg.get("lruCapacity") as null | number; } + displayInlayHints() { return this.cfg.get("displayInlayHints") as boolean; } + maxInlayHintLength() { return this.cfg.get("maxInlayHintLength") as number; } + excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; } + useClientWatching() { return this.cfg.get("useClientWatching") as boolean; } + featureFlags() { return this.cfg.get("featureFlags") as Record; } - if (config.has('cargo-watch.allTargets')) { - this.cargoWatchOptions.allTargets = config.get( - 'cargo-watch.allTargets', - true, - ); - } - - if (config.has('lruCapacity')) { - this.lruCapacity = config.get('lruCapacity') as number; - } - - if (config.has('displayInlayHints')) { - this.displayInlayHints = config.get('displayInlayHints') as boolean; - } - if (config.has('maxInlayHintLength')) { - this.maxInlayHintLength = config.get( - 'maxInlayHintLength', - ) as number; - } - if (config.has('excludeGlobs')) { - this.excludeGlobs = config.get('excludeGlobs') || []; - } - if (config.has('useClientWatching')) { - this.useClientWatching = config.get('useClientWatching') || true; - } - if (config.has('featureFlags')) { - this.featureFlags = config.get('featureFlags') || {}; - } - if (config.has('withSysroot')) { - this.withSysroot = config.get('withSysroot') || false; - } - - if (config.has('cargoFeatures.noDefaultFeatures')) { - this.cargoFeatures.noDefaultFeatures = config.get( - 'cargoFeatures.noDefaultFeatures', - false, - ); - } - if (config.has('cargoFeatures.allFeatures')) { - this.cargoFeatures.allFeatures = config.get( - 'cargoFeatures.allFeatures', - true, - ); - } - if (config.has('cargoFeatures.features')) { - this.cargoFeatures.features = config.get( - 'cargoFeatures.features', - [], - ); - } - - if ( - this.prevCargoFeatures !== null && - (this.cargoFeatures.allFeatures !== - this.prevCargoFeatures.allFeatures || - this.cargoFeatures.noDefaultFeatures !== - this.prevCargoFeatures.noDefaultFeatures || - this.cargoFeatures.features.length !== - this.prevCargoFeatures.features.length || - this.cargoFeatures.features.some( - (v, i) => v !== this.prevCargoFeatures!.features[i], - )) - ) { - requireReloadMessage = 'Changing cargo features requires a reload'; - } - this.prevCargoFeatures = { ...this.cargoFeatures }; - - if (this.prevCargoWatchOptions !== null) { - const changed = - this.cargoWatchOptions.enable !== this.prevCargoWatchOptions.enable || - this.cargoWatchOptions.command !== this.prevCargoWatchOptions.command || - this.cargoWatchOptions.allTargets !== this.prevCargoWatchOptions.allTargets || - this.cargoWatchOptions.arguments.length !== this.prevCargoWatchOptions.arguments.length || - this.cargoWatchOptions.arguments.some( - (v, i) => v !== this.prevCargoWatchOptions!.arguments[i], - ); - if (changed) { - requireReloadMessage = 'Changing cargo-watch options requires a reload'; - } - } - this.prevCargoWatchOptions = { ...this.cargoWatchOptions }; + cargoWatchOptions(): CargoWatchOptions { + return { + enable: this.cfg.get("cargo-watch.enable") as boolean, + arguments: this.cfg.get("cargo-watch.arguments") as string[], + allTargets: this.cfg.get("cargo-watch.allTargets") as boolean, + command: this.cfg.get("cargo-watch.command") as string, + }; + } - if (requireReloadMessage !== null) { - const reloadAction = 'Reload now'; - vscode.window - .showInformationMessage(requireReloadMessage, reloadAction) - .then(selectedAction => { - if (selectedAction === reloadAction) { - vscode.commands.executeCommand( - 'workbench.action.reloadWindow', - ); - } - }); - } + cargoFeatures(): CargoFeatures { + return { + noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean, + allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean, + features: this.cfg.get("cargoFeatures.features") as string[], + }; } + + // for internal use + withSysroot() { return this.cfg.get("withSysroot", false); } } diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 4fbbe3ddc..e2ae31d29 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -11,7 +11,7 @@ export function activateHighlighting(ctx: Ctx) { client.onNotification( 'rust-analyzer/publishDecorations', (params: PublishDecorationsParams) => { - if (!ctx.config.highlightingOn) return; + if (!ctx.config.highlightingOn()) return; const targetEditor = vscode.window.visibleTextEditors.find( editor => { @@ -39,7 +39,7 @@ export function activateHighlighting(ctx: Ctx) { vscode.window.onDidChangeActiveTextEditor( async (editor: vscode.TextEditor | undefined) => { if (!editor || editor.document.languageId !== 'rust') return; - if (!ctx.config.highlightingOn) return; + if (!ctx.config.highlightingOn()) return; const client = ctx.client; if (!client) return; @@ -122,7 +122,7 @@ class Highlighter { string, [vscode.Range[], boolean] > = new Map(); - const rainbowTime = this.ctx.config.rainbowHighlightingOn; + const rainbowTime = this.ctx.config.rainbowHighlightingOn(); for (const tag of this.decorations.keys()) { byTag.set(tag, []); diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 1c019a51b..3ff45a625 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -22,12 +22,12 @@ export function activateInlayHints(ctx: Ctx) { ); vscode.workspace.onDidChangeConfiguration( - async _ => hintsUpdater.setEnabled(ctx.config.displayInlayHints), + async _ => hintsUpdater.setEnabled(ctx.config.displayInlayHints()), null, ctx.subscriptions ); - ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints)); + ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints())); } interface InlayHintsParams { @@ -59,7 +59,7 @@ class HintsUpdater { constructor(ctx: Ctx) { this.ctx = ctx; - this.enabled = ctx.config.displayInlayHints; + this.enabled = ctx.config.displayInlayHints(); } async setEnabled(enabled: boolean) { diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index 51dbf388b..ae9a7b1b5 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts @@ -7,7 +7,7 @@ import { Ctx } from './ctx'; const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; export function activateStatusDisplay(ctx: Ctx) { - const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); + const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions().command); ctx.pushCleanup(statusDisplay); ctx.onDidRestart(client => ctx.pushCleanup(client.onProgress( WorkDoneProgress.type, @@ -66,9 +66,9 @@ class StatusDisplay implements Disposable { refreshLabel() { if (this.packageName) { - this.statusBarItem!.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`; + this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`; } else { - this.statusBarItem!.text = `${spinnerFrames[this.i]} cargo ${this.command}`; + this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command}`; } } -- cgit v1.2.3 From fd37151ade9948398e863c38418fb4f0d0acdfa7 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 13 Feb 2020 23:05:32 +0200 Subject: vscode: reordered config constructor before methods --- editors/code/src/config.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'editors/code/src') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 6c4742464..349f80278 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -26,16 +26,17 @@ export class Config { private cfg!: vscode.WorkspaceConfiguration; + constructor(private readonly ctx: vscode.ExtensionContext) { + vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions); + this.refreshConfig(); + } + + private refreshConfig() { this.cfg = vscode.workspace.getConfiguration(Config.rootSection); console.log("Using configuration:", this.cfg); } - constructor(private ctx: vscode.ExtensionContext) { - vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions); - this.refreshConfig(); - } - async onConfigChange(event: vscode.ConfigurationChangeEvent) { this.refreshConfig(); -- cgit v1.2.3 From 7a832cdf6b9efef2e6c45f56a4385adddd493ea6 Mon Sep 17 00:00:00 2001 From: kjeremy Date: Fri, 14 Feb 2020 11:48:27 -0500 Subject: Do not register all proposed features Instead only opt-in to CallHierarchy since it has a vscode API but LSP support is still proposed. Discovered while working on SemanticTokens which does not have a vscode API and is still in the proposed state. Somehow enabling it would crash the language server. See https://github.com/microsoft/vscode-languageserver-node/issues/572 --- editors/code/src/client.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'editors/code/src') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 2e3d4aba2..d2759969b 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -3,6 +3,7 @@ import * as lc from 'vscode-languageclient'; import { window, workspace } from 'vscode'; import { Config } from './config'; import { ensureLanguageServerBinary } from './installation/language_server'; +import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; export async function createClient(config: Config): Promise { // '.' Is the fallback if no folder is open @@ -78,6 +79,10 @@ export async function createClient(config: Config): Promise Date: Fri, 14 Feb 2020 23:04:50 +0200 Subject: vscode: moved to getters as per matklad --- editors/code/src/client.ts | 18 ++++++++--------- editors/code/src/config.ts | 40 ++++++++++++++++---------------------- editors/code/src/highlighting.ts | 6 +++--- editors/code/src/inlay_hints.ts | 6 +++--- editors/code/src/status_display.ts | 2 +- 5 files changed, 33 insertions(+), 39 deletions(-) (limited to 'editors/code/src') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index a6fb04536..4484b2167 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 (langServerPath) { @@ -111,9 +108,7 @@ export class Config { }; } - const prebuiltBinaryName = Config.prebuiltLangServerFileName( - process.platform, process.arch - ); + const prebuiltBinaryName = this.prebuiltLangServerFileName; if (!prebuiltBinaryName) return null; @@ -131,17 +126,16 @@ export class Config { // We don't do runtime config validation here for simplicity. More on stackoverflow: // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension - // FIXME: add codegen for primitive configurations - highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } - rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } - lruCapacity() { return this.cfg.get("lruCapacity") as null | number; } - displayInlayHints() { return this.cfg.get("displayInlayHints") as boolean; } - maxInlayHintLength() { return this.cfg.get("maxInlayHintLength") as number; } - excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; } - useClientWatching() { return this.cfg.get("useClientWatching") as boolean; } - featureFlags() { return this.cfg.get("featureFlags") as Record; } - - cargoWatchOptions(): CargoWatchOptions { + get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } + get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } + get lruCapacity() { return this.cfg.get("lruCapacity") as null | number; } + get displayInlayHints() { return this.cfg.get("displayInlayHints") as boolean; } + get maxInlayHintLength() { return this.cfg.get("maxInlayHintLength") as number; } + get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; } + get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; } + get featureFlags() { return this.cfg.get("featureFlags") as Record; } + + get cargoWatchOptions(): CargoWatchOptions { return { enable: this.cfg.get("cargo-watch.enable") as boolean, arguments: this.cfg.get("cargo-watch.arguments") as string[], @@ -150,7 +144,7 @@ export class Config { }; } - cargoFeatures(): CargoFeatures { + get cargoFeatures(): CargoFeatures { return { noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean, allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean, @@ -159,5 +153,5 @@ export class Config { } // for internal use - withSysroot() { return this.cfg.get("withSysroot", false); } + get withSysroot() { return this.cfg.get("withSysroot", false); } } diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index e2ae31d29..4fbbe3ddc 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts @@ -11,7 +11,7 @@ export function activateHighlighting(ctx: Ctx) { client.onNotification( 'rust-analyzer/publishDecorations', (params: PublishDecorationsParams) => { - if (!ctx.config.highlightingOn()) return; + if (!ctx.config.highlightingOn) return; const targetEditor = vscode.window.visibleTextEditors.find( editor => { @@ -39,7 +39,7 @@ export function activateHighlighting(ctx: Ctx) { vscode.window.onDidChangeActiveTextEditor( async (editor: vscode.TextEditor | undefined) => { if (!editor || editor.document.languageId !== 'rust') return; - if (!ctx.config.highlightingOn()) return; + if (!ctx.config.highlightingOn) return; const client = ctx.client; if (!client) return; @@ -122,7 +122,7 @@ class Highlighter { string, [vscode.Range[], boolean] > = new Map(); - const rainbowTime = this.ctx.config.rainbowHighlightingOn(); + const rainbowTime = this.ctx.config.rainbowHighlightingOn; for (const tag of this.decorations.keys()) { byTag.set(tag, []); diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 3ff45a625..1c019a51b 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -22,12 +22,12 @@ export function activateInlayHints(ctx: Ctx) { ); vscode.workspace.onDidChangeConfiguration( - async _ => hintsUpdater.setEnabled(ctx.config.displayInlayHints()), + async _ => hintsUpdater.setEnabled(ctx.config.displayInlayHints), null, ctx.subscriptions ); - ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints())); + ctx.onDidRestart(_ => hintsUpdater.setEnabled(ctx.config.displayInlayHints)); } interface InlayHintsParams { @@ -59,7 +59,7 @@ class HintsUpdater { constructor(ctx: Ctx) { this.ctx = ctx; - this.enabled = ctx.config.displayInlayHints(); + this.enabled = ctx.config.displayInlayHints; } async setEnabled(enabled: boolean) { diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts index ae9a7b1b5..993e79d70 100644 --- a/editors/code/src/status_display.ts +++ b/editors/code/src/status_display.ts @@ -7,7 +7,7 @@ import { Ctx } from './ctx'; const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; export function activateStatusDisplay(ctx: Ctx) { - const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions().command); + const statusDisplay = new StatusDisplay(ctx.config.cargoWatchOptions.command); ctx.pushCleanup(statusDisplay); ctx.onDidRestart(client => ctx.pushCleanup(client.onProgress( WorkDoneProgress.type, -- cgit v1.2.3 From 20fabaf1eeec32e1115627a5552eddf0c074ce37 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 14 Feb 2020 23:06:11 +0200 Subject: make onConfigChange handler private --- editors/code/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code/src') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 3ce669330..8cd89e119 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -37,7 +37,7 @@ export class Config { console.log("Using configuration:", this.cfg); } - async onConfigChange(event: vscode.ConfigurationChangeEvent) { + private async onConfigChange(event: vscode.ConfigurationChangeEvent) { this.refreshConfig(); const requiresReloadOpt = Config.requiresReloadOpts.find( -- cgit v1.2.3 From f61134e1980580050f34701db3441081a5519e4c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 15 Feb 2020 00:15:06 +0200 Subject: vscode: renmed ArtifactMetadata -> ArtifactReleaseInfo, languageServer -> langServer --- editors/code/src/client.ts | 4 +- .../installation/fetch_latest_artifact_metadata.ts | 46 ------- .../fetch_latest_artifact_release_info.ts | 46 +++++++ editors/code/src/installation/interfaces.ts | 2 +- editors/code/src/installation/lang_server.ts | 148 +++++++++++++++++++++ editors/code/src/installation/language_server.ts | 148 --------------------- 6 files changed, 197 insertions(+), 197 deletions(-) delete mode 100644 editors/code/src/installation/fetch_latest_artifact_metadata.ts create mode 100644 editors/code/src/installation/fetch_latest_artifact_release_info.ts create mode 100644 editors/code/src/installation/lang_server.ts delete mode 100644 editors/code/src/installation/language_server.ts (limited to 'editors/code/src') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index dcf9d0c06..33d9b66df 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -2,7 +2,7 @@ import * as lc from 'vscode-languageclient'; import * as vscode from 'vscode'; import { Config } from './config'; -import { ensureLanguageServerBinary } from './installation/language_server'; +import { ensureLangServerBinary } from './installation/lang_server'; import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; export async function createClient(config: Config): Promise { @@ -11,7 +11,7 @@ export async function createClient(config: Config): Promise { - - 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 `GithubRelease`) - - console.log("Issuing request for released artifacts metadata to", requestUrl); - - 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 === artifactFileName); - - if (!artifact) return null; - - return { - releaseName: response.name, - downloadUrl: artifact.browser_download_url - }; - - // 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; - }>; - } -} diff --git a/editors/code/src/installation/fetch_latest_artifact_release_info.ts b/editors/code/src/installation/fetch_latest_artifact_release_info.ts new file mode 100644 index 000000000..29ee029a7 --- /dev/null +++ b/editors/code/src/installation/fetch_latest_artifact_release_info.ts @@ -0,0 +1,46 @@ +import fetch from "node-fetch"; +import { GithubRepo, ArtifactReleaseInfo } from "./interfaces"; + +const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; + +/** + * 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 fetchLatestArtifactReleaseInfo( + repo: GithubRepo, artifactFileName: string +): Promise { + + 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 `GithubRelease`) + + console.log("Issuing request for released artifacts metadata to", requestUrl); + + 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 === artifactFileName); + + if (!artifact) return null; + + return { + releaseName: response.name, + downloadUrl: artifact.browser_download_url + }; + + // 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; + }>; + } +} diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts index 8039d0b90..93ea577d4 100644 --- a/editors/code/src/installation/interfaces.ts +++ b/editors/code/src/installation/interfaces.ts @@ -6,7 +6,7 @@ export interface GithubRepo { /** * Metadata about particular artifact retrieved from GitHub releases. */ -export interface ArtifactMetadata { +export interface ArtifactReleaseInfo { releaseName: string; downloadUrl: string; } diff --git a/editors/code/src/installation/lang_server.ts b/editors/code/src/installation/lang_server.ts new file mode 100644 index 000000000..ccb936bf5 --- /dev/null +++ b/editors/code/src/installation/lang_server.ts @@ -0,0 +1,148 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import { strict as assert } from "assert"; +import { promises as fs } from "fs"; +import { promises as dns } from "dns"; +import { spawnSync } from "child_process"; +import { throttle } from "throttle-debounce"; + +import { BinarySource } from "./interfaces"; +import { fetchLatestArtifactReleaseInfo } from "./fetch_latest_artifact_release_info"; +import { downloadFile } from "./download_file"; + +export async function downloadLatestLangServer( + {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease +) { + const { releaseName, downloadUrl } = (await fetchLatestArtifactReleaseInfo( + repo, artifactFileName + ))!; + + await fs.mkdir(installationDir).catch(err => assert.strictEqual( + err?.code, + "EEXIST", + `Couldn't create directory "${installationDir}" to download `+ + `language server binary: ${err.message}` + )); + + const installationPath = path.join(installationDir, artifactFileName); + + console.time("Downloading ra_lsp_server"); + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + cancellable: false, // FIXME: add support for canceling download? + title: `Downloading language server (${releaseName})` + }, + async (progress, _cancellationToken) => { + let lastPrecentage = 0; + const filePermissions = 0o755; // (rwx, r_x, r_x) + await downloadFile(downloadUrl, installationPath, filePermissions, throttle( + 200, + /* noTrailing: */ true, + (readBytes, totalBytes) => { + const newPercentage = (readBytes / totalBytes) * 100; + progress.report({ + message: newPercentage.toFixed(0) + "%", + increment: newPercentage - lastPrecentage + }); + + lastPrecentage = newPercentage; + }) + ); + } + ); + console.timeEnd("Downloading ra_lsp_server"); +} +export async function ensureLangServerBinary( + 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 BinarySource.Type.ExplicitPath: { + if (isBinaryAvailable(langServerSource.path)) { + return langServerSource.path; + } + + vscode.window.showErrorMessage( + `Unable to run ${langServerSource.path} binary. ` + + `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + + "value to `null` or remove it from the settings to use it by default." + ); + return null; + } + case BinarySource.Type.GithubRelease: { + const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file); + + if (isBinaryAvailable(prebuiltBinaryPath)) { + return prebuiltBinaryPath; + } + + const userResponse = await vscode.window.showInformationMessage( + "Language server binary for rust-analyzer was not found. " + + "Do you want to download it now?", + "Download now", "Cancel" + ); + if (userResponse !== "Download now") return null; + + try { + await downloadLatestLangServer(langServerSource); + } catch (err) { + vscode.window.showErrorMessage( + `Failed to download language server from ${langServerSource.repo.name} ` + + `GitHub repository: ${err.message}` + ); + + console.error(err); + + dns.resolve('example.com').then( + addrs => console.log("DNS resolution for example.com was successful", addrs), + err => { + console.error( + "DNS resolution for example.com failed, " + + "there might be an issue with Internet availability" + ); + console.error(err); + } + ); + + return null; + } + + if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, + `Downloaded language server binary is not functional.` + + `Downloaded from: ${JSON.stringify(langServerSource)}` + ); + + + vscode.window.showInformationMessage( + "Rust analyzer language server was successfully installed 🦀" + ); + + return prebuiltBinaryPath; + } + } + + function isBinaryAvailable(binaryPath: string) { + const res = spawnSync(binaryPath, ["--version"]); + + // ACHTUNG! `res` type declaration is inherently wrong, see + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 + + console.log("Checked binary availablity via --version", res); + console.log(binaryPath, "--version output:", res.output?.map(String)); + + return res.status === 0; + } +} diff --git a/editors/code/src/installation/language_server.ts b/editors/code/src/installation/language_server.ts deleted file mode 100644 index 4797c3f01..000000000 --- a/editors/code/src/installation/language_server.ts +++ /dev/null @@ -1,148 +0,0 @@ -import * as vscode from "vscode"; -import * as path from "path"; -import { strict as assert } from "assert"; -import { promises as fs } from "fs"; -import { promises as dns } from "dns"; -import { spawnSync } from "child_process"; -import { throttle } from "throttle-debounce"; - -import { BinarySource } from "./interfaces"; -import { fetchLatestArtifactMetadata } from "./fetch_latest_artifact_metadata"; -import { downloadFile } from "./download_file"; - -export async function downloadLatestLanguageServer( - {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease -) { - const { releaseName, downloadUrl } = (await fetchLatestArtifactMetadata( - repo, artifactFileName - ))!; - - await fs.mkdir(installationDir).catch(err => assert.strictEqual( - err?.code, - "EEXIST", - `Couldn't create directory "${installationDir}" to download `+ - `language server binary: ${err.message}` - )); - - const installationPath = path.join(installationDir, artifactFileName); - - console.time("Downloading ra_lsp_server"); - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, // FIXME: add support for canceling download? - title: `Downloading language server (${releaseName})` - }, - async (progress, _cancellationToken) => { - let lastPrecentage = 0; - const filePermissions = 0o755; // (rwx, r_x, r_x) - await downloadFile(downloadUrl, installationPath, filePermissions, throttle( - 200, - /* noTrailing: */ true, - (readBytes, totalBytes) => { - const newPercentage = (readBytes / totalBytes) * 100; - progress.report({ - message: newPercentage.toFixed(0) + "%", - increment: newPercentage - lastPrecentage - }); - - lastPrecentage = newPercentage; - }) - ); - } - ); - console.timeEnd("Downloading ra_lsp_server"); -} -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 BinarySource.Type.ExplicitPath: { - if (isBinaryAvailable(langServerSource.path)) { - return langServerSource.path; - } - - vscode.window.showErrorMessage( - `Unable to run ${langServerSource.path} binary. ` + - `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + - "value to `null` or remove it from the settings to use it by default." - ); - return null; - } - case BinarySource.Type.GithubRelease: { - const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file); - - if (isBinaryAvailable(prebuiltBinaryPath)) { - return prebuiltBinaryPath; - } - - const userResponse = await vscode.window.showInformationMessage( - "Language server binary for rust-analyzer was not found. " + - "Do you want to download it now?", - "Download now", "Cancel" - ); - if (userResponse !== "Download now") return null; - - try { - await downloadLatestLanguageServer(langServerSource); - } catch (err) { - vscode.window.showErrorMessage( - `Failed to download language server from ${langServerSource.repo.name} ` + - `GitHub repository: ${err.message}` - ); - - console.error(err); - - dns.resolve('example.com').then( - addrs => console.log("DNS resolution for example.com was successful", addrs), - err => { - console.error( - "DNS resolution for example.com failed, " + - "there might be an issue with Internet availability" - ); - console.error(err); - } - ); - - return null; - } - - if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, - `Downloaded language server binary is not functional.` + - `Downloaded from: ${JSON.stringify(langServerSource)}` - ); - - - vscode.window.showInformationMessage( - "Rust analyzer language server was successfully installed 🦀" - ); - - return prebuiltBinaryPath; - } - } - - function isBinaryAvailable(binaryPath: string) { - const res = spawnSync(binaryPath, ["--version"]); - - // ACHTUNG! `res` type declaration is inherently wrong, see - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 - - console.log("Checked binary availablity via --version", res); - console.log(binaryPath, "--version output:", res.output?.map(String)); - - return res.status === 0; - } -} -- cgit v1.2.3 From 80d5ba68da2785280cf154d5d812915b99fc0e87 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 15 Feb 2020 00:42:32 +0200 Subject: vscode: renamed langServer to server --- editors/code/src/client.ts | 8 +- editors/code/src/config.ts | 12 +-- editors/code/src/installation/lang_server.ts | 148 --------------------------- editors/code/src/installation/server.ts | 148 +++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 158 deletions(-) delete mode 100644 editors/code/src/installation/lang_server.ts create mode 100644 editors/code/src/installation/server.ts (limited to 'editors/code/src') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 33d9b66df..12c97be2f 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -2,7 +2,7 @@ import * as lc from 'vscode-languageclient'; import * as vscode from 'vscode'; import { Config } from './config'; -import { ensureLangServerBinary } from './installation/lang_server'; +import { ensureServerBinary } from './installation/server'; import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; export async function createClient(config: Config): Promise { @@ -11,11 +11,11 @@ export async function createClient(config: Config): Promise("raLspServerPath"); + get serverBinarySource(): null | BinarySource { + const serverPath = RA_LSP_DEBUG ?? this.cfg.get("raLspServerPath"); - if (langServerPath) { + if (serverPath) { return { type: BinarySource.Type.ExplicitPath, - path: Config.replaceTildeWithHomeDir(langServerPath) + path: Config.replaceTildeWithHomeDir(serverPath) }; } - const prebuiltBinaryName = this.prebuiltLangServerFileName; + const prebuiltBinaryName = this.prebuiltServerFileName; if (!prebuiltBinaryName) return null; diff --git a/editors/code/src/installation/lang_server.ts b/editors/code/src/installation/lang_server.ts deleted file mode 100644 index ccb936bf5..000000000 --- a/editors/code/src/installation/lang_server.ts +++ /dev/null @@ -1,148 +0,0 @@ -import * as vscode from "vscode"; -import * as path from "path"; -import { strict as assert } from "assert"; -import { promises as fs } from "fs"; -import { promises as dns } from "dns"; -import { spawnSync } from "child_process"; -import { throttle } from "throttle-debounce"; - -import { BinarySource } from "./interfaces"; -import { fetchLatestArtifactReleaseInfo } from "./fetch_latest_artifact_release_info"; -import { downloadFile } from "./download_file"; - -export async function downloadLatestLangServer( - {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease -) { - const { releaseName, downloadUrl } = (await fetchLatestArtifactReleaseInfo( - repo, artifactFileName - ))!; - - await fs.mkdir(installationDir).catch(err => assert.strictEqual( - err?.code, - "EEXIST", - `Couldn't create directory "${installationDir}" to download `+ - `language server binary: ${err.message}` - )); - - const installationPath = path.join(installationDir, artifactFileName); - - console.time("Downloading ra_lsp_server"); - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, // FIXME: add support for canceling download? - title: `Downloading language server (${releaseName})` - }, - async (progress, _cancellationToken) => { - let lastPrecentage = 0; - const filePermissions = 0o755; // (rwx, r_x, r_x) - await downloadFile(downloadUrl, installationPath, filePermissions, throttle( - 200, - /* noTrailing: */ true, - (readBytes, totalBytes) => { - const newPercentage = (readBytes / totalBytes) * 100; - progress.report({ - message: newPercentage.toFixed(0) + "%", - increment: newPercentage - lastPrecentage - }); - - lastPrecentage = newPercentage; - }) - ); - } - ); - console.timeEnd("Downloading ra_lsp_server"); -} -export async function ensureLangServerBinary( - 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 BinarySource.Type.ExplicitPath: { - if (isBinaryAvailable(langServerSource.path)) { - return langServerSource.path; - } - - vscode.window.showErrorMessage( - `Unable to run ${langServerSource.path} binary. ` + - `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + - "value to `null` or remove it from the settings to use it by default." - ); - return null; - } - case BinarySource.Type.GithubRelease: { - const prebuiltBinaryPath = path.join(langServerSource.dir, langServerSource.file); - - if (isBinaryAvailable(prebuiltBinaryPath)) { - return prebuiltBinaryPath; - } - - const userResponse = await vscode.window.showInformationMessage( - "Language server binary for rust-analyzer was not found. " + - "Do you want to download it now?", - "Download now", "Cancel" - ); - if (userResponse !== "Download now") return null; - - try { - await downloadLatestLangServer(langServerSource); - } catch (err) { - vscode.window.showErrorMessage( - `Failed to download language server from ${langServerSource.repo.name} ` + - `GitHub repository: ${err.message}` - ); - - console.error(err); - - dns.resolve('example.com').then( - addrs => console.log("DNS resolution for example.com was successful", addrs), - err => { - console.error( - "DNS resolution for example.com failed, " + - "there might be an issue with Internet availability" - ); - console.error(err); - } - ); - - return null; - } - - if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, - `Downloaded language server binary is not functional.` + - `Downloaded from: ${JSON.stringify(langServerSource)}` - ); - - - vscode.window.showInformationMessage( - "Rust analyzer language server was successfully installed 🦀" - ); - - return prebuiltBinaryPath; - } - } - - function isBinaryAvailable(binaryPath: string) { - const res = spawnSync(binaryPath, ["--version"]); - - // ACHTUNG! `res` type declaration is inherently wrong, see - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 - - console.log("Checked binary availablity via --version", res); - console.log(binaryPath, "--version output:", res.output?.map(String)); - - return res.status === 0; - } -} diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts new file mode 100644 index 000000000..406e2299c --- /dev/null +++ b/editors/code/src/installation/server.ts @@ -0,0 +1,148 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import { strict as assert } from "assert"; +import { promises as fs } from "fs"; +import { promises as dns } from "dns"; +import { spawnSync } from "child_process"; +import { throttle } from "throttle-debounce"; + +import { BinarySource } from "./interfaces"; +import { fetchLatestArtifactReleaseInfo } from "./fetch_latest_artifact_release_info"; +import { downloadFile } from "./download_file"; + +export async function downloadLatestServer( + {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease +) { + const { releaseName, downloadUrl } = (await fetchLatestArtifactReleaseInfo( + repo, artifactFileName + ))!; + + await fs.mkdir(installationDir).catch(err => assert.strictEqual( + err?.code, + "EEXIST", + `Couldn't create directory "${installationDir}" to download `+ + `language server binary: ${err.message}` + )); + + const installationPath = path.join(installationDir, artifactFileName); + + console.time("Downloading ra_lsp_server"); + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + cancellable: false, // FIXME: add support for canceling download? + title: `Downloading language server (${releaseName})` + }, + async (progress, _cancellationToken) => { + let lastPrecentage = 0; + const filePermissions = 0o755; // (rwx, r_x, r_x) + await downloadFile(downloadUrl, installationPath, filePermissions, throttle( + 200, + /* noTrailing: */ true, + (readBytes, totalBytes) => { + const newPercentage = (readBytes / totalBytes) * 100; + progress.report({ + message: newPercentage.toFixed(0) + "%", + increment: newPercentage - lastPrecentage + }); + + lastPrecentage = newPercentage; + }) + ); + } + ); + console.timeEnd("Downloading ra_lsp_server"); +} +export async function ensureServerBinary( + serverSource: null | BinarySource +): Promise { + + if (!serverSource) { + 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 (serverSource.type) { + case BinarySource.Type.ExplicitPath: { + if (isBinaryAvailable(serverSource.path)) { + return serverSource.path; + } + + vscode.window.showErrorMessage( + `Unable to run ${serverSource.path} binary. ` + + `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + + "value to `null` or remove it from the settings to use it by default." + ); + return null; + } + case BinarySource.Type.GithubRelease: { + const prebuiltBinaryPath = path.join(serverSource.dir, serverSource.file); + + if (isBinaryAvailable(prebuiltBinaryPath)) { + return prebuiltBinaryPath; + } + + const userResponse = await vscode.window.showInformationMessage( + "Language server binary for rust-analyzer was not found. " + + "Do you want to download it now?", + "Download now", "Cancel" + ); + if (userResponse !== "Download now") return null; + + try { + await downloadLatestServer(serverSource); + } catch (err) { + vscode.window.showErrorMessage( + `Failed to download language server from ${serverSource.repo.name} ` + + `GitHub repository: ${err.message}` + ); + + console.error(err); + + dns.resolve('example.com').then( + addrs => console.log("DNS resolution for example.com was successful", addrs), + err => { + console.error( + "DNS resolution for example.com failed, " + + "there might be an issue with Internet availability" + ); + console.error(err); + } + ); + + return null; + } + + if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, + `Downloaded language server binary is not functional.` + + `Downloaded from: ${JSON.stringify(serverSource)}` + ); + + + vscode.window.showInformationMessage( + "Rust analyzer language server was successfully installed 🦀" + ); + + return prebuiltBinaryPath; + } + } + + function isBinaryAvailable(binaryPath: string) { + const res = spawnSync(binaryPath, ["--version"]); + + // ACHTUNG! `res` type declaration is inherently wrong, see + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 + + console.log("Checked binary availablity via --version", res); + console.log(binaryPath, "--version output:", res.output?.map(String)); + + return res.status === 0; + } +} -- cgit v1.2.3 From 8533fc437b7619e1c289fa7913fdafda533903b8 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 16 Feb 2020 03:08:36 +0200 Subject: vscode: add version and storage parameters to github binary source --- editors/code/src/client.ts | 2 +- editors/code/src/config.ts | 17 ++++++++++++++++- editors/code/src/ctx.ts | 4 ++++ editors/code/src/installation/interfaces.ts | 13 +++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) (limited to 'editors/code/src') diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 12c97be2f..efef820ab 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -11,7 +11,7 @@ export async function createClient(config: Config): Promise `${Config.rootSection}.${opt}`); + private static readonly extensionVersion: string = (() => { + const packageJsonVersion = vscode + .extensions + .getExtension("matklad.rust-analyzer")! + .packageJSON + .version as string; // n.n.YYYYMMDD + + const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; + const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!; + + return `${yyyy}-${mm}-${dd}`; + })(); + private cfg!: vscode.WorkspaceConfiguration; constructor(private readonly ctx: vscode.ExtensionContext) { @@ -98,7 +111,7 @@ export class Config { } } - get serverBinarySource(): null | BinarySource { + get serverSource(): null | BinarySource { const serverPath = RA_LSP_DEBUG ?? this.cfg.get("raLspServerPath"); if (serverPath) { @@ -116,6 +129,8 @@ export class Config { type: BinarySource.Type.GithubRelease, dir: this.ctx.globalStoragePath, file: prebuiltBinaryName, + storage: this.ctx.globalState, + version: Config.extensionVersion, repo: { name: "rust-analyzer", owner: "rust-analyzer", diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 70042a479..9fcf2ec38 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -60,6 +60,10 @@ export class Ctx { this.pushCleanup(d); } + get globalState(): vscode.Memento { + return this.extCtx.globalState; + } + get subscriptions(): Disposable[] { return this.extCtx.subscriptions; } diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts index 93ea577d4..e40839e4b 100644 --- a/editors/code/src/installation/interfaces.ts +++ b/editors/code/src/installation/interfaces.ts @@ -1,3 +1,5 @@ +import * as vscode from "vscode"; + export interface GithubRepo { name: string; owner: string; @@ -50,6 +52,17 @@ export namespace BinarySource { * and in local `.dir`. */ file: string; + + /** + * Tag of github release that denotes a version required by this extension. + */ + version: string; + + /** + * Object that provides `get()/update()` operations to store metadata + * about the actual binary, e.g. its actual version. + */ + storage: vscode.Memento; } } -- cgit v1.2.3 From 0f7abeb03599964e58d979820134c9e7a61a690e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 16 Feb 2020 03:12:25 +0200 Subject: vscode: add release tag option to fetchArtifactReleaseInfo() --- .../installation/fetch_artifact_release_info.ts | 52 ++++++++++++++++++++++ .../fetch_latest_artifact_release_info.ts | 46 ------------------- 2 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 editors/code/src/installation/fetch_artifact_release_info.ts delete mode 100644 editors/code/src/installation/fetch_latest_artifact_release_info.ts (limited to 'editors/code/src') diff --git a/editors/code/src/installation/fetch_artifact_release_info.ts b/editors/code/src/installation/fetch_artifact_release_info.ts new file mode 100644 index 000000000..7d497057a --- /dev/null +++ b/editors/code/src/installation/fetch_artifact_release_info.ts @@ -0,0 +1,52 @@ +import fetch from "node-fetch"; +import { GithubRepo, ArtifactReleaseInfo } from "./interfaces"; + +const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; + + +/** + * Fetches the release with `releaseTag` (or just latest release when not specified) + * from GitHub `repo` and returns metadata about `artifactFileName` shipped with + * this release or `null` if no such artifact was published. + */ +export async function fetchArtifactReleaseInfo( + repo: GithubRepo, artifactFileName: string, releaseTag?: string +): Promise { + + const repoOwner = encodeURIComponent(repo.owner); + const repoName = encodeURIComponent(repo.name); + + const apiEndpointPath = releaseTag + ? `/repos/${repoOwner}/${repoName}/releases/tags/${releaseTag}` + : `/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 `GithubRelease`) + + console.log("Issuing request for released artifacts metadata to", requestUrl); + + // FIXME: handle non-ok response + 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 === artifactFileName); + + if (!artifact) return null; + + return { + releaseName: response.name, + downloadUrl: artifact.browser_download_url + }; + + // 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; + }>; + } +} diff --git a/editors/code/src/installation/fetch_latest_artifact_release_info.ts b/editors/code/src/installation/fetch_latest_artifact_release_info.ts deleted file mode 100644 index 29ee029a7..000000000 --- a/editors/code/src/installation/fetch_latest_artifact_release_info.ts +++ /dev/null @@ -1,46 +0,0 @@ -import fetch from "node-fetch"; -import { GithubRepo, ArtifactReleaseInfo } from "./interfaces"; - -const GITHUB_API_ENDPOINT_URL = "https://api.github.com"; - -/** - * 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 fetchLatestArtifactReleaseInfo( - repo: GithubRepo, artifactFileName: string -): Promise { - - 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 `GithubRelease`) - - console.log("Issuing request for released artifacts metadata to", requestUrl); - - 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 === artifactFileName); - - if (!artifact) return null; - - return { - releaseName: response.name, - downloadUrl: artifact.browser_download_url - }; - - // 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; - }>; - } -} -- cgit v1.2.3 From b9188226fabb00de3c5f3706186e96b01c223566 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 16 Feb 2020 03:13:06 +0200 Subject: vscode: extract downloadArtifact() function --- editors/code/src/installation/download_artifact.ts | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 editors/code/src/installation/download_artifact.ts (limited to 'editors/code/src') 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 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import { promises as fs } from "fs"; +import { strict as assert } from "assert"; + +import { ArtifactReleaseInfo } from "./interfaces"; +import { downloadFile } from "./download_file"; +import { throttle } from "throttle-debounce"; + +/** + * Downloads artifact from given `downloadUrl`. + * Creates `installationDir` if it is not yet created and put the artifact under + * `artifactFileName`. + * Displays info about the download progress in an info message printing the name + * of the artifact as `displayName`. + */ +export async function downloadArtifact( + {downloadUrl, releaseName}: ArtifactReleaseInfo, + artifactFileName: string, + installationDir: string, + displayName: string, +) { + await fs.mkdir(installationDir).catch(err => assert.strictEqual( + err?.code, + "EEXIST", + `Couldn't create directory "${installationDir}" to download `+ + `${artifactFileName} artifact: ${err.message}` + )); + + const installationPath = path.join(installationDir, artifactFileName); + + console.time(`Downloading ${artifactFileName}`); + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + cancellable: false, // FIXME: add support for canceling download? + title: `Downloading ${displayName} (${releaseName})` + }, + async (progress, _cancellationToken) => { + let lastPrecentage = 0; + const filePermissions = 0o755; // (rwx, r_x, r_x) + await downloadFile(downloadUrl, installationPath, filePermissions, throttle( + 200, + /* noTrailing: */ true, + (readBytes, totalBytes) => { + const newPercentage = (readBytes / totalBytes) * 100; + progress.report({ + message: newPercentage.toFixed(0) + "%", + increment: newPercentage - lastPrecentage + }); + + lastPrecentage = newPercentage; + }) + ); + } + ); + console.timeEnd(`Downloading ${artifactFileName}`); +} -- cgit v1.2.3 From 467b925b53edfd77981344346c3ae2200acdaabe Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 16 Feb 2020 03:14:20 +0200 Subject: vscode: save binary version when downloading and download only version that matches TypeScript extension version --- editors/code/src/installation/server.ts | 168 ++++++++++++++------------------ 1 file changed, 72 insertions(+), 96 deletions(-) (limited to 'editors/code/src') diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index 406e2299c..80cb719e3 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts @@ -1,63 +1,15 @@ import * as vscode from "vscode"; import * as path from "path"; import { strict as assert } from "assert"; -import { promises as fs } from "fs"; import { promises as dns } from "dns"; import { spawnSync } from "child_process"; -import { throttle } from "throttle-debounce"; import { BinarySource } from "./interfaces"; -import { fetchLatestArtifactReleaseInfo } from "./fetch_latest_artifact_release_info"; -import { downloadFile } from "./download_file"; - -export async function downloadLatestServer( - {file: artifactFileName, dir: installationDir, repo}: BinarySource.GithubRelease -) { - const { releaseName, downloadUrl } = (await fetchLatestArtifactReleaseInfo( - repo, artifactFileName - ))!; - - await fs.mkdir(installationDir).catch(err => assert.strictEqual( - err?.code, - "EEXIST", - `Couldn't create directory "${installationDir}" to download `+ - `language server binary: ${err.message}` - )); - - const installationPath = path.join(installationDir, artifactFileName); - - console.time("Downloading ra_lsp_server"); - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, // FIXME: add support for canceling download? - title: `Downloading language server (${releaseName})` - }, - async (progress, _cancellationToken) => { - let lastPrecentage = 0; - const filePermissions = 0o755; // (rwx, r_x, r_x) - await downloadFile(downloadUrl, installationPath, filePermissions, throttle( - 200, - /* noTrailing: */ true, - (readBytes, totalBytes) => { - const newPercentage = (readBytes / totalBytes) * 100; - progress.report({ - message: newPercentage.toFixed(0) + "%", - increment: newPercentage - lastPrecentage - }); - - lastPrecentage = newPercentage; - }) - ); - } - ); - console.timeEnd("Downloading ra_lsp_server"); -} -export async function ensureServerBinary( - serverSource: null | BinarySource -): Promise { +import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; +import { downloadArtifact } from "./download_artifact"; - if (!serverSource) { +export async function ensureServerBinary(source: null | BinarySource): Promise { + if (!source) { vscode.window.showErrorMessage( "Unfortunately we don't ship binaries for your platform yet. " + "You need to manually clone rust-analyzer repository and " + @@ -69,80 +21,104 @@ export async function ensureServerBinary( return null; } - switch (serverSource.type) { + switch (source.type) { case BinarySource.Type.ExplicitPath: { - if (isBinaryAvailable(serverSource.path)) { - return serverSource.path; + if (isBinaryAvailable(source.path)) { + return source.path; } vscode.window.showErrorMessage( - `Unable to run ${serverSource.path} binary. ` + + `Unable to run ${source.path} binary. ` + `To use the pre-built language server, set "rust-analyzer.raLspServerPath" ` + "value to `null` or remove it from the settings to use it by default." ); return null; } case BinarySource.Type.GithubRelease: { - const prebuiltBinaryPath = path.join(serverSource.dir, serverSource.file); + const prebuiltBinaryPath = path.join(source.dir, source.file); + + const installedVersion: null | string = getServerVersion(source.storage); + const requiredVersion: string = source.version; - if (isBinaryAvailable(prebuiltBinaryPath)) { + console.log("Installed version:", installedVersion, "required:", requiredVersion); + + if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion == requiredVersion) { + // FIXME: check for new releases and notify the user to update if possible return prebuiltBinaryPath; } const userResponse = await vscode.window.showInformationMessage( - "Language server binary for rust-analyzer was not found. " + + `Language server version ${source.version} for rust-analyzer is not installed. ` + "Do you want to download it now?", "Download now", "Cancel" ); if (userResponse !== "Download now") return null; - try { - await downloadLatestServer(serverSource); - } catch (err) { - vscode.window.showErrorMessage( - `Failed to download language server from ${serverSource.repo.name} ` + - `GitHub repository: ${err.message}` - ); + if (!await downloadServer(source)) return null; - console.error(err); + return prebuiltBinaryPath; + } + } +} - dns.resolve('example.com').then( - addrs => console.log("DNS resolution for example.com was successful", addrs), - err => { - console.error( - "DNS resolution for example.com failed, " + - "there might be an issue with Internet availability" - ); - console.error(err); - } - ); +async function downloadServer(source: BinarySource.GithubRelease): Promise { + try { + const releaseInfo = (await fetchArtifactReleaseInfo(source.repo, source.file, source.version))!; + + await downloadArtifact(releaseInfo, source.file, source.dir, "language server"); + await setServerVersion(source.storage, releaseInfo.releaseName); + } catch (err) { + vscode.window.showErrorMessage( + `Failed to download language server from ${source.repo.name} ` + + `GitHub repository: ${err.message}` + ); + + console.error(err); - return null; + dns.resolve('example.com').then( + addrs => console.log("DNS resolution for example.com was successful", addrs), + err => { + console.error( + "DNS resolution for example.com failed, " + + "there might be an issue with Internet availability" + ); + console.error(err); } + ); + return false; + } - if (!isBinaryAvailable(prebuiltBinaryPath)) assert(false, - `Downloaded language server binary is not functional.` + - `Downloaded from: ${JSON.stringify(serverSource)}` - ); + if (!isBinaryAvailable(path.join(source.dir, source.file))) assert(false, + `Downloaded language server binary is not functional.` + + `Downloaded from: ${JSON.stringify(source, null, 4)}` + ); + vscode.window.showInformationMessage( + "Rust analyzer language server was successfully installed 🦀" + ); - vscode.window.showInformationMessage( - "Rust analyzer language server was successfully installed 🦀" - ); + return true; +} - return prebuiltBinaryPath; - } - } +function isBinaryAvailable(binaryPath: string): boolean { + const res = spawnSync(binaryPath, ["--version"]); - function isBinaryAvailable(binaryPath: string) { - const res = spawnSync(binaryPath, ["--version"]); + // ACHTUNG! `res` type declaration is inherently wrong, see + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/42221 - // 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)); - console.log("Checked binary availablity via --version", res); - console.log(binaryPath, "--version output:", res.output?.map(String)); + return res.status === 0; +} - return res.status === 0; - } +function getServerVersion(storage: vscode.Memento): null | string { + const version = storage.get("server-version", null); + console.log("Get server-version:", version); + return version; +} + +async function setServerVersion(storage: vscode.Memento, version: string): Promise { + console.log("Set server-version:", version); + await storage.update("server-version", version.toString()); } -- cgit v1.2.3 From 3068aab82d06f4fa2082bcaa34d57452247a6cbb Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 16 Feb 2020 11:15:19 +0200 Subject: vscode: fix the default value for withSysroot --- editors/code/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code/src') diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 7866ed7e1..8c033052b 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -153,5 +153,5 @@ export class Config { } // for internal use - get withSysroot() { return this.cfg.get("withSysroot", false); } + get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } } -- cgit v1.2.3