diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 48a6077f2ef..8f7bb194d2a 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -22,6 +22,29 @@ export interface ICheckbox { checked?: boolean; } +export interface IConfirmDialogArgs { + confirmation: IConfirmation; +} + +export interface IShowDialogArgs { + severity: Severity; + message: string; + buttons: string[]; + options?: IDialogOptions; +} + +export interface IInputDialogArgs extends IShowDialogArgs { + inputs: IInput[], +} + +export interface IDialog { + confirmArgs?: IConfirmDialogArgs; + showArgs?: IShowDialogArgs; + inputArgs?: IInputDialogArgs; +} + +export type IDialogResult = IConfirmationResult | IInputResult | IShowResult; + export interface IConfirmation { title?: string; type?: DialogType; @@ -166,6 +189,40 @@ export interface IInput { value?: string; } +/** + * A handler to bring up modal dialogs. + */ +export interface IDialogHandler { + /** + * Ask the user for confirmation with a modal dialog. + */ + confirm(confirmation: IConfirmation): Promise; + + /** + * Present a modal dialog to the user. + * + * @returns A promise with the selected choice index. If the user refused to choose, + * then a promise with index of `cancelId` option is returned. If there is no such + * option then promise with index `0` is returned. + */ + show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise; + + /** + * Present a modal dialog to the user asking for input. + * + * @returns A promise with the selected choice index. If the user refused to choose, + * then a promise with index of `cancelId` option is returned. If there is no such + * option then promise with index `0` is returned. In addition, the values for the + * inputs are returned as well. + */ + input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise; + + /** + * Present the about dialog to the user. + */ + about(): Promise; +} + /** * A service to bring up modal dialogs. * diff --git a/src/vs/workbench/common/dialogs.ts b/src/vs/workbench/common/dialogs.ts new file mode 100644 index 00000000000..5de8eac6849 --- /dev/null +++ b/src/vs/workbench/common/dialogs.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IDialog, IDialogResult } from 'vs/platform/dialogs/common/dialogs'; + +export interface IDialogViewItem { + args: IDialog; + close(result?: IDialogResult): void; +} + +export interface IDialogHandle { + item: IDialogViewItem; + result: Promise; +} + +export interface IDialogsModel { + readonly onDidShowDialog: Event; + + readonly dialogs: IDialogViewItem[]; + + show(dialog: IDialog): IDialogHandle; +} + +export class DialogsModel extends Disposable implements IDialogsModel { + readonly dialogs: IDialogViewItem[] = []; + + private readonly _onDidShowDialog = this._register(new Emitter()); + readonly onDidShowDialog = this._onDidShowDialog.event; + + show(dialog: IDialog): IDialogHandle { + let resolver: (value?: IDialogResult) => void; + + const item: IDialogViewItem = { + args: dialog, + close: (result) => { this.dialogs.splice(0, 1); resolver(result); } + }; + + this.dialogs.push(item); + this._onDidShowDialog.fire(); + + return { + item, + result: new Promise(resolve => { resolver = resolve; }) + }; + } +} diff --git a/src/vs/workbench/contrib/dialogs/browser/dialog.contribution.ts b/src/vs/workbench/contrib/dialogs/browser/dialog.contribution.ts new file mode 100644 index 00000000000..98748bce501 --- /dev/null +++ b/src/vs/workbench/contrib/dialogs/browser/dialog.contribution.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IDialogHandler, IDialogResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; +import { HTMLDialogHandler } from 'vs/workbench/contrib/dialogs/browser/dialogHandler'; +import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +export class DialogHandlerContribution implements IWorkbenchContribution { + private impl: IDialogHandler; + + private model: IDialogsModel; + private currentDialog: IDialogViewItem | undefined; + + constructor( + @IDialogService private dialogService: IDialogService, + @ILogService logService: ILogService, + @ILayoutService layoutService: ILayoutService, + @IThemeService themeService: IThemeService, + @IKeybindingService keybindingService: IKeybindingService, + @IProductService productService: IProductService, + @IClipboardService clipboardService: IClipboardService + ) { + this.impl = new HTMLDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService); + + this.model = (this.dialogService as DialogService).model; + + this.model.onDidShowDialog(() => { + if (!this.currentDialog) { + this.processDialogs(); + } + }); + + this.processDialogs(); + } + + private async processDialogs(): Promise { + while (this.model.dialogs.length) { + this.currentDialog = this.model.dialogs[0]; + + let result: IDialogResult | undefined = undefined; + if (this.currentDialog.args.confirmArgs) { + const args = this.currentDialog.args.confirmArgs; + result = await this.impl.confirm(args.confirmation); + } else if (this.currentDialog.args.inputArgs) { + const args = this.currentDialog.args.inputArgs; + result = await this.impl.input(args.severity, args.message, args.buttons, args.inputs, args.options); + } else if (this.currentDialog.args.showArgs) { + const args = this.currentDialog.args.showArgs; + result = await this.impl.show(args.severity, args.message, args.buttons, args.options); + } else { + await this.impl.about(); + } + + this.currentDialog.close(result); + this.currentDialog = undefined; + } + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(DialogHandlerContribution, LifecyclePhase.Starting); + diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/contrib/dialogs/browser/dialogHandler.ts similarity index 91% rename from src/vs/workbench/services/dialogs/browser/dialogService.ts rename to src/vs/workbench/contrib/dialogs/browser/dialogHandler.ts index 85d83f37da1..e9e5ca79d9b 100644 --- a/src/vs/workbench/services/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/contrib/dialogs/browser/dialogHandler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IDialogService, IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult, IInputResult, ICheckbox, IInput, IDialogHandler } from 'vs/platform/dialogs/common/dialogs'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; import Severity from 'vs/base/common/severity'; @@ -17,12 +17,9 @@ 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'; import { fromNow } from 'vs/base/common/date'; -export class DialogService implements IDialogService { - - declare readonly _serviceBrand: undefined; +export class HTMLDialogHandler implements IDialogHandler { private static readonly ALLOWABLE_COMMANDS = [ 'copy', @@ -91,7 +88,7 @@ export class DialogService implements IDialogService { keyEventProcessor: (event: StandardKeyboardEvent) => { const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); if (resolved && resolved.commandId) { - if (DialogService.ALLOWABLE_COMMANDS.indexOf(resolved.commandId) === -1) { + if (HTMLDialogHandler.ALLOWABLE_COMMANDS.indexOf(resolved.commandId) === -1) { EventHelper.stop(event, true); } } @@ -144,5 +141,3 @@ export class DialogService implements IDialogService { } } } - -registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/contrib/dialogs/electron-sandbox/dialog.contribution.ts b/src/vs/workbench/contrib/dialogs/electron-sandbox/dialog.contribution.ts new file mode 100644 index 00000000000..523a86a32f1 --- /dev/null +++ b/src/vs/workbench/contrib/dialogs/electron-sandbox/dialog.contribution.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDialogHandler, IDialogResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IDialogsModel, IDialogViewItem } from 'vs/workbench/common/dialogs'; +import { HTMLDialogHandler } from 'vs/workbench/contrib/dialogs/browser/dialogHandler'; +import { NativeDialogHandler } from 'vs/workbench/contrib/dialogs/electron-sandbox/dialogHandler'; +import { DialogService } from 'vs/workbench/services/dialogs/common/dialogService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +export class DialogHandlerContribution implements IWorkbenchContribution { + private nativeImpl: IDialogHandler; + private customImpl: IDialogHandler; + + private model: IDialogsModel; + private currentDialog: IDialogViewItem | undefined; + + constructor( + @IConfigurationService private configurationService: IConfigurationService, + @IDialogService private dialogService: IDialogService, + @ILogService logService: ILogService, + @ILayoutService layoutService: ILayoutService, + @IThemeService themeService: IThemeService, + @IKeybindingService keybindingService: IKeybindingService, + @IProductService productService: IProductService, + @IClipboardService clipboardService: IClipboardService, + @INativeHostService nativeHostService: INativeHostService + ) { + this.customImpl = new HTMLDialogHandler(logService, layoutService, themeService, keybindingService, productService, clipboardService); + this.nativeImpl = new NativeDialogHandler(logService, nativeHostService, productService, clipboardService); + + this.model = (this.dialogService as DialogService).model; + + this.model.onDidShowDialog(() => { + if (!this.currentDialog) { + this.processDialogs(); + } + }); + + this.processDialogs(); + } + + private async processDialogs(): Promise { + while (this.model.dialogs.length) { + this.currentDialog = this.model.dialogs[0]; + + let result: IDialogResult | undefined = undefined; + if (this.currentDialog.args.confirmArgs) { + const args = this.currentDialog.args.confirmArgs; + result = this.useCustomDialog ? await this.customImpl.confirm(args.confirmation) : await this.nativeImpl.confirm(args.confirmation); + } else if (this.currentDialog.args.inputArgs) { + const args = this.currentDialog.args.inputArgs; + result = this.useCustomDialog ? + await this.customImpl.input(args.severity, args.message, args.buttons, args.inputs, args.options) : + await this.nativeImpl.input(args.severity, args.message, args.buttons, args.inputs, args.options); + } else if (this.currentDialog.args.showArgs) { + const args = this.currentDialog.args.showArgs; + result = this.useCustomDialog ? + await this.customImpl.show(args.severity, args.message, args.buttons, args.options) : + await this.nativeImpl.show(args.severity, args.message, args.buttons, args.options); + } else { + await this.nativeImpl.about(); + } + + this.currentDialog.close(result); + this.currentDialog = undefined; + } + } + + private get useCustomDialog(): boolean { + return this.configurationService.getValue('window.dialogStyle') === 'custom'; + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(DialogHandlerContribution, LifecyclePhase.Starting); + diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts b/src/vs/workbench/contrib/dialogs/electron-sandbox/dialogHandler.ts similarity index 73% rename from src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts rename to src/vs/workbench/contrib/dialogs/electron-sandbox/dialogHandler.ts index c6e5fb62d02..6ef757cc084 100644 --- a/src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts +++ b/src/vs/workbench/contrib/dialogs/electron-sandbox/dialogHandler.ts @@ -4,23 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import Severity from 'vs/base/common/severity'; -import { isLinux, isWindows } from 'vs/base/common/platform'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IShowResult, IInputResult, IInput } from 'vs/platform/dialogs/common/dialogs'; -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 { 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 { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { MessageBoxOptions } from 'vs/base/parts/sandbox/common/electronTypes'; import { fromNow } from 'vs/base/common/date'; -import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { isLinux, isWindows } from 'vs/base/common/platform'; +import Severity from 'vs/base/common/severity'; +import { MessageBoxOptions } from 'vs/base/parts/sandbox/common/electronTypes'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfirmation, IConfirmationResult, IDialogHandler, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IProductService } from 'vs/platform/product/common/productService'; interface IMassagedMessageBoxOptions { @@ -37,57 +30,9 @@ interface IMassagedMessageBoxOptions { buttonIndexMap: number[]; } -export class DialogService implements IDialogService { - declare readonly _serviceBrand: undefined; - private nativeImpl: IDialogService; - private customImpl: IDialogService; - - constructor( - @IConfigurationService private configurationService: IConfigurationService, - @ILogService logService: ILogService, - @ILayoutService layoutService: ILayoutService, - @IThemeService themeService: IThemeService, - @IKeybindingService keybindingService: IKeybindingService, - @IProductService productService: IProductService, - @IClipboardService clipboardService: IClipboardService, - @INativeHostService nativeHostService: INativeHostService - ) { - this.customImpl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService); - this.nativeImpl = new NativeDialogService(logService, nativeHostService, productService, clipboardService); - } - - private get useCustomDialog(): boolean { - return this.configurationService.getValue('window.dialogStyle') === 'custom'; - } - - confirm(confirmation: IConfirmation): Promise { - if (this.useCustomDialog) { - return this.customImpl.confirm(confirmation); - } - - return this.nativeImpl.confirm(confirmation); - } - - show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { - if (this.useCustomDialog) { - return this.customImpl.show(severity, message, buttons, options); - } - - return this.nativeImpl.show(severity, message, buttons, options); - } - - input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise { - return this.customImpl.input(severity, message, buttons, inputs, options); - } - - about(): Promise { - return this.nativeImpl.about(); - } -} - -class NativeDialogService implements IDialogService { +export class NativeDialogHandler implements IDialogHandler { declare readonly _serviceBrand: undefined; @@ -265,4 +210,3 @@ class NativeDialogService implements IDialogService { } } -registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/services/dialogs/common/dialogService.ts b/src/vs/workbench/services/dialogs/common/dialogService.ts new file mode 100644 index 00000000000..df940085921 --- /dev/null +++ b/src/vs/workbench/services/dialogs/common/dialogService.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Severity from 'vs/base/common/severity'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInput, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; +import { DialogsModel, IDialogsModel } from 'vs/workbench/common/dialogs'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class DialogService extends Disposable implements IDialogService { + _serviceBrand: undefined; + + readonly model: IDialogsModel = this._register(new DialogsModel()); + + async confirm(confirmation: IConfirmation): Promise { + const handle = this.model.show({ confirmArgs: { confirmation } }); + return await handle.result as IConfirmationResult; + } + + async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { + const handle = this.model.show({ showArgs: { severity, message, buttons, options } }); + return await handle.result as IShowResult; + } + + async input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise { + const handle = this.model.show({ inputArgs: { severity, message, buttons, inputs, options } }); + return await handle.result as IInputResult; + } + + async about(): Promise { + const handle = this.model.show({}); + await handle.result; + } +} + +registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 35c7bee21c3..c5dc396505a 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -56,6 +56,7 @@ import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/workbench/services/extensions/browser/extensionUrlHandler'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; import 'vs/workbench/services/decorations/browser/decorationsService'; +import 'vs/workbench/services/dialogs/common/dialogService'; import 'vs/workbench/services/progress/browser/progressService'; import 'vs/workbench/services/editor/browser/codeEditorService'; import 'vs/workbench/services/preferences/browser/preferencesService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 5f08dc5d96d..980faafb6ff 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -132,6 +132,9 @@ import 'vs/workbench/contrib/debug/node/debugHelperService'; // Webview import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; +// Dialogs +import 'vs/workbench/contrib/dialogs/electron-sandbox/dialog.contribution'; + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index cb86008e491..61c11145ee3 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -28,7 +28,6 @@ import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService'; import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService'; import 'vs/workbench/services/textMate/electron-sandbox/textMateService'; import 'vs/workbench/services/menubar/electron-sandbox/menubarService'; -import 'vs/workbench/services/dialogs/electron-sandbox/dialogService'; import 'vs/workbench/services/issue/electron-sandbox/issueService'; import 'vs/workbench/services/update/electron-sandbox/updateService'; import 'vs/workbench/services/url/electron-sandbox/urlService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index fb9badeac4b..74cd19bab4b 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -42,7 +42,6 @@ import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/services/update/browser/updateService'; import 'vs/workbench/services/workspaces/browser/workspacesService'; import 'vs/workbench/services/workspaces/browser/workspaceEditingService'; -import 'vs/workbench/services/dialogs/browser/dialogService'; import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; @@ -117,6 +116,9 @@ import 'vs/workbench/contrib/debug/browser/extensionHostDebugService'; // Webview import 'vs/workbench/contrib/webview/browser/webview.web.contribution'; +// Dialog +import 'vs/workbench/contrib/dialogs/browser/dialog.contribution'; + // Terminal import 'vs/workbench/contrib/terminal/browser/terminal.web.contribution'; import 'vs/workbench/contrib/terminal/browser/terminalInstanceService';