Skip to content

Commit bc59b29

Browse files
author
Benjamin Pasero
committed
debt - add new prompt method to notification service
1 parent 55ea9fe commit bc59b29

29 files changed

Lines changed: 204 additions & 120 deletions

File tree

src/vs/editor/standalone/browser/simpleServices.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe
3838
import { OS } from 'vs/base/common/platform';
3939
import { IRange } from 'vs/editor/common/core/range';
4040
import { ITextModel } from 'vs/editor/common/model';
41-
import { INotificationService, INotification, INotificationHandle, NoOpNotification } from 'vs/platform/notification/common/notification';
41+
import { INotificationService, INotification, INotificationHandle, NoOpNotification, PromptOption } from 'vs/platform/notification/common/notification';
4242
import { IConfirmation, IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
4343
import { IPosition, Position as Pos } from 'vs/editor/common/core/position';
4444

@@ -296,6 +296,10 @@ export class SimpleNotificationService implements INotificationService {
296296

297297
return SimpleNotificationService.NO_OP;
298298
}
299+
300+
public prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise<number> {
301+
return TPromise.as(0);
302+
}
299303
}
300304

301305
export class StandaloneCommandService implements ICommandService {

src/vs/platform/integrity/node/integrityServiceImpl.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import URI from 'vs/base/common/uri';
1414
import Severity from 'vs/base/common/severity';
1515
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
1616
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
17-
import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs';
17+
import { INotificationService, PromptOption } from 'vs/platform/notification/common/notification';
1818

1919
interface IStorageData {
2020
dontShowPrompt: boolean;
@@ -62,7 +62,7 @@ export class IntegrityServiceImpl implements IIntegrityService {
6262
private _isPurePromise: Thenable<IntegrityTestResult>;
6363

6464
constructor(
65-
@IChoiceService private choiceService: IChoiceService,
65+
@INotificationService private notificationService: INotificationService,
6666
@IStorageService storageService: IStorageService,
6767
@ILifecycleService private lifecycleService: ILifecycleService
6868
) {
@@ -86,9 +86,9 @@ export class IntegrityServiceImpl implements IIntegrityService {
8686
return;
8787
}
8888

89-
const choices: Choice[] = [nls.localize('integrity.moreInformation', "More Information"), { label: nls.localize('integrity.dontShowAgain', "Don't Show Again") }];
89+
const choices: PromptOption[] = [nls.localize('integrity.moreInformation', "More Information"), { label: nls.localize('integrity.dontShowAgain', "Don't Show Again") }];
9090

91-
this.choiceService.choose(Severity.Warning, nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort), choices).then(choice => {
91+
this.notificationService.prompt(Severity.Warning, nls.localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", product.nameShort), choices).then(choice => {
9292
switch (choice) {
9393
case 0 /* More Information */:
9494
const uri = URI.parse(product.checksumFailMoreInfoUrl);

src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ suite('AbstractKeybindingService', () => {
138138
error: (message: any) => {
139139
showMessageCalls.push({ sev: Severity.Error, message });
140140
return new NoOpNotification();
141+
},
142+
prompt: () => {
143+
return TPromise.as(0);
141144
}
142145
};
143146

src/vs/platform/notification/common/notification.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
1010
import { IDisposable } from 'vs/base/common/lifecycle';
1111
import { IAction } from 'vs/base/common/actions';
1212
import Event, { Emitter } from 'vs/base/common/event';
13+
import { TPromise } from 'vs/base/common/winjs.base';
1314

1415
export import Severity = Severity;
1516

@@ -44,7 +45,7 @@ export interface INotification {
4445
* close automatically when invoking a secondary action.
4546
*
4647
* **Note:** If your intent is to show a message with actions to the user, consider
47-
* the `IChoiceService` or `IDialogService` instead which are optimized for
48+
* the `INotificationService.prompt()` method instead which are optimized for
4849
* this usecase and much easier to use!
4950
*/
5051
actions?: INotificationActions;
@@ -120,6 +121,27 @@ export interface INotificationHandle extends IDisposable {
120121
updateActions(actions?: INotificationActions): void;
121122
}
122123

124+
125+
/**
126+
* Primary choices show up as buttons in the notification below the message.
127+
*/
128+
export type PrimaryPromptChoice = string;
129+
130+
/**
131+
* Secondary choices show up under the gear icon in the header of the notification.
132+
*/
133+
export interface SecondaryPromptChoice {
134+
label: string;
135+
136+
/**
137+
* Wether to keep the notification open after the secondary choice was selected
138+
* by the user. By default, will close the notification upon click.
139+
*/
140+
keepOpen?: boolean;
141+
}
142+
143+
export type PromptOption = PrimaryPromptChoice | SecondaryPromptChoice;
144+
123145
export interface INotificationService {
124146

125147
_serviceBrand: any;
@@ -129,8 +151,10 @@ export interface INotificationService {
129151
* can be used to control the notification afterwards.
130152
*
131153
* **Note:** If your intent is to show a message with actions to the user, consider
132-
* the `IChoiceService` or `IDialogService` instead which are optimized for
154+
* the `INotificationService.prompt()` method instead which are optimized for
133155
* this usecase and much easier to use!
156+
*
157+
* @returns a handle on the notification to e.g. hide it or update message, buttons, etc.
134158
*/
135159
notify(notification: INotification): INotificationHandle;
136160

@@ -151,6 +175,15 @@ export interface INotificationService {
151175
* method if you need more control over the notification.
152176
*/
153177
error(message: NotificationMessage | NotificationMessage[]): void;
178+
179+
/**
180+
* Shows a prompt in the notification area with the provided choices. The prompt
181+
* is non-modal. If you want to show a modal dialog instead, use `IDialogService`.
182+
*
183+
* @returns a promise that will resolve to the index of the choice that was picked.
184+
* The promise can be cancelled to hide the notification prompt.
185+
*/
186+
prompt(severity: Severity, message: string, choices: PromptOption[]): TPromise<number>;
154187
}
155188

156189
export class NoOpNotification implements INotificationHandle {

src/vs/workbench/parts/extensions/browser/extensionsActions.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
4444
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
4545
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
4646
import { IOpenerService } from 'vs/platform/opener/common/opener';
47-
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
4847
import { mnemonicButtonLabel } from 'vs/base/common/labels';
4948
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
5049
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
@@ -1881,7 +1880,7 @@ export class InstallVSIXAction extends Action {
18811880
id = InstallVSIXAction.ID,
18821881
label = InstallVSIXAction.LABEL,
18831882
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
1884-
@IChoiceService private choiceService: IChoiceService,
1883+
@INotificationService private notificationService: INotificationService,
18851884
@IWindowService private windowsService: IWindowService
18861885
) {
18871886
super(id, label, 'extension-action install-vsix', true);
@@ -1899,7 +1898,7 @@ export class InstallVSIXAction extends Action {
18991898
}
19001899

19011900
return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => {
1902-
return this.choiceService.choose(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => {
1901+
return this.notificationService.prompt(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => {
19031902
if (choice === 0) {
19041903
return this.windowsService.reloadWindow();
19051904
}

src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspace
3434
import { IRequestService } from 'vs/platform/request/node/request';
3535
import { asJson } from 'vs/base/node/request';
3636
import { isNumber } from 'vs/base/common/types';
37-
import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs';
3837
import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform';
3938
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
40-
import { INotificationService } from 'vs/platform/notification/common/notification';
39+
import { INotificationService, PromptOption } from 'vs/platform/notification/common/notification';
4140

4241
interface IExtensionsContent {
4342
recommendations: string[];
@@ -72,7 +71,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
7271
@IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService,
7372
@IModelService private readonly _modelService: IModelService,
7473
@IStorageService private storageService: IStorageService,
75-
@IChoiceService private choiceService: IChoiceService,
7674
@IExtensionManagementService private extensionsService: IExtensionManagementService,
7775
@IInstantiationService private instantiationService: IInstantiationService,
7876
@IFileService private fileService: IFileService,
@@ -163,12 +161,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
163161
return;
164162
}
165163
const message = localize('showLanguagePackExtensions', "The Marketplace has extensions that can help localizing VS Code to '{0}' locale", language);
166-
const options: Choice[] = [
164+
const options: PromptOption[] = [
167165
searchMarketplace,
168166
{ label: choiceNever }
169167
];
170168

171-
this.choiceService.choose(Severity.Info, message, options).done(choice => {
169+
this.notificationService.prompt(Severity.Info, message, options).done(choice => {
172170
switch (choice) {
173171
case 0 /* Search Marketplace */:
174172
/* __GDPR__
@@ -491,13 +489,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
491489

492490
const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations"));
493491
const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id);
494-
const options: Choice[] = [
492+
const options: PromptOption[] = [
495493
localize('install', 'Install'),
496494
recommendationsAction.label,
497495
{ label: choiceNever }
498496
];
499497

500-
this.choiceService.choose(Severity.Info, message, options).done(choice => {
498+
this.notificationService.prompt(Severity.Info, message, options).done(choice => {
501499
switch (choice) {
502500
case 0 /* Install */:
503501
/* __GDPR__
@@ -571,12 +569,12 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
571569
}
572570

573571
const message = localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension);
574-
const options: Choice[] = [
572+
const options: PromptOption[] = [
575573
searchMarketplace,
576574
{ label: choiceNever }
577575
];
578576

579-
this.choiceService.choose(Severity.Info, message, options).done(choice => {
577+
this.notificationService.prompt(Severity.Info, message, options).done(choice => {
580578
switch (choice) {
581579
case 0 /* Search Marketplace */:
582580
/* __GDPR__
@@ -644,13 +642,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
644642
const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations"));
645643
const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All"));
646644

647-
const options: Choice[] = [
645+
const options: PromptOption[] = [
648646
installAllAction.label,
649647
showAction.label,
650648
{ label: choiceNever }
651649
];
652650

653-
return this.choiceService.choose(Severity.Info, message, options).done(choice => {
651+
return this.notificationService.prompt(Severity.Info, message, options).done(choice => {
654652
switch (choice) {
655653
case 0 /* Install */:
656654
/* __GDPR__
@@ -696,7 +694,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
696694
localize('no', "No")
697695
];
698696

699-
this.choiceService.choose(Severity.Info, message, options).done(choice => {
697+
this.notificationService.prompt(Severity.Info, message, options).done(choice => {
700698
switch (choice) {
701699
case 0: // If the user ignores the current message and selects different file type
702700
return this.setIgnoreRecommendationsConfig(true);

src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { IFileService } from 'vs/platform/files/common/files';
1414
import URI from 'vs/base/common/uri';
1515
import Severity from 'vs/base/common/severity';
1616
import { mnemonicButtonLabel } from 'vs/base/common/labels';
17-
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
1817
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
1918
import { INotificationService } from 'vs/platform/notification/common/notification';
2019
import { LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -59,7 +58,7 @@ export class InstallVSIXAction extends Action {
5958
id = InstallVSIXAction.ID,
6059
label = InstallVSIXAction.LABEL,
6160
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
62-
@IChoiceService private choiceService: IChoiceService,
61+
@INotificationService private notificationService: INotificationService,
6362
@IWindowService private windowsService: IWindowService
6463
) {
6564
super(id, label, 'extension-action install-vsix', true);
@@ -77,7 +76,7 @@ export class InstallVSIXAction extends Action {
7776
}
7877

7978
return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => {
80-
return this.choiceService.choose(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => {
79+
return this.notificationService.prompt(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => {
8180
if (choice === 0) {
8281
return this.windowsService.reloadWindow();
8382
}

src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
2020
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
2121
import { BetterMergeDisabledNowKey, BetterMergeId, areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
2222
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
23-
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
24-
import { Severity } from 'vs/platform/notification/common/notification';
23+
import { Severity, INotificationService } from 'vs/platform/notification/common/notification';
2524

2625
export interface IExtensionStatus {
2726
identifier: IExtensionIdentifier;
@@ -37,8 +36,8 @@ export class KeymapExtensions implements IWorkbenchContribution {
3736
@IInstantiationService private instantiationService: IInstantiationService,
3837
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
3938
@IExtensionTipsService private tipsService: IExtensionTipsService,
40-
@IChoiceService private choiceService: IChoiceService,
4139
@ILifecycleService lifecycleService: ILifecycleService,
40+
@INotificationService private notificationService: INotificationService,
4241
@ITelemetryService private telemetryService: ITelemetryService,
4342
) {
4443
this.disposables.push(
@@ -70,7 +69,7 @@ export class KeymapExtensions implements IWorkbenchContribution {
7069
localize('yes', "Yes"),
7170
localize('no', "No")
7271
];
73-
return this.choiceService.choose(Severity.Info, message, options)
72+
return this.notificationService.prompt(Severity.Info, message, options)
7473
.then(value => {
7574
const confirmed = value === 0;
7675
const telemetryData: { [key: string]: any; } = {
@@ -149,7 +148,7 @@ export class BetterMergeDisabled implements IWorkbenchContribution {
149148

150149
constructor(
151150
@IStorageService storageService: IStorageService,
152-
@IChoiceService choiceService: IChoiceService,
151+
@INotificationService notificationService: INotificationService,
153152
@IExtensionService extensionService: IExtensionService,
154153
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
155154
@ITelemetryService telemetryService: ITelemetryService,
@@ -158,7 +157,7 @@ export class BetterMergeDisabled implements IWorkbenchContribution {
158157
if (storageService.getBoolean(BetterMergeDisabledNowKey, StorageScope.GLOBAL, false)) {
159158
storageService.remove(BetterMergeDisabledNowKey, StorageScope.GLOBAL);
160159

161-
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 => {
160+
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 => {
162161
if (choice === 0) {
163162
extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
164163
return Promise.all(extensions.filter(e => stripVersion(e.identifier.id) === BetterMergeId)

src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
5252
import { getGalleryExtensionIdFromLocal, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
5353
import { ILogService } from 'vs/platform/log/common/log';
5454
import { INotificationService } from 'vs/platform/notification/common/notification';
55-
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
5655
import { IWindowService } from 'vs/platform/windows/common/windows';
5756
import { IPartService } from 'vs/workbench/services/part/common/partService';
5857

@@ -488,7 +487,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
488487
@IExtensionManagementService private extensionsManagementService: IExtensionManagementService,
489488
@IWindowService private windowService: IWindowService,
490489
@ILogService private logService: ILogService,
491-
@IChoiceService private choiceService: IChoiceService
490+
@INotificationService private notificationService: INotificationService
492491
) {
493492
this.loopCheckForMaliciousExtensions();
494493
}
@@ -509,7 +508,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
509508

510509
if (maliciousExtensions.length) {
511510
return TPromise.join(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
512-
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 => {
511+
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 => {
513512
if (choice === 0) {
514513
return this.windowService.reloadWindow();
515514
}

0 commit comments

Comments
 (0)