src/vs/workbench/services/configurationResolver/common/variableResolver.ts

345 LOC · 66 covered · 279 uncovered · 11 ranges · 30 concepts · 1 introducers · 26 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/workbench/api/common/extHostDocumentData.ts · 312 LOCcommon/extHostDocumentDa…src/vs/workbench/api/common/extHostDocumentsAndEditors.ts · 197 LOCcommon/extHostDocumentsA…src/vs/workbench/api/common/extHostEditorTabs.ts · 444 LOCcommon/extHostEditorTabs…src/vs/workbench/api/common/extHostMcp.ts · 1202 LOCcommon/extHostMcp.tssrc/vs/workbench/api/common/extHostTextEditor.ts · 690 LOCcommon/extHostTextEditor…src/vs/workbench/api/common/extHostVariableResolverService.ts · 176 LOCcommon/extHostVariableRe…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 3 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 4 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×2 · 4 introduced LOCextHostMcp.ts ×2extHostMcp.ts ×1 · 1 introduced LOCextHostMcp.ts ×1extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×4 · 6 introduced LOCextHostMcp.ts ×4extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1, extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×1 · 6 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×4 · 13 introduced LOCextHostMcp.ts ×4extHostMcpNode.ts ×8 · 45 introduced LOCextHostMcpNode.ts ×8extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×5 · 25 introduced LOCextHostMcp.ts ×5extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · 0 introduced LOCextHostMcp.test|title=Ex…extHostMcp.ts ×3 · 23 introduced LOCextHostMcp.ts ×3extHostMcp.ts ×1 · 2 introduced LOCextHostMcp.ts ×1extHostMcp.ts ×8 · 60 introduced LOCextHostMcp.ts ×8extHostMcp.ts ×43 · 874 introduced LOCextHostMcp.ts ×43extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should allow undefined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata properties should expose readonly properties|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should handle multiple Bearer challenges and use first scope|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should ignore non-Bearer schemes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when no WWW-Authenticate header and scopes are already undefined|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are same but in different order|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return false when scopes are the same|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true and update scopes when WWW-Authenticate header contains new scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from defined scopes to undefined (no scope in header)|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp IAuthMetadata update() should return true when updating from undefined scopes to defined scopes|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should create IAuthMetadata with fetched server metadata|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should fall back to default metadata when server metadata fetch fails|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle empty scope string in WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle invalid JSON in resource metadata response|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle malformed WWW-Authenticate header gracefully|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should handle non-401 status codes in update()|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should pass launch headers when fetching metadata from same origin|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use resource_metadata challenge URL from WWW-Authenticate header|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header even when resource metadata has scopes_supported|occurrence=1extHostMcp.test|title=Ex…extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/common/extHostMcp.test|title=ExtHostMcp createAuthMetadata should use scopes from WWW-Authenticate header when resource metadata has none|occurrence=1extHostMcp.test|title=Ex…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg does not add stray ^ to argument values|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg doubles embedded double quotes (cmd.exe convention)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg neutralizes cmd.exe metacharacters inside quotes (CVE-2024-27980)|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with parentheses without injecting ^|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg preserves paths with spaces|occurrence=1extHostMcpNode.test|titl…extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/api/test/node/extHostMcpNode.test|title=extHostMcpNode - escapeCmdArg wraps simple values in double quotes|occurrence=1extHostMcpNode.test|titl…Focused file · src/vs/workbench/services/configurationResolver/common/variableResolver.ts · 345 LOCcommon/variableResolver.…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extHostMcp.ts ×43
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { IStringDictionary } from '../../../../base/common/collections.js';
7 > import { normalizeDriveLetter } from '../../../../base/common/labels.js';
8 > import * as paths from '../../../../base/common/path.js';
9 > import { IProcessEnvironment, isWindows } from '../../../../base/common/platform.js';
10 > import * as process from '../../../../base/common/process.js';
11 > import * as types from '../../../../base/common/types.js';
12 > import { URI as uri } from '../../../../base/common/uri.js';
13 > import { localize } from '../../../../nls.js';
14 > import { ILabelService } from '../../../../platform/label/common/label.js';
15 > import { IWorkspaceFolderData } from '../../../../platform/workspace/common/workspace.js';
16 > import { allVariableKinds, IConfigurationResolverService, VariableError, VariableKind } from './configurationResolver.js';
17 > import { ConfigurationResolverExpression, IResolvedValue, Replacement } from './configurationResolverExpression.js';
18 >
19 > interface IVariableResolveContext {
20 > getFolderUri(folderName: string): uri | undefined;
21 > getWorkspaceFolderCount(): number;
22 > getConfigurationValue(folderUri: uri | undefined, section: string): string | undefined;
23 > getAppRoot(): string | undefined;
24 > getExecPath(): string | undefined;
25 > getFilePath(): string | undefined;
26 > getWorkspaceFolderPathForFile?(): string | undefined;
27 > getSelectedText(): string | undefined;
28 > getLineNumber(): string | undefined;
29 > getColumnNumber(): string | undefined;
30 > getExtension(id: string): Promise<{ readonly extensionLocation: uri } | undefined>;
31 > }
32 >
33 > type Environment = { env: IProcessEnvironment | undefined; userHome: string | undefined };
34 >
35 > export abstract class AbstractVariableResolverService implements IConfigurationResolverService {
36 >
37 > declare readonly _serviceBrand: undefined;
38 >
39 > private _context: IVariableResolveContext;
40 > private _labelService?: ILabelService;
41 > private _envVariablesPromise?: Promise<IProcessEnvironment>;
42 > private _userHomePromise?: Promise<string>;
43 > protected _contributedVariables: Map<string, () => Promise<string | undefined>> = new Map();
44 >
45 > public readonly resolvableVariables = new Set<string>(allVariableKinds);
46 >
47 > constructor(_context: IVariableResolveContext, _labelService?: ILabelService, _userHomePromise?: Promise<string>, _envVariablesPromise?: Promise<IProcessEnvironment>) {
48 this._context = _context;
49 this._labelService = _labelService;
50 this._userHomePromise = _userHomePromise;
51 if (_envVariablesPromise) {
52 this._envVariablesPromise = _envVariablesPromise.then(envVariables => {
53 return this.prepareEnv(envVariables);
54 });
55 }
56 }
58 > private prepareEnv(envVariables: IProcessEnvironment): IProcessEnvironment {
59 // windows env variables are case insensitive
60 if (isWindows) {
61 const ev: IProcessEnvironment = Object.create(null);
62 Object.keys(envVariables).forEach(key => {
63 ev[key.toLowerCase()] = envVariables[key];
64 });
65 return ev;
66 }
67 return envVariables;
68 }
70 > public async resolveWithEnvironment(environment: IProcessEnvironment, folder: IWorkspaceFolderData | undefined, value: string): Promise<string> {
71 const expr = ConfigurationResolverExpression.parse(value);
72
73 for (const replacement of expr.unresolved()) {
74 const resolvedValue = await this.evaluateSingleVariable(replacement, folder?.uri, environment);
75 if (resolvedValue !== undefined) {
76 expr.resolve(replacement, String(resolvedValue));
77 }
78 }
79
80 return expr.toObject();
81 }
83 > public async resolveAsync<T>(folder: IWorkspaceFolderData | undefined, config: T): Promise<T extends ConfigurationResolverExpression<infer R> ? R : T> {
84 const expr = ConfigurationResolverExpression.parse(config);
85
86 for (const replacement of expr.unresolved()) {
87 const resolvedValue = await this.evaluateSingleVariable(replacement, folder?.uri);
88 if (resolvedValue !== undefined) {
89 expr.resolve(replacement, String(resolvedValue));
90 }
91 }
92
93 return expr.toObject() as (T extends ConfigurationResolverExpression<infer R> ? R : T);
94 }
96 > public resolveWithInteractionReplace(folder: IWorkspaceFolderData | undefined, config: unknown): Promise<unknown> {
97 throw new Error('resolveWithInteractionReplace not implemented.');
98 }
100 > public resolveWithInteraction(folder: IWorkspaceFolderData | undefined, config: unknown): Promise<Map<string, string> | undefined> {
101 throw new Error('resolveWithInteraction not implemented.');
102 }
104 > public contributeVariable(variable: string, resolution: () => Promise<string | undefined>): void {
105 if (this._contributedVariables.has(variable)) {
106 throw new Error('Variable ' + variable + ' is contributed twice.');
107 } else {
108 this.resolvableVariables.add(variable);
109 this._contributedVariables.set(variable, resolution);
110 }
111 }
113 > private fsPath(displayUri: uri): string {
114 return this._labelService ? this._labelService.getUriLabel(displayUri, { noPrefix: true }) : displayUri.fsPath;
115 }
117 > protected async evaluateSingleVariable(replacement: Replacement, folderUri: uri | undefined, processEnvironment?: IProcessEnvironment, commandValueMapping?: IStringDictionary<IResolvedValue>): Promise<IResolvedValue | string | undefined> {
118
119
120 const environment: Environment = {
121 env: (processEnvironment !== undefined) ? this.prepareEnv(processEnvironment) : await this._envVariablesPromise,
122 userHome: (processEnvironment !== undefined) ? undefined : await this._userHomePromise
123 };
124
125 const { name: variable, arg: argument } = replacement;
126
127 // common error handling for all variables that require an open editor
128 const getFilePath = (variableKind: VariableKind): string => {
129 const filePath = this._context.getFilePath();
130 if (filePath) {
131 return normalizeDriveLetter(filePath);
132 }
133 throw new VariableError(variableKind, (localize('canNotResolveFile', "Variable {0} can not be resolved. Please open an editor.", replacement.id)));
134 };
135
136 // common error handling for all variables that require an open editor
137 const getFolderPathForFile = (variableKind: VariableKind): string => {
138 const filePath = getFilePath(variableKind); // throws error if no editor open
139 if (this._context.getWorkspaceFolderPathForFile) {
140 const folderPath = this._context.getWorkspaceFolderPathForFile();
141 if (folderPath) {
142 return normalizeDriveLetter(folderPath);
143 }
144 }
145 throw new VariableError(variableKind, localize('canNotResolveFolderForFile', "Variable {0}: can not find workspace folder of '{1}'.", replacement.id, paths.basename(filePath)));
146 };
147
148 // common error handling for all variables that require an open folder and accept a folder name argument
149 const getFolderUri = (variableKind: VariableKind): uri => {
150 if (argument) {
151 const folder = this._context.getFolderUri(argument);
152 if (folder) {
153 return folder;
154 }
155 throw new VariableError(variableKind, localize('canNotFindFolder', "Variable {0} can not be resolved. No such folder '{1}'.", variableKind, argument));
156 }
157
158 if (folderUri) {
159 return folderUri;
160 }
161
162 if (this._context.getWorkspaceFolderCount() > 1) {
163 throw new VariableError(variableKind, localize('canNotResolveWorkspaceFolderMultiRoot', "Variable {0} can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", variableKind));
164 }
165 throw new VariableError(variableKind, localize('canNotResolveWorkspaceFolder', "Variable {0} can not be resolved. Please open a folder.", variableKind));
166 };
167
168 switch (variable) {
169 case 'env':
170 if (argument) {
171 if (environment.env) {
172 const env = environment.env[isWindows ? argument.toLowerCase() : argument];
173 if (types.isString(env)) {
174 return env;
175 }
176 }
177 return '';
178 }
179 throw new VariableError(VariableKind.Env, localize('missingEnvVarName', "Variable {0} can not be resolved because no environment variable name is given.", replacement.id));
180
181 case 'config':
182 if (argument) {
183 const config = this._context.getConfigurationValue(folderUri, argument);
184 if (types.isUndefinedOrNull(config)) {
185 throw new VariableError(VariableKind.Config, localize('configNotFound', "Variable {0} can not be resolved because setting '{1}' not found.", replacement.id, argument));
186 }
187 if (types.isObject(config)) {
188 throw new VariableError(VariableKind.Config, localize('configNoString', "Variable {0} can not be resolved because '{1}' is a structured value.", replacement.id, argument));
189 }
190 return config;
191 }
192 throw new VariableError(VariableKind.Config, localize('missingConfigName', "Variable {0} can not be resolved because no settings name is given.", replacement.id));
193
194 case 'command':
195 return this.resolveFromMap(VariableKind.Command, replacement.id, argument, commandValueMapping, 'command');
196
197 case 'input':
198 return this.resolveFromMap(VariableKind.Input, replacement.id, argument, commandValueMapping, 'input');
199
200 case 'extensionInstallFolder':
201 if (argument) {
202 const ext = await this._context.getExtension(argument);
203 if (!ext) {
204 throw new VariableError(VariableKind.ExtensionInstallFolder, localize('extensionNotInstalled', "Variable {0} can not be resolved because the extension {1} is not installed.", replacement.id, argument));
205 }
206 return this.fsPath(ext.extensionLocation);
207 }
208 throw new VariableError(VariableKind.ExtensionInstallFolder, localize('missingExtensionName', "Variable {0} can not be resolved because no extension name is given.", replacement.id));
209
210 default: {
211 switch (variable) {
212 case 'workspaceRoot':
213 case 'workspaceFolder': {
214 const uri = getFolderUri(VariableKind.WorkspaceFolder);
215 return uri ? normalizeDriveLetter(this.fsPath(uri)) : undefined;
216 }
217
218 case 'cwd': {
219 if (!folderUri && !argument) {
220 return process.cwd();
221 }
222 const uri = getFolderUri(VariableKind.Cwd);
223 return uri ? normalizeDriveLetter(this.fsPath(uri)) : undefined;
224 }
225
226 case 'workspaceRootFolderName':
227 case 'workspaceFolderBasename': {
228 const uri = getFolderUri(VariableKind.WorkspaceFolderBasename);
229 return uri ? normalizeDriveLetter(paths.basename(this.fsPath(uri))) : undefined;
230 }
231
232 case 'userHome':
233 if (environment.userHome) {
234 return environment.userHome;
235 }
236 throw new VariableError(VariableKind.UserHome, localize('canNotResolveUserHome', "Variable {0} can not be resolved. UserHome path is not defined", replacement.id));
237
238 case 'lineNumber': {
239 const lineNumber = this._context.getLineNumber();
240 if (lineNumber) {
241 return lineNumber;
242 }
243 throw new VariableError(VariableKind.LineNumber, localize('canNotResolveLineNumber', "Variable {0} can not be resolved. Make sure to have a line selected in the active editor.", replacement.id));
244 }
245
246 case 'columnNumber': {
247 const columnNumber = this._context.getColumnNumber();
248 if (columnNumber) {
249 return columnNumber;
250 }
251 throw new Error(localize('canNotResolveColumnNumber', "Variable {0} can not be resolved. Make sure to have a column selected in the active editor.", replacement.id));
252 }
253
254 case 'selectedText': {
255 const selectedText = this._context.getSelectedText();
256 if (selectedText) {
257 return selectedText;
258 }
259 throw new VariableError(VariableKind.SelectedText, localize('canNotResolveSelectedText', "Variable {0} can not be resolved. Make sure to have some text selected in the active editor.", replacement.id));
260 }
261
262 case 'file':
263 return getFilePath(VariableKind.File);
264
265 case 'fileWorkspaceFolder':
266 return getFolderPathForFile(VariableKind.FileWorkspaceFolder);
267
268 case 'fileWorkspaceFolderBasename':
269 return paths.basename(getFolderPathForFile(VariableKind.FileWorkspaceFolderBasename));
270
271 case 'relativeFile':
272 if (folderUri || argument) {
273 return paths.relative(this.fsPath(getFolderUri(VariableKind.RelativeFile)), getFilePath(VariableKind.RelativeFile));
274 }
275 return getFilePath(VariableKind.RelativeFile);
276
277 case 'relativeFileDirname': {
278 const dirname = paths.dirname(getFilePath(VariableKind.RelativeFileDirname));
279 if (folderUri || argument) {
280 const relative = paths.relative(this.fsPath(getFolderUri(VariableKind.RelativeFileDirname)), dirname);
281 return relative.length === 0 ? '.' : relative;
282 }
283 return dirname;
284 }
285
286 case 'fileDirname':
287 return paths.dirname(getFilePath(VariableKind.FileDirname));
288
289 case 'fileExtname':
290 return paths.extname(getFilePath(VariableKind.FileExtname));
291
292 case 'fileBasename':
293 return paths.basename(getFilePath(VariableKind.FileBasename));
294
295 case 'fileBasenameNoExtension': {
296 const basename = paths.basename(getFilePath(VariableKind.FileBasenameNoExtension));
297 return (basename.slice(0, basename.length - paths.extname(basename).length));
298 }
299
300 case 'fileDirnameBasename':
301 return paths.basename(paths.dirname(getFilePath(VariableKind.FileDirnameBasename)));
302
303 case 'execPath': {
304 const ep = this._context.getExecPath();
305 if (ep) {
306 return ep;
307 }
308 return replacement.id;
309 }
310
311 case 'execInstallFolder': {
312 const ar = this._context.getAppRoot();
313 if (ar) {
314 return ar;
315 }
316 return replacement.id;
317 }
318
319 case 'pathSeparator':
320 case '/':
321 return paths.sep;
322
323 default: {
324 try {
325 return this.resolveFromMap(VariableKind.Unknown, replacement.id, argument, commandValueMapping, undefined);
326 } catch {
327 return replacement.id;
328 }
329 }
330 }
331 }
332 }
333 }
335 > private resolveFromMap(variableKind: VariableKind, match: string, argument: string | undefined, commandValueMapping: IStringDictionary<IResolvedValue> | undefined, prefix: string | undefined): string {
336 if (argument && commandValueMapping) {
337 const v = (prefix === undefined) ? commandValueMapping[argument] : commandValueMapping[prefix + ':' + argument];
338 if (typeof v === 'string') {
339 return v;
340 }
341 throw new VariableError(variableKind, localize('noValueForCommand', "Variable {0} can not be resolved because the command has no value.", match));
342 }
343 return match;
344 }