Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/environment/browser/environmentService.ts
5242 views
1
/*---------------------------------------------------------------------------------------------
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 { Schemas } from '../../../../base/common/network.js';
7
import { joinPath } from '../../../../base/common/resources.js';
8
import { URI } from '../../../../base/common/uri.js';
9
import { ExtensionKind, IEnvironmentService, IExtensionHostDebugParams } from '../../../../platform/environment/common/environment.js';
10
import { IPath } from '../../../../platform/window/common/window.js';
11
import { IWorkbenchEnvironmentService } from '../common/environmentService.js';
12
import { IWorkbenchConstructionOptions } from '../../../browser/web.api.js';
13
import { IProductService } from '../../../../platform/product/common/productService.js';
14
import { memoize } from '../../../../base/common/decorators.js';
15
import { onUnexpectedError } from '../../../../base/common/errors.js';
16
import { parseLineAndColumnAware } from '../../../../base/common/extpath.js';
17
import { LogLevelToString } from '../../../../platform/log/common/log.js';
18
import { isUndefined } from '../../../../base/common/types.js';
19
import { refineServiceDecorator } from '../../../../platform/instantiation/common/instantiation.js';
20
import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js';
21
import { EXTENSION_IDENTIFIER_WITH_LOG_REGEX } from '../../../../platform/environment/common/environmentService.js';
22
23
export const IBrowserWorkbenchEnvironmentService = refineServiceDecorator<IEnvironmentService, IBrowserWorkbenchEnvironmentService>(IEnvironmentService);
24
25
/**
26
* A subclass of the `IWorkbenchEnvironmentService` to be used only environments
27
* where the web API is available (browsers, Electron).
28
*/
29
export interface IBrowserWorkbenchEnvironmentService extends IWorkbenchEnvironmentService {
30
31
/**
32
* Options used to configure the workbench.
33
*/
34
readonly options?: IWorkbenchConstructionOptions;
35
36
/**
37
* Gets whether a resolver extension is expected for the environment.
38
*/
39
readonly expectsResolverExtension: boolean;
40
}
41
42
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
43
44
declare readonly _serviceBrand: undefined;
45
46
@memoize
47
get remoteAuthority(): string | undefined { return this.options.remoteAuthority; }
48
49
@memoize
50
get expectsResolverExtension(): boolean {
51
return !!this.options.remoteAuthority?.includes('+') && !this.options.webSocketFactory;
52
}
53
54
@memoize
55
get isBuilt(): boolean { return !!this.productService.commit; }
56
57
@memoize
58
get logLevel(): string | undefined {
59
const logLevelFromPayload = this.payload?.get('logLevel');
60
if (logLevelFromPayload) {
61
return logLevelFromPayload.split(',').find(entry => !EXTENSION_IDENTIFIER_WITH_LOG_REGEX.test(entry));
62
}
63
64
return this.options.developmentOptions?.logLevel !== undefined ? LogLevelToString(this.options.developmentOptions?.logLevel) : undefined;
65
}
66
67
get extensionLogLevel(): [string, string][] | undefined {
68
const logLevelFromPayload = this.payload?.get('logLevel');
69
if (logLevelFromPayload) {
70
const result: [string, string][] = [];
71
for (const entry of logLevelFromPayload.split(',')) {
72
const matches = EXTENSION_IDENTIFIER_WITH_LOG_REGEX.exec(entry);
73
if (matches?.[1] && matches[2]) {
74
result.push([matches[1], matches[2]]);
75
}
76
}
77
78
return result.length ? result : undefined;
79
}
80
81
return this.options.developmentOptions?.extensionLogLevel !== undefined ? this.options.developmentOptions?.extensionLogLevel.map(([extension, logLevel]) => ([extension, LogLevelToString(logLevel)])) : undefined;
82
}
83
84
get profDurationMarkers(): string[] | undefined {
85
const profDurationMarkersFromPayload = this.payload?.get('profDurationMarkers');
86
if (profDurationMarkersFromPayload) {
87
const result: string[] = [];
88
for (const entry of profDurationMarkersFromPayload.split(',')) {
89
result.push(entry);
90
}
91
92
return result.length === 2 ? result : undefined;
93
}
94
95
return undefined;
96
}
97
98
@memoize
99
get windowLogsPath(): URI { return this.logsHome; }
100
101
@memoize
102
get logFile(): URI { return joinPath(this.windowLogsPath, 'window.log'); }
103
104
@memoize
105
get userRoamingDataHome(): URI { return URI.file('/User').with({ scheme: Schemas.vscodeUserData }); }
106
107
@memoize
108
get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); }
109
110
@memoize
111
get cacheHome(): URI { return joinPath(this.userRoamingDataHome, 'caches'); }
112
113
@memoize
114
get workspaceStorageHome(): URI { return joinPath(this.userRoamingDataHome, 'workspaceStorage'); }
115
116
@memoize
117
get localHistoryHome(): URI { return joinPath(this.userRoamingDataHome, 'History'); }
118
119
@memoize
120
get stateResource(): URI { return joinPath(this.userRoamingDataHome, 'State', 'storage.json'); }
121
122
/**
123
* In Web every workspace can potentially have scoped user-data
124
* and/or extensions and if Sync state is shared then it can make
125
* Sync error prone - say removing extensions from another workspace.
126
* Hence scope Sync state per workspace. Sync scoped to a workspace
127
* is capable of handling opening same workspace in multiple windows.
128
*/
129
@memoize
130
get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync', this.workspaceId); }
131
132
@memoize
133
get sync(): 'on' | 'off' | undefined { return undefined; }
134
135
@memoize
136
get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); }
137
138
@memoize
139
get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); }
140
141
@memoize
142
get builtinWorkbenchModesHome(): URI { return joinPath(this.userRoamingDataHome, 'builtinWorkbenchModes'); }
143
144
@memoize
145
get serviceMachineIdResource(): URI { return joinPath(this.userRoamingDataHome, 'machineid'); }
146
147
@memoize
148
get extHostLogsPath(): URI { return joinPath(this.logsHome, 'exthost'); }
149
150
private extensionHostDebugEnvironment: IExtensionHostDebugEnvironment | undefined = undefined;
151
152
@memoize
153
get debugExtensionHost(): IExtensionHostDebugParams {
154
if (!this.extensionHostDebugEnvironment) {
155
this.extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
156
}
157
158
return this.extensionHostDebugEnvironment.params;
159
}
160
161
@memoize
162
get isExtensionDevelopment(): boolean {
163
if (!this.extensionHostDebugEnvironment) {
164
this.extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
165
}
166
167
return this.extensionHostDebugEnvironment.isExtensionDevelopment;
168
}
169
170
@memoize
171
get extensionDevelopmentLocationURI(): URI[] | undefined {
172
if (!this.extensionHostDebugEnvironment) {
173
this.extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
174
}
175
176
return this.extensionHostDebugEnvironment.extensionDevelopmentLocationURI;
177
}
178
179
@memoize
180
get extensionDevelopmentLocationKind(): ExtensionKind[] | undefined {
181
if (!this.extensionHostDebugEnvironment) {
182
this.extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
183
}
184
185
return this.extensionHostDebugEnvironment.extensionDevelopmentKind;
186
}
187
188
@memoize
189
get extensionTestsLocationURI(): URI | undefined {
190
if (!this.extensionHostDebugEnvironment) {
191
this.extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
192
}
193
194
return this.extensionHostDebugEnvironment.extensionTestsLocationURI;
195
}
196
197
@memoize
198
get extensionEnabledProposedApi(): string[] | undefined {
199
if (!this.extensionHostDebugEnvironment) {
200
this.extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
201
}
202
203
return this.extensionHostDebugEnvironment.extensionEnabledProposedApi;
204
}
205
206
@memoize
207
get debugRenderer(): boolean {
208
if (!this.extensionHostDebugEnvironment) {
209
this.extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
210
}
211
212
return this.extensionHostDebugEnvironment.debugRenderer;
213
}
214
215
@memoize
216
get enableSmokeTestDriver() { return this.options.developmentOptions?.enableSmokeTestDriver; }
217
218
@memoize
219
get disableExtensions() { return this.payload?.get('disableExtensions') === 'true'; }
220
221
@memoize
222
get enableExtensions() { return this.options.enabledExtensions; }
223
224
@memoize
225
get webviewExternalEndpoint(): string {
226
const endpoint = this.options.webviewEndpoint
227
|| this.productService.webviewContentExternalBaseUrlTemplate
228
|| 'https://{{uuid}}.vscode-cdn.net/{{quality}}/{{commit}}/out/vs/workbench/contrib/webview/browser/pre/';
229
230
const webviewExternalEndpointCommit = this.payload?.get('webviewExternalEndpointCommit');
231
return endpoint
232
.replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? 'ef65ac1ba57f57f2a3961bfe94aa20481caca4c6')
233
.replace('{{quality}}', (webviewExternalEndpointCommit ? 'insider' : this.productService.quality) ?? 'insider');
234
}
235
236
@memoize
237
get extensionTelemetryLogResource(): URI { return joinPath(this.logsHome, 'extensionTelemetry.log'); }
238
239
@memoize
240
get disableTelemetry(): boolean { return false; }
241
242
@memoize
243
get disableExperiments(): boolean { return false; }
244
245
@memoize
246
get verbose(): boolean { return this.payload?.get('verbose') === 'true'; }
247
248
@memoize
249
get logExtensionHostCommunication(): boolean { return this.payload?.get('logExtensionHostCommunication') === 'true'; }
250
251
@memoize
252
get skipReleaseNotes(): boolean { return this.payload?.get('skipReleaseNotes') === 'true'; }
253
254
@memoize
255
get skipWelcome(): boolean { return this.payload?.get('skipWelcome') === 'true'; }
256
257
@memoize
258
get disableWorkspaceTrust(): boolean { return !this.options.enableWorkspaceTrust; }
259
260
@memoize
261
get profile(): string | undefined { return this.payload?.get('profile'); }
262
263
@memoize
264
get editSessionId(): string | undefined { return this.options.editSessionId; }
265
266
private payload: Map<string, string> | undefined;
267
268
constructor(
269
private readonly workspaceId: string,
270
readonly logsHome: URI,
271
readonly options: IWorkbenchConstructionOptions,
272
private readonly productService: IProductService
273
) {
274
if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) {
275
try {
276
this.payload = new Map(options.workspaceProvider.payload);
277
} catch (error) {
278
onUnexpectedError(error); // possible invalid payload for map
279
}
280
}
281
}
282
283
private resolveExtensionHostDebugEnvironment(): IExtensionHostDebugEnvironment {
284
const extensionHostDebugEnvironment: IExtensionHostDebugEnvironment = {
285
params: {
286
port: null,
287
break: false
288
},
289
debugRenderer: false,
290
isExtensionDevelopment: false,
291
extensionDevelopmentLocationURI: undefined,
292
extensionDevelopmentKind: undefined
293
};
294
295
// Fill in selected extra environmental properties
296
if (this.payload) {
297
for (const [key, value] of this.payload) {
298
switch (key) {
299
case 'extensionDevelopmentPath':
300
if (!extensionHostDebugEnvironment.extensionDevelopmentLocationURI) {
301
extensionHostDebugEnvironment.extensionDevelopmentLocationURI = [];
302
}
303
extensionHostDebugEnvironment.extensionDevelopmentLocationURI.push(URI.parse(value));
304
extensionHostDebugEnvironment.isExtensionDevelopment = true;
305
break;
306
case 'extensionDevelopmentKind':
307
extensionHostDebugEnvironment.extensionDevelopmentKind = [<ExtensionKind>value];
308
break;
309
case 'extensionTestsPath':
310
extensionHostDebugEnvironment.extensionTestsLocationURI = URI.parse(value);
311
break;
312
case 'debugRenderer':
313
extensionHostDebugEnvironment.debugRenderer = value === 'true';
314
break;
315
case 'debugId':
316
extensionHostDebugEnvironment.params.debugId = value;
317
break;
318
case 'inspect-brk-extensions':
319
extensionHostDebugEnvironment.params.port = parseInt(value);
320
extensionHostDebugEnvironment.params.break = true;
321
break;
322
case 'inspect-extensions':
323
extensionHostDebugEnvironment.params.port = parseInt(value);
324
break;
325
case 'enableProposedApi':
326
extensionHostDebugEnvironment.extensionEnabledProposedApi = [];
327
break;
328
}
329
}
330
}
331
332
const developmentOptions = this.options.developmentOptions;
333
if (developmentOptions && !extensionHostDebugEnvironment.isExtensionDevelopment) {
334
if (developmentOptions.extensions?.length) {
335
extensionHostDebugEnvironment.extensionDevelopmentLocationURI = developmentOptions.extensions.map(e => URI.revive(e));
336
extensionHostDebugEnvironment.isExtensionDevelopment = true;
337
}
338
339
if (developmentOptions.extensionTestsPath) {
340
extensionHostDebugEnvironment.extensionTestsLocationURI = URI.revive(developmentOptions.extensionTestsPath);
341
}
342
}
343
344
return extensionHostDebugEnvironment;
345
}
346
347
@memoize
348
get filesToOpenOrCreate(): IPath<ITextEditorOptions>[] | undefined {
349
if (this.payload) {
350
const fileToOpen = this.payload.get('openFile');
351
if (fileToOpen) {
352
const fileUri = URI.parse(fileToOpen);
353
354
// Support: --goto parameter to open on line/col
355
if (this.payload.has('gotoLineMode')) {
356
const pathColumnAware = parseLineAndColumnAware(fileUri.path);
357
358
return [{
359
fileUri: fileUri.with({ path: pathColumnAware.path }),
360
options: {
361
selection: !isUndefined(pathColumnAware.line) ? { startLineNumber: pathColumnAware.line, startColumn: pathColumnAware.column || 1 } : undefined
362
}
363
}];
364
}
365
366
return [{ fileUri }];
367
}
368
}
369
370
return undefined;
371
}
372
373
@memoize
374
get filesToDiff(): IPath[] | undefined {
375
if (this.payload) {
376
const fileToDiffPrimary = this.payload.get('diffFilePrimary');
377
const fileToDiffSecondary = this.payload.get('diffFileSecondary');
378
if (fileToDiffPrimary && fileToDiffSecondary) {
379
return [
380
{ fileUri: URI.parse(fileToDiffSecondary) },
381
{ fileUri: URI.parse(fileToDiffPrimary) }
382
];
383
}
384
}
385
386
return undefined;
387
}
388
389
@memoize
390
get filesToMerge(): IPath[] | undefined {
391
if (this.payload) {
392
const fileToMerge1 = this.payload.get('mergeFile1');
393
const fileToMerge2 = this.payload.get('mergeFile2');
394
const fileToMergeBase = this.payload.get('mergeFileBase');
395
const fileToMergeResult = this.payload.get('mergeFileResult');
396
if (fileToMerge1 && fileToMerge2 && fileToMergeBase && fileToMergeResult) {
397
return [
398
{ fileUri: URI.parse(fileToMerge1) },
399
{ fileUri: URI.parse(fileToMerge2) },
400
{ fileUri: URI.parse(fileToMergeBase) },
401
{ fileUri: URI.parse(fileToMergeResult) }
402
];
403
}
404
}
405
406
return undefined;
407
}
408
}
409
410
interface IExtensionHostDebugEnvironment {
411
params: IExtensionHostDebugParams;
412
debugRenderer: boolean;
413
isExtensionDevelopment: boolean;
414
extensionDevelopmentLocationURI?: URI[];
415
extensionDevelopmentKind?: ExtensionKind[];
416
extensionTestsLocationURI?: URI;
417
extensionEnabledProposedApi?: string[];
418
}
419
420