diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts index c4d84b4439..83b1acf677 100644 --- a/editors/code/src/commands/server_version.ts +++ b/editors/code/src/commands/server_version.ts @@ -5,7 +5,7 @@ import { spawnSync } from 'child_process'; export function serverVersion(ctx: Ctx): Cmd { return async () => { - const binaryPath = await ensureServerBinary(ctx.config); + const binaryPath = await ensureServerBinary(ctx.config, ctx.state); if (binaryPath == null) { throw new Error( diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index f63e1d20e8..bd8096dd6e 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -182,13 +182,6 @@ export class Config { return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG); } - readonly installedNightlyExtensionReleaseDate = new DateStorage( - "installed-nightly-extension-release-date", - this.ctx.globalState - ); - readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState); - readonly serverReleaseTag = new Storage("server-release-tag", this.ctx.globalState, null); - // 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 @@ -232,37 +225,3 @@ export class Config { // for internal use get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } } - -export class Storage { - constructor( - private readonly key: string, - private readonly storage: vscode.Memento, - private readonly defaultVal: T - ) { } - - get(): T { - const val = this.storage.get(this.key, this.defaultVal); - log.debug(this.key, "==", val); - return val; - } - async set(val: T) { - log.debug(this.key, "=", val); - await this.storage.update(this.key, val); - } -} -export class DateStorage { - inner: Storage; - - constructor(key: string, storage: vscode.Memento) { - this.inner = new Storage(key, storage, null); - } - - get(): null | Date { - const dateStr = this.inner.get(); - return dateStr ? new Date(dateStr) : null; - } - - async set(date: null | Date) { - await this.inner.set(date ? date.toString() : null); - } -} diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 25ef38aed0..c929ab0632 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -4,19 +4,21 @@ import * as lc from 'vscode-languageclient'; import { Config } from './config'; import { createClient } from './client'; import { isRustEditor, RustEditor } from './util'; +import { PersistentState } from './persistent_state'; export class Ctx { private constructor( readonly config: Config, + readonly state: PersistentState, private readonly extCtx: vscode.ExtensionContext, readonly client: lc.LanguageClient ) { } - static async create(config: Config, extCtx: vscode.ExtensionContext, serverPath: string): Promise { + static async create(config: Config, state: PersistentState, extCtx: vscode.ExtensionContext, serverPath: string): Promise { const client = await createClient(config, serverPath); - const res = new Ctx(config, extCtx, client); + const res = new Ctx(config, state, extCtx, client); res.pushCleanup(client.start()); await client.onReady(); return res; diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index eea6fded23..a1db96f052 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts @@ -7,6 +7,7 @@ import { Config, UpdatesChannel } from "../config"; import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces"; import { downloadArtifactWithProgressUi } from "./downloads"; import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; +import { PersistentState } from "../persistent_state"; const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; @@ -14,7 +15,7 @@ const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; * Installs `stable` or latest `nightly` version or does nothing if the current * extension version is what's needed according to `desiredUpdateChannel`. */ -export async function ensureProperExtensionVersion(config: Config): Promise { +export async function ensureProperExtensionVersion(config: Config, state: PersistentState): Promise { // User has built lsp server from sources, she should manage updates manually if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return; @@ -23,7 +24,7 @@ export async function ensureProperExtensionVersion(config: Config): Promise { + await tryDownloadNightlyExtension(config, state, releaseInfo => { assert( - currentExtReleaseDate.getTime() === config.installedNightlyExtensionReleaseDate.get()?.getTime(), + currentExtReleaseDate.getTime() === state.installedNightlyExtensionReleaseDate.get()?.getTime(), "Other active VSCode instance has reinstalled the extension" ); @@ -111,6 +112,7 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") */ const tryDownloadNightlyExtension = notReentrant(async ( config: Config, + state: PersistentState, shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true ): Promise => { const vsixSource = config.nightlyVsixSource; @@ -124,7 +126,7 @@ const tryDownloadNightlyExtension = notReentrant(async ( const vsixPath = path.join(vsixSource.dir, vsixSource.file); await vscodeInstallExtensionFromVsix(vsixPath); - await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); + await state.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); await fs.unlink(vsixPath); await vscodeReloadWindow(); // never returns diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index 05730a7788..05d3261315 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts @@ -7,8 +7,9 @@ import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; import { downloadArtifactWithProgressUi } from "./downloads"; import { log, assert, notReentrant } from "../util"; import { Config, NIGHTLY_TAG } from "../config"; +import { PersistentState } from "../persistent_state"; -export async function ensureServerBinary(config: Config): Promise { +export async function ensureServerBinary(config: Config, state: PersistentState): Promise { const source = config.serverSource; if (!source) { @@ -37,7 +38,7 @@ export async function ensureServerBinary(config: Config): Promise return null; } case ArtifactSource.Type.GithubRelease: { - if (!shouldDownloadServer(source, config)) { + if (!shouldDownloadServer(state, source)) { return path.join(source.dir, source.file); } @@ -50,24 +51,24 @@ export async function ensureServerBinary(config: Config): Promise if (userResponse !== "Download now") return null; } - return await downloadServer(source, config); + return await downloadServer(state, source); } } } function shouldDownloadServer( + state: PersistentState, source: ArtifactSource.GithubRelease, - config: Config ): boolean { if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; const installed = { - tag: config.serverReleaseTag.get(), - date: config.serverReleaseDate.get() + tag: state.serverReleaseTag.get(), + date: state.serverReleaseDate.get() }; const required = { tag: source.tag, - date: config.installedNightlyExtensionReleaseDate.get() + date: state.installedNightlyExtensionReleaseDate.get() }; log.debug("Installed server:", installed, "required:", required); @@ -86,16 +87,16 @@ function shouldDownloadServer( * Enforcing no reentrancy for this is best-effort. */ const downloadServer = notReentrant(async ( + state: PersistentState, source: ArtifactSource.GithubRelease, - config: Config, ): Promise => { try { const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); await Promise.all([ - config.serverReleaseTag.set(releaseInfo.releaseName), - config.serverReleaseDate.set(releaseInfo.releaseDate) + state.serverReleaseTag.set(releaseInfo.releaseName), + state.serverReleaseDate.set(releaseInfo.releaseDate) ]); } catch (err) { log.downloadError(err, "language server", source.repo.name); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index bd4661a367..94ecd4dab6 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -9,6 +9,7 @@ import { ensureServerBinary } from './installation/server'; import { Config } from './config'; import { log } from './util'; import { ensureProperExtensionVersion } from './installation/extension'; +import { PersistentState } from './persistent_state'; let ctx: Ctx | undefined; @@ -34,13 +35,14 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(defaultOnEnter); const config = new Config(context); + const state = new PersistentState(context); - vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config).catch(log.error)); + vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config, state).catch(log.error)); // Don't await the user response here, otherwise we will block the lsp server bootstrap - void ensureProperExtensionVersion(config).catch(log.error); + void ensureProperExtensionVersion(config, state).catch(log.error); - const serverPath = await ensureServerBinary(config); + const serverPath = await ensureServerBinary(config, state); if (serverPath == null) { throw new Error( @@ -53,7 +55,7 @@ export async function activate(context: vscode.ExtensionContext) { // registers its `onDidChangeDocument` handler before us. // // This a horribly, horribly wrong way to deal with this problem. - ctx = await Ctx.create(config, context, serverPath); + ctx = await Ctx.create(config, state, context, serverPath); // Commands which invokes manually via command palette, shortcut, etc. ctx.registerCommand('reload', (ctx) => { diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts new file mode 100644 index 0000000000..13095b8065 --- /dev/null +++ b/editors/code/src/persistent_state.ts @@ -0,0 +1,49 @@ +import * as vscode from 'vscode'; +import { log } from "./util"; + +export class PersistentState { + constructor(private readonly ctx: vscode.ExtensionContext) { + } + + readonly installedNightlyExtensionReleaseDate = new DateStorage( + "installed-nightly-extension-release-date", + this.ctx.globalState + ); + readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState); + readonly serverReleaseTag = new Storage("server-release-tag", this.ctx.globalState, null); +} + + +export class Storage { + constructor( + private readonly key: string, + private readonly storage: vscode.Memento, + private readonly defaultVal: T + ) { } + + get(): T { + const val = this.storage.get(this.key, this.defaultVal); + log.debug(this.key, "==", val); + return val; + } + async set(val: T) { + log.debug(this.key, "=", val); + await this.storage.update(this.key, val); + } +} +export class DateStorage { + inner: Storage; + + constructor(key: string, storage: vscode.Memento) { + this.inner = new Storage(key, storage, null); + } + + get(): null | Date { + const dateStr = this.inner.get(); + return dateStr ? new Date(dateStr) : null; + } + + async set(date: null | Date) { + await this.inner.set(date ? date.toString() : null); + } +}