1 import * as os from "os";
2 import * as vscode from 'vscode';
3 import { ArtifactSource } from "./installation/interfaces";
4 import { log, vscodeReloadWindow } from "./util";
6 const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
8 export interface InlayHintOptions {
10 parameterHints: boolean;
11 maxLength: number | null;
14 export interface CargoWatchOptions {
21 export interface CargoFeatures {
22 noDefaultFeatures: boolean;
25 loadOutDirsFromCheck: boolean;
28 export const enum UpdatesChannel {
33 export const NIGHTLY_TAG = "nightly";
35 readonly extensionId = "matklad.rust-analyzer";
37 private readonly rootSection = "rust-analyzer";
38 private readonly requiresReloadOpts = [
42 "highlighting.semanticTokens",
45 .map(opt => `${this.rootSection}.${opt}`);
47 readonly packageJsonVersion = vscode
49 .getExtension(this.extensionId)!
51 .version as string; // n.n.YYYYMMDD[-nightly]
54 * Either `nightly` or `YYYY-MM-DD` (i.e. `stable` release)
56 readonly extensionReleaseTag: string = (() => {
57 if (this.packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG;
59 const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/;
60 const [, yyyy, mm, dd] = this.packageJsonVersion.match(realVersionRegexp)!;
62 return `${yyyy}-${mm}-${dd}`;
65 private cfg!: vscode.WorkspaceConfiguration;
67 constructor(private readonly ctx: vscode.ExtensionContext) {
68 vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions);
72 private refreshConfig() {
73 this.cfg = vscode.workspace.getConfiguration(this.rootSection);
74 const enableLogging = this.cfg.get("trace.extension") as boolean;
75 log.setEnabled(enableLogging);
77 "Extension version:", this.packageJsonVersion,
78 "using configuration:", this.cfg
82 private async onConfigChange(event: vscode.ConfigurationChangeEvent) {
85 const requiresReloadOpt = this.requiresReloadOpts.find(
86 opt => event.affectsConfiguration(opt)
89 if (!requiresReloadOpt) return;
91 const userResponse = await vscode.window.showInformationMessage(
92 `Changing "${requiresReloadOpt}" requires a reload`,
96 if (userResponse === "Reload now") {
97 await vscodeReloadWindow();
101 private static replaceTildeWithHomeDir(path: string) {
102 if (path.startsWith("~/")) {
103 return os.homedir() + path.slice("~".length);
109 * Name of the binary artifact for `rust-analyzer` that is published for
110 * `platform` on GitHub releases. (It is also stored under the same name when
111 * downloaded by the extension).
113 get prebuiltServerFileName(): null | string {
114 // See possible `arch` values here:
115 // https://nodejs.org/api/process.html#process_process_arch
117 switch (process.platform) {
120 switch (process.arch) {
122 case "arm64": return null;
124 default: return "rust-analyzer-linux";
128 case "darwin": return "rust-analyzer-mac";
129 case "win32": return "rust-analyzer-windows.exe";
131 // Users on these platforms yet need to manually build from sources
138 case "netbsd": return null;
139 // The list of platforms is exhaustive (see `NodeJS.Platform` type definition)
143 get installedExtensionUpdateChannel(): UpdatesChannel {
144 return this.extensionReleaseTag === NIGHTLY_TAG
145 ? UpdatesChannel.Nightly
146 : UpdatesChannel.Stable;
149 get serverSource(): null | ArtifactSource {
150 const serverPath = RA_LSP_DEBUG ?? this.serverPath;
154 type: ArtifactSource.Type.ExplicitPath,
155 path: Config.replaceTildeWithHomeDir(serverPath)
159 const prebuiltBinaryName = this.prebuiltServerFileName;
161 if (!prebuiltBinaryName) return null;
163 return this.createGithubReleaseSource(
165 this.extensionReleaseTag
169 private createGithubReleaseSource(file: string, tag: string): ArtifactSource.GithubRelease {
171 type: ArtifactSource.Type.GithubRelease,
174 dir: this.ctx.globalStoragePath,
176 name: "rust-analyzer",
177 owner: "rust-analyzer",
182 get nightlyVsixSource(): ArtifactSource.GithubRelease {
183 return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG);
186 // We don't do runtime config validation here for simplicity. More on stackoverflow:
187 // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension
189 private get serverPath() { return this.cfg.get("serverPath") as null | string; }
190 get updatesChannel() { return this.cfg.get("updates.channel") as UpdatesChannel; }
191 get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; }
192 get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; }
193 get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; }
194 get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; }
195 get lruCapacity() { return this.cfg.get("lruCapacity") as null | number; }
196 get inlayHints(): InlayHintOptions {
198 typeHints: this.cfg.get("inlayHints.typeHints") as boolean,
199 parameterHints: this.cfg.get("inlayHints.parameterHints") as boolean,
200 maxLength: this.cfg.get("inlayHints.maxLength") as null | number,
203 get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; }
204 get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; }
205 get featureFlags() { return this.cfg.get("featureFlags") as Record<string, boolean>; }
206 get additionalOutDirs() { return this.cfg.get("additionalOutDirs") as Record<string, string>; }
207 get rustfmtArgs() { return this.cfg.get("rustfmtArgs") as string[]; }
208 get loadOutDirsFromCheck() { return this.cfg.get("loadOutDirsFromCheck") as boolean; }
210 get cargoWatchOptions(): CargoWatchOptions {
212 enable: this.cfg.get("cargo-watch.enable") as boolean,
213 arguments: this.cfg.get("cargo-watch.arguments") as string[],
214 allTargets: this.cfg.get("cargo-watch.allTargets") as boolean,
215 command: this.cfg.get("cargo-watch.command") as string,
219 get cargoFeatures(): CargoFeatures {
221 noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean,
222 allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean,
223 features: this.cfg.get("cargoFeatures.features") as string[],
224 loadOutDirsFromCheck: this.cfg.get("cargoFeatures.loadOutDirsFromCheck") as boolean,
229 get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; }