diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-03-16 10:26:31 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-03-16 10:26:31 +0000 |
commit | 200c275c2e9955371e61f6ad7684084655df46fc (patch) | |
tree | c4b61de644cec37cffca9010d56afc4136d23ca8 /editors/code/src/config.ts | |
parent | a99cac671c3e6105a0192acbb1a91cb83e453018 (diff) | |
parent | 5a0041c5aaeee49be84ce771fb0360ae55cbd8b2 (diff) |
Merge #3534
3534: Feature: vscode impl nightlies download and installation r=Veetaha a=Veetaha
I need to test things more, but the core shape of the code is quite well-formed.
The main problem is that we save the release date only for nightlies and there are no means to get the release date of the stable extension (i.e. for this we would need to consult the github releases via a network request, or we would need to somehow save this info into package.json or any other file accessible from the extension code during the deployment step, but this will be very hard I guess).
So there is an invariant that the users can install nightly only from our extension and they can't do it manually, because when installing the nightly `.vsix` we actually save its release date to `globalState`
Closes: #3402
TODO:
- [x] More manual tests and documentation
cc @matklad @lnicola
Co-authored-by: Veetaha <[email protected]>
Co-authored-by: Veetaha <[email protected]>
Diffstat (limited to 'editors/code/src/config.ts')
-rw-r--r-- | editors/code/src/config.ts | 117 |
1 files changed, 97 insertions, 20 deletions
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 6db073bec..f63e1d20e 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as os from "os"; | 1 | import * as os from "os"; |
2 | import * as vscode from 'vscode'; | 2 | import * as vscode from 'vscode'; |
3 | import { ArtifactSource } from "./installation/interfaces"; | 3 | import { ArtifactSource } from "./installation/interfaces"; |
4 | import { log } from "./util"; | 4 | import { log, vscodeReloadWindow } from "./util"; |
5 | 5 | ||
6 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; | 6 | const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; |
7 | 7 | ||
@@ -23,25 +23,40 @@ export interface CargoFeatures { | |||
23 | allFeatures: boolean; | 23 | allFeatures: boolean; |
24 | features: string[]; | 24 | features: string[]; |
25 | } | 25 | } |
26 | |||
27 | export const enum UpdatesChannel { | ||
28 | Stable = "stable", | ||
29 | Nightly = "nightly" | ||
30 | } | ||
31 | |||
32 | export const NIGHTLY_TAG = "nightly"; | ||
26 | export class Config { | 33 | export class Config { |
27 | private static readonly rootSection = "rust-analyzer"; | 34 | readonly extensionId = "matklad.rust-analyzer"; |
28 | private static readonly requiresReloadOpts = [ | 35 | |
36 | private readonly rootSection = "rust-analyzer"; | ||
37 | private readonly requiresReloadOpts = [ | ||
38 | "serverPath", | ||
29 | "cargoFeatures", | 39 | "cargoFeatures", |
30 | "cargo-watch", | 40 | "cargo-watch", |
31 | "highlighting.semanticTokens", | 41 | "highlighting.semanticTokens", |
32 | "inlayHints", | 42 | "inlayHints", |
33 | ] | 43 | ] |
34 | .map(opt => `${Config.rootSection}.${opt}`); | 44 | .map(opt => `${this.rootSection}.${opt}`); |
35 | 45 | ||
36 | private static readonly extensionVersion: string = (() => { | 46 | readonly packageJsonVersion = vscode |
37 | const packageJsonVersion = vscode | 47 | .extensions |
38 | .extensions | 48 | .getExtension(this.extensionId)! |
39 | .getExtension("matklad.rust-analyzer")! | 49 | .packageJSON |
40 | .packageJSON | 50 | .version as string; // n.n.YYYYMMDD[-nightly] |
41 | .version as string; // n.n.YYYYMMDD | 51 | |
52 | /** | ||
53 | * Either `nightly` or `YYYY-MM-DD` (i.e. `stable` release) | ||
54 | */ | ||
55 | readonly extensionReleaseTag: string = (() => { | ||
56 | if (this.packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG; | ||
42 | 57 | ||
43 | const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; | 58 | const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; |
44 | const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!; | 59 | const [, yyyy, mm, dd] = this.packageJsonVersion.match(realVersionRegexp)!; |
45 | 60 | ||
46 | return `${yyyy}-${mm}-${dd}`; | 61 | return `${yyyy}-${mm}-${dd}`; |
47 | })(); | 62 | })(); |
@@ -54,16 +69,19 @@ export class Config { | |||
54 | } | 69 | } |
55 | 70 | ||
56 | private refreshConfig() { | 71 | private refreshConfig() { |
57 | this.cfg = vscode.workspace.getConfiguration(Config.rootSection); | 72 | this.cfg = vscode.workspace.getConfiguration(this.rootSection); |
58 | const enableLogging = this.cfg.get("trace.extension") as boolean; | 73 | const enableLogging = this.cfg.get("trace.extension") as boolean; |
59 | log.setEnabled(enableLogging); | 74 | log.setEnabled(enableLogging); |
60 | log.debug("Using configuration:", this.cfg); | 75 | log.debug( |
76 | "Extension version:", this.packageJsonVersion, | ||
77 | "using configuration:", this.cfg | ||
78 | ); | ||
61 | } | 79 | } |
62 | 80 | ||
63 | private async onConfigChange(event: vscode.ConfigurationChangeEvent) { | 81 | private async onConfigChange(event: vscode.ConfigurationChangeEvent) { |
64 | this.refreshConfig(); | 82 | this.refreshConfig(); |
65 | 83 | ||
66 | const requiresReloadOpt = Config.requiresReloadOpts.find( | 84 | const requiresReloadOpt = this.requiresReloadOpts.find( |
67 | opt => event.affectsConfiguration(opt) | 85 | opt => event.affectsConfiguration(opt) |
68 | ); | 86 | ); |
69 | 87 | ||
@@ -75,7 +93,7 @@ export class Config { | |||
75 | ); | 93 | ); |
76 | 94 | ||
77 | if (userResponse === "Reload now") { | 95 | if (userResponse === "Reload now") { |
78 | vscode.commands.executeCommand("workbench.action.reloadWindow"); | 96 | await vscodeReloadWindow(); |
79 | } | 97 | } |
80 | } | 98 | } |
81 | 99 | ||
@@ -121,8 +139,14 @@ export class Config { | |||
121 | } | 139 | } |
122 | } | 140 | } |
123 | 141 | ||
142 | get installedExtensionUpdateChannel(): UpdatesChannel { | ||
143 | return this.extensionReleaseTag === NIGHTLY_TAG | ||
144 | ? UpdatesChannel.Nightly | ||
145 | : UpdatesChannel.Stable; | ||
146 | } | ||
147 | |||
124 | get serverSource(): null | ArtifactSource { | 148 | get serverSource(): null | ArtifactSource { |
125 | const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("serverPath"); | 149 | const serverPath = RA_LSP_DEBUG ?? this.serverPath; |
126 | 150 | ||
127 | if (serverPath) { | 151 | if (serverPath) { |
128 | return { | 152 | return { |
@@ -135,13 +159,18 @@ export class Config { | |||
135 | 159 | ||
136 | if (!prebuiltBinaryName) return null; | 160 | if (!prebuiltBinaryName) return null; |
137 | 161 | ||
162 | return this.createGithubReleaseSource( | ||
163 | prebuiltBinaryName, | ||
164 | this.extensionReleaseTag | ||
165 | ); | ||
166 | } | ||
167 | |||
168 | private createGithubReleaseSource(file: string, tag: string): ArtifactSource.GithubRelease { | ||
138 | return { | 169 | return { |
139 | type: ArtifactSource.Type.GithubRelease, | 170 | type: ArtifactSource.Type.GithubRelease, |
171 | file, | ||
172 | tag, | ||
140 | dir: this.ctx.globalStoragePath, | 173 | dir: this.ctx.globalStoragePath, |
141 | file: prebuiltBinaryName, | ||
142 | storage: this.ctx.globalState, | ||
143 | tag: Config.extensionVersion, | ||
144 | askBeforeDownload: this.cfg.get("updates.askBeforeDownload") as boolean, | ||
145 | repo: { | 174 | repo: { |
146 | name: "rust-analyzer", | 175 | name: "rust-analyzer", |
147 | owner: "rust-analyzer", | 176 | owner: "rust-analyzer", |
@@ -149,9 +178,23 @@ export class Config { | |||
149 | }; | 178 | }; |
150 | } | 179 | } |
151 | 180 | ||
181 | get nightlyVsixSource(): ArtifactSource.GithubRelease { | ||
182 | return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG); | ||
183 | } | ||
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 | |||
152 | // We don't do runtime config validation here for simplicity. More on stackoverflow: | 192 | // We don't do runtime config validation here for simplicity. More on stackoverflow: |
153 | // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension | 193 | // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension |
154 | 194 | ||
195 | private get serverPath() { return this.cfg.get("serverPath") as null | string; } | ||
196 | get updatesChannel() { return this.cfg.get("updates.channel") as UpdatesChannel; } | ||
197 | get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; } | ||
155 | get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } | 198 | get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } |
156 | get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } | 199 | get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } |
157 | get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } | 200 | get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } |
@@ -189,3 +232,37 @@ export class Config { | |||
189 | // for internal use | 232 | // for internal use |
190 | get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } | 233 | get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } |
191 | } | 234 | } |
235 | |||
236 | export 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 | } | ||
253 | export 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 | } | ||