From bc59b294bbd616eadeba611b94ea458dfb776b4c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 7 Mar 2018 11:44:03 +0100 Subject: [PATCH] debt - add new prompt method to notification service --- .../standalone/browser/simpleServices.ts | 6 +- .../integrity/node/integrityServiceImpl.ts | 8 +-- .../common/abstractKeybindingService.test.ts | 3 + .../notification/common/notification.ts | 37 +++++++++++- .../extensions/browser/extensionsActions.ts | 5 +- .../electron-browser/extensionTipsService.ts | 22 ++++--- .../electron-browser/extensionsActions.ts | 5 +- .../electron-browser/extensionsUtils.ts | 11 ++-- .../electron-browser/extensionsViewlet.ts | 5 +- .../node/extensionsWorkbenchService.ts | 7 +-- .../extensionsTipsService.test.ts | 10 ++-- .../extensionsWorkbenchService.test.ts | 7 ++- .../languageSurveys.contribution.ts | 13 ++-- .../electron-browser/nps.contribution.ts | 9 ++- .../electron-browser/task.contribution.ts | 7 +-- .../electron-browser/terminalConfigHelper.ts | 6 +- .../electron-browser/terminalService.ts | 7 ++- ...supportedWorkspaceSettings.contribution.ts | 11 ++-- .../page/electron-browser/welcomePage.ts | 4 +- .../node/configurationEditingService.ts | 10 ++-- .../node/configurationEditingService.test.ts | 4 +- .../electron-browser/extensionHost.ts | 4 +- .../electron-browser/extensionService.ts | 20 +++---- .../files/electron-browser/fileService.ts | 13 ++-- .../electron-browser/remoteFileService.ts | 6 +- .../common/notificationService.ts | 59 ++++++++++++++++++- .../workspace/node/workspaceEditingService.ts | 4 +- .../api/extHostMessagerService.test.ts | 8 ++- .../workbench/test/workbenchTestServices.ts | 13 ++-- 29 files changed, 204 insertions(+), 120 deletions(-) diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 0454d044852..73396ee6d4b 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -38,7 +38,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { OS } from 'vs/base/common/platform'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { INotificationService, INotification, INotificationHandle, NoOpNotification } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle, NoOpNotification, PromptOption } from 'vs/platform/notification/common/notification'; import { IConfirmation, IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IPosition, Position as Pos } from 'vs/editor/common/core/position'; @@ -296,6 +296,10 @@ export class SimpleNotificationService implements INotificationService { return SimpleNotificationService.NO_OP; } + + public prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { + return TPromise.as(0); + } } export class StandaloneCommandService implements ICommandService { diff --git a/src/vs/platform/integrity/node/integrityServiceImpl.ts b/src/vs/platform/integrity/node/integrityServiceImpl.ts index f5274caa770..34fd1e44a4f 100644 --- a/src/vs/platform/integrity/node/integrityServiceImpl.ts +++ b/src/vs/platform/integrity/node/integrityServiceImpl.ts @@ -14,7 +14,7 @@ import URI from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; interface IStorageData { dontShowPrompt: boolean; @@ -62,7 +62,7 @@ export class IntegrityServiceImpl implements IIntegrityService { private _isPurePromise: Thenable; constructor( - @IChoiceService private choiceService: IChoiceService, + @INotificationService private notificationService: INotificationService, @IStorageService storageService: IStorageService, @ILifecycleService private lifecycleService: ILifecycleService ) { @@ -86,9 +86,9 @@ export class IntegrityServiceImpl implements IIntegrityService { return; } - const choices: Choice[] = [nls.localize('integrity.moreInformation', "More Information"), { label: nls.localize('integrity.dontShowAgain', "Don't Show Again") }]; + const choices: PromptOption[] = [nls.localize('integrity.moreInformation', "More Information"), { label: nls.localize('integrity.dontShowAgain', "Don't Show Again") }]; - this.choiceService.choose(Severity.Warning, nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort), choices).then(choice => { + this.notificationService.prompt(Severity.Warning, nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort), choices).then(choice => { switch (choice) { case 0 /* More Information */: const uri = URI.parse(product.checksumFailMoreInfoUrl); diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 81926ed06fa..6c00827eac5 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -138,6 +138,9 @@ suite('AbstractKeybindingService', () => { error: (message: any) => { showMessageCalls.push({ sev: Severity.Error, message }); return new NoOpNotification(); + }, + prompt: () => { + return TPromise.as(0); } }; diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 56251e78ec7..4e18964aff8 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; import Event, { Emitter } from 'vs/base/common/event'; +import { TPromise } from 'vs/base/common/winjs.base'; export import Severity = Severity; @@ -44,7 +45,7 @@ export interface INotification { * close automatically when invoking a secondary action. * * **Note:** If your intent is to show a message with actions to the user, consider - * the `IChoiceService` or `IDialogService` instead which are optimized for + * the `INotificationService.prompt()` method instead which are optimized for * this usecase and much easier to use! */ actions?: INotificationActions; @@ -120,6 +121,27 @@ export interface INotificationHandle extends IDisposable { updateActions(actions?: INotificationActions): void; } + +/** + * Primary choices show up as buttons in the notification below the message. + */ +export type PrimaryPromptChoice = string; + +/** + * Secondary choices show up under the gear icon in the header of the notification. + */ +export interface SecondaryPromptChoice { + label: string; + + /** + * Wether to keep the notification open after the secondary choice was selected + * by the user. By default, will close the notification upon click. + */ + keepOpen?: boolean; +} + +export type PromptOption = PrimaryPromptChoice | SecondaryPromptChoice; + export interface INotificationService { _serviceBrand: any; @@ -129,8 +151,10 @@ export interface INotificationService { * can be used to control the notification afterwards. * * **Note:** If your intent is to show a message with actions to the user, consider - * the `IChoiceService` or `IDialogService` instead which are optimized for + * the `INotificationService.prompt()` method instead which are optimized for * this usecase and much easier to use! + * + * @returns a handle on the notification to e.g. hide it or update message, buttons, etc. */ notify(notification: INotification): INotificationHandle; @@ -151,6 +175,15 @@ export interface INotificationService { * method if you need more control over the notification. */ error(message: NotificationMessage | NotificationMessage[]): void; + + /** + * Shows a prompt in the notification area with the provided choices. The prompt + * is non-modal. If you want to show a modal dialog instead, use `IDialogService`. + * + * @returns a promise that will resolve to the index of the choice that was picked. + * The promise can be cancelled to hide the notification prompt. + */ + prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise; } export class NoOpNotification implements INotificationHandle { diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index 05c57e64f98..e93363d84e9 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -44,7 +44,6 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; @@ -1881,7 +1880,7 @@ export class InstallVSIXAction extends Action { id = InstallVSIXAction.ID, label = InstallVSIXAction.LABEL, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IChoiceService private choiceService: IChoiceService, + @INotificationService private notificationService: INotificationService, @IWindowService private windowsService: IWindowService ) { super(id, label, 'extension-action install-vsix', true); @@ -1899,7 +1898,7 @@ export class InstallVSIXAction extends Action { } return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => { - return this.choiceService.choose(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => { + return this.notificationService.prompt(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => { if (choice === 0) { return this.windowsService.reloadWindow(); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 2586ce2bed8..d0c433695a3 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -34,10 +34,9 @@ import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspace import { IRequestService } from 'vs/platform/request/node/request'; import { asJson } from 'vs/base/node/request'; import { isNumber } from 'vs/base/common/types'; -import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs'; import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; interface IExtensionsContent { recommendations: string[]; @@ -72,7 +71,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService, @IModelService private readonly _modelService: IModelService, @IStorageService private storageService: IStorageService, - @IChoiceService private choiceService: IChoiceService, @IExtensionManagementService private extensionsService: IExtensionManagementService, @IInstantiationService private instantiationService: IInstantiationService, @IFileService private fileService: IFileService, @@ -163,12 +161,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } const message = localize('showLanguagePackExtensions', "The Marketplace has extensions that can help localizing VS Code to '{0}' locale", language); - const options: Choice[] = [ + const options: PromptOption[] = [ searchMarketplace, { label: choiceNever } ]; - this.choiceService.choose(Severity.Info, message, options).done(choice => { + this.notificationService.prompt(Severity.Info, message, options).done(choice => { switch (choice) { case 0 /* Search Marketplace */: /* __GDPR__ @@ -491,13 +489,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id); - const options: Choice[] = [ + const options: PromptOption[] = [ localize('install', 'Install'), recommendationsAction.label, { label: choiceNever } ]; - this.choiceService.choose(Severity.Info, message, options).done(choice => { + this.notificationService.prompt(Severity.Info, message, options).done(choice => { switch (choice) { case 0 /* Install */: /* __GDPR__ @@ -571,12 +569,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } const message = localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension); - const options: Choice[] = [ + const options: PromptOption[] = [ searchMarketplace, { label: choiceNever } ]; - this.choiceService.choose(Severity.Info, message, options).done(choice => { + this.notificationService.prompt(Severity.Info, message, options).done(choice => { switch (choice) { case 0 /* Search Marketplace */: /* __GDPR__ @@ -644,13 +642,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All")); - const options: Choice[] = [ + const options: PromptOption[] = [ installAllAction.label, showAction.label, { label: choiceNever } ]; - return this.choiceService.choose(Severity.Info, message, options).done(choice => { + return this.notificationService.prompt(Severity.Info, message, options).done(choice => { switch (choice) { case 0 /* Install */: /* __GDPR__ @@ -696,7 +694,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe localize('no', "No") ]; - this.choiceService.choose(Severity.Info, message, options).done(choice => { + this.notificationService.prompt(Severity.Info, message, options).done(choice => { switch (choice) { case 0: // If the user ignores the current message and selects different file type return this.setIgnoreRecommendationsConfig(true); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index bfc33fcfcac..b9c27924e0f 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -14,7 +14,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import URI from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -59,7 +58,7 @@ export class InstallVSIXAction extends Action { id = InstallVSIXAction.ID, label = InstallVSIXAction.LABEL, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IChoiceService private choiceService: IChoiceService, + @INotificationService private notificationService: INotificationService, @IWindowService private windowsService: IWindowService ) { super(id, label, 'extension-action install-vsix', true); @@ -77,7 +76,7 @@ export class InstallVSIXAction extends Action { } return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => { - return this.choiceService.choose(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => { + return this.notificationService.prompt(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => { if (choice === 0) { return this.windowsService.reloadWindow(); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts index 37544964229..3606cb4ba4e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts @@ -20,8 +20,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { BetterMergeDisabledNowKey, BetterMergeId, areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; -import { Severity } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; export interface IExtensionStatus { identifier: IExtensionIdentifier; @@ -37,8 +36,8 @@ export class KeymapExtensions implements IWorkbenchContribution { @IInstantiationService private instantiationService: IInstantiationService, @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, @IExtensionTipsService private tipsService: IExtensionTipsService, - @IChoiceService private choiceService: IChoiceService, @ILifecycleService lifecycleService: ILifecycleService, + @INotificationService private notificationService: INotificationService, @ITelemetryService private telemetryService: ITelemetryService, ) { this.disposables.push( @@ -70,7 +69,7 @@ export class KeymapExtensions implements IWorkbenchContribution { localize('yes', "Yes"), localize('no', "No") ]; - return this.choiceService.choose(Severity.Info, message, options) + return this.notificationService.prompt(Severity.Info, message, options) .then(value => { const confirmed = value === 0; const telemetryData: { [key: string]: any; } = { @@ -149,7 +148,7 @@ export class BetterMergeDisabled implements IWorkbenchContribution { constructor( @IStorageService storageService: IStorageService, - @IChoiceService choiceService: IChoiceService, + @INotificationService notificationService: INotificationService, @IExtensionService extensionService: IExtensionService, @IExtensionManagementService extensionManagementService: IExtensionManagementService, @ITelemetryService telemetryService: ITelemetryService, @@ -158,7 +157,7 @@ export class BetterMergeDisabled implements IWorkbenchContribution { if (storageService.getBoolean(BetterMergeDisabledNowKey, StorageScope.GLOBAL, false)) { storageService.remove(BetterMergeDisabledNowKey, StorageScope.GLOBAL); - choiceService.choose(Severity.Info, localize('betterMergeDisabled', "The Better Merge extension is now built-in, the installed extension was disabled and can be uninstalled."), [localize('uninstall', "Uninstall")]).then(choice => { + notificationService.prompt(Severity.Info, localize('betterMergeDisabled', "The Better Merge extension is now built-in, the installed extension was disabled and can be uninstalled."), [localize('uninstall', "Uninstall")]).then(choice => { if (choice === 0) { extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => { return Promise.all(extensions.filter(e => stripVersion(e.identifier.id) === BetterMergeId) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 8295e319410..44adb6652ed 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -52,7 +52,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { getGalleryExtensionIdFromLocal, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IPartService } from 'vs/workbench/services/part/common/partService'; @@ -488,7 +487,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { @IExtensionManagementService private extensionsManagementService: IExtensionManagementService, @IWindowService private windowService: IWindowService, @ILogService private logService: ILogService, - @IChoiceService private choiceService: IChoiceService + @INotificationService private notificationService: INotificationService ) { this.loopCheckForMaliciousExtensions(); } @@ -509,7 +508,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { if (maliciousExtensions.length) { return TPromise.join(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => { - return this.choiceService.choose(Severity.Warning, localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", getGalleryExtensionIdFromLocal(e)), [localize('reloadNow', "Reload Now")]).then(choice => { + return this.notificationService.prompt(Severity.Warning, localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", getGalleryExtensionIdFromLocal(e)), [localize('reloadNow', "Reload Now")]).then(choice => { if (choice === 0) { return this.windowService.reloadWindow(); } diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 9e76dd3e097..dd974eff6b0 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -35,7 +35,7 @@ import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensions import product from 'vs/platform/node/product'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IChoiceService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; interface IExtensionStateProvider { @@ -370,9 +370,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { @IExtensionGalleryService private galleryService: IExtensionGalleryService, @IConfigurationService private configurationService: IConfigurationService, @ITelemetryService private telemetryService: ITelemetryService, - @INotificationService private notificationService: INotificationService, @IDialogService private dialogService: IDialogService, - @IChoiceService private choiceService: IChoiceService, + @INotificationService private notificationService: INotificationService, @IURLService urlService: IURLService, @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, @IWindowService private windowService: IWindowService, @@ -1001,7 +1000,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { const options = [ nls.localize('install', "Install") ]; - return this.choiceService.choose(Severity.Info, message, options).then(value => { + return this.notificationService.prompt(Severity.Info, message, options).then(value => { if (value === 0) { return this.install(extension); } diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index af44cde792f..4b77d0803a2 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -46,7 +46,6 @@ import product from 'vs/platform/node/product'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; const mockExtensionGallery: IGalleryExtension[] = [ @@ -222,12 +221,15 @@ suite('ExtensionsTipsService Test', () => { instantiationService.stub(IExtensionsWorkbenchService, extensionsWorkbenchService); prompted = false; - instantiationService.stub(IChoiceService, { - choose: () => { + + class TestNotificationService2 extends TestNotificationService { + public prompt() { prompted = true; return TPromise.as(3); } - }); + } + + instantiationService.stub(INotificationService, new TestNotificationService2()); testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false, showRecommendationsOnlyOnDemand: false }); instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => c, store: () => { } }); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 8914e11186d..c74232470db 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -35,7 +35,8 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IProgressService2 } from 'vs/platform/progress/common/progress'; import { ProgressService2 } from 'vs/workbench/services/progress/browser/progressService2'; -import { IChoiceService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService } from 'vs/platform/notification/common/notification'; suite('ExtensionsWorkbenchService Test', () => { @@ -76,7 +77,7 @@ suite('ExtensionsWorkbenchService Test', () => { instantiationService.stub(IExtensionTipsService, ExtensionTipsService); instantiationService.stub(IExtensionTipsService, 'getKeymapRecommendations', () => []); - instantiationService.stub(IChoiceService, { choose: () => null }); + instantiationService.stub(INotificationService, { prompt: () => null }); instantiationService.stub(IDialogService, { show: () => TPromise.as(0) }); }); @@ -85,7 +86,7 @@ suite('ExtensionsWorkbenchService Test', () => { instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); instantiationService.stub(IDialogService, { show: () => TPromise.as(0) }); - instantiationService.stubPromise(IChoiceService, 'choose', 0); + instantiationService.stubPromise(INotificationService, 'prompt', 0); (instantiationService.get(IExtensionEnablementService)).reset(); }); diff --git a/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts b/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts index ebd52903f20..44f166ae835 100644 --- a/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts @@ -17,8 +17,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import pkg from 'vs/platform/node/package'; import product, { ISurveyData } from 'vs/platform/node/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs'; -import { Severity } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; class LanguageSurvey { @@ -26,7 +25,7 @@ class LanguageSurvey { data: ISurveyData, instantiationService: IInstantiationService, storageService: IStorageService, - choiceService: IChoiceService, + notificationService: INotificationService, telemetryService: ITelemetryService, fileService: IFileService, modelService: IModelService @@ -88,8 +87,8 @@ class LanguageSurvey { // __GDPR__TODO__ Need to move away from dynamic event names as those cannot be registered statically telemetryService.publicLog(`${data.surveyId}.survey/userAsked`); - const choices: Choice[] = [nls.localize('takeShortSurvey', "Take Short Survey"), nls.localize('remindLater', "Remind Me later"), { label: nls.localize('neverAgain', "Don't Show Again") }]; - choiceService.choose(Severity.Info, nls.localize('helpUs', "Help us improve our support for {0}", data.languageId), choices).then(choice => { + const choices: PromptOption[] = [nls.localize('takeShortSurvey', "Take Short Survey"), nls.localize('remindLater', "Remind Me later"), { label: nls.localize('neverAgain', "Don't Show Again") }]; + notificationService.prompt(Severity.Info, nls.localize('helpUs', "Help us improve our support for {0}", data.languageId), choices).then(choice => { switch (choice) { case 0 /* Take Survey */: telemetryService.publicLog(`${data.surveyId}.survey/takeShortSurvey`); @@ -119,13 +118,13 @@ class LanguageSurveysContribution implements IWorkbenchContribution { constructor( @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @IChoiceService choiceService: IChoiceService, + @INotificationService notificationService: INotificationService, @ITelemetryService telemetryService: ITelemetryService, @IFileService fileService: IFileService, @IModelService modelService: IModelService ) { product.surveys.filter(surveyData => surveyData.surveyId && surveyData.editCount && surveyData.languageId && surveyData.surveyUrl && surveyData.userProbability).map(surveyData => - new LanguageSurvey(surveyData, instantiationService, storageService, choiceService, telemetryService, fileService, modelService)); + new LanguageSurvey(surveyData, instantiationService, storageService, notificationService, telemetryService, fileService, modelService)); } } diff --git a/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts b/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts index 698016ef6d4..26a3cc3b48f 100644 --- a/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts +++ b/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts @@ -15,8 +15,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import pkg from 'vs/platform/node/package'; import product from 'vs/platform/node/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs'; -import { Severity } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; const PROBABILITY = 0.15; const SESSION_COUNT_KEY = 'nps/sessionCount'; @@ -29,7 +28,7 @@ class NPSContribution implements IWorkbenchContribution { constructor( @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @IChoiceService choiceService: IChoiceService, + @INotificationService notificationService: INotificationService, @ITelemetryService telemetryService: ITelemetryService ) { const skipVersion = storageService.get(SKIP_VERSION_KEY, StorageScope.GLOBAL, ''); @@ -63,8 +62,8 @@ class NPSContribution implements IWorkbenchContribution { return; } - const choices: Choice[] = [nls.localize('takeSurvey', "Take Survey"), nls.localize('remindLater', "Remind Me later"), { label: nls.localize('neverAgain', "Don't Show Again") }]; - choiceService.choose(Severity.Info, nls.localize('surveyQuestion', "Do you mind taking a quick feedback survey?"), choices).then(choice => { + const choices: PromptOption[] = [nls.localize('takeSurvey', "Take Survey"), nls.localize('remindLater', "Remind Me later"), { label: nls.localize('neverAgain', "Don't Show Again") }]; + notificationService.prompt(Severity.Info, nls.localize('surveyQuestion', "Do you mind taking a quick feedback survey?"), choices).then(choice => { switch (choice) { case 0 /* Take Survey */: telemetryService.getTelemetryInfo().then(info => { diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index c2acd6caf5a..278b41bec89 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -46,7 +46,7 @@ import { IProgressService2, IProgressOptions, ProgressLocation } from 'vs/platfo import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IDialogService, IChoiceService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -472,8 +472,7 @@ class TaskService implements ITaskService { @IOpenerService private openerService: IOpenerService, @IWindowService private readonly _windowService: IWindowService, @IDialogService private dialogService: IDialogService, - @INotificationService private notificationService: INotificationService, - @IChoiceService private choiceService: IChoiceService + @INotificationService private notificationService: INotificationService ) { this._configHasErrors = false; this._workspaceTasksPromise = undefined; @@ -491,7 +490,7 @@ class TaskService implements ITaskService { let folderSetup = this.computeWorkspaceFolderSetup(); if (this.executionEngine !== folderSetup[2]) { if (this._taskSystem && this._taskSystem.getActiveTasks().length > 0) { - this.choiceService.choose(Severity.Info, nls.localize( + this.notificationService.prompt(Severity.Info, nls.localize( 'TaskSystem.noHotSwap', 'Changing the task execution engine with an active task running requires to reload the Window' ), [nls.localize('reloadWindow', "Reload Window")]).then(choice => { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index 0fd4de9e7a4..314511a4e90 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -13,8 +13,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION } from 'vs/workbench/parts/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; import { isFedora } from 'vs/workbench/parts/terminal/electron-browser/terminal'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { Terminal as XTermTerminal } from 'vscode-xterm'; +import { INotificationService } from 'vs/platform/notification/common/notification'; const DEFAULT_LINE_HEIGHT = 1.0; @@ -35,7 +35,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { public constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @IWorkspaceConfigurationService private readonly _workspaceConfigurationService: IWorkspaceConfigurationService, - @IChoiceService private readonly _choiceService: IChoiceService, + @INotificationService private readonly _notificationService: INotificationService, @IStorageService private readonly _storageService: IStorageService ) { this._updateConfig(); @@ -165,7 +165,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { } const message = nls.localize('terminal.integrated.allowWorkspaceShell', "Do you allow {0} (defined as a workspace setting) to be launched in the terminal?", changeString); const options = [nls.localize('allow', "Allow"), nls.localize('disallow', "Disallow")]; - this._choiceService.choose(Severity.Info, message, options).then(choice => { + this._notificationService.prompt(Severity.Info, message, options).then(choice => { switch (choice) { case 0: /* Allow */ this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, true, StorageScope.WORKSPACE); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 3994f5ccb26..814bf288b17 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -22,7 +22,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { getTerminalDefaultShellWindows } from 'vs/workbench/parts/terminal/electron-browser/terminal'; import { TerminalPanel } from 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; import { TerminalTab } from 'vs/workbench/parts/terminal/electron-browser/terminalTab'; -import { IChoiceService, IDialogService, Choice } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, Choice } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class TerminalService extends AbstractTerminalService implements ITerminalService { private _configHelper: TerminalConfigHelper; @@ -42,7 +43,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, - @IChoiceService private readonly _choiceService: IChoiceService, + @INotificationService private readonly _notificationService: INotificationService, @IDialogService private readonly _dialogService: IDialogService ) { super(contextKeyService, panelService, partService, lifecycleService, storageService); @@ -128,7 +129,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina const message = nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."); const options: Choice[] = [nls.localize('customize', "Customize"), { label: nls.localize('never again', "Don't Show Again") }]; - this._choiceService.choose(Severity.Info, message, options).then(choice => { + this._notificationService.prompt(Severity.Info, message, options).then(choice => { switch (choice) { case 0 /* Customize */: this.selectDefaultWindowsShell().then(shell => { diff --git a/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts b/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts index d3edbef785c..3550191127d 100644 --- a/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts +++ b/src/vs/workbench/parts/trust/electron-browser/unsupportedWorkspaceSettings.contribution.ts @@ -14,8 +14,7 @@ import { IPreferencesService } from 'vs/workbench/parts/preferences/common/prefe import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs'; -import { Severity } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; class UnsupportedWorkspaceSettingsContribution implements IWorkbenchContribution { @@ -28,8 +27,8 @@ class UnsupportedWorkspaceSettingsContribution implements IWorkbenchContribution @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService, @IPreferencesService private preferencesService: IPreferencesService, - @IChoiceService private choiceService: IChoiceService, - @IStorageService private storageService: IStorageService + @IStorageService private storageService: IStorageService, + @INotificationService private notificationService: INotificationService ) { lifecycleService.onShutdown(this.dispose, this); this.toDispose.push(this.workspaceConfigurationService.onDidChangeConfiguration(e => this.checkWorkspaceSettings())); @@ -61,8 +60,8 @@ class UnsupportedWorkspaceSettingsContribution implements IWorkbenchContribution } private showWarning(unsupportedKeys: string[]): void { - const choices: Choice[] = [nls.localize('openWorkspaceSettings', 'Open Workspace Settings'), { label: nls.localize('dontShowAgain', 'Don\'t Show Again') }]; - this.choiceService.choose(Severity.Warning, nls.localize('unsupportedWorkspaceSettings', 'This Workspace contains settings that can only be set in User Settings ({0}). Click [here]({1}) to learn more.', unsupportedKeys.join(', '), 'https://go.microsoft.com/fwlink/?linkid=839878'), choices).then(choice => { + const choices: PromptOption[] = [nls.localize('openWorkspaceSettings', 'Open Workspace Settings'), { label: nls.localize('dontShowAgain', 'Don\'t Show Again') }]; + this.notificationService.prompt(Severity.Warning, nls.localize('unsupportedWorkspaceSettings', 'This Workspace contains settings that can only be set in User Settings ({0}). Click [here]({1}) to learn more.', unsupportedKeys.join(', '), 'https://go.microsoft.com/fwlink/?linkid=839878'), choices).then(choice => { switch (choice) { case 0 /* Open Workspace Settings */: this.rememberWarningWasShown(); diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 79c5dc579b4..290a2b95ac3 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -39,7 +39,6 @@ import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifi import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; used(); @@ -227,7 +226,6 @@ class WelcomePage { @IConfigurationService private configurationService: IConfigurationService, @IEnvironmentService private environmentService: IEnvironmentService, @INotificationService private notificationService: INotificationService, - @IChoiceService private choiceService: IChoiceService, @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, @IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService, @IExtensionManagementService private extensionManagementService: IExtensionManagementService, @@ -429,7 +427,7 @@ class WelcomePage { }); }); - this.choiceService.choose(Severity.Info, strings.reloadAfterInstall.replace('{0}', extensionSuggestion.name), [localize('ok', "OK"), localize('details', "Details")]).then(choice => { + this.notificationService.prompt(Severity.Info, strings.reloadAfterInstall.replace('{0}', extensionSuggestion.name), [localize('ok', "OK"), localize('details', "Details")]).then(choice => { switch (choice) { case 0 /* OK */: const messageDelay = TPromise.timeout(300); diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index b81c48bc42e..edcd13dd67d 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -30,7 +30,6 @@ import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as Config import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextModel } from 'vs/editor/common/model'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; export enum ConfigurationEditingErrorCode { @@ -127,7 +126,6 @@ export class ConfigurationEditingService { @IFileService private fileService: IFileService, @ITextModelService private textModelResolverService: ITextModelService, @ITextFileService private textFileService: ITextFileService, - @IChoiceService private choiceService: IChoiceService, @INotificationService private notificationService: INotificationService, @ICommandService private commandService: ICommandService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService @@ -194,14 +192,14 @@ export class ConfigurationEditingService { : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; if (openStandAloneConfigurationActionLabel) { - this.choiceService.choose(Severity.Error, error.message, [openStandAloneConfigurationActionLabel]) + this.notificationService.prompt(Severity.Error, error.message, [openStandAloneConfigurationActionLabel]) .then(option => { if (option === 0) { this.openFile(operation.resource); } }); } else { - this.choiceService.choose(Severity.Error, error.message, [nls.localize('open', "Open Settings")]) + this.notificationService.prompt(Severity.Error, error.message, [nls.localize('open', "Open Settings")]) .then(option => { if (option === 0) { this.openSettings(operation); @@ -215,7 +213,7 @@ export class ConfigurationEditingService { : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; if (openStandAloneConfigurationActionLabel) { - this.choiceService.choose(Severity.Error, error.message, [nls.localize('saveAndRetry', "Save and Retry"), openStandAloneConfigurationActionLabel]) + this.notificationService.prompt(Severity.Error, error.message, [nls.localize('saveAndRetry', "Save and Retry"), openStandAloneConfigurationActionLabel]) .then(option => { switch (option) { case 0 /* Save & Retry */: @@ -228,7 +226,7 @@ export class ConfigurationEditingService { } }); } else { - this.choiceService.choose(Severity.Error, error.message, [nls.localize('saveAndRetry', "Save and Retry"), nls.localize('open', "Open Settings")]) + this.notificationService.prompt(Severity.Error, error.message, [nls.localize('saveAndRetry', "Save and Retry"), nls.localize('open', "Open Settings")]) .then(option => { switch (option) { case 0 /* Save and Retry */: diff --git a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts index 032c4a8770b..0bd35b5d87a 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -34,7 +34,7 @@ import { TextModelResolverService } from 'vs/workbench/services/textmodelResolve import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { mkdirp } from 'vs/base/node/pfs'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService } from 'vs/platform/notification/common/notification'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -176,7 +176,7 @@ suite('ConfigurationEditingService', () => { test('do not notify error', () => { instantiationService.stub(ITextFileService, 'isDirty', true); const target = sinon.stub(); - instantiationService.stubPromise(IChoiceService, 'choose', target); + instantiationService.stubPromise(INotificationService, 'prompt', target); return testObject.writeConfiguration(ConfigurationTarget.USER, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true }) .then(() => assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'), (error: ConfigurationEditingError) => { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index f905e0d7361..5d2170bd704 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -36,7 +36,6 @@ import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console'; import { getScopes } from 'vs/platform/configuration/common/configurationRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; export class ExtensionHostProcessWorker { @@ -73,7 +72,6 @@ export class ExtensionHostProcessWorker { @IWorkspaceConfigurationService private readonly _configurationService: IWorkspaceConfigurationService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ICrashReporterService private readonly _crashReporterService: ICrashReporterService, - @IChoiceService private readonly _choiceService: IChoiceService, @ILogService private readonly _logService: ILogService ) { // handle extension host lifecycle a bit special when we know we are developing an extension that runs inside @@ -238,7 +236,7 @@ export class ExtensionHostProcessWorker { ? nls.localize('extensionHostProcess.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.") : nls.localize('extensionHostProcess.startupFail', "Extension host did not start in 10 seconds, that might be a problem."); - this._choiceService.choose(Severity.Warning, msg, [nls.localize('reloadWindow', "Reload Window")]).then(choice => { + this._notificationService.prompt(Severity.Warning, msg, [nls.localize('reloadWindow', "Reload Window")]).then(choice => { if (choice === 0) { this._windowService.reloadWindow(); } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 9939086f025..65530a4cd49 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -41,7 +41,6 @@ import product from 'vs/platform/node/product'; import * as strings from 'vs/base/common/strings'; import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; let _SystemExtensionsRoot: string = null; function getSystemExtensionsRoot(): string { @@ -144,7 +143,6 @@ export class ExtensionService extends Disposable implements IExtensionService { constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @INotificationService private readonly _notificationService: INotificationService, - @IChoiceService private readonly _choiceService: IChoiceService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService, @@ -280,7 +278,7 @@ export class ExtensionService extends Disposable implements IExtensionService { message = nls.localize('extensionHostProcess.unresponsiveCrash', "Extension host terminated because it was not responsive."); } - this._choiceService.choose(Severity.Error, message, [nls.localize('devTools', "Developer Tools"), nls.localize('restart', "Restart Extension Host")]).then(choice => { + this._notificationService.prompt(Severity.Error, message, [nls.localize('devTools', "Developer Tools"), nls.localize('restart', "Restart Extension Host")]).then(choice => { switch (choice) { case 0 /* Open Dev Tools */: this._windowService.openDevTools(); @@ -455,7 +453,7 @@ export class ExtensionService extends Disposable implements IExtensionService { this._logOrShowMessage(severity, this._isDev ? messageWithSource2(source, message) : message); }); - return ExtensionService._scanInstalledExtensions(this._windowService, this._choiceService, this._environmentService, log) + return ExtensionService._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, log) .then(({ system, user, development }) => { return this._extensionEnablementService.getDisabledExtensions() .then(disabledExtensions => { @@ -547,7 +545,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private static async _validateExtensionsCache(windowService: IWindowService, choiceService: IChoiceService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): TPromise { + private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): TPromise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -572,7 +570,7 @@ export class ExtensionService extends Disposable implements IExtensionService { console.error(err); } - choiceService.choose(Severity.Error, nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."), [nls.localize('reloadWindow', "Reload Window")]).then(choice => { + notificationService.prompt(Severity.Error, nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."), [nls.localize('reloadWindow', "Reload Window")]).then(choice => { if (choice === 0) { windowService.reloadWindow(); } @@ -610,7 +608,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private static async _scanExtensionsWithCache(windowService: IWindowService, choiceService: IChoiceService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): TPromise { + private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): TPromise { if (input.devMode) { // Do not cache when running out of sources... return ExtensionScanner.scanExtensions(input, log); @@ -628,7 +626,7 @@ export class ExtensionService extends Disposable implements IExtensionService { // Validate the cache asynchronously after 5s setTimeout(async () => { try { - await this._validateExtensionsCache(windowService, choiceService, environmentService, cacheKey, input); + await this._validateExtensionsCache(windowService, notificationService, environmentService, cacheKey, input); } catch (err) { errors.onUnexpectedError(err); } @@ -650,7 +648,7 @@ export class ExtensionService extends Disposable implements IExtensionService { return result; } - private static _scanInstalledExtensions(windowService: IWindowService, choiceService: IChoiceService, environmentService: IEnvironmentService, log: ILog): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> { + private static _scanInstalledExtensions(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, log: ILog): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> { const translationConfig: TPromise = platform.translationsConfigFile ? pfs.readFile(platform.translationsConfigFile, 'utf8').then((content) => { @@ -672,7 +670,7 @@ export class ExtensionService extends Disposable implements IExtensionService { const builtinExtensions = this._scanExtensionsWithCache( windowService, - choiceService, + notificationService, environmentService, BUILTIN_MANIFEST_CACHE_FILE, new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, translations), @@ -726,7 +724,7 @@ export class ExtensionService extends Disposable implements IExtensionService { ? TPromise.as([]) : this._scanExtensionsWithCache( windowService, - choiceService, + notificationService, environmentService, USER_MANIFEST_CACHE_FILE, new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, translations), diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index 8704b7f68f2..6c09819220b 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -24,8 +24,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { isMacintosh, isWindows } from 'vs/base/common/platform'; import product from 'vs/platform/node/product'; import { Schemas } from 'vs/base/common/network'; -import { Severity } from 'vs/platform/notification/common/notification'; -import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs'; +import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification'; export class FileService implements IFileService { @@ -50,7 +49,7 @@ export class FileService implements IFileService { @IWorkspaceContextService private contextService: IWorkspaceContextService, @IEnvironmentService private environmentService: IEnvironmentService, @ILifecycleService private lifecycleService: ILifecycleService, - @IChoiceService private choiceService: IChoiceService, + @INotificationService private notificationService: INotificationService, @IStorageService private storageService: IStorageService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService ) { @@ -109,8 +108,8 @@ export class FileService implements IFileService { // Detect if we run < .NET Framework 4.5 (TODO@ben remove with new watcher impl) if (msg.indexOf(FileService.NET_VERSION_ERROR) >= 0 && !this.storageService.getBoolean(FileService.NET_VERSION_ERROR_IGNORE_KEY, StorageScope.WORKSPACE)) { - const choices: Choice[] = [nls.localize('installNet', "Download .NET Framework 4.5"), { label: nls.localize('neverShowAgain', "Don't Show Again") }]; - this.choiceService.choose(Severity.Warning, nls.localize('netVersionError', "The Microsoft .NET Framework 4.5 is required. Please follow the link to install it."), choices).then(choice => { + const choices: PromptOption[] = [nls.localize('installNet', "Download .NET Framework 4.5"), { label: nls.localize('neverShowAgain', "Don't Show Again") }]; + this.notificationService.prompt(Severity.Warning, nls.localize('netVersionError', "The Microsoft .NET Framework 4.5 is required. Please follow the link to install it."), choices).then(choice => { switch (choice) { case 0 /* Read More */: window.open('https://go.microsoft.com/fwlink/?LinkId=786533'); @@ -124,8 +123,8 @@ export class FileService implements IFileService { // Detect if we run into ENOSPC issues if (msg.indexOf(FileService.ENOSPC_ERROR) >= 0 && !this.storageService.getBoolean(FileService.ENOSPC_ERROR_IGNORE_KEY, StorageScope.WORKSPACE)) { - const choices: Choice[] = [nls.localize('learnMore', "Instructions"), { label: nls.localize('neverShowAgain', "Don't Show Again") }]; - this.choiceService.choose(Severity.Warning, nls.localize('enospcError', "{0} is running out of file handles. Please follow the instructions link to resolve this issue.", product.nameLong), choices).then(choice => { + const choices: PromptOption[] = [nls.localize('learnMore', "Instructions"), { label: nls.localize('neverShowAgain', "Don't Show Again") }]; + this.notificationService.prompt(Severity.Warning, nls.localize('enospcError', "{0} is running out of file handles. Please follow the instructions link to resolve this issue.", product.nameLong), choices).then(choice => { switch (choice) { case 0 /* Read More */: window.open('https://go.microsoft.com/fwlink/?linkid=867693'); diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index 01fef549f4d..d93f0a80eda 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -25,7 +25,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { maxBufferLen, detectMimeAndEncodingFromBuffer } from 'vs/base/node/mime'; import { MIME_BINARY } from 'vs/base/common/mime'; import { localize } from 'vs/nls'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService } from 'vs/platform/notification/common/notification'; function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse?: (tuple: [URI, IStat]) => boolean): TPromise { const [resource, stat] = tuple; @@ -86,7 +86,7 @@ export class RemoteFileService extends FileService { @IWorkspaceContextService contextService: IWorkspaceContextService, @IEnvironmentService environmentService: IEnvironmentService, @ILifecycleService lifecycleService: ILifecycleService, - @IChoiceService choiceService: IChoiceService, + @INotificationService notificationService: INotificationService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, ) { super( @@ -94,7 +94,7 @@ export class RemoteFileService extends FileService { contextService, environmentService, lifecycleService, - choiceService, + notificationService, _storageService, textResourceConfigurationService, ); diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 3781071048f..920eb822543 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -5,9 +5,12 @@ 'use strict'; -import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, PromptOption, INotificationActions } from 'vs/platform/notification/common/notification'; import { INotificationsModel, NotificationsModel } from 'vs/workbench/common/notifications'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Action } from 'vs/base/common/actions'; +import { once } from 'vs/base/common/event'; export class NotificationService implements INotificationService { @@ -62,6 +65,60 @@ export class NotificationService implements INotificationService { return this.model.notify(notification); } + public prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { + let handle: INotificationHandle; + + const promise = new TPromise((c, e) => { + + // Complete promise with index of action that was picked + const callback = (index: number, closeNotification: boolean) => () => { + c(index); + + if (closeNotification) { + handle.dispose(); + } + + return TPromise.as(void 0); + }; + + // Convert choices into primary/secondary actions + const actions: INotificationActions = { + primary: [], + secondary: [] + }; + + choices.forEach((choice, index) => { + let isPrimary = true; + let label: string; + let closeNotification = false; + + if (typeof choice === 'string') { + label = choice; + } else { + isPrimary = false; + label = choice.label; + closeNotification = !choice.keepOpen; + } + + const action = new Action(`workbench.dialog.choice.${index}`, label, null, true, callback(index, closeNotification)); + if (isPrimary) { + actions.primary.push(action); + } else { + actions.secondary.push(action); + } + }); + + // Show notification with actions + handle = this.notify({ severity, message, actions }); + + // Cancel promise when notification gets disposed + once(handle.onDidDispose)(() => promise.cancel()); + + }, () => handle.dispose()); + + return promise; + } + public dispose(): void { this.toDispose = dispose(this.toDispose); } diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 9b758c0c981..f5acaa9c5ba 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -27,7 +27,6 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { distinct } from 'vs/base/common/arrays'; import { isLinux } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; -import { IChoiceService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; export class WorkspaceEditingService implements IWorkspaceEditingService { @@ -42,7 +41,6 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @IStorageService private storageService: IStorageService, @IExtensionService private extensionService: IExtensionService, @IBackupFileService private backupFileService: IBackupFileService, - @IChoiceService private choiceService: IChoiceService, @INotificationService private notificationService: INotificationService, @ICommandService private commandService: ICommandService ) { @@ -176,7 +174,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } private askToOpenWorkspaceConfigurationFile(message: string): TPromise { - return this.choiceService.choose(Severity.Error, message, [nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration")]) + return this.notificationService.prompt(Severity.Error, message, [nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration")]) .then(option => { if (option === 0) { this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile'); 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 4f471f38864..e6695eb7d85 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -9,7 +9,7 @@ import * as assert from 'assert'; import { MainThreadMessageService } from 'vs/workbench/api/electron-browser/mainThreadMessageService'; import { TPromise as Promise, TPromise } from 'vs/base/common/winjs.base'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotification, NoOpNotification, INotificationHandle } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, NoOpNotification, INotificationHandle, PromptOption, Severity } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; const emptyDialogService = new class implements IDialogService { @@ -45,6 +45,9 @@ const emptyNotificationService = new class implements INotificationService { error(...args: any[]): never { throw new Error('not implemented'); } + prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { + throw new Error('not implemented'); + } }; class EmptyNotificationService implements INotificationService { @@ -68,6 +71,9 @@ class EmptyNotificationService implements INotificationService { error(message: any): void { throw new Error('Method not implemented.'); } + prompt(severity: Severity, message: string, choices: PromptOption[]): Promise { + throw new Error('Method not implemented.'); + } } suite('ExtHostMessageService', function () { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index d8c9721d668..9fadf06875b 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -62,8 +62,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; -import { IChoiceService, IConfirmation, IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotificationHandle, INotification, NoOpNotification } from 'vs/platform/notification/common/notification'; +import { IConfirmation, IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService, INotificationHandle, INotification, NoOpNotification, PromptOption } from 'vs/platform/notification/common/notification'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, void 0); @@ -263,11 +263,6 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IEnvironmentService, TestEnvironmentService); instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(IHashService, new TestHashService()); - instantiationService.stub(IChoiceService, { - choose: (severity, message, options): TPromise => { - return TPromise.as(0); - } - } as IChoiceService); return instantiationService; } @@ -331,6 +326,10 @@ export class TestNotificationService implements INotificationService { public notify(notification: INotification): INotificationHandle { return TestNotificationService.NO_OP; } + + public prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise { + return TPromise.as(0); + } } export class TestDialogService implements IDialogService {