]> git.lizzy.rs Git - rust.git/blob - editors/code/src/config.ts
Support loading OUT_DIR from cargo check at launch
[rust.git] / editors / code / src / config.ts
1 import * as os from "os";
2 import * as vscode from 'vscode';
3 import { ArtifactSource } from "./installation/interfaces";
4 import { log, vscodeReloadWindow } from "./util";
5
6 const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
7
8 export interface InlayHintOptions {
9     typeHints: boolean;
10     parameterHints: boolean;
11     maxLength: number | null;
12 }
13
14 export interface CargoWatchOptions {
15     enable: boolean;
16     arguments: string[];
17     command: string;
18     allTargets: boolean;
19 }
20
21 export interface CargoFeatures {
22     noDefaultFeatures: boolean;
23     allFeatures: boolean;
24     features: string[];
25     loadOutDirsFromCheck: boolean;
26 }
27
28 export const enum UpdatesChannel {
29     Stable = "stable",
30     Nightly = "nightly"
31 }
32
33 export const NIGHTLY_TAG = "nightly";
34 export class Config {
35     readonly extensionId = "matklad.rust-analyzer";
36
37     private readonly rootSection = "rust-analyzer";
38     private readonly requiresReloadOpts = [
39         "serverPath",
40         "cargoFeatures",
41         "cargo-watch",
42         "highlighting.semanticTokens",
43         "inlayHints",
44     ]
45         .map(opt => `${this.rootSection}.${opt}`);
46
47     readonly packageJsonVersion = vscode
48         .extensions
49         .getExtension(this.extensionId)!
50         .packageJSON
51         .version as string; // n.n.YYYYMMDD[-nightly]
52
53     /**
54      * Either `nightly` or `YYYY-MM-DD` (i.e. `stable` release)
55      */
56     readonly extensionReleaseTag: string = (() => {
57         if (this.packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG;
58
59         const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/;
60         const [, yyyy, mm, dd] = this.packageJsonVersion.match(realVersionRegexp)!;
61
62         return `${yyyy}-${mm}-${dd}`;
63     })();
64
65     private cfg!: vscode.WorkspaceConfiguration;
66
67     constructor(private readonly ctx: vscode.ExtensionContext) {
68         vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions);
69         this.refreshConfig();
70     }
71
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);
76         log.debug(
77             "Extension version:", this.packageJsonVersion,
78             "using configuration:", this.cfg
79         );
80     }
81
82     private async onConfigChange(event: vscode.ConfigurationChangeEvent) {
83         this.refreshConfig();
84
85         const requiresReloadOpt = this.requiresReloadOpts.find(
86             opt => event.affectsConfiguration(opt)
87         );
88
89         if (!requiresReloadOpt) return;
90
91         const userResponse = await vscode.window.showInformationMessage(
92             `Changing "${requiresReloadOpt}" requires a reload`,
93             "Reload now"
94         );
95
96         if (userResponse === "Reload now") {
97             await vscodeReloadWindow();
98         }
99     }
100
101     private static replaceTildeWithHomeDir(path: string) {
102         if (path.startsWith("~/")) {
103             return os.homedir() + path.slice("~".length);
104         }
105         return path;
106     }
107
108     /**
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).
112      */
113     get prebuiltServerFileName(): null | string {
114         // See possible `arch` values here:
115         // https://nodejs.org/api/process.html#process_process_arch
116
117         switch (process.platform) {
118
119             case "linux": {
120                 switch (process.arch) {
121                     case "arm":
122                     case "arm64": return null;
123
124                     default: return "rust-analyzer-linux";
125                 }
126             }
127
128             case "darwin": return "rust-analyzer-mac";
129             case "win32": return "rust-analyzer-windows.exe";
130
131             // Users on these platforms yet need to manually build from sources
132             case "aix":
133             case "android":
134             case "freebsd":
135             case "openbsd":
136             case "sunos":
137             case "cygwin":
138             case "netbsd": return null;
139             // The list of platforms is exhaustive (see `NodeJS.Platform` type definition)
140         }
141     }
142
143     get installedExtensionUpdateChannel(): UpdatesChannel {
144         return this.extensionReleaseTag === NIGHTLY_TAG
145             ? UpdatesChannel.Nightly
146             : UpdatesChannel.Stable;
147     }
148
149     get serverSource(): null | ArtifactSource {
150         const serverPath = RA_LSP_DEBUG ?? this.serverPath;
151
152         if (serverPath) {
153             return {
154                 type: ArtifactSource.Type.ExplicitPath,
155                 path: Config.replaceTildeWithHomeDir(serverPath)
156             };
157         }
158
159         const prebuiltBinaryName = this.prebuiltServerFileName;
160
161         if (!prebuiltBinaryName) return null;
162
163         return this.createGithubReleaseSource(
164             prebuiltBinaryName,
165             this.extensionReleaseTag
166         );
167     }
168
169     private createGithubReleaseSource(file: string, tag: string): ArtifactSource.GithubRelease {
170         return {
171             type: ArtifactSource.Type.GithubRelease,
172             file,
173             tag,
174             dir: this.ctx.globalStoragePath,
175             repo: {
176                 name: "rust-analyzer",
177                 owner: "rust-analyzer",
178             }
179         };
180     }
181
182     get nightlyVsixSource(): ArtifactSource.GithubRelease {
183         return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG);
184     }
185
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
188
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 {
197         return {
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,
201         };
202     }
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; }
209
210     get cargoWatchOptions(): CargoWatchOptions {
211         return {
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,
216         };
217     }
218
219     get cargoFeatures(): CargoFeatures {
220         return {
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,
225         };
226     }
227
228     // for internal use
229     get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; }
230 }