diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 073b3587f81..4dce5dc8ec4 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -74,8 +74,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -import { IElectronService } from 'vs/platform/electron/node/electron'; -import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; +import { IElectronMainService, ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; import { assign } from 'vs/base/common/objects'; import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; @@ -459,7 +458,7 @@ export class CodeApplication extends Disposable { services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel])); services.set(IIssueService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); - services.set(IElectronService, new SyncDescriptor(ElectronMainService)); + services.set(IElectronMainService, new SyncDescriptor(ElectronMainService)); services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService)); services.set(IMenubarService, new SyncDescriptor(MenubarMainService)); @@ -546,8 +545,8 @@ export class CodeApplication extends Disposable { const issueChannel = createChannelReceiver(issueService); electronIpcServer.registerChannel('issue', issueChannel); - const electronService = accessor.get(IElectronService); - const electronChannel = createChannelReceiver(electronService); + const electronMainService = accessor.get(IElectronMainService); + const electronChannel = createChannelReceiver(electronMainService); electronIpcServer.registerChannel('electron', electronChannel); sharedProcessClient.then(client => client.registerChannel('electron', electronChannel)); @@ -605,7 +604,7 @@ export class CodeApplication extends Disposable { }); // Create a URL handler which forwards to the last active window - const activeWindowManager = new ActiveWindowManager(electronService); + const activeWindowManager = new ActiveWindowManager(electronMainService); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter); const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 1efa3b6e3bf..65fb5ec946c 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -299,7 +299,7 @@ class CodeMain { await launchService.start(environmentService.args, process.env as platform.IProcessEnvironment); // Cleanup - await client.dispose(); + client.dispose(); // Now that we started, make sure the warning dialog is prevented if (startupWarningDialogHandle) { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 61638921926..72ef9276caf 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -19,11 +19,10 @@ import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMai import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; -import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; -import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; @@ -32,7 +31,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { dirExists } from 'vs/base/node/pfs'; import { getComparisonKey, isEqual, normalizePath, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; @@ -1718,6 +1716,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { type: WindowError; }; this.telemetryService.publicLog2('windowerror', { type: error }); + // Unresponsive if (error === WindowError.UNRESPONSIVE) { if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) { @@ -1790,80 +1789,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService { this._onWindowClose.fire(win.id); } - async pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickFileFolder(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData); - 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, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen, - forceNewWindow: options.forceNewWindow - }); - } - } - - async pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickFolder(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFolder', options.telemetryExtraData); - this.open({ - context: OpenContext.DIALOG, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen: paths.map(path => ({ folderUri: URI.file(path) })), - forceNewWindow: options.forceNewWindow - }); - } - } - - async pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickFile(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFile', options.telemetryExtraData); - this.open({ - context: OpenContext.DIALOG, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen: paths.map(path => ({ fileUri: URI.file(path) })), - forceNewWindow: options.forceNewWindow - }); - } - } - - async pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickWorkspace(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openWorkspace', options.telemetryExtraData); - this.open({ - context: OpenContext.DIALOG, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen: paths.map(path => ({ workspaceUri: URI.file(path) })), - forceNewWindow: options.forceNewWindow - }); - } - - } - - private sendPickerTelemetry(paths: string[], telemetryEventName: string, telemetryExtraData?: ITelemetryData) { - const numberOfPaths = paths ? paths.length : 0; - - // Telemetry - // __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically. - this.telemetryService.publicLog(telemetryEventName, { - ...telemetryExtraData, - outcome: numberOfPaths ? 'success' : 'canceled', - numberOfPaths - }); - } - quit(): void { // If the user selected to exit from an extension development host window, do not quit, but just diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 85bc12ccaf5..78af3d7b78b 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -16,8 +16,16 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { dirExists } from 'vs/base/node/pfs'; +import { URI } from 'vs/base/common/uri'; +import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export class ElectronMainService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { +export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { } + +export const IElectronMainService = createDecorator('electronMainService'); + +export class ElectronMainService implements IElectronMainService { _serviceBrand: undefined; @@ -25,7 +33,8 @@ export class ElectronMainService implements AddFirstParameterToFunctions { - return this.windowsMainService.pickFileFolderAndOpen(options, this.windowsMainService.getWindowById(windowId)); - } - - async pickFileAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { - return this.windowsMainService.pickFileAndOpen(options, this.windowsMainService.getWindowById(windowId)); + const paths = await this.dialogMainService.pickFileFolder(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData); + this.doOpenPicked(await Promise.all(paths.map(async path => (await dirExists(path)) ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) })), options, windowId); + } } async pickFolderAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { - return this.windowsMainService.pickFolderAndOpen(options, this.windowsMainService.getWindowById(windowId)); + const paths = await this.dialogMainService.pickFolder(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFolder', options.telemetryExtraData); + this.doOpenPicked(paths.map(path => ({ folderUri: URI.file(path) })), options, windowId); + } + } + + async pickFileAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { + const paths = await this.dialogMainService.pickFile(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFile', options.telemetryExtraData); + this.doOpenPicked(paths.map(path => ({ fileUri: URI.file(path) })), options, windowId); + } } async pickWorkspaceAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { - return this.windowsMainService.pickWorkspaceAndOpen(options, this.windowsMainService.getWindowById(windowId)); + const paths = await this.dialogMainService.pickWorkspace(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openWorkspace', options.telemetryExtraData); + this.doOpenPicked(paths.map(path => ({ workspaceUri: URI.file(path) })), options, windowId); + } + } + + private doOpenPicked(openable: IWindowOpenable[], options: INativeOpenDialogOptions, windowId: number): void { + this.windowsMainService.open({ + context: OpenContext.DIALOG, + contextWindowId: windowId, + cli: this.environmentService.args, + urisToOpen: openable, + forceNewWindow: options.forceNewWindow + }); + } + + private sendPickerTelemetry(paths: string[], telemetryEventName: string, telemetryExtraData?: ITelemetryData) { + const numberOfPaths = paths ? paths.length : 0; + + // Telemetry + // __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically. + this.telemetryService.publicLog(telemetryEventName, { + ...telemetryExtraData, + outcome: numberOfPaths ? 'success' : 'canceled', + numberOfPaths + }); } //#endregion diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 0f00ac45280..d269dc47da4 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -22,6 +22,7 @@ import { URI } from 'vs/base/common/uri'; import { IStateService } from 'vs/platform/state/node/state'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; const telemetryFrom = 'menu'; @@ -69,7 +70,8 @@ export class Menubar { @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, @IStateService private readonly stateService: IStateService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IElectronMainService private readonly electronMainService: IElectronMainService ) { this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); @@ -111,8 +113,8 @@ export class Menubar { // File Menu Items this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); this.fallbackMenuHandlers['workbench.action.newWindow'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); - this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.windowsMainService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); - this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.windowsMainService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); + this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.electronMainService.pickFileFolderAndOpen(-1, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); + this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.electronMainService.pickWorkspaceAndOpen(-1, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); // Recent Menu Items this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.workspacesHistoryMainService.clearRecentlyOpened(); diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 5dd270d8c25..4cd686508aa 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { OpenContext, IWindowConfiguration, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; -import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -97,11 +96,6 @@ export interface IWindowsMainService { openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[]; openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[]; - pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - sendToFocused(channel: string, ...args: any[]): void; sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;