From 2105bcef65f603bfe364e5bc0e5d2468657f2081 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Sep 2019 11:28:03 +0200 Subject: [PATCH] debt - introduce electron service and adopt for showMessageBox --- src/vs/code/electron-main/app.ts | 9 ++- .../standalone/browser/simpleServices.ts | 4 ++ src/vs/platform/dialogs/common/dialogs.ts | 6 +- .../{node => electron-browser}/dialogIpc.ts | 21 +----- .../electron-browser/electronService.ts | 28 ++++++++ .../electron-main/electronMainService.ts | 59 +++++++++++++++ src/vs/platform/electron/node/electron.ts | 17 +++++ src/vs/platform/windows/common/windows.ts | 1 - src/vs/platform/windows/common/windowsIpc.ts | 1 - .../electron-browser/windowsService.ts | 4 -- .../windows/electron-main/windowsService.ts | 54 +------------- .../browser/actions/windowActions.ts | 7 +- .../workbench/browser/web.simpleservices.ts | 28 -------- .../electron-browser/remote.contribution.ts | 2 +- .../dialogs/browser/dialogService.ts | 26 ++++++- .../dialogs/electron-browser/dialogService.ts | 71 +++++++++++++++++-- .../api/extHostMessagerService.test.ts | 4 ++ .../workbench/test/workbenchTestServices.ts | 8 +-- src/vs/workbench/workbench.desktop.main.ts | 3 + src/vs/workbench/workbench.web.main.ts | 4 +- 20 files changed, 232 insertions(+), 125 deletions(-) rename src/vs/platform/dialogs/{node => electron-browser}/dialogIpc.ts (58%) create mode 100644 src/vs/platform/electron/electron-browser/electronService.ts create mode 100644 src/vs/platform/electron/electron-main/electronMainService.ts create mode 100644 src/vs/platform/electron/node/electron.ts rename src/vs/{platform => workbench/services}/dialogs/browser/dialogService.ts (82%) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 12c5430291dcf..48fdc99153642 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -77,6 +77,8 @@ 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, ElectronChannel } from 'vs/platform/electron/electron-main/electronMainService'; export class CodeApplication extends Disposable { @@ -423,7 +425,6 @@ export class CodeApplication extends Disposable { private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessClient: Promise>): Promise { const services = new ServiceCollection(); - // Files const fileService = this._register(new FileService(this.logService)); services.set(IFileService, fileService); @@ -456,6 +457,7 @@ export class CodeApplication extends Disposable { services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel])); services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv])); + services.set(IElectronService, new SyncDescriptor(ElectronMainService)); services.set(IMenubarService, new SyncDescriptor(MenubarService)); const storageMainService = new StorageMainService(this.logService, this.environmentService); @@ -541,6 +543,10 @@ export class CodeApplication extends Disposable { const issueChannel = new IssueChannel(issueService); electronIpcServer.registerChannel('issue', issueChannel); + const electronService = accessor.get(IElectronService); + const electronChannel = new ElectronChannel(electronService); + electronIpcServer.registerChannel('electron', electronChannel); + const workspacesService = accessor.get(IWorkspacesMainService); const workspacesChannel = new WorkspacesChannel(workspacesService); electronIpcServer.registerChannel('workspaces', workspacesChannel); @@ -562,7 +568,6 @@ export class CodeApplication extends Disposable { const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService)); electronIpcServer.registerChannel('storage', storageChannel); - // Log level management const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService)); electronIpcServer.registerChannel('loglevel', logLevelChannel); sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 8989321aead49..d0da84973a7b6 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -186,6 +186,10 @@ export class SimpleDialogService implements IDialogService { public show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } + + public about(): Promise { + return Promise.resolve(undefined); + } } export class SimpleNotificationService implements INotificationService { diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 559f1c3afb4ee..faffec2aa1e58 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -172,6 +172,11 @@ export interface IDialogService { * option then promise with index `0` is returned. */ show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise; + + /** + * Present the about dialog to the user. + */ + about(): Promise; } export const IFileDialogService = createDecorator('fileDialogService'); @@ -235,7 +240,6 @@ export interface IFileDialogService { * Shows a open file dialog and returns the chosen file URI. */ showOpenDialog(options: IOpenDialogOptions): Promise; - } const MAX_CONFIRM_FILES = 10; diff --git a/src/vs/platform/dialogs/node/dialogIpc.ts b/src/vs/platform/dialogs/electron-browser/dialogIpc.ts similarity index 58% rename from src/vs/platform/dialogs/node/dialogIpc.ts rename to src/vs/platform/dialogs/electron-browser/dialogIpc.ts index 00db1a7bf21ba..d3d576439200b 100644 --- a/src/vs/platform/dialogs/node/dialogIpc.ts +++ b/src/vs/platform/dialogs/electron-browser/dialogIpc.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IDialogService, IConfirmation, IConfirmationResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; -import Severity from 'vs/base/common/severity'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Event } from 'vs/base/common/event'; export class DialogChannel implements IServerChannel { @@ -20,22 +19,8 @@ export class DialogChannel implements IServerChannel { switch (command) { case 'show': return this.dialogService.show(args![0], args![1], args![2]); case 'confirm': return this.dialogService.confirm(args![0]); + case 'about': return this.dialogService.about(); } return Promise.reject(new Error('invalid command')); } } - -export class DialogChannelClient implements IDialogService { - - _serviceBrand: undefined; - - constructor(private channel: IChannel) { } - - show(severity: Severity, message: string, options: string[]): Promise { - return this.channel.call('show', [severity, message, options]); - } - - confirm(confirmation: IConfirmation): Promise { - return this.channel.call('confirm', [confirmation]); - } -} diff --git a/src/vs/platform/electron/electron-browser/electronService.ts b/src/vs/platform/electron/electron-browser/electronService.ts new file mode 100644 index 0000000000000..fa2b5f09b18a1 --- /dev/null +++ b/src/vs/platform/electron/electron-browser/electronService.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; + +export class ElectronService { + + _serviceBrand: undefined; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + const channel = mainProcessService.getChannel('electron'); + + // Proxy: forward any property access to the channel + return new Proxy({}, { + get(_target, propKey, _receiver) { + if (typeof propKey === 'string') { + return function (...args: any[]) { + return channel.call(propKey, ...args); + }; + } + + throw new Error(`Not Implemented in ElectronService: ${String(propKey)}`); + } + }) as ElectronService; + } +} diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts new file mode 100644 index 0000000000000..a8c92d2414bec --- /dev/null +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +import { MessageBoxOptions, MessageBoxReturnValue } from 'electron'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; + +export class ElectronMainService implements IElectronService { + + _serviceBrand: undefined; + + constructor( + @IWindowsMainService private readonly windowsMainService: IWindowsMainService + ) { + } + + private get window(): ICodeWindow | undefined { + return this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + } + + async showMessageBox(options: MessageBoxOptions): Promise { + const result = await this.windowsMainService.showMessageBox(options, this.window); + + return { + response: result.button, + checkboxChecked: !!result.checkboxChecked + }; + } +} + +export class ElectronChannel implements IServerChannel { + + private service: { [key: string]: unknown }; + + constructor(service: IElectronService) { + this.service = service as unknown as { [key: string]: unknown }; + } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(_: unknown, command: string, arg?: any): Promise { + const target = this.service[command]; + if (typeof target === 'function') { + if (Array.isArray(arg)) { + return target.apply(this.service, arg); + } + + return target.call(this.service, arg); + } + + throw new Error(`Call Not Found in ElectronService: ${command}`); + } +} diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts new file mode 100644 index 0000000000000..0f6c3a83091b6 --- /dev/null +++ b/src/vs/platform/electron/node/electron.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MessageBoxOptions, MessageBoxReturnValue } from 'electron'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IElectronService = createDecorator('electronService'); + +export interface IElectronService { + + _serviceBrand: undefined; + + // Dialogs + showMessageBox(options: MessageBoxOptions): Promise; +} diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 57ffdc63bc0a8..7f697c0d44586 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -167,7 +167,6 @@ export interface IWindowsService { // TODO: this is a bit backwards startCrashReporter(config: CrashReporterStartOptions): Promise; - openAboutDialog(): Promise; resolveProxy(windowId: number, url: string): Promise; } diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 3cdf71e2f4ab2..5e3c0944d94e9 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -114,7 +114,6 @@ export class WindowsChannel implements IServerChannel { case 'getActiveWindowId': return this.service.getActiveWindowId(); case 'openExternal': return this.service.openExternal(arg); case 'startCrashReporter': return this.service.startCrashReporter(arg); - case 'openAboutDialog': return this.service.openAboutDialog(); case 'resolveProxy': return this.service.resolveProxy(arg[0], arg[1]); } diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 9321a89c8a6c3..d6089d907765d 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -250,10 +250,6 @@ export class WindowsService implements IWindowsService { return this.channel.call('updateTouchBar', [windowId, items]); } - openAboutDialog(): Promise { - return this.channel.call('openAboutDialog'); - } - resolveProxy(windowId: number, url: string): Promise { return Promise.resolve(this.channel.call('resolveProxy', [windowId, url])); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 81b232cd0ba25..7d7d9b4f20106 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -3,15 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as os from 'os'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; -import product from 'vs/platform/product/common/product'; import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, INewWindowOptions, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { shell, crashReporter, app, Menu, clipboard } from 'electron'; +import { shell, crashReporter, app, Menu } from 'electron'; import { Event } from 'vs/base/common/event'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; @@ -20,8 +17,7 @@ import { IHistoryMainService, IRecentlyOpened, IRecent } from 'vs/platform/histo import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { Schemas } from 'vs/base/common/network'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { isMacintosh, isLinux, IProcessEnvironment } from 'vs/base/common/platform'; +import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; export class WindowsService extends Disposable implements IWindowsService, IURLHandler { @@ -407,52 +403,6 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH } - async openAboutDialog(): Promise { - this.logService.trace('windowsService#openAboutDialog'); - - let version = app.getVersion(); - if (product.target) { - version = `${version} (${product.target} setup)`; - } - - const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; - const detail = nls.localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", - version, - product.commit || 'Unknown', - product.date || 'Unknown', - process.versions['electron'], - process.versions['chrome'], - process.versions['node'], - process.versions['v8'], - `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` - ); - - const ok = nls.localize('okButton', "OK"); - const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); - let buttons: string[]; - if (isLinux) { - buttons = [copy, ok]; - } else { - buttons = [ok, copy]; - } - - const result = await this.windowsMainService.showMessageBox({ - title: product.nameLong, - type: 'info', - message: product.nameLong, - detail: `\n${detail}`, - buttons, - noLink: true, - defaultId: buttons.indexOf(ok), - cancelId: buttons.indexOf(ok) - }, this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow()); - - if (buttons[result.button] === copy) { - clipboard.writeText(detail); - } - } - async handleURL(uri: URI): Promise { // Catch file URLs diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 0f57a22be53f0..5c4d1d63c1eb4 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -7,7 +7,8 @@ import 'vs/css!./media/actions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IWindowService, IURIToOpen, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -234,13 +235,13 @@ class ShowAboutDialogAction extends Action { constructor( id: string, label: string, - @IWindowsService private readonly windowsService: IWindowsService + @IDialogService private readonly dialogService: IDialogService ) { super(id, label); } run(): Promise { - return this.windowsService.openAboutDialog(); + return this.dialogService.about(); } } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 81f898eb98d24..86e496ceedd60 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -23,11 +23,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/productService'; -import Severity from 'vs/base/common/severity'; -import { localize } from 'vs/nls'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; //#region Window @@ -332,13 +327,6 @@ export class SimpleWindowsService implements IWindowsService { readonly onWindowUnmaximize: Event = Event.None; readonly onRecentlyOpenedChange: Event = Event.None; - constructor( - @IDialogService private readonly dialogService: IDialogService, - @IProductService private readonly productService: IProductService, - @IClipboardService private readonly clipboardService: IClipboardService - ) { - } - isFocused(_windowId: number): Promise { return Promise.resolve(true); } @@ -585,22 +573,6 @@ export class SimpleWindowsService implements IWindowsService { throw new Error('not implemented'); } - async openAboutDialog(): Promise { - const detail = localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", - this.productService.version || 'Unknown', - this.productService.commit || 'Unknown', - this.productService.date || 'Unknown', - navigator.userAgent - ); - - const { choice } = await this.dialogService.show(Severity.Info, this.productService.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail }); - - if (choice === 0) { - this.clipboardService.writeText(detail); - } - } - resolveProxy(windowId: number, url: string): Promise { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 185c5a1d08556..3af7ae70e3ab1 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -24,7 +24,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; +import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts similarity index 82% rename from src/vs/platform/dialogs/browser/dialogService.ts rename to src/vs/workbench/services/dialogs/browser/dialogService.ts index 5ddc105e9935c..495d0ea725f72 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts @@ -15,8 +15,12 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class DialogService implements IDialogService { + _serviceBrand: undefined; private allowableCommands = ['copy', 'cut']; @@ -25,7 +29,9 @@ export class DialogService implements IDialogService { @ILogService private readonly logService: ILogService, @ILayoutService private readonly layoutService: ILayoutService, @IThemeService private readonly themeService: IThemeService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IProductService private readonly productService: IProductService, + @IClipboardService private readonly clipboardService: IClipboardService ) { } async confirm(confirmation: IConfirmation): Promise { @@ -113,4 +119,22 @@ export class DialogService implements IDialogService { checkboxChecked: result.checkboxChecked }; } + + async about(): Promise { + const detail = nls.localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", + this.productService.version || 'Unknown', + this.productService.commit || 'Unknown', + this.productService.date || 'Unknown', + navigator.userAgent + ); + + const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail }); + + if (choice === 0) { + this.clipboardService.writeText(detail); + } + } } + +registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index cdc8cf34c4f3a..974ab5019498a 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -4,21 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import * as os from 'os'; import product from 'vs/platform/product/common/product'; import Severity from 'vs/base/common/severity'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs'; -import { DialogService as HTMLDialogService } from 'vs/platform/dialogs/browser/dialogService'; +import { DialogService as HTMLDialogService } from 'vs/workbench/services/dialogs/browser/dialogService'; import { ILogService } from 'vs/platform/log/common/log'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; +import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; interface IMassagedMessageBoxOptions { @@ -36,6 +40,7 @@ interface IMassagedMessageBoxOptions { } export class DialogService implements IDialogService { + _serviceBrand: undefined; private impl: IDialogService; @@ -47,25 +52,33 @@ export class DialogService implements IDialogService { @IThemeService themeService: IThemeService, @IWindowService windowService: IWindowService, @ISharedProcessService sharedProcessService: ISharedProcessService, - @IKeybindingService keybindingService: IKeybindingService + @IKeybindingService keybindingService: IKeybindingService, + @IProductService productService: IProductService, + @IClipboardService clipboardService: IClipboardService, + @IElectronService electronService: IElectronService ) { // Use HTML based dialogs if (configurationService.getValue('workbench.dialogs.customEnabled') === true) { - this.impl = new HTMLDialogService(logService, layoutService, themeService, keybindingService); + this.impl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService); } // Electron dialog service else { - this.impl = new NativeDialogService(windowService, logService, sharedProcessService); + this.impl = new NativeDialogService(windowService, logService, sharedProcessService, electronService, clipboardService); } } confirm(confirmation: IConfirmation): Promise { return this.impl.confirm(confirmation); } + show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions | undefined): Promise { return this.impl.show(severity, message, buttons, options); } + + about(): Promise { + return this.impl.about(); + } } class NativeDialogService implements IDialogService { @@ -75,7 +88,9 @@ class NativeDialogService implements IDialogService { constructor( @IWindowService private readonly windowService: IWindowService, @ILogService private readonly logService: ILogService, - @ISharedProcessService sharedProcessService: ISharedProcessService + @ISharedProcessService sharedProcessService: ISharedProcessService, + @IElectronService private readonly electronService: IElectronService, + @IClipboardService private readonly clipboardService: IClipboardService ) { sharedProcessService.registerChannel('dialog', new DialogChannel(this)); } @@ -189,6 +204,50 @@ class NativeDialogService implements IDialogService { return { options, buttonIndexMap }; } + + async about(): Promise { + let version = product.version; + if (product.target) { + version = `${version} (${product.target} setup)`; + } + + const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; + const detail = nls.localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", + version, + product.commit || 'Unknown', + product.date || 'Unknown', + process.versions['electron'], + process.versions['chrome'], + process.versions['node'], + process.versions['v8'], + `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` + ); + + const ok = nls.localize('okButton', "OK"); + const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); + let buttons: string[]; + if (isLinux) { + buttons = [copy, ok]; + } else { + buttons = [ok, copy]; + } + + const result = await this.electronService.showMessageBox({ + title: product.nameLong, + type: 'info', + message: product.nameLong, + detail: `\n${detail}`, + buttons, + noLink: true, + defaultId: buttons.indexOf(ok), + cancelId: buttons.indexOf(ok) + }); + + if (buttons[result.response] === copy) { + this.clipboardService.writeText(detail); + } + } } registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts index b3f8ec0258a0b..307a8e3cf63ff 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -20,6 +20,10 @@ const emptyDialogService = new class implements IDialogService { confirm(): never { throw new Error('not implemented'); } + + about(): never { + throw new Error('not implemented'); + } }; const emptyCommandService: ICommandService = { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 240bdc365f349..6c4add507aa6e 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -402,6 +402,10 @@ export class TestDialogService implements IDialogService { public show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } + + public about(): Promise { + return Promise.resolve(); + } } export class TestFileDialogService implements IFileDialogService { @@ -1553,10 +1557,6 @@ export class TestWindowsService implements IWindowsService { throw new Error('not implemented'); } - openAboutDialog(): Promise { - return Promise.resolve(); - } - resolveProxy(windowId: number, url: string): Promise { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 8cfb27e7fa170..f22592cb8fe1f 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -68,6 +68,8 @@ import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { ElectronService } from 'vs/platform/electron/electron-browser/electronService'; registerSingleton(IClipboardService, ClipboardService, true); registerSingleton(IRequestService, RequestService, true); @@ -78,6 +80,7 @@ registerSingleton(IWindowsService, WindowsService); registerSingleton(IUpdateService, UpdateService); registerSingleton(IIssueService, IssueService); registerSingleton(IMenubarService, MenubarService); +registerSingleton(IElectronService, ElectronService, true); //#endregion diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 8c8a470aa8a9c..dc8eb1bb5e6d3 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -41,6 +41,7 @@ import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/services/update/browser/updateService'; import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; import 'vs/workbench/services/workspace/browser/workspacesService'; +import 'vs/workbench/services/dialogs/browser/dialogService'; import 'vs/workbench/browser/web.simpleservices'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -53,8 +54,6 @@ import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/ac import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { BrowserLifecycleService } from 'vs/platform/lifecycle/browser/lifecycleService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { DialogService } from 'vs/platform/dialogs/browser/dialogService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -66,7 +65,6 @@ import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; registerSingleton(IRequestService, RequestService, true); registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); -registerSingleton(IDialogService, DialogService, true); registerSingleton(IClipboardService, BrowserClipboardService, true); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(ILifecycleService, BrowserLifecycleService);