Skip to content

Commit e94b715

Browse files
committed
Resolve settings variables in Swift settings
VS Code defines several predefined variables that can be substituted in task and launch configurations. It is useful to have some of these defined for use in Swift settings, specifically the ones that are comprised of paths. This patch introduces a limited list of variables that are substituted in some settings. The variables supported are: - `${workspaceFolder}` - `${workspaceFolderBasename}` - `${cwd}` - `${userHome}` - `${pathSeparator}` The settings that support variable subsitution are: - `${swift.path}` - `${swift.runtimePath}` - `${swift.sdk}` - `${swift.buildArguments}` - `${swift.packageArguments}` - `${swift.buildPath}` - `${swift.serverArguments}` - `${swift.additionalTestArguments}` - `${swift.attachmentsPath}` - `${swift.debugger.customDebugAdapterPath}` - `${swift.excludeFromCodeCoverage}` - `${swift.sourcekit-lsp.serverPath}` Issue: #1438
1 parent 41e8872 commit e94b715

File tree

2 files changed

+140
-16
lines changed

2 files changed

+140
-16
lines changed

src/configuration.ts

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import * as vscode from "vscode";
16+
import * as os from "os";
17+
import * as path from "path";
1618

1719
export type DebugAdapters = "auto" | "lldb-dap" | "CodeLLDB";
1820
export type SetupCodeLLDBOptions =
@@ -106,14 +108,17 @@ const configuration = {
106108
get lsp(): LSPConfiguration {
107109
return {
108110
get serverPath(): string {
109-
return vscode.workspace
110-
.getConfiguration("swift.sourcekit-lsp")
111-
.get<string>("serverPath", "");
111+
return substituteVariablesInString(
112+
vscode.workspace
113+
.getConfiguration("swift.sourcekit-lsp")
114+
.get<string>("serverPath", "")
115+
);
112116
},
113117
get serverArguments(): string[] {
114118
return vscode.workspace
115119
.getConfiguration("swift.sourcekit-lsp")
116-
.get<string[]>("serverArguments", []);
120+
.get<string[]>("serverArguments", [])
121+
.map(substituteVariablesInString);
117122
},
118123
get inlayHintsEnabled(): boolean {
119124
return vscode.workspace
@@ -192,7 +197,8 @@ const configuration = {
192197
get additionalTestArguments(): string[] {
193198
return vscode.workspace
194199
.getConfiguration("swift", workspaceFolder)
195-
.get<string[]>("additionalTestArguments", []);
200+
.get<string[]>("additionalTestArguments", [])
201+
.map(substituteVariablesInString);
196202
},
197203
/** auto-generate launch.json configurations */
198204
get autoGenerateLaunchConfigurations(): boolean {
@@ -213,9 +219,11 @@ const configuration = {
213219
.get<boolean>("searchSubfoldersForPackages", false);
214220
},
215221
get attachmentsPath(): string {
216-
return vscode.workspace
217-
.getConfiguration("swift", workspaceFolder)
218-
.get<string>("attachmentsPath", "./.build/attachments");
222+
return substituteVariablesInString(
223+
vscode.workspace
224+
.getConfiguration("swift", workspaceFolder)
225+
.get<string>("attachmentsPath", "./.build/attachments")
226+
);
219227
},
220228
pluginPermissions(pluginId?: string): PluginPermissionConfiguration {
221229
return pluginSetting("pluginPermissions", pluginId, false) ?? {};
@@ -256,7 +264,9 @@ const configuration = {
256264
}
257265
},
258266
get customDebugAdapterPath(): string {
259-
return vscode.workspace.getConfiguration("swift.debugger").get<string>("path", "");
267+
return substituteVariablesInString(
268+
vscode.workspace.getConfiguration("swift.debugger").get<string>("path", "")
269+
);
260270
},
261271
get disable(): boolean {
262272
return vscode.workspace
@@ -274,7 +284,8 @@ const configuration = {
274284
get excludeFromCodeCoverage(): string[] {
275285
return vscode.workspace
276286
.getConfiguration("swift")
277-
.get<string[]>("excludeFromCodeCoverage", []);
287+
.get<string[]>("excludeFromCodeCoverage", [])
288+
.map(substituteVariablesInString);
278289
},
279290
/** Files and directories to exclude from the Package Dependencies view. */
280291
get excludePathsFromPackageDependencies(): string[] {
@@ -284,15 +295,21 @@ const configuration = {
284295
},
285296
/** Path to folder that include swift executable */
286297
get path(): string {
287-
return vscode.workspace.getConfiguration("swift").get<string>("path", "");
298+
return substituteVariablesInString(
299+
vscode.workspace.getConfiguration("swift").get<string>("path", "")
300+
);
288301
},
289302
/** Path to folder that include swift runtime */
290303
get runtimePath(): string {
291-
return vscode.workspace.getConfiguration("swift").get<string>("runtimePath", "");
304+
return substituteVariablesInString(
305+
vscode.workspace.getConfiguration("swift").get<string>("runtimePath", "")
306+
);
292307
},
293308
/** Path to custom --sdk */
294309
get sdk(): string {
295-
return vscode.workspace.getConfiguration("swift").get<string>("SDK", "");
310+
return substituteVariablesInString(
311+
vscode.workspace.getConfiguration("swift").get<string>("SDK", "")
312+
);
296313
},
297314
set sdk(value: string | undefined) {
298315
vscode.workspace.getConfiguration("swift").update("SDK", value);
@@ -306,18 +323,26 @@ const configuration = {
306323
},
307324
/** swift build arguments */
308325
get buildArguments(): string[] {
309-
return vscode.workspace.getConfiguration("swift").get<string[]>("buildArguments", []);
326+
return vscode.workspace
327+
.getConfiguration("swift")
328+
.get<string[]>("buildArguments", [])
329+
.map(substituteVariablesInString);
310330
},
311331
/** swift package arguments */
312332
get packageArguments(): string[] {
313-
return vscode.workspace.getConfiguration("swift").get<string[]>("packageArguments", []);
333+
return vscode.workspace
334+
.getConfiguration("swift")
335+
.get<string[]>("packageArguments", [])
336+
.map(substituteVariablesInString);
314337
},
315338
/** thread/address sanitizer */
316339
get sanitizer(): string {
317340
return vscode.workspace.getConfiguration("swift").get<string>("sanitizer", "off");
318341
},
319342
get buildPath(): string {
320-
return vscode.workspace.getConfiguration("swift").get<string>("buildPath", "");
343+
return substituteVariablesInString(
344+
vscode.workspace.getConfiguration("swift").get<string>("buildPath", "")
345+
);
321346
},
322347
get disableSwiftPMIntegration(): boolean {
323348
return vscode.workspace
@@ -437,4 +462,39 @@ const configuration = {
437462
},
438463
};
439464

465+
const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g);
466+
function substituteVariablesInString(val: string): string {
467+
return val.replace(vsCodeVariableRegex, (substring: string, varName: string) =>
468+
typeof varName === "string" ? computeVscodeVar(varName) || substring : substring
469+
);
470+
}
471+
472+
function computeVscodeVar(varName: string): string | null {
473+
const workspaceFolder = () => {
474+
const activeEditor = vscode.window.activeTextEditor;
475+
if (activeEditor) {
476+
const documentUri = activeEditor.document.uri;
477+
const folder = vscode.workspace.getWorkspaceFolder(documentUri);
478+
if (folder) {
479+
return folder.uri.fsPath;
480+
}
481+
}
482+
483+
// If there is no active editor then return the first workspace folder
484+
return vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath ?? "";
485+
};
486+
487+
// https://code.visualstudio.com/docs/editor/variables-reference
488+
// Variables to be substituted should be added here.
489+
const supportedVariables: { [k: string]: () => string } = {
490+
workspaceFolder,
491+
workspaceFolderBasename: () => path.basename(workspaceFolder()),
492+
cwd: () => process.cwd(),
493+
userHome: () => os.homedir(),
494+
pathSeparator: () => path.sep,
495+
};
496+
497+
return varName in supportedVariables ? supportedVariables[varName]() : null;
498+
}
499+
440500
export default configuration;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2025 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as vscode from "vscode";
16+
import * as path from "path";
17+
import { activateExtensionForSuite, updateSettings } from "./utilities/testutilities";
18+
import { expect } from "chai";
19+
import { afterEach } from "mocha";
20+
import configuration from "../../src/configuration";
21+
import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider";
22+
import { WorkspaceContext } from "../../src/WorkspaceContext";
23+
24+
suite("Configuration Test Suite", function () {
25+
let workspaceContext: WorkspaceContext;
26+
27+
activateExtensionForSuite({
28+
async setup(ctx) {
29+
workspaceContext = ctx;
30+
},
31+
});
32+
33+
let resetSettings: (() => Promise<void>) | undefined;
34+
afterEach(async () => {
35+
if (resetSettings) {
36+
await resetSettings();
37+
}
38+
});
39+
40+
test("Should substitute variables in build task", async function () {
41+
resetSettings = await updateSettings({
42+
"swift.buildPath": "${workspaceFolder}/somepath",
43+
});
44+
45+
const task = createBuildAllTask(workspaceContext.folders[0], false);
46+
expect(task).to.not.be.undefined;
47+
expect(task.definition.args).to.not.be.undefined;
48+
const index = task.definition.args.indexOf("--scratch-path");
49+
expect(task.definition.args[index + 1]).to.equal(
50+
vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath + "/somepath"
51+
);
52+
});
53+
54+
test("Should substitute variables in configuration", async function () {
55+
resetSettings = await updateSettings({
56+
"swift.buildPath": "${workspaceFolder}${pathSeparator}${workspaceFolderBasename}",
57+
});
58+
59+
const basePath = vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath;
60+
const baseName = path.basename(basePath ?? "");
61+
const sep = path.sep;
62+
expect(configuration.buildPath).to.equal(`${basePath}${sep}${baseName}`);
63+
});
64+
});

0 commit comments

Comments
 (0)