diff options
Diffstat (limited to 'editors/code/src/config.ts')
-rw-r--r-- | editors/code/src/config.ts | 294 |
1 files changed, 91 insertions, 203 deletions
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 418845436..70cb0a612 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -16,45 +16,62 @@ export interface CargoFeatures { | |||
16 | allFeatures: boolean; | 16 | allFeatures: boolean; |
17 | features: string[]; | 17 | features: string[]; |
18 | } | 18 | } |
19 | |||
20 | export class Config { | 19 | export class Config { |
21 | langServerSource!: null | BinarySource; | 20 | private static readonly rootSection = "rust-analyzer"; |
22 | 21 | private static readonly requiresReloadOpts = [ | |
23 | highlightingOn = true; | 22 | "cargoFeatures", |
24 | rainbowHighlightingOn = false; | 23 | "cargo-watch", |
25 | enableEnhancedTyping = true; | 24 | ] |
26 | lruCapacity: null | number = null; | 25 | .map(opt => `${Config.rootSection}.${opt}`); |
27 | displayInlayHints = true; | 26 | |
28 | maxInlayHintLength: null | number = null; | 27 | private static readonly extensionVersion: string = (() => { |
29 | excludeGlobs: string[] = []; | 28 | const packageJsonVersion = vscode |
30 | useClientWatching = true; | 29 | .extensions |
31 | featureFlags: Record<string, boolean> = {}; | 30 | .getExtension("matklad.rust-analyzer")! |
32 | // for internal use | 31 | .packageJSON |
33 | withSysroot: null | boolean = null; | 32 | .version as string; // n.n.YYYYMMDD |
34 | cargoWatchOptions: CargoWatchOptions = { | 33 | |
35 | enable: true, | 34 | const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; |
36 | arguments: [], | 35 | const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!; |
37 | command: '', | 36 | |
38 | allTargets: true, | 37 | return `${yyyy}-${mm}-${dd}`; |
39 | }; | 38 | })(); |
40 | cargoFeatures: CargoFeatures = { | 39 | |
41 | noDefaultFeatures: false, | 40 | private cfg!: vscode.WorkspaceConfiguration; |
42 | allFeatures: true, | 41 | |
43 | features: [], | 42 | constructor(private readonly ctx: vscode.ExtensionContext) { |
44 | }; | 43 | vscode.workspace.onDidChangeConfiguration(this.onConfigChange, this, ctx.subscriptions); |
45 | 44 | this.refreshConfig(); | |
46 | private prevEnhancedTyping: null | boolean = null; | 45 | } |
47 | private prevCargoFeatures: null | CargoFeatures = null; | 46 | |
48 | private prevCargoWatchOptions: null | CargoWatchOptions = null; | 47 | |
49 | 48 | private refreshConfig() { | |
50 | constructor(ctx: vscode.ExtensionContext) { | 49 | this.cfg = vscode.workspace.getConfiguration(Config.rootSection); |
51 | vscode.workspace.onDidChangeConfiguration(_ => this.refresh(ctx), null, ctx.subscriptions); | 50 | console.log("Using configuration:", this.cfg); |
52 | this.refresh(ctx); | ||
53 | } | 51 | } |
54 | 52 | ||
55 | private static expandPathResolving(path: string) { | 53 | private async onConfigChange(event: vscode.ConfigurationChangeEvent) { |
56 | if (path.startsWith('~/')) { | 54 | this.refreshConfig(); |
57 | return path.replace('~', os.homedir()); | 55 | |
56 | const requiresReloadOpt = Config.requiresReloadOpts.find( | ||
57 | opt => event.affectsConfiguration(opt) | ||
58 | ); | ||
59 | |||
60 | if (!requiresReloadOpt) return; | ||
61 | |||
62 | const userResponse = await vscode.window.showInformationMessage( | ||
63 | `Changing "${requiresReloadOpt}" requires a reload`, | ||
64 | "Reload now" | ||
65 | ); | ||
66 | |||
67 | if (userResponse === "Reload now") { | ||
68 | vscode.commands.executeCommand("workbench.action.reloadWindow"); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | private static replaceTildeWithHomeDir(path: string) { | ||
73 | if (path.startsWith("~/")) { | ||
74 | return os.homedir() + path.slice("~".length); | ||
58 | } | 75 | } |
59 | return path; | 76 | return path; |
60 | } | 77 | } |
@@ -64,17 +81,14 @@ export class Config { | |||
64 | * `platform` on GitHub releases. (It is also stored under the same name when | 81 | * `platform` on GitHub releases. (It is also stored under the same name when |
65 | * downloaded by the extension). | 82 | * downloaded by the extension). |
66 | */ | 83 | */ |
67 | private static prebuiltLangServerFileName( | 84 | get prebuiltServerFileName(): null | string { |
68 | platform: NodeJS.Platform, | ||
69 | arch: string | ||
70 | ): null | string { | ||
71 | // See possible `arch` values here: | 85 | // See possible `arch` values here: |
72 | // https://nodejs.org/api/process.html#process_process_arch | 86 | // https://nodejs.org/api/process.html#process_process_arch |
73 | 87 | ||
74 | switch (platform) { | 88 | switch (process.platform) { |
75 | 89 | ||
76 | case "linux": { | 90 | case "linux": { |
77 | switch (arch) { | 91 | switch (process.arch) { |
78 | case "arm": | 92 | case "arm": |
79 | case "arm64": return null; | 93 | case "arm64": return null; |
80 | 94 | ||
@@ -97,29 +111,26 @@ export class Config { | |||
97 | } | 111 | } |
98 | } | 112 | } |
99 | 113 | ||
100 | private static langServerBinarySource( | 114 | get serverSource(): null | BinarySource { |
101 | ctx: vscode.ExtensionContext, | 115 | const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("raLspServerPath"); |
102 | config: vscode.WorkspaceConfiguration | ||
103 | ): null | BinarySource { | ||
104 | const langServerPath = RA_LSP_DEBUG ?? config.get<null | string>("raLspServerPath"); | ||
105 | 116 | ||
106 | if (langServerPath) { | 117 | if (serverPath) { |
107 | return { | 118 | return { |
108 | type: BinarySource.Type.ExplicitPath, | 119 | type: BinarySource.Type.ExplicitPath, |
109 | path: Config.expandPathResolving(langServerPath) | 120 | path: Config.replaceTildeWithHomeDir(serverPath) |
110 | }; | 121 | }; |
111 | } | 122 | } |
112 | 123 | ||
113 | const prebuiltBinaryName = Config.prebuiltLangServerFileName( | 124 | const prebuiltBinaryName = this.prebuiltServerFileName; |
114 | process.platform, process.arch | ||
115 | ); | ||
116 | 125 | ||
117 | if (!prebuiltBinaryName) return null; | 126 | if (!prebuiltBinaryName) return null; |
118 | 127 | ||
119 | return { | 128 | return { |
120 | type: BinarySource.Type.GithubRelease, | 129 | type: BinarySource.Type.GithubRelease, |
121 | dir: ctx.globalStoragePath, | 130 | dir: this.ctx.globalStoragePath, |
122 | file: prebuiltBinaryName, | 131 | file: prebuiltBinaryName, |
132 | storage: this.ctx.globalState, | ||
133 | version: Config.extensionVersion, | ||
123 | repo: { | 134 | repo: { |
124 | name: "rust-analyzer", | 135 | name: "rust-analyzer", |
125 | owner: "rust-analyzer", | 136 | owner: "rust-analyzer", |
@@ -127,158 +138,35 @@ export class Config { | |||
127 | }; | 138 | }; |
128 | } | 139 | } |
129 | 140 | ||
141 | // We don't do runtime config validation here for simplicity. More on stackoverflow: | ||
142 | // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension | ||
130 | 143 | ||
131 | // FIXME: revisit the logic for `if (.has(...)) config.get(...)` set default | 144 | get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } |
132 | // values only in one place (i.e. remove default values from non-readonly members declarations) | 145 | get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } |
133 | private refresh(ctx: vscode.ExtensionContext) { | 146 | get lruCapacity() { return this.cfg.get("lruCapacity") as null | number; } |
134 | const config = vscode.workspace.getConfiguration('rust-analyzer'); | 147 | get displayInlayHints() { return this.cfg.get("displayInlayHints") as boolean; } |
135 | 148 | get maxInlayHintLength() { return this.cfg.get("maxInlayHintLength") as number; } | |
136 | let requireReloadMessage = null; | 149 | get excludeGlobs() { return this.cfg.get("excludeGlobs") as string[]; } |
137 | 150 | get useClientWatching() { return this.cfg.get("useClientWatching") as boolean; } | |
138 | if (config.has('highlightingOn')) { | 151 | get featureFlags() { return this.cfg.get("featureFlags") as Record<string, boolean>; } |
139 | this.highlightingOn = config.get('highlightingOn') as boolean; | ||
140 | } | ||
141 | |||
142 | if (config.has('rainbowHighlightingOn')) { | ||
143 | this.rainbowHighlightingOn = config.get( | ||
144 | 'rainbowHighlightingOn', | ||
145 | ) as boolean; | ||
146 | } | ||
147 | |||
148 | if (config.has('enableEnhancedTyping')) { | ||
149 | this.enableEnhancedTyping = config.get( | ||
150 | 'enableEnhancedTyping', | ||
151 | ) as boolean; | ||
152 | |||
153 | if (this.prevEnhancedTyping === null) { | ||
154 | this.prevEnhancedTyping = this.enableEnhancedTyping; | ||
155 | } | ||
156 | } else if (this.prevEnhancedTyping === null) { | ||
157 | this.prevEnhancedTyping = this.enableEnhancedTyping; | ||
158 | } | ||
159 | |||
160 | if (this.prevEnhancedTyping !== this.enableEnhancedTyping) { | ||
161 | requireReloadMessage = | ||
162 | 'Changing enhanced typing setting requires a reload'; | ||
163 | this.prevEnhancedTyping = this.enableEnhancedTyping; | ||
164 | } | ||
165 | |||
166 | this.langServerSource = Config.langServerBinarySource(ctx, config); | ||
167 | |||
168 | if (config.has('cargo-watch.enable')) { | ||
169 | this.cargoWatchOptions.enable = config.get<boolean>( | ||
170 | 'cargo-watch.enable', | ||
171 | true, | ||
172 | ); | ||
173 | } | ||
174 | |||
175 | if (config.has('cargo-watch.arguments')) { | ||
176 | this.cargoWatchOptions.arguments = config.get<string[]>( | ||
177 | 'cargo-watch.arguments', | ||
178 | [], | ||
179 | ); | ||
180 | } | ||
181 | |||
182 | if (config.has('cargo-watch.command')) { | ||
183 | this.cargoWatchOptions.command = config.get<string>( | ||
184 | 'cargo-watch.command', | ||
185 | '', | ||
186 | ); | ||
187 | } | ||
188 | |||
189 | if (config.has('cargo-watch.allTargets')) { | ||
190 | this.cargoWatchOptions.allTargets = config.get<boolean>( | ||
191 | 'cargo-watch.allTargets', | ||
192 | true, | ||
193 | ); | ||
194 | } | ||
195 | |||
196 | if (config.has('lruCapacity')) { | ||
197 | this.lruCapacity = config.get('lruCapacity') as number; | ||
198 | } | ||
199 | |||
200 | if (config.has('displayInlayHints')) { | ||
201 | this.displayInlayHints = config.get('displayInlayHints') as boolean; | ||
202 | } | ||
203 | if (config.has('maxInlayHintLength')) { | ||
204 | this.maxInlayHintLength = config.get( | ||
205 | 'maxInlayHintLength', | ||
206 | ) as number; | ||
207 | } | ||
208 | if (config.has('excludeGlobs')) { | ||
209 | this.excludeGlobs = config.get('excludeGlobs') || []; | ||
210 | } | ||
211 | if (config.has('useClientWatching')) { | ||
212 | this.useClientWatching = config.get('useClientWatching') || true; | ||
213 | } | ||
214 | if (config.has('featureFlags')) { | ||
215 | this.featureFlags = config.get('featureFlags') || {}; | ||
216 | } | ||
217 | if (config.has('withSysroot')) { | ||
218 | this.withSysroot = config.get('withSysroot') || false; | ||
219 | } | ||
220 | 152 | ||
221 | if (config.has('cargoFeatures.noDefaultFeatures')) { | 153 | get cargoWatchOptions(): CargoWatchOptions { |
222 | this.cargoFeatures.noDefaultFeatures = config.get( | 154 | return { |
223 | 'cargoFeatures.noDefaultFeatures', | 155 | enable: this.cfg.get("cargo-watch.enable") as boolean, |
224 | false, | 156 | arguments: this.cfg.get("cargo-watch.arguments") as string[], |
225 | ); | 157 | allTargets: this.cfg.get("cargo-watch.allTargets") as boolean, |
226 | } | 158 | command: this.cfg.get("cargo-watch.command") as string, |
227 | if (config.has('cargoFeatures.allFeatures')) { | 159 | }; |
228 | this.cargoFeatures.allFeatures = config.get( | 160 | } |
229 | 'cargoFeatures.allFeatures', | ||
230 | true, | ||
231 | ); | ||
232 | } | ||
233 | if (config.has('cargoFeatures.features')) { | ||
234 | this.cargoFeatures.features = config.get( | ||
235 | 'cargoFeatures.features', | ||
236 | [], | ||
237 | ); | ||
238 | } | ||
239 | 161 | ||
240 | if ( | 162 | get cargoFeatures(): CargoFeatures { |
241 | this.prevCargoFeatures !== null && | 163 | return { |
242 | (this.cargoFeatures.allFeatures !== | 164 | noDefaultFeatures: this.cfg.get("cargoFeatures.noDefaultFeatures") as boolean, |
243 | this.prevCargoFeatures.allFeatures || | 165 | allFeatures: this.cfg.get("cargoFeatures.allFeatures") as boolean, |
244 | this.cargoFeatures.noDefaultFeatures !== | 166 | features: this.cfg.get("cargoFeatures.features") as string[], |
245 | this.prevCargoFeatures.noDefaultFeatures || | 167 | }; |
246 | this.cargoFeatures.features.length !== | ||
247 | this.prevCargoFeatures.features.length || | ||
248 | this.cargoFeatures.features.some( | ||
249 | (v, i) => v !== this.prevCargoFeatures!.features[i], | ||
250 | )) | ||
251 | ) { | ||
252 | requireReloadMessage = 'Changing cargo features requires a reload'; | ||
253 | } | ||
254 | this.prevCargoFeatures = { ...this.cargoFeatures }; | ||
255 | |||
256 | if (this.prevCargoWatchOptions !== null) { | ||
257 | const changed = | ||
258 | this.cargoWatchOptions.enable !== this.prevCargoWatchOptions.enable || | ||
259 | this.cargoWatchOptions.command !== this.prevCargoWatchOptions.command || | ||
260 | this.cargoWatchOptions.allTargets !== this.prevCargoWatchOptions.allTargets || | ||
261 | this.cargoWatchOptions.arguments.length !== this.prevCargoWatchOptions.arguments.length || | ||
262 | this.cargoWatchOptions.arguments.some( | ||
263 | (v, i) => v !== this.prevCargoWatchOptions!.arguments[i], | ||
264 | ); | ||
265 | if (changed) { | ||
266 | requireReloadMessage = 'Changing cargo-watch options requires a reload'; | ||
267 | } | ||
268 | } | ||
269 | this.prevCargoWatchOptions = { ...this.cargoWatchOptions }; | ||
270 | |||
271 | if (requireReloadMessage !== null) { | ||
272 | const reloadAction = 'Reload now'; | ||
273 | vscode.window | ||
274 | .showInformationMessage(requireReloadMessage, reloadAction) | ||
275 | .then(selectedAction => { | ||
276 | if (selectedAction === reloadAction) { | ||
277 | vscode.commands.executeCommand( | ||
278 | 'workbench.action.reloadWindow', | ||
279 | ); | ||
280 | } | ||
281 | }); | ||
282 | } | ||
283 | } | 168 | } |
169 | |||
170 | // for internal use | ||
171 | get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } | ||
284 | } | 172 | } |