diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index a4761e883d43..7fa44bd843df 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -310,7 +310,7 @@ export class CodeApplication extends Disposable { } } - startup(): Promise { + async startup(): Promise { this.logService.debug('Starting VS Code'); this.logService.debug(`from: ${this.environmentService.appRoot}`); this.logService.debug('args:', this.environmentService.args); @@ -340,62 +340,55 @@ export class CodeApplication extends Disposable { // Create Electron IPC Server this.electronIpcServer = new ElectronIPCServer(); - const startupWithMachineId = (machineId: string) => { - this.logService.trace(`Resolved machine identifier: ${machineId}`); - - // Spawn shared process - this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); - this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); - - // Services - return this.initServices(machineId).then(appInstantiationService => { - - // Create driver - if (this.environmentService.driverHandle) { - serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => { - this.logService.info('Driver started at:', this.environmentService.driverHandle); - this._register(server); - }); - } - - // Setup Auth Handler - const authHandler = appInstantiationService.createInstance(ProxyAuthHandler); - this._register(authHandler); - - // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor)); - - // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); - - // Tracing: Stop tracing after windows are ready if enabled - if (this.environmentService.args.trace) { - this.stopTracingEventually(windows); - } - }); - }; - // Resolve unique machine ID this.logService.trace('Resolving machine identifier...'); - const resolvedMachineId = this.resolveMachineId(); - if (typeof resolvedMachineId === 'string') { - return startupWithMachineId(resolvedMachineId); - } else { - return resolvedMachineId.then(machineId => startupWithMachineId(machineId)); + const machineId = await this.resolveMachineId(); + this.logService.trace(`Resolved machine identifier: ${machineId}`); + + // Spawn shared process + this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); + this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); + + // Services + const appInstantiationService = await this.initServices(machineId); + + // Create driver + if (this.environmentService.driverHandle) { + (async () => { + const server = await serveDriver(this.electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService); + + this.logService.info('Driver started at:', this.environmentService.driverHandle); + this._register(server); + })(); + } + + // Setup Auth Handler + const authHandler = appInstantiationService.createInstance(ProxyAuthHandler); + this._register(authHandler); + + // Open Windows + const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor)); + + // Post Open Windows Tasks + appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + + // Tracing: Stop tracing after windows are ready if enabled + if (this.environmentService.args.trace) { + this.stopTracingEventually(windows); } } - private resolveMachineId(): string | Promise { - const machineId = this.stateService.getItem(CodeApplication.MACHINE_ID_KEY); + private async resolveMachineId(): Promise { + let machineId = this.stateService.getItem(CodeApplication.MACHINE_ID_KEY); if (machineId) { return machineId; } - return getMachineId().then(machineId => { - this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId); + machineId = await getMachineId(); - return machineId; - }); + this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId); + + return machineId; } private stopTracingEventually(windows: ICodeWindow[]): void { @@ -433,7 +426,7 @@ export class CodeApplication extends Disposable { }); } - private initServices(machineId: string): Promise { + private async initServices(machineId: string): Promise { const services = new ServiceCollection(); if (process.platform === 'win32') { @@ -475,10 +468,12 @@ export class CodeApplication extends Disposable { const appInstantiationService = this.instantiationService.createChild(services); // Init services that require it - return appInstantiationService.invokeFunction(accessor => Promise.all([ + await appInstantiationService.invokeFunction(accessor => Promise.all([ this.initStorageService(accessor), this.initBackupService(accessor) - ])).then(() => appInstantiationService); + ])); + + return appInstantiationService; } private initStorageService(accessor: ServicesAccessor): Promise { @@ -558,15 +553,17 @@ export class CodeApplication extends Disposable { const environmentService = accessor.get(IEnvironmentService); urlService.registerHandler({ - handleURL(uri: URI): Promise { + async handleURL(uri: URI): Promise { if (windowsMainService.getWindowCount() === 0) { const cli = { ...environmentService.args, goto: true }; const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true }); - return window.ready().then(() => urlService.open(uri)); + await window.ready(); + + return urlService.open(uri); } - return Promise.resolve(false); + return false; } }); } @@ -738,9 +735,7 @@ export class CodeApplication extends Disposable { dispose(): void { this._disposeRunner.dispose(); connectionPool.delete(this._authority); - this._connection.then((connection) => { - connection.dispose(); - }); + this._connection.then(connection => connection.dispose()); } async getClient(): Promise> { diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 740d068e4975..631dd7c81381 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -61,42 +61,21 @@ function setupIPC(accessor: ServicesAccessor): Promise { } } - function setup(retry: boolean): Promise { - return serve(environmentService.mainIPCHandle).then(server => { - - // Print --status usage info - if (environmentService.args.status) { - logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); - throw new ExpectedError('Terminating...'); - } - - // Log uploader usage info - if (typeof environmentService.args['upload-logs'] !== 'undefined') { - logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.'); - throw new ExpectedError('Terminating...'); - } - - // dock might be hidden at this case due to a retry - if (platform.isMacintosh) { - app.dock.show(); - } - - // Set the VSCODE_PID variable here when we are sure we are the first - // instance to startup. Otherwise we would wrongly overwrite the PID - process.env['VSCODE_PID'] = String(process.pid); - - return server; - }, err => { + async function setup(retry: boolean): Promise { + let server: Server; + try { + server = await serve(environmentService.mainIPCHandle); + } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that // indicates a second instance of Code is running) - if (err.code !== 'EADDRINUSE') { + if (error.code !== 'EADDRINUSE') { // Show a dialog for errors that can be resolved by the user - handleStartupDataDirError(environmentService, err); + handleStartupDataDirError(environmentService, error); // Any other runtime error is just printed to the console - return Promise.reject(err); + throw error; } // Since we are the second instance, we do not want to show the dock @@ -105,93 +84,120 @@ function setupIPC(accessor: ServicesAccessor): Promise { } // there's a running instance, let's connect to it - return connect(environmentService.mainIPCHandle, 'main').then( - client => { + try { + const client = await connect(environmentService.mainIPCHandle, 'main'); - // Tests from CLI require to be the only instance currently - if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) { - const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.'; - logService.error(msg); - client.dispose(); + // Tests from CLI require to be the only instance currently + if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) { + const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.'; + logService.error(msg); + client.dispose(); - return Promise.reject(new Error(msg)); - } - - // Show a warning dialog after some timeout if it takes long to talk to the other instance - // Skip this if we are running with --wait where it is expected that we wait for a while. - // Also skip when gathering diagnostics (--status) which can take a longer time. - let startupWarningDialogHandle: NodeJS.Timeout; - if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) { - startupWarningDialogHandle = setTimeout(() => { - showStartupWarningDialog( - localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), - localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.") - ); - }, 10000); - } - - const channel = client.getChannel('launch'); - const service = new LaunchChannelClient(channel); - - // Process Info - if (environmentService.args.status) { - return instantiationService.invokeFunction(accessor => { - return accessor.get(IDiagnosticsService).getDiagnostics(service).then(diagnostics => { - console.log(diagnostics); - return Promise.reject(new ExpectedError()); - }); - }); - } - - // Log uploader - if (typeof environmentService.args['upload-logs'] !== 'undefined') { - return instantiationService.invokeFunction(accessor => { - return uploadLogs(service, accessor.get(IRequestService), environmentService) - .then(() => Promise.reject(new ExpectedError())); - }); - } - - logService.trace('Sending env to running instance...'); - - return windowsAllowSetForegroundWindow(service) - .then(() => service.start(environmentService.args, process.env as platform.IProcessEnvironment)) - .then(() => client.dispose()) - .then(() => { - - // Now that we started, make sure the warning dialog is prevented - if (startupWarningDialogHandle) { - clearTimeout(startupWarningDialogHandle); - } - - return Promise.reject(new ExpectedError('Sent env to running instance. Terminating...')); - }); - }, - err => { - if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') { - if (err.code === 'EPERM') { - showStartupWarningDialog( - localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort), - localize('secondInstanceAdminDetail', "Please close the other instance and try again.") - ); - } - - return Promise.reject(err); - } - - // it happens on Linux and OS X that the pipe is left behind - // let's delete it, since we can't connect to it - // and then retry the whole thing - try { - fs.unlinkSync(environmentService.mainIPCHandle); - } catch (e) { - logService.warn('Could not delete obsolete instance handle', e); - return Promise.reject(e); - } - - return setup(false); + throw new Error(msg); } - ); - }); + + // Show a warning dialog after some timeout if it takes long to talk to the other instance + // Skip this if we are running with --wait where it is expected that we wait for a while. + // Also skip when gathering diagnostics (--status) which can take a longer time. + let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined; + if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) { + startupWarningDialogHandle = setTimeout(() => { + showStartupWarningDialog( + localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), + localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.") + ); + }, 10000); + } + + const channel = client.getChannel('launch'); + const service = new LaunchChannelClient(channel); + + // Process Info + if (environmentService.args.status) { + return instantiationService.invokeFunction(async accessor => { + const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(service); + + console.log(diagnostics); + throw new ExpectedError(); + }); + } + + // Log uploader + if (typeof environmentService.args['upload-logs'] !== 'undefined') { + return instantiationService.invokeFunction(async accessor => { + await uploadLogs(service, accessor.get(IRequestService), environmentService); + + throw new ExpectedError(); + }); + } + + + // Windows: allow to set foreground + if (platform.isWindows) { + await windowsAllowSetForegroundWindow(service); + } + + // Send environment over... + logService.trace('Sending env to running instance...'); + await service.start(environmentService.args, process.env as platform.IProcessEnvironment); + + // Cleanup + await client.dispose(); + + // Now that we started, make sure the warning dialog is prevented + if (startupWarningDialogHandle) { + clearTimeout(startupWarningDialogHandle); + } + + throw new ExpectedError('Sent env to running instance. Terminating...'); + } catch (error) { + if (!retry || platform.isWindows || error.code !== 'ECONNREFUSED') { + if (error.code === 'EPERM') { + showStartupWarningDialog( + localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort), + localize('secondInstanceAdminDetail', "Please close the other instance and try again.") + ); + } + + throw error; + } + + // it happens on Linux and OS X that the pipe is left behind + // let's delete it, since we can't connect to it + // and then retry the whole thing + try { + fs.unlinkSync(environmentService.mainIPCHandle); + } catch (error) { + logService.warn('Could not delete obsolete instance handle', error); + throw error; + } + + return setup(false); + } + } + + // Print --status usage info + if (environmentService.args.status) { + logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); + throw new ExpectedError('Terminating...'); + } + + // Log uploader usage info + if (typeof environmentService.args['upload-logs'] !== 'undefined') { + logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.'); + throw new ExpectedError('Terminating...'); + } + + // dock might be hidden at this case due to a retry + if (platform.isMacintosh) { + app.dock.show(); + } + + // Set the VSCODE_PID variable here when we are sure we are the first + // instance to startup. Otherwise we would wrongly overwrite the PID + process.env['VSCODE_PID'] = String(process.pid); + + return server; } return setup(true); @@ -258,7 +264,7 @@ function patchEnvironment(environmentService: IEnvironmentService): typeof proce return instanceEnvironment; } -function startup(args: ParsedArgs): void { +async function startup(args: ParsedArgs): Promise { // We need to buffer the spdlog logs until we are sure // we are the only instance running, otherwise we'll have concurrent @@ -266,28 +272,37 @@ function startup(args: ParsedArgs): void { const bufferLogService = new BufferLogService(); const instantiationService = createServices(args, bufferLogService); - instantiationService.invokeFunction(accessor => { - const environmentService = accessor.get(IEnvironmentService); - const stateService = accessor.get(IStateService); + try { + await instantiationService.invokeFunction(async accessor => { + const environmentService = accessor.get(IEnvironmentService); + const stateService = accessor.get(IStateService); - // Patch `process.env` with the instance's environment - const instanceEnvironment = patchEnvironment(environmentService); + // Patch `process.env` with the instance's environment + const instanceEnvironment = patchEnvironment(environmentService); - // Startup - return initServices(environmentService, stateService as StateService) - .then(() => instantiationService.invokeFunction(setupIPC), error => { + // Startup + try { + await initServices(environmentService, stateService as StateService); + } catch (error) { // Show a dialog for errors that can be resolved by the user handleStartupDataDirError(environmentService, error); - return Promise.reject(error); - }) - .then(mainIpcServer => { - createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath).then(logger => bufferLogService.logger = logger); + throw error; + } - return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); - }); - }).then(null, err => instantiationService.invokeFunction(quit, err)); + const mainIpcServer = await instantiationService.invokeFunction(setupIPC); + + (async () => { + const logger = await createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath); + bufferLogService.logger = logger; + })(); + + return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); + }); + } catch (error) { + instantiationService.invokeFunction(quit, error); + } } function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 26db33a6d80a..b0213a2876e2 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -1536,14 +1536,13 @@ export class WindowsManager implements IWindowsMainService { return state; } - reload(win: ICodeWindow, cli?: ParsedArgs): void { + async reload(win: ICodeWindow, cli?: ParsedArgs): Promise { // Only reload when the window has not vetoed this - this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => { - if (!veto) { - win.reload(undefined, cli); - } - }); + const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD); + if (!veto) { + win.reload(undefined, cli); + } } closeWorkspace(win: ICodeWindow): void { @@ -1554,8 +1553,10 @@ export class WindowsManager implements IWindowsMainService { }); } - enterWorkspace(win: ICodeWindow, path: URI): Promise { - return this.workspacesManager.enterWorkspace(win, path).then(result => result ? this.doEnterWorkspace(win, result) : undefined); + async enterWorkspace(win: ICodeWindow, path: URI): Promise { + const result = await this.workspacesManager.enterWorkspace(win, path); + + return result ? this.doEnterWorkspace(win, result) : undefined; } private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult { @@ -1751,8 +1752,10 @@ export class WindowsManager implements IWindowsMainService { const paths = await this.dialogs.pick({ ...options, pickFolders: true, pickFiles: true, title }); if (paths) { this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData); - const urisToOpen = await Promise.all(paths.map(path => { - return dirExists(path).then(isDir => isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) }); + const urisToOpen = await Promise.all(paths.map(async path => { + const isDir = await dirExists(path); + + return isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) }; })); this.open({ context: OpenContext.DIALOG, @@ -1880,7 +1883,7 @@ class Dialogs { this.noWindowDialogQueue = new Queue(); } - pick(options: IInternalNativeOpenDialogOptions): Promise { + async pick(options: IInternalNativeOpenDialogOptions): Promise { // Ensure dialog options const dialogOptions: Electron.OpenDialogOptions = { @@ -1913,16 +1916,16 @@ class Dialogs { // Show Dialog const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow(); - return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => { - if (paths && paths.length > 0) { + const paths = await this.showOpenDialog(dialogOptions, focusedWindow); + if (paths && paths.length > 0) { - // Remember path in storage for next time - this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); - return paths; - } + // Remember path in storage for next time + this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); - return undefined; - }); + return paths; + } + + return; } private getDialogQueue(window?: ICodeWindow): Queue { @@ -2028,28 +2031,26 @@ class WorkspacesManager { private readonly windowsMainService: IWindowsMainService, ) { } - enterWorkspace(window: ICodeWindow, path: URI): Promise { + async enterWorkspace(window: ICodeWindow, path: URI): Promise { if (!window || !window.win || !window.isReady) { - return Promise.resolve(null); // return early if the window is not ready or disposed + return null; // return early if the window is not ready or disposed } - return this.isValidTargetWorkspacePath(window, path).then(isValid => { - if (!isValid) { - return null; // return early if the workspace is not valid - } - const workspaceIdentifier = getWorkspaceIdentifier(path); - return this.doOpenWorkspace(window, workspaceIdentifier); - }); + const isValid = await this.isValidTargetWorkspacePath(window, path); + if (!isValid) { + return null; // return early if the workspace is not valid + } + return this.doOpenWorkspace(window, getWorkspaceIdentifier(path)); } - private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise { + private async isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise { if (!path) { - return Promise.resolve(true); + return true; } if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) { - return Promise.resolve(false); // window is already opened on a workspace with that path + return false; // window is already opened on a workspace with that path } // Prevent overwriting a workspace that is currently opened in another window @@ -2063,10 +2064,12 @@ class WorkspacesManager { noLink: true }; - return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false); + await this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()); + + return false; } - return Promise.resolve(true); // OK + return true; // OK } private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult { @@ -2090,14 +2093,16 @@ class WorkspacesManager { return { workspace, backupPath }; } - } -function resourceFromURIToOpen(u: IURIToOpen) { +function resourceFromURIToOpen(u: IURIToOpen): URI { if (isWorkspaceToOpen(u)) { return u.workspaceUri; - } else if (isFolderToOpen(u)) { + } + + if (isFolderToOpen(u)) { return u.folderUri; } + return u.fileUri; } \ No newline at end of file