Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/code/electron-main/main.ts
5240 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 '../../platform/update/common/update.config.contribution.js';
7
8
import { app, dialog } from 'electron';
9
import { unlinkSync, promises } from 'fs';
10
import { URI } from '../../base/common/uri.js';
11
import { coalesce, distinct } from '../../base/common/arrays.js';
12
import { Promises } from '../../base/common/async.js';
13
import { toErrorMessage } from '../../base/common/errorMessage.js';
14
import { ExpectedError, setUnexpectedErrorHandler } from '../../base/common/errors.js';
15
import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from '../../base/common/extpath.js';
16
import { Event } from '../../base/common/event.js';
17
import { getPathLabel } from '../../base/common/labels.js';
18
import { Schemas } from '../../base/common/network.js';
19
import { basename, resolve } from '../../base/common/path.js';
20
import { mark } from '../../base/common/performance.js';
21
import { IProcessEnvironment, isLinux, isMacintosh, isWindows, OS } from '../../base/common/platform.js';
22
import { cwd } from '../../base/common/process.js';
23
import { rtrim, trim } from '../../base/common/strings.js';
24
import { Promises as FSPromises } from '../../base/node/pfs.js';
25
import { ProxyChannel } from '../../base/parts/ipc/common/ipc.js';
26
import { Client as NodeIPCClient } from '../../base/parts/ipc/common/ipc.net.js';
27
import { connect as nodeIPCConnect, serve as nodeIPCServe, Server as NodeIPCServer, XDG_RUNTIME_DIR } from '../../base/parts/ipc/node/ipc.net.js';
28
import { CodeApplication } from './app.js';
29
import { localize } from '../../nls.js';
30
import { IConfigurationService } from '../../platform/configuration/common/configuration.js';
31
import { ConfigurationService } from '../../platform/configuration/common/configurationService.js';
32
import { IDiagnosticsMainService } from '../../platform/diagnostics/electron-main/diagnosticsMainService.js';
33
import { DiagnosticsService } from '../../platform/diagnostics/node/diagnosticsService.js';
34
import { NativeParsedArgs } from '../../platform/environment/common/argv.js';
35
import { EnvironmentMainService, IEnvironmentMainService } from '../../platform/environment/electron-main/environmentMainService.js';
36
import { addArg, parseMainProcessArgv } from '../../platform/environment/node/argvHelper.js';
37
import { createWaitMarkerFileSync } from '../../platform/environment/node/wait.js';
38
import { IFileService } from '../../platform/files/common/files.js';
39
import { FileService } from '../../platform/files/common/fileService.js';
40
import { DiskFileSystemProvider } from '../../platform/files/node/diskFileSystemProvider.js';
41
import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js';
42
import { IInstantiationService, ServicesAccessor } from '../../platform/instantiation/common/instantiation.js';
43
import { InstantiationService } from '../../platform/instantiation/common/instantiationService.js';
44
import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js';
45
import { ILaunchMainService } from '../../platform/launch/electron-main/launchMainService.js';
46
import { ILifecycleMainService, LifecycleMainService } from '../../platform/lifecycle/electron-main/lifecycleMainService.js';
47
import { BufferLogger } from '../../platform/log/common/bufferLog.js';
48
import { ConsoleMainLogger, getLogLevel, ILoggerService, ILogService } from '../../platform/log/common/log.js';
49
import product from '../../platform/product/common/product.js';
50
import { IProductService } from '../../platform/product/common/productService.js';
51
import { IProtocolMainService } from '../../platform/protocol/electron-main/protocol.js';
52
import { ProtocolMainService } from '../../platform/protocol/electron-main/protocolMainService.js';
53
import { ITunnelService } from '../../platform/tunnel/common/tunnel.js';
54
import { TunnelService } from '../../platform/tunnel/node/tunnelService.js';
55
import { IRequestService } from '../../platform/request/common/request.js';
56
import { RequestService } from '../../platform/request/electron-utility/requestService.js';
57
import { ISignService } from '../../platform/sign/common/sign.js';
58
import { SignService } from '../../platform/sign/node/signService.js';
59
import { IStateReadService, IStateService } from '../../platform/state/node/state.js';
60
import { NullTelemetryService } from '../../platform/telemetry/common/telemetryUtils.js';
61
import { IThemeMainService } from '../../platform/theme/electron-main/themeMainService.js';
62
import { IUserDataProfilesMainService, UserDataProfilesMainService } from '../../platform/userDataProfile/electron-main/userDataProfile.js';
63
import { IPolicyService, NullPolicyService } from '../../platform/policy/common/policy.js';
64
import { NativePolicyService } from '../../platform/policy/node/nativePolicyService.js';
65
import { FilePolicyService } from '../../platform/policy/common/filePolicyService.js';
66
import { DisposableStore } from '../../base/common/lifecycle.js';
67
import { IUriIdentityService } from '../../platform/uriIdentity/common/uriIdentity.js';
68
import { UriIdentityService } from '../../platform/uriIdentity/common/uriIdentityService.js';
69
import { ILoggerMainService, LoggerMainService } from '../../platform/log/electron-main/loggerService.js';
70
import { LogService } from '../../platform/log/common/logService.js';
71
import { massageMessageBoxOptions } from '../../platform/dialogs/common/dialogs.js';
72
import { SaveStrategy, StateService } from '../../platform/state/node/stateService.js';
73
import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js';
74
import { addUNCHostToAllowlist, getUNCHost } from '../../base/node/unc.js';
75
import { ThemeMainService } from '../../platform/theme/electron-main/themeMainServiceImpl.js';
76
import { LINUX_SYSTEM_POLICY_FILE_PATH } from '../../base/common/policy.js';
77
78
/**
79
* The main VS Code entry point.
80
*
81
* Note: This class can exist more than once for example when VS Code is already
82
* running and a second instance is started from the command line. It will always
83
* try to communicate with an existing instance to prevent that 2 VS Code instances
84
* are running at the same time.
85
*/
86
class CodeMain {
87
88
main(): void {
89
try {
90
this.startup();
91
} catch (error) {
92
console.error(error.message);
93
app.exit(1);
94
}
95
}
96
97
private async startup(): Promise<void> {
98
99
// Set the error handler early enough so that we are not getting the
100
// default electron error dialog popping up
101
setUnexpectedErrorHandler(err => console.error(err));
102
103
// Create services
104
const [instantiationService, instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogger, productService, userDataProfilesMainService] = this.createServices();
105
106
try {
107
108
// Init services
109
try {
110
await this.initServices(environmentMainService, userDataProfilesMainService, configurationService, stateMainService, productService);
111
} catch (error) {
112
113
// Show a dialog for errors that can be resolved by the user
114
this.handleStartupDataDirError(environmentMainService, productService, error);
115
116
throw error;
117
}
118
119
// Startup
120
await instantiationService.invokeFunction(async accessor => {
121
const logService = accessor.get(ILogService);
122
const lifecycleMainService = accessor.get(ILifecycleMainService);
123
const fileService = accessor.get(IFileService);
124
const loggerService = accessor.get(ILoggerService);
125
126
// Create the main IPC server by trying to be the server
127
// If this throws an error it means we are not the first
128
// instance of VS Code running and so we would quit.
129
const mainProcessNodeIpcServer = await this.claimInstance(logService, environmentMainService, lifecycleMainService, instantiationService, productService, true);
130
131
// Write a lockfile to indicate an instance is running
132
// (https://github.com/microsoft/vscode/issues/127861#issuecomment-877417451)
133
FSPromises.writeFile(environmentMainService.mainLockfile, String(process.pid)).catch(err => {
134
logService.warn(`app#startup(): Error writing main lockfile: ${err.stack}`);
135
});
136
137
// Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906)
138
bufferLogger.logger = loggerService.createLogger('main', { name: localize('mainLog', "Main") });
139
140
// Lifecycle
141
Event.once(lifecycleMainService.onWillShutdown)(evt => {
142
fileService.dispose();
143
configurationService.dispose();
144
evt.join('instanceLockfile', promises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ }));
145
});
146
147
// Check if Inno Setup is running
148
const innoSetupActive = await this.checkInnoSetupMutex(productService);
149
if (innoSetupActive) {
150
const message = `${productService.nameShort} is currently being updated. Please wait for the update to complete before launching.`;
151
instantiationService.invokeFunction(this.quit, new Error(message));
152
return;
153
}
154
155
return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();
156
});
157
} catch (error) {
158
instantiationService.invokeFunction(this.quit, error);
159
}
160
}
161
162
private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogger, IProductService, UserDataProfilesMainService] {
163
const services = new ServiceCollection();
164
const disposables = new DisposableStore();
165
process.once('exit', () => disposables.dispose());
166
167
// Product
168
const productService = { _serviceBrand: undefined, ...product };
169
services.set(IProductService, productService);
170
171
// Environment
172
const environmentMainService = new EnvironmentMainService(this.resolveArgs(), productService);
173
const instanceEnvironment = this.patchEnvironment(environmentMainService); // Patch `process.env` with the instance's environment
174
services.set(IEnvironmentMainService, environmentMainService);
175
176
// Logger
177
const loggerService = new LoggerMainService(getLogLevel(environmentMainService), environmentMainService.logsHome);
178
services.set(ILoggerMainService, loggerService);
179
180
// Log: We need to buffer the spdlog logs until we are sure
181
// we are the only instance running, otherwise we'll have concurrent
182
// log file access on Windows (https://github.com/microsoft/vscode/issues/41218)
183
const bufferLogger = new BufferLogger(loggerService.getLogLevel());
184
const logService = disposables.add(new LogService(bufferLogger, [new ConsoleMainLogger(loggerService.getLogLevel())]));
185
services.set(ILogService, logService);
186
187
// Files
188
const fileService = new FileService(logService);
189
services.set(IFileService, fileService);
190
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
191
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
192
193
// URI Identity
194
const uriIdentityService = new UriIdentityService(fileService);
195
services.set(IUriIdentityService, uriIdentityService);
196
197
// State
198
const stateService = new StateService(SaveStrategy.DELAYED, environmentMainService, logService, fileService);
199
services.set(IStateReadService, stateService);
200
services.set(IStateService, stateService);
201
202
// User Data Profiles
203
const userDataProfilesMainService = new UserDataProfilesMainService(stateService, uriIdentityService, environmentMainService, fileService, logService);
204
services.set(IUserDataProfilesMainService, userDataProfilesMainService);
205
206
// Use FileUserDataProvider for user data to
207
// enable atomic read / write operations.
208
fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, userDataProfilesMainService, uriIdentityService, logService));
209
210
// Policy
211
let policyService: IPolicyService | undefined;
212
if (isWindows && productService.win32RegValueName) {
213
policyService = disposables.add(new NativePolicyService(logService, productService.win32RegValueName));
214
} else if (isMacintosh && productService.darwinBundleIdentifier) {
215
policyService = disposables.add(new NativePolicyService(logService, productService.darwinBundleIdentifier));
216
} else if (isLinux) {
217
policyService = disposables.add(new FilePolicyService(URI.file(LINUX_SYSTEM_POLICY_FILE_PATH), fileService, logService));
218
} else if (environmentMainService.policyFile) {
219
policyService = disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService));
220
} else {
221
policyService = new NullPolicyService();
222
}
223
services.set(IPolicyService, policyService);
224
225
// Configuration
226
const configurationService = new ConfigurationService(userDataProfilesMainService.defaultProfile.settingsResource, fileService, policyService, logService);
227
services.set(IConfigurationService, configurationService);
228
229
// Lifecycle
230
services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService, undefined, false));
231
232
// Request
233
services.set(IRequestService, new SyncDescriptor(RequestService, undefined, true));
234
235
// Themes
236
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
237
238
// Signing
239
services.set(ISignService, new SyncDescriptor(SignService, undefined, false /* proxied to other processes */));
240
241
// Tunnel
242
services.set(ITunnelService, new SyncDescriptor(TunnelService));
243
244
// Protocol (instantiated early and not using sync descriptor for security reasons)
245
services.set(IProtocolMainService, new ProtocolMainService(environmentMainService, userDataProfilesMainService, logService));
246
247
return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogger, productService, userDataProfilesMainService];
248
}
249
250
private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment {
251
const instanceEnvironment: IProcessEnvironment = {
252
VSCODE_IPC_HOOK: environmentMainService.mainIPCHandle
253
};
254
255
['VSCODE_NLS_CONFIG', 'VSCODE_PORTABLE'].forEach(key => {
256
const value = process.env[key];
257
if (typeof value === 'string') {
258
instanceEnvironment[key] = value;
259
}
260
});
261
262
Object.assign(process.env, instanceEnvironment);
263
264
return instanceEnvironment;
265
}
266
267
private async initServices(environmentMainService: IEnvironmentMainService, userDataProfilesMainService: UserDataProfilesMainService, configurationService: ConfigurationService, stateService: StateService, productService: IProductService): Promise<void> {
268
await Promises.settled<unknown>([
269
270
// Environment service (paths)
271
Promise.all<string | undefined>([
272
this.allowWindowsUNCPath(environmentMainService.extensionsPath), // enable extension paths on UNC drives...
273
environmentMainService.codeCachePath, // ...other user-data-derived paths should already be enlisted from `main.js`
274
environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath,
275
userDataProfilesMainService.defaultProfile.globalStorageHome.with({ scheme: Schemas.file }).fsPath,
276
environmentMainService.workspaceStorageHome.with({ scheme: Schemas.file }).fsPath,
277
environmentMainService.localHistoryHome.with({ scheme: Schemas.file }).fsPath,
278
environmentMainService.backupHome
279
].map(path => path ? promises.mkdir(path, { recursive: true }) : undefined)),
280
281
// State service
282
stateService.init(),
283
284
// Configuration service
285
configurationService.initialize()
286
]);
287
288
// Initialize user data profiles after initializing the state
289
userDataProfilesMainService.init();
290
}
291
292
private allowWindowsUNCPath(path: string): string {
293
if (isWindows) {
294
const host = getUNCHost(path);
295
if (host) {
296
addUNCHostToAllowlist(host);
297
}
298
}
299
300
return path;
301
}
302
303
private async claimInstance(logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise<NodeIPCServer> {
304
305
// Try to setup a server for running. If that succeeds it means
306
// we are the first instance to startup. Otherwise it is likely
307
// that another instance is already running.
308
let mainProcessNodeIpcServer: NodeIPCServer;
309
try {
310
mark('code/willStartMainServer');
311
mainProcessNodeIpcServer = await nodeIPCServe(environmentMainService.mainIPCHandle);
312
mark('code/didStartMainServer');
313
Event.once(lifecycleMainService.onWillShutdown)(() => mainProcessNodeIpcServer.dispose());
314
} catch (error) {
315
316
// Handle unexpected errors (the only expected error is EADDRINUSE that
317
// indicates another instance of VS Code is running)
318
if (error.code !== 'EADDRINUSE') {
319
320
// Show a dialog for errors that can be resolved by the user
321
this.handleStartupDataDirError(environmentMainService, productService, error);
322
323
// Any other runtime error is just printed to the console
324
throw error;
325
}
326
327
// there's a running instance, let's connect to it
328
let client: NodeIPCClient<string>;
329
try {
330
client = await nodeIPCConnect(environmentMainService.mainIPCHandle, 'main');
331
} catch (error) {
332
333
// Handle unexpected connection errors by showing a dialog to the user
334
if (!retry || isWindows || error.code !== 'ECONNREFUSED') {
335
if (error.code === 'EPERM') {
336
this.showStartupWarningDialog(
337
localize('secondInstanceAdmin', "Another instance of {0} is already running as administrator.", productService.nameShort),
338
localize('secondInstanceAdminDetail', "Please close the other instance and try again."),
339
productService
340
);
341
}
342
343
throw error;
344
}
345
346
// it happens on Linux and OS X that the pipe is left behind
347
// let's delete it, since we can't connect to it and then
348
// retry the whole thing
349
try {
350
unlinkSync(environmentMainService.mainIPCHandle);
351
} catch (error) {
352
logService.warn('Could not delete obsolete instance handle', error);
353
354
throw error;
355
}
356
357
return this.claimInstance(logService, environmentMainService, lifecycleMainService, instantiationService, productService, false);
358
}
359
360
// Tests from CLI require to be the only instance currently
361
if (environmentMainService.extensionTestsLocationURI && !environmentMainService.debugExtensionHost.break) {
362
const msg = `Running extension tests from the command line is currently only supported if no other instance of ${productService.nameShort} is running.`;
363
logService.error(msg);
364
client.dispose();
365
366
throw new Error(msg);
367
}
368
369
// Show a warning dialog after some timeout if it takes long to talk to the other instance
370
// Skip this if we are running with --wait where it is expected that we wait for a while.
371
// Also skip when gathering diagnostics (--status) which can take a longer time.
372
let startupWarningDialogHandle: Timeout | undefined = undefined;
373
if (!environmentMainService.args.wait && !environmentMainService.args.status) {
374
startupWarningDialogHandle = setTimeout(() => {
375
this.showStartupWarningDialog(
376
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", productService.nameShort),
377
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again."),
378
productService
379
);
380
}, 10000);
381
}
382
383
const otherInstanceLaunchMainService = ProxyChannel.toService<ILaunchMainService>(client.getChannel('launch'), { disableMarshalling: true });
384
const otherInstanceDiagnosticsMainService = ProxyChannel.toService<IDiagnosticsMainService>(client.getChannel('diagnostics'), { disableMarshalling: true });
385
386
// Process Info
387
if (environmentMainService.args.status) {
388
return instantiationService.invokeFunction(async () => {
389
const diagnosticsService = new DiagnosticsService(NullTelemetryService, productService);
390
const mainDiagnostics = await otherInstanceDiagnosticsMainService.getMainDiagnostics();
391
const remoteDiagnostics = await otherInstanceDiagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true });
392
const diagnostics = await diagnosticsService.getDiagnostics(mainDiagnostics, remoteDiagnostics);
393
console.log(diagnostics);
394
395
throw new ExpectedError();
396
});
397
}
398
399
// Windows: allow to set foreground
400
if (isWindows) {
401
await this.windowsAllowSetForegroundWindow(otherInstanceLaunchMainService, logService);
402
}
403
404
// Send environment over...
405
logService.trace('Sending env to running instance...');
406
await otherInstanceLaunchMainService.start(environmentMainService.args, process.env as IProcessEnvironment);
407
408
// Cleanup
409
client.dispose();
410
411
// Now that we started, make sure the warning dialog is prevented
412
if (startupWarningDialogHandle) {
413
clearTimeout(startupWarningDialogHandle);
414
}
415
416
throw new ExpectedError('Sent env to running instance. Terminating...');
417
}
418
419
// Print --status usage info
420
if (environmentMainService.args.status) {
421
console.log(localize('statusWarning', "Warning: The --status argument can only be used if {0} is already running. Please run it again after {0} has started.", productService.nameShort));
422
423
throw new ExpectedError('Terminating...');
424
}
425
426
// Set the VSCODE_PID variable here when we are sure we are the first
427
// instance to startup. Otherwise we would wrongly overwrite the PID
428
process.env['VSCODE_PID'] = String(process.pid);
429
430
return mainProcessNodeIpcServer;
431
}
432
433
private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, productService: IProductService, error: NodeJS.ErrnoException): void {
434
if (error.code === 'EACCES' || error.code === 'EPERM') {
435
const directories = coalesce([environmentMainService.userDataPath, environmentMainService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(URI.file(folder), { os: OS, tildify: environmentMainService }));
436
437
this.showStartupWarningDialog(
438
localize('startupDataDirError', "Unable to write program user data."),
439
localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n')),
440
productService
441
);
442
}
443
}
444
445
private showStartupWarningDialog(message: string, detail: string, productService: IProductService): void {
446
447
// use sync variant here because we likely exit after this method
448
// due to startup issues and otherwise the dialog seems to disappear
449
// https://github.com/microsoft/vscode/issues/104493
450
451
dialog.showMessageBoxSync(massageMessageBoxOptions({
452
type: 'warning',
453
buttons: [localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close")],
454
message,
455
detail
456
}, productService).options);
457
}
458
459
private async windowsAllowSetForegroundWindow(launchMainService: ILaunchMainService, logService: ILogService): Promise<void> {
460
if (isWindows) {
461
const processId = await launchMainService.getMainProcessId();
462
463
logService.trace('Sending some foreground love to the running instance:', processId);
464
465
try {
466
(await import('windows-foreground-love')).allowSetForegroundWindow(processId);
467
} catch (error) {
468
logService.error(error);
469
}
470
}
471
}
472
473
private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
474
const logService = accessor.get(ILogService);
475
const lifecycleMainService = accessor.get(ILifecycleMainService);
476
477
let exitCode = 0;
478
479
if (reason) {
480
if ((reason as ExpectedError).isExpected) {
481
if (reason.message) {
482
logService.trace(reason.message);
483
}
484
} else {
485
exitCode = 1; // signal error to the outside
486
487
if (reason.stack) {
488
logService.error(reason.stack);
489
} else {
490
logService.error(`Startup error: ${reason.toString()}`);
491
}
492
}
493
}
494
495
lifecycleMainService.kill(exitCode);
496
}
497
498
private async checkInnoSetupMutex(productService: IProductService): Promise<boolean> {
499
if (!(isWindows && productService.win32MutexName && productService.win32VersionedUpdate)) {
500
return false;
501
}
502
503
try {
504
const updatingMutexName = `${productService.win32MutexName}-updating`;
505
const mutex = await import('@vscode/windows-mutex');
506
return mutex.isActive(updatingMutexName);
507
} catch (error) {
508
console.error('Failed to check Inno Setup mutex:', error);
509
return false;
510
}
511
}
512
513
//#region Command line arguments utilities
514
515
private resolveArgs(): NativeParsedArgs {
516
517
// Parse arguments
518
const args = this.validatePaths(parseMainProcessArgv(process.argv));
519
520
if (args.wait && !args.waitMarkerFilePath) {
521
// If we are started with --wait create a random temporary file
522
// and pass it over to the starting instance. We can use this file
523
// to wait for it to be deleted to monitor that the edited file
524
// is closed and then exit the waiting process.
525
//
526
// Note: we are not doing this if the wait marker has been already
527
// added as argument. This can happen if VS Code was started from CLI.
528
const waitMarkerFilePath = createWaitMarkerFileSync(args.verbose);
529
if (waitMarkerFilePath) {
530
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
531
args.waitMarkerFilePath = waitMarkerFilePath;
532
}
533
}
534
535
if (args.chat) {
536
if (args.chat['new-window']) {
537
// Apply `--new-window` flag to the main arguments
538
args['new-window'] = true;
539
} else if (args.chat['reuse-window']) {
540
// Apply `--reuse-window` flag to the main arguments
541
args['reuse-window'] = true;
542
} else if (args.chat['profile']) {
543
// Apply `--profile` flag to the main arguments
544
args['profile'] = args.chat['profile'];
545
} else {
546
// Unless we are started with specific instructions about
547
// new windows or reusing existing ones, always take the
548
// current working directory as workspace to open.
549
args._ = [cwd()];
550
}
551
}
552
553
return args;
554
}
555
556
private validatePaths(args: NativeParsedArgs): NativeParsedArgs {
557
558
// Track URLs if they're going to be used
559
if (args['open-url']) {
560
args._urls = args._;
561
args._ = [];
562
}
563
564
// Normalize paths and watch out for goto line mode
565
if (!args['remote']) {
566
const paths = this.doValidatePaths(args._, args.goto);
567
args._ = paths;
568
}
569
570
return args;
571
}
572
573
private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
574
const currentWorkingDir = cwd();
575
const result = args.map(arg => {
576
let pathCandidate = String(arg);
577
578
let parsedPath: IPathWithLineAndColumn | undefined = undefined;
579
if (gotoLineMode) {
580
parsedPath = parseLineAndColumnAware(pathCandidate);
581
pathCandidate = parsedPath.path;
582
}
583
584
if (pathCandidate) {
585
pathCandidate = this.preparePath(currentWorkingDir, pathCandidate);
586
}
587
588
const sanitizedFilePath = sanitizeFilePath(pathCandidate, currentWorkingDir);
589
590
const filePathBasename = basename(sanitizedFilePath);
591
if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) {
592
return null; // do not allow invalid file names
593
}
594
595
if (gotoLineMode && parsedPath) {
596
parsedPath.path = sanitizedFilePath;
597
598
return this.toPath(parsedPath);
599
}
600
601
return sanitizedFilePath;
602
});
603
604
const caseInsensitive = isWindows || isMacintosh;
605
const distinctPaths = distinct(result, path => path && caseInsensitive ? path.toLowerCase() : (path || ''));
606
607
return coalesce(distinctPaths);
608
}
609
610
private preparePath(cwd: string, path: string): string {
611
612
// Trim trailing quotes
613
if (isWindows) {
614
path = rtrim(path, '"'); // https://github.com/microsoft/vscode/issues/1498
615
}
616
617
// Trim whitespaces
618
path = trim(trim(path, ' '), '\t');
619
620
if (isWindows) {
621
622
// Resolve the path against cwd if it is relative
623
path = resolve(cwd, path);
624
625
// Trim trailing '.' chars on Windows to prevent invalid file names
626
path = rtrim(path, '.');
627
}
628
629
return path;
630
}
631
632
private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string {
633
const segments = [pathWithLineAndCol.path];
634
635
if (typeof pathWithLineAndCol.line === 'number') {
636
segments.push(String(pathWithLineAndCol.line));
637
}
638
639
if (typeof pathWithLineAndCol.column === 'number') {
640
segments.push(String(pathWithLineAndCol.column));
641
}
642
643
return segments.join(':');
644
}
645
646
//#endregion
647
}
648
649
// Main Startup
650
const code = new CodeMain();
651
code.main();
652
653