aboutsummaryrefslogtreecommitdiff
path: root/editors/code
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code')
-rw-r--r--editors/code/src/commands/server_version.ts2
-rw-r--r--editors/code/src/config.ts41
-rw-r--r--editors/code/src/ctx.ts6
-rw-r--r--editors/code/src/installation/extension.ts16
-rw-r--r--editors/code/src/installation/server.ts21
-rw-r--r--editors/code/src/main.ts10
-rw-r--r--editors/code/src/persistent_state.ts49
7 files changed, 80 insertions, 65 deletions
diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts
index c4d84b443..83b1acf67 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';
5 5
6export function serverVersion(ctx: Ctx): Cmd { 6export function serverVersion(ctx: Ctx): Cmd {
7 return async () => { 7 return async () => {
8 const binaryPath = await ensureServerBinary(ctx.config); 8 const binaryPath = await ensureServerBinary(ctx.config, ctx.state);
9 9
10 if (binaryPath == null) { 10 if (binaryPath == null) {
11 throw new Error( 11 throw new Error(
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index f63e1d20e..bd8096dd6 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -182,13 +182,6 @@ export class Config {
182 return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG); 182 return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG);
183 } 183 }
184 184
185 readonly installedNightlyExtensionReleaseDate = new DateStorage(
186 "installed-nightly-extension-release-date",
187 this.ctx.globalState
188 );
189 readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState);
190 readonly serverReleaseTag = new Storage<null | string>("server-release-tag", this.ctx.globalState, null);
191
192 // We don't do runtime config validation here for simplicity. More on stackoverflow: 185 // We don't do runtime config validation here for simplicity. More on stackoverflow:
193 // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension 186 // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension
194 187
@@ -232,37 +225,3 @@ export class Config {
232 // for internal use 225 // for internal use
233 get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } 226 get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; }
234} 227}
235
236export class Storage<T> {
237 constructor(
238 private readonly key: string,
239 private readonly storage: vscode.Memento,
240 private readonly defaultVal: T
241 ) { }
242
243 get(): T {
244 const val = this.storage.get(this.key, this.defaultVal);
245 log.debug(this.key, "==", val);
246 return val;
247 }
248 async set(val: T) {
249 log.debug(this.key, "=", val);
250 await this.storage.update(this.key, val);
251 }
252}
253export class DateStorage {
254 inner: Storage<null | string>;
255
256 constructor(key: string, storage: vscode.Memento) {
257 this.inner = new Storage(key, storage, null);
258 }
259
260 get(): null | Date {
261 const dateStr = this.inner.get();
262 return dateStr ? new Date(dateStr) : null;
263 }
264
265 async set(date: null | Date) {
266 await this.inner.set(date ? date.toString() : null);
267 }
268}
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 25ef38aed..c929ab063 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -4,19 +4,21 @@ import * as lc from 'vscode-languageclient';
4import { Config } from './config'; 4import { Config } from './config';
5import { createClient } from './client'; 5import { createClient } from './client';
6import { isRustEditor, RustEditor } from './util'; 6import { isRustEditor, RustEditor } from './util';
7import { PersistentState } from './persistent_state';
7 8
8export class Ctx { 9export class Ctx {
9 private constructor( 10 private constructor(
10 readonly config: Config, 11 readonly config: Config,
12 readonly state: PersistentState,
11 private readonly extCtx: vscode.ExtensionContext, 13 private readonly extCtx: vscode.ExtensionContext,
12 readonly client: lc.LanguageClient 14 readonly client: lc.LanguageClient
13 ) { 15 ) {
14 16
15 } 17 }
16 18
17 static async create(config: Config, extCtx: vscode.ExtensionContext, serverPath: string): Promise<Ctx> { 19 static async create(config: Config, state: PersistentState, extCtx: vscode.ExtensionContext, serverPath: string): Promise<Ctx> {
18 const client = await createClient(config, serverPath); 20 const client = await createClient(config, serverPath);
19 const res = new Ctx(config, extCtx, client); 21 const res = new Ctx(config, state, extCtx, client);
20 res.pushCleanup(client.start()); 22 res.pushCleanup(client.start());
21 await client.onReady(); 23 await client.onReady();
22 return res; 24 return res;
diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts
index eea6fded2..a1db96f05 100644
--- a/editors/code/src/installation/extension.ts
+++ b/editors/code/src/installation/extension.ts
@@ -7,6 +7,7 @@ import { Config, UpdatesChannel } from "../config";
7import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces"; 7import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces";
8import { downloadArtifactWithProgressUi } from "./downloads"; 8import { downloadArtifactWithProgressUi } from "./downloads";
9import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; 9import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
10import { PersistentState } from "../persistent_state";
10 11
11const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; 12const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25;
12 13
@@ -14,7 +15,7 @@ const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25;
14 * Installs `stable` or latest `nightly` version or does nothing if the current 15 * Installs `stable` or latest `nightly` version or does nothing if the current
15 * extension version is what's needed according to `desiredUpdateChannel`. 16 * extension version is what's needed according to `desiredUpdateChannel`.
16 */ 17 */
17export async function ensureProperExtensionVersion(config: Config): Promise<never | void> { 18export async function ensureProperExtensionVersion(config: Config, state: PersistentState): Promise<never | void> {
18 // User has built lsp server from sources, she should manage updates manually 19 // User has built lsp server from sources, she should manage updates manually
19 if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return; 20 if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return;
20 21
@@ -23,7 +24,7 @@ export async function ensureProperExtensionVersion(config: Config): Promise<neve
23 24
24 if (currentUpdChannel === UpdatesChannel.Stable) { 25 if (currentUpdChannel === UpdatesChannel.Stable) {
25 // Release date is present only when we are on nightly 26 // Release date is present only when we are on nightly
26 await config.installedNightlyExtensionReleaseDate.set(null); 27 await state.installedNightlyExtensionReleaseDate.set(null);
27 } 28 }
28 29
29 if (desiredUpdChannel === UpdatesChannel.Stable) { 30 if (desiredUpdChannel === UpdatesChannel.Stable) {
@@ -39,10 +40,10 @@ export async function ensureProperExtensionVersion(config: Config): Promise<neve
39 if (currentUpdChannel === UpdatesChannel.Stable) { 40 if (currentUpdChannel === UpdatesChannel.Stable) {
40 if (!await askToDownloadProperExtensionVersion(config)) return; 41 if (!await askToDownloadProperExtensionVersion(config)) return;
41 42
42 return await tryDownloadNightlyExtension(config); 43 return await tryDownloadNightlyExtension(config, state);
43 } 44 }
44 45
45 const currentExtReleaseDate = config.installedNightlyExtensionReleaseDate.get(); 46 const currentExtReleaseDate = state.installedNightlyExtensionReleaseDate.get();
46 47
47 if (currentExtReleaseDate === null) { 48 if (currentExtReleaseDate === null) {
48 void vscode.window.showErrorMessage( 49 void vscode.window.showErrorMessage(
@@ -66,9 +67,9 @@ export async function ensureProperExtensionVersion(config: Config): Promise<neve
66 return; 67 return;
67 } 68 }
68 69
69 await tryDownloadNightlyExtension(config, releaseInfo => { 70 await tryDownloadNightlyExtension(config, state, releaseInfo => {
70 assert( 71 assert(
71 currentExtReleaseDate.getTime() === config.installedNightlyExtensionReleaseDate.get()?.getTime(), 72 currentExtReleaseDate.getTime() === state.installedNightlyExtensionReleaseDate.get()?.getTime(),
72 "Other active VSCode instance has reinstalled the extension" 73 "Other active VSCode instance has reinstalled the extension"
73 ); 74 );
74 75
@@ -111,6 +112,7 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "")
111 */ 112 */
112const tryDownloadNightlyExtension = notReentrant(async ( 113const tryDownloadNightlyExtension = notReentrant(async (
113 config: Config, 114 config: Config,
115 state: PersistentState,
114 shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true 116 shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true
115): Promise<never | void> => { 117): Promise<never | void> => {
116 const vsixSource = config.nightlyVsixSource; 118 const vsixSource = config.nightlyVsixSource;
@@ -124,7 +126,7 @@ const tryDownloadNightlyExtension = notReentrant(async (
124 const vsixPath = path.join(vsixSource.dir, vsixSource.file); 126 const vsixPath = path.join(vsixSource.dir, vsixSource.file);
125 127
126 await vscodeInstallExtensionFromVsix(vsixPath); 128 await vscodeInstallExtensionFromVsix(vsixPath);
127 await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); 129 await state.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate);
128 await fs.unlink(vsixPath); 130 await fs.unlink(vsixPath);
129 131
130 await vscodeReloadWindow(); // never returns 132 await vscodeReloadWindow(); // never returns
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts
index 05730a778..05d326131 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";
7import { downloadArtifactWithProgressUi } from "./downloads"; 7import { downloadArtifactWithProgressUi } from "./downloads";
8import { log, assert, notReentrant } from "../util"; 8import { log, assert, notReentrant } from "../util";
9import { Config, NIGHTLY_TAG } from "../config"; 9import { Config, NIGHTLY_TAG } from "../config";
10import { PersistentState } from "../persistent_state";
10 11
11export async function ensureServerBinary(config: Config): Promise<null | string> { 12export async function ensureServerBinary(config: Config, state: PersistentState): Promise<null | string> {
12 const source = config.serverSource; 13 const source = config.serverSource;
13 14
14 if (!source) { 15 if (!source) {
@@ -37,7 +38,7 @@ export async function ensureServerBinary(config: Config): Promise<null | string>
37 return null; 38 return null;
38 } 39 }
39 case ArtifactSource.Type.GithubRelease: { 40 case ArtifactSource.Type.GithubRelease: {
40 if (!shouldDownloadServer(source, config)) { 41 if (!shouldDownloadServer(state, source)) {
41 return path.join(source.dir, source.file); 42 return path.join(source.dir, source.file);
42 } 43 }
43 44
@@ -50,24 +51,24 @@ export async function ensureServerBinary(config: Config): Promise<null | string>
50 if (userResponse !== "Download now") return null; 51 if (userResponse !== "Download now") return null;
51 } 52 }
52 53
53 return await downloadServer(source, config); 54 return await downloadServer(state, source);
54 } 55 }
55 } 56 }
56} 57}
57 58
58function shouldDownloadServer( 59function shouldDownloadServer(
60 state: PersistentState,
59 source: ArtifactSource.GithubRelease, 61 source: ArtifactSource.GithubRelease,
60 config: Config
61): boolean { 62): boolean {
62 if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; 63 if (!isBinaryAvailable(path.join(source.dir, source.file))) return true;
63 64
64 const installed = { 65 const installed = {
65 tag: config.serverReleaseTag.get(), 66 tag: state.serverReleaseTag.get(),
66 date: config.serverReleaseDate.get() 67 date: state.serverReleaseDate.get()
67 }; 68 };
68 const required = { 69 const required = {
69 tag: source.tag, 70 tag: source.tag,
70 date: config.installedNightlyExtensionReleaseDate.get() 71 date: state.installedNightlyExtensionReleaseDate.get()
71 }; 72 };
72 73
73 log.debug("Installed server:", installed, "required:", required); 74 log.debug("Installed server:", installed, "required:", required);
@@ -86,16 +87,16 @@ function shouldDownloadServer(
86 * Enforcing no reentrancy for this is best-effort. 87 * Enforcing no reentrancy for this is best-effort.
87 */ 88 */
88const downloadServer = notReentrant(async ( 89const downloadServer = notReentrant(async (
90 state: PersistentState,
89 source: ArtifactSource.GithubRelease, 91 source: ArtifactSource.GithubRelease,
90 config: Config,
91): Promise<null | string> => { 92): Promise<null | string> => {
92 try { 93 try {
93 const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); 94 const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag);
94 95
95 await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); 96 await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server");
96 await Promise.all([ 97 await Promise.all([
97 config.serverReleaseTag.set(releaseInfo.releaseName), 98 state.serverReleaseTag.set(releaseInfo.releaseName),
98 config.serverReleaseDate.set(releaseInfo.releaseDate) 99 state.serverReleaseDate.set(releaseInfo.releaseDate)
99 ]); 100 ]);
100 } catch (err) { 101 } catch (err) {
101 log.downloadError(err, "language server", source.repo.name); 102 log.downloadError(err, "language server", source.repo.name);
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index bd4661a36..94ecd4dab 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -9,6 +9,7 @@ import { ensureServerBinary } from './installation/server';
9import { Config } from './config'; 9import { Config } from './config';
10import { log } from './util'; 10import { log } from './util';
11import { ensureProperExtensionVersion } from './installation/extension'; 11import { ensureProperExtensionVersion } from './installation/extension';
12import { PersistentState } from './persistent_state';
12 13
13let ctx: Ctx | undefined; 14let ctx: Ctx | undefined;
14 15
@@ -34,13 +35,14 @@ export async function activate(context: vscode.ExtensionContext) {
34 context.subscriptions.push(defaultOnEnter); 35 context.subscriptions.push(defaultOnEnter);
35 36
36 const config = new Config(context); 37 const config = new Config(context);
38 const state = new PersistentState(context);
37 39
38 vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config).catch(log.error)); 40 vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config, state).catch(log.error));
39 41
40 // Don't await the user response here, otherwise we will block the lsp server bootstrap 42 // Don't await the user response here, otherwise we will block the lsp server bootstrap
41 void ensureProperExtensionVersion(config).catch(log.error); 43 void ensureProperExtensionVersion(config, state).catch(log.error);
42 44
43 const serverPath = await ensureServerBinary(config); 45 const serverPath = await ensureServerBinary(config, state);
44 46
45 if (serverPath == null) { 47 if (serverPath == null) {
46 throw new Error( 48 throw new Error(
@@ -53,7 +55,7 @@ export async function activate(context: vscode.ExtensionContext) {
53 // registers its `onDidChangeDocument` handler before us. 55 // registers its `onDidChangeDocument` handler before us.
54 // 56 //
55 // This a horribly, horribly wrong way to deal with this problem. 57 // This a horribly, horribly wrong way to deal with this problem.
56 ctx = await Ctx.create(config, context, serverPath); 58 ctx = await Ctx.create(config, state, context, serverPath);
57 59
58 // Commands which invokes manually via command palette, shortcut, etc. 60 // Commands which invokes manually via command palette, shortcut, etc.
59 ctx.registerCommand('reload', (ctx) => { 61 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 000000000..13095b806
--- /dev/null
+++ b/editors/code/src/persistent_state.ts
@@ -0,0 +1,49 @@
1import * as vscode from 'vscode';
2import { log } from "./util";
3
4export class PersistentState {
5 constructor(private readonly ctx: vscode.ExtensionContext) {
6 }
7
8 readonly installedNightlyExtensionReleaseDate = new DateStorage(
9 "installed-nightly-extension-release-date",
10 this.ctx.globalState
11 );
12 readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState);
13 readonly serverReleaseTag = new Storage<null | string>("server-release-tag", this.ctx.globalState, null);
14}
15
16
17export class Storage<T> {
18 constructor(
19 private readonly key: string,
20 private readonly storage: vscode.Memento,
21 private readonly defaultVal: T
22 ) { }
23
24 get(): T {
25 const val = this.storage.get(this.key, this.defaultVal);
26 log.debug(this.key, "==", val);
27 return val;
28 }
29 async set(val: T) {
30 log.debug(this.key, "=", val);
31 await this.storage.update(this.key, val);
32 }
33}
34export class DateStorage {
35 inner: Storage<null | string>;
36
37 constructor(key: string, storage: vscode.Memento) {
38 this.inner = new Storage(key, storage, null);
39 }
40
41 get(): null | Date {
42 const dateStr = this.inner.get();
43 return dateStr ? new Date(dateStr) : null;
44 }
45
46 async set(date: null | Date) {
47 await this.inner.set(date ? date.toString() : null);
48 }
49}