|
1 | 1 | import vscode from "vscode"; |
2 | 2 |
|
3 | 3 | import { |
4 | | - CancellationToken, |
5 | 4 | ConfigurationParams, |
6 | 5 | LSPAny, |
7 | 6 | LanguageClient, |
8 | 7 | LanguageClientOptions, |
9 | | - RequestHandler, |
10 | 8 | ResponseError, |
11 | 9 | ServerOptions, |
12 | 10 | } from "vscode-languageclient/node"; |
13 | | -import camelCase from "camelcase"; |
| 11 | +import { camelCase, snakeCase } from "lodash-es"; |
14 | 12 | import semver from "semver"; |
15 | 13 |
|
16 | 14 | import * as minisign from "./minisign"; |
@@ -165,105 +163,137 @@ async function getZLSPath(context: vscode.ExtensionContext): Promise<{ exe: stri |
165 | 163 | }; |
166 | 164 | } |
167 | 165 |
|
168 | | -async function configurationMiddleware( |
169 | | - params: ConfigurationParams, |
170 | | - token: CancellationToken, |
171 | | - next: RequestHandler<ConfigurationParams, LSPAny[], void>, |
172 | | -): Promise<LSPAny[] | ResponseError> { |
173 | | - const optionIndices: Record<string, number | undefined> = {}; |
174 | | - |
175 | | - params.items.forEach((param, index) => { |
176 | | - if (param.section) { |
177 | | - if (param.section === "zls.zig_exe_path") { |
178 | | - param.section = "zig.path"; |
179 | | - } else { |
180 | | - param.section = `zig.zls.${camelCase(param.section.slice(4))}`; |
181 | | - } |
182 | | - optionIndices[param.section] = index; |
183 | | - } |
184 | | - }); |
| 166 | +function configurationMiddleware(params: ConfigurationParams): LSPAny[] | ResponseError { |
| 167 | + void validateAdditionalOptions(); |
| 168 | + return params.items.map((param) => { |
| 169 | + if (!param.section) return null; |
185 | 170 |
|
186 | | - const result = await next(params, token); |
187 | | - if (result instanceof ResponseError) { |
188 | | - return result; |
189 | | - } |
| 171 | + const scopeUri = param.scopeUri ? client?.protocol2CodeConverter.asUri(param.scopeUri) : undefined; |
| 172 | + const configuration = vscode.workspace.getConfiguration("zig", scopeUri); |
| 173 | + const workspaceFolder = scopeUri ? vscode.workspace.getWorkspaceFolder(scopeUri) : undefined; |
190 | 174 |
|
191 | | - const configuration = vscode.workspace.getConfiguration("zig.zls"); |
| 175 | + const updateConfigOption = (section: string, value: unknown) => { |
| 176 | + if (section === "zls.zigExePath") { |
| 177 | + return zigProvider.getZigPath(); |
| 178 | + } |
192 | 179 |
|
193 | | - for (const name in optionIndices) { |
194 | | - const index = optionIndices[name] as unknown as number; |
195 | | - const section = name.slice("zig.zls.".length); |
196 | | - const configValue = configuration.get(section); |
197 | | - if (typeof configValue === "string") { |
198 | | - // Make sure that `""` gets converted to `null` and resolve predefined values |
199 | | - result[index] = configValue ? handleConfigOption(configValue) : null; |
200 | | - } |
| 180 | + if (typeof value === "string") { |
| 181 | + // Make sure that `""` gets converted to `undefined` and resolve predefined values |
| 182 | + value = value ? handleConfigOption(value, workspaceFolder ?? "guess") : undefined; |
| 183 | + } else if (typeof value === "object" && value !== null && !Array.isArray(value)) { |
| 184 | + // Recursively update the config options |
| 185 | + const newValue: Record<string, unknown> = {}; |
| 186 | + for (const [fieldName, fieldValue] of Object.entries(value)) { |
| 187 | + newValue[snakeCase(fieldName)] = updateConfigOption(section + "." + fieldName, fieldValue); |
| 188 | + } |
| 189 | + return newValue; |
| 190 | + } |
201 | 191 |
|
202 | | - const inspect = configuration.inspect(section); |
203 | | - const isDefaultValue = |
204 | | - configValue === inspect?.defaultValue && |
205 | | - inspect?.globalValue === undefined && |
206 | | - inspect?.workspaceValue === undefined && |
207 | | - inspect?.workspaceFolderValue === undefined; |
208 | | - if (isDefaultValue) { |
209 | | - if (name === "zig.zls.semanticTokens") { |
210 | | - // The extension has a different default value for this config |
211 | | - // option compared to ZLS |
212 | | - continue; |
| 192 | + const inspect = configuration.inspect(section); |
| 193 | + const isDefaultValue = |
| 194 | + value === inspect?.defaultValue && |
| 195 | + inspect?.globalValue === undefined && |
| 196 | + inspect?.workspaceValue === undefined && |
| 197 | + inspect?.workspaceFolderValue === undefined; |
| 198 | + |
| 199 | + if (isDefaultValue) { |
| 200 | + if (section === "zls.semanticTokens") { |
| 201 | + // The extension has a different default value for this config |
| 202 | + // option compared to ZLS |
| 203 | + return value; |
| 204 | + } else { |
| 205 | + return undefined; |
| 206 | + } |
213 | 207 | } |
214 | | - result[index] = null; |
215 | | - } |
216 | | - } |
| 208 | + return value; |
| 209 | + }; |
217 | 210 |
|
218 | | - const indexOfZigPath = optionIndices["zig.path"]; |
219 | | - if (indexOfZigPath !== undefined) { |
220 | | - result[indexOfZigPath] = zigProvider.getZigPath(); |
221 | | - } |
| 211 | + let additionalOptions = configuration.get<Record<string, unknown>>("zls.additionalOptions", {}); |
| 212 | + |
| 213 | + // Remove the `zig.zls.` prefix from the entries in `zig.zls.additionalOptions` |
| 214 | + additionalOptions = Object.fromEntries( |
| 215 | + Object.entries(additionalOptions) |
| 216 | + .filter(([key]) => key.startsWith("zig.zls.")) |
| 217 | + .map(([key, value]) => [key.slice("zig.zls.".length), value]), |
| 218 | + ); |
| 219 | + |
| 220 | + if (param.section === "zls") { |
| 221 | + // ZLS has requested all config options. |
| 222 | + |
| 223 | + const options = { ...configuration.get<Record<string, unknown>>(param.section, {}) }; |
| 224 | + // Some config options are specific to the VS Code |
| 225 | + // extension. ZLS should ignore unknown values but |
| 226 | + // we remove them here anyway. |
| 227 | + delete options["debugLog"]; // zig.zls.debugLog |
| 228 | + delete options["trace"]; // zig.zls.trace.server |
| 229 | + delete options["enabled"]; // zig.zls.enabled |
| 230 | + delete options["path"]; // zig.zls.path |
| 231 | + delete options["additionalOptions"]; // zig.zls.additionalOptions |
| 232 | + |
| 233 | + return updateConfigOption(param.section, { |
| 234 | + ...additionalOptions, |
| 235 | + ...options, |
| 236 | + // eslint-disable-next-line @typescript-eslint/naming-convention |
| 237 | + zig_exe_path: zigProvider.getZigPath(), |
| 238 | + }); |
| 239 | + } else if (param.section.startsWith("zls.")) { |
| 240 | + // ZLS has requested a specific config option. |
| 241 | + |
| 242 | + // ZLS names it's config options in snake_case but the VS Code extension uses camelCase |
| 243 | + const camelCaseSection = param.section |
| 244 | + .split(".") |
| 245 | + .map((str) => camelCase(str)) |
| 246 | + .join("."); |
| 247 | + |
| 248 | + return updateConfigOption( |
| 249 | + camelCaseSection, |
| 250 | + configuration.get(camelCaseSection, additionalOptions[camelCaseSection.slice("zls.".length)]), |
| 251 | + ); |
| 252 | + } else { |
| 253 | + // Do not allow ZLS to request other editor config options. |
| 254 | + return null; |
| 255 | + } |
| 256 | + }); |
| 257 | +} |
222 | 258 |
|
| 259 | +async function validateAdditionalOptions(): Promise<void> { |
| 260 | + const configuration = vscode.workspace.getConfiguration("zig.zls", null); |
223 | 261 | const additionalOptions = configuration.get<Record<string, unknown>>("additionalOptions", {}); |
224 | 262 |
|
225 | 263 | for (const optionName in additionalOptions) { |
| 264 | + if (!optionName.startsWith("zig.zls.")) continue; |
226 | 265 | const section = optionName.slice("zig.zls.".length); |
227 | 266 |
|
228 | | - const doesOptionExist = configuration.inspect(section)?.defaultValue !== undefined; |
229 | | - if (doesOptionExist) { |
230 | | - // The extension has defined a config option with the given name but the user still used `additionalOptions`. |
231 | | - const response = await vscode.window.showWarningMessage( |
232 | | - `The config option 'zig.zls.additionalOptions' contains the already existing option '${optionName}'`, |
233 | | - `Use ${optionName} instead`, |
234 | | - "Show zig.zls.additionalOptions", |
235 | | - ); |
236 | | - switch (response) { |
237 | | - case `Use ${optionName} instead`: |
238 | | - const { [optionName]: newValue, ...updatedAdditionalOptions } = additionalOptions; |
239 | | - await workspaceConfigUpdateNoThrow( |
240 | | - configuration, |
241 | | - "additionalOptions", |
242 | | - updatedAdditionalOptions, |
243 | | - true, |
244 | | - ); |
245 | | - await workspaceConfigUpdateNoThrow(configuration, section, newValue, true); |
246 | | - break; |
247 | | - case "Show zig.zls.additionalOptions": |
248 | | - await vscode.commands.executeCommand("workbench.action.openSettingsJson", { |
249 | | - revealSetting: { key: "zig.zls.additionalOptions" }, |
250 | | - }); |
251 | | - continue; |
252 | | - case undefined: |
253 | | - continue; |
254 | | - } |
255 | | - } |
256 | | - |
257 | | - const optionIndex = optionIndices[optionName]; |
258 | | - if (!optionIndex) { |
259 | | - // ZLS has not requested a config option with the given name. |
260 | | - continue; |
| 267 | + const inspect = configuration.inspect(section); |
| 268 | + const doesOptionExist = inspect?.defaultValue !== undefined; |
| 269 | + if (!doesOptionExist) continue; |
| 270 | + |
| 271 | + // The extension has defined a config option with the given name but the user still used `additionalOptions`. |
| 272 | + const response = await vscode.window.showWarningMessage( |
| 273 | + `The config option 'zig.zls.additionalOptions' contains the already existing option '${optionName}'`, |
| 274 | + `Use ${optionName} instead`, |
| 275 | + "Show zig.zls.additionalOptions", |
| 276 | + ); |
| 277 | + switch (response) { |
| 278 | + case `Use ${optionName} instead`: |
| 279 | + const { [optionName]: newValue, ...updatedAdditionalOptions } = additionalOptions; |
| 280 | + await workspaceConfigUpdateNoThrow( |
| 281 | + configuration, |
| 282 | + "additionalOptions", |
| 283 | + Object.keys(updatedAdditionalOptions).length ? updatedAdditionalOptions : undefined, |
| 284 | + true, |
| 285 | + ); |
| 286 | + await workspaceConfigUpdateNoThrow(configuration, section, newValue, true); |
| 287 | + break; |
| 288 | + case "Show zig.zls.additionalOptions": |
| 289 | + await vscode.commands.executeCommand("workbench.action.openSettingsJson", { |
| 290 | + revealSetting: { key: "zig.zls.additionalOptions" }, |
| 291 | + }); |
| 292 | + break; |
| 293 | + case undefined: |
| 294 | + return; |
261 | 295 | } |
262 | | - |
263 | | - result[optionIndex] = additionalOptions[optionName]; |
264 | 296 | } |
265 | | - |
266 | | - return result as unknown[]; |
267 | 297 | } |
268 | 298 |
|
269 | 299 | /** |
|
0 commit comments