From 0ff10eb8f33b5c6c22bbf0e07d4e1caa91fe052d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Dec 2023 10:51:34 +0100 Subject: [PATCH] notifications - add service methods to DND per source --- .../standalone/browser/standaloneServices.ts | 17 +++-- .../common/abstractKeybindingService.test.ts | 15 +++- .../notification/common/notification.ts | 50 ++++++++++-- .../test/common/testNotificationService.ts | 18 +++-- src/vs/platform/progress/common/progress.ts | 4 +- .../api/browser/mainThreadMessageService.ts | 4 +- .../browser/extHostMessagerService.test.ts | 28 +++++-- src/vs/workbench/browser/layout.ts | 8 +- .../notifications/notificationsCenter.ts | 4 +- .../notifications/notificationsCommands.ts | 2 +- .../notifications/notificationsStatus.ts | 4 +- src/vs/workbench/common/notifications.ts | 6 +- .../test/browser/configurationEditing.test.ts | 2 +- .../common/notificationService.ts | 76 +++++++++++++++---- .../progress/browser/progressService.ts | 14 +++- 15 files changed, 191 insertions(+), 61 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f5c9945705f..38a7ffccae8 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -42,7 +42,7 @@ import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/com import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent, Verbosity } from 'vs/platform/label/common/label'; -import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, INotificationSource } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService, IProgressService, IProgress, IProgressCompositeOptions, IProgressDialogOptions, IProgressNotificationOptions, IProgressOptions, IProgressStep, IProgressWindowOptions } from 'vs/platform/progress/common/progress'; import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceFoldersWillChangeEvent, WorkbenchState, WorkspaceFolder, STANDALONE_EDITOR_WORKSPACE_ID } from 'vs/platform/workspace/common/workspace'; @@ -306,11 +306,13 @@ export class StandaloneNotificationService implements INotificationService { readonly onDidRemoveNotification: Event = Event.None; - readonly onDidChangeDoNotDisturbMode: Event = Event.None; + readonly onDidChangeGlobalDoNotDisturbMode: Event = Event.None; + + readonly onDidChangePerSourceDoNotDisturbMode = Event.None; public _serviceBrand: undefined; - public isDoNotDisturbMode: boolean = false; + public isGlobalDoNotDisturbMode: boolean = false; private static readonly NO_OP: INotificationHandle = new NoOpNotification(); @@ -350,9 +352,14 @@ export class StandaloneNotificationService implements INotificationService { return Disposable.None; } - public setDoNotDisturbMode(mode: boolean): void { - this.isDoNotDisturbMode = mode; + public setGlobalDoNotDisturbMode(mode: boolean): void { + this.isGlobalDoNotDisturbMode = mode; } + + public isSourceDoNotDisturb(source: INotificationSource): boolean { + return false; + } + public setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { } } export class StandaloneCommandService implements ICommandService { diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index afedaa9a0e9..1cdf2b23134 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -17,7 +17,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { createUSLayoutResolvedKeybinding } from 'vs/platform/keybinding/test/common/keybindingsTestUtils'; import { NullLogService } from 'vs/platform/log/common/log'; -import { INotification, INotificationService, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationService, INotificationSource, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification } from 'vs/platform/notification/common/notification'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; function createContext(ctx: any) { @@ -142,10 +142,11 @@ suite('AbstractKeybindingService', () => { const notificationService: INotificationService = { _serviceBrand: undefined, - isDoNotDisturbMode: false, + isGlobalDoNotDisturbMode: false, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, - onDidChangeDoNotDisturbMode: undefined!, + onDidChangeGlobalDoNotDisturbMode: undefined!, + onDidChangePerSourceDoNotDisturbMode: undefined!, notify: (notification: INotification) => { showMessageCalls.push({ sev: notification.severity, message: notification.message }); return new NoOpNotification(); @@ -173,8 +174,14 @@ suite('AbstractKeybindingService', () => { } }; }, - setDoNotDisturbMode(mode: boolean) { + setGlobalDoNotDisturbMode(mode: boolean) { throw new Error('not implemented'); + }, + isSourceDoNotDisturb(source: INotificationSource): boolean { + throw new Error('Method not implemented.'); + }, + setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { + throw new Error('Method not implemented.'); } }; diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index d5c52168926..ac74db48931 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -97,6 +97,29 @@ export interface INeverShowAgainOptions { readonly scope?: NeverShowAgainScope; } +export interface INotificationSource { + + /** + * The id of the source. + */ + readonly id: string; + + /** + * The label of the source. + */ + readonly label: string; +} + +export function isNotificationSource(thing: unknown): thing is INotificationSource { + if (thing) { + const candidate = thing as INotificationSource; + + return typeof candidate.id === 'string' && typeof candidate.label === 'string'; + } + + return false; +} + export interface INotification extends INotificationProperties { /** @@ -120,7 +143,7 @@ export interface INotification extends INotificationProperties { /** * The source of the notification appears as additional information. */ - readonly source?: string | { label: string; id: string }; + readonly source?: string | INotificationSource; /** * Actions to show as part of the notification. Primary actions show up as @@ -343,19 +366,34 @@ export interface INotificationService { readonly onDidRemoveNotification: Event; /** - * Emitted when a do not disturb mode has changed. + * Emitted when the global do not disturb mode has changed. */ - readonly onDidChangeDoNotDisturbMode: Event; + readonly onDidChangeGlobalDoNotDisturbMode: Event; /** * If enabled, only error messages will show as toasts. */ - readonly isDoNotDisturbMode: boolean; + readonly isGlobalDoNotDisturbMode: boolean; /** - * Enables or disables the do not disturb mode. + * Enables or disables the global do not disturb mode. */ - setDoNotDisturbMode(mode: boolean): void; + setGlobalDoNotDisturbMode(mode: boolean): void; + + /** + * Emitted when the per-source do not disturb mode has changed. + */ + readonly onDidChangePerSourceDoNotDisturbMode: Event; + + /** + * Whether the provided source is configured as do not disturb. + */ + isSourceDoNotDisturb(source: INotificationSource): boolean; + + /** + * Enables or disables the per-source do not disturb mode. + */ + setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void; /** * Show the provided notification to the user. The returned `INotificationHandle` diff --git a/src/vs/platform/notification/test/common/testNotificationService.ts b/src/vs/platform/notification/test/common/testNotificationService.ts index cec3c59b247..1acb12f9dac 100644 --- a/src/vs/platform/notification/test/common/testNotificationService.ts +++ b/src/vs/platform/notification/test/common/testNotificationService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, Severity } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationService, INotificationSource, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, Severity } from 'vs/platform/notification/common/notification'; export class TestNotificationService implements INotificationService { @@ -13,11 +13,13 @@ export class TestNotificationService implements INotificationService { readonly onDidRemoveNotification: Event = Event.None; - readonly onDidChangeDoNotDisturbMode: Event = Event.None; + readonly onDidChangeGlobalDoNotDisturbMode: Event = Event.None; + + readonly onDidChangePerSourceDoNotDisturbMode = Event.None; declare readonly _serviceBrand: undefined; - isDoNotDisturbMode: boolean = false; + isGlobalDoNotDisturbMode: boolean = false; private static readonly NO_OP: INotificationHandle = new NoOpNotification(); @@ -45,7 +47,13 @@ export class TestNotificationService implements INotificationService { return Disposable.None; } - setDoNotDisturbMode(mode: boolean): void { - this.isDoNotDisturbMode = mode; + setGlobalDoNotDisturbMode(mode: boolean): void { + this.isGlobalDoNotDisturbMode = mode; } + + isSourceDoNotDisturb(source: INotificationSource): boolean { + return false; + } + + setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { } } diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index f6fa371d73d..e1eb0a2f5f2 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -8,7 +8,7 @@ import { DeferredPromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { NotificationPriority } from 'vs/platform/notification/common/notification'; +import { INotificationSource, NotificationPriority } from 'vs/platform/notification/common/notification'; export const IProgressService = createDecorator('progressService'); @@ -53,7 +53,7 @@ export const enum ProgressLocation { export interface IProgressOptions { readonly location: ProgressLocation | string; readonly title?: string; - readonly source?: string | { label: string; id: string }; + readonly source?: string | INotificationSource; readonly total?: number; readonly cancellable?: boolean; readonly buttons?: string[]; diff --git a/src/vs/workbench/api/browser/mainThreadMessageService.ts b/src/vs/workbench/api/browser/mainThreadMessageService.ts index 43ee94f3893..2a170222325 100644 --- a/src/vs/workbench/api/browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/browser/mainThreadMessageService.ts @@ -9,7 +9,7 @@ import { IAction, toAction } from 'vs/base/common/actions'; import { MainThreadMessageServiceShape, MainContext, MainThreadMessageOptions } from '../common/extHost.protocol'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotificationSource } from 'vs/platform/notification/common/notification'; import { Event } from 'vs/base/common/event'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -51,7 +51,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { } })); - let source: string | { label: string; id: string } | undefined; + let source: string | INotificationSource | undefined; if (options.source) { source = { label: nls.localize('extensionSource', "{0} (Extension)", options.source.label), diff --git a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts index 2832368fb6e..d283a4e7969 100644 --- a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts +++ b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService'; import { IDialogService, IPrompt, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions, INotificationSource } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { mock } from 'vs/base/test/common/mock'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; @@ -25,10 +25,11 @@ const emptyCommandService: ICommandService = { const emptyNotificationService = new class implements INotificationService { declare readonly _serviceBrand: undefined; - isDoNotDisturbMode: boolean = false; + isGlobalDoNotDisturbMode: boolean = false; onDidAddNotification: Event = Event.None; onDidRemoveNotification: Event = Event.None; - onDidChangeDoNotDisturbMode: Event = Event.None; + onDidChangeGlobalDoNotDisturbMode: Event = Event.None; + onDidChangePerSourceDoNotDisturbMode = Event.None; notify(...args: any[]): never { throw new Error('not implemented'); } @@ -47,20 +48,27 @@ const emptyNotificationService = new class implements INotificationService { status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - setDoNotDisturbMode(mode: boolean): void { + setGlobalDoNotDisturbMode(mode: boolean): void { throw new Error('not implemented'); } + isSourceDoNotDisturb(source: INotificationSource): boolean { + throw new Error('Method not implemented.'); + } + setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { + throw new Error('Method not implemented.'); + } }; class EmptyNotificationService implements INotificationService { declare readonly _serviceBrand: undefined; - isDoNotDisturbMode: boolean = false; + isGlobalDoNotDisturbMode: boolean = false; constructor(private withNotify: (notification: INotification) => void) { } onDidAddNotification: Event = Event.None; onDidRemoveNotification: Event = Event.None; - onDidChangeDoNotDisturbMode: Event = Event.None; + onDidChangeGlobalDoNotDisturbMode: Event = Event.None; + onDidChangePerSourceDoNotDisturbMode = Event.None; notify(notification: INotification): INotificationHandle { this.withNotify(notification); @@ -81,7 +89,13 @@ class EmptyNotificationService implements INotificationService { status(message: string, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - setDoNotDisturbMode(mode: boolean): void { + setGlobalDoNotDisturbMode(mode: boolean): void { + throw new Error('Method not implemented.'); + } + isSourceDoNotDisturb(source: INotificationSource): boolean { + throw new Error('Method not implemented.'); + } + setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { throw new Error('Method not implemented.'); } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a368d8e5f55..09502f3c142 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1338,7 +1338,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (!restoring) { zenModeExitInfo.transitionedToFullScreen = toggleFullScreen; zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isMainEditorLayoutCentered() && config.centerLayout; - zenModeExitInfo.handleNotificationsDoNotDisturbMode = !this.notificationService.isDoNotDisturbMode; + zenModeExitInfo.handleNotificationsDoNotDisturbMode = !this.notificationService.isGlobalDoNotDisturbMode; zenModeExitInfo.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART); zenModeExitInfo.wasVisible.panel = this.isVisible(Parts.PANEL_PART); zenModeExitInfo.wasVisible.auxiliaryBar = this.isVisible(Parts.AUXILIARYBAR_PART); @@ -1367,7 +1367,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (config.silentNotifications && zenModeExitInfo.handleNotificationsDoNotDisturbMode) { - this.notificationService.setDoNotDisturbMode(true); + this.notificationService.setGlobalDoNotDisturbMode(true); } if (config.centerLayout) { @@ -1404,7 +1404,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (e.affectsConfiguration(ZenModeSettings.SILENT_NOTIFICATIONS)) { const zenModeSilentNotifications = !!this.configurationService.getValue(ZenModeSettings.SILENT_NOTIFICATIONS); if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) { - this.notificationService.setDoNotDisturbMode(zenModeSilentNotifications); + this.notificationService.setGlobalDoNotDisturbMode(zenModeSilentNotifications); } } @@ -1444,7 +1444,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) { - this.notificationService.setDoNotDisturbMode(false); + this.notificationService.setGlobalDoNotDisturbMode(false); } setLineNumbers(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 27d642b195a..919ad9dd8ed 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -67,10 +67,10 @@ export class NotificationsCenter extends Themable implements INotificationsCente private registerListeners(): void { this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.layoutService.onDidLayoutMainContainer(dimension => this.layout(Dimension.lift(dimension)))); - this._register(this.notificationService.onDidChangeDoNotDisturbMode(() => this.onDidChangeDoNotDisturbMode())); + this._register(this.notificationService.onDidChangeGlobalDoNotDisturbMode(() => this.onDidChangeGlobalDoNotDisturbMode())); } - private onDidChangeDoNotDisturbMode(): void { + private onDidChangeGlobalDoNotDisturbMode(): void { this.hide(); // hide the notification center when do not disturb is toggled } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index d41a4f37103..e879437683f 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -287,7 +287,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl CommandsRegistry.registerCommand(TOGGLE_DO_NOT_DISTURB_MODE, accessor => { const notificationService = accessor.get(INotificationService); - notificationService.setDoNotDisturbMode(!notificationService.isDoNotDisturbMode); + notificationService.setGlobalDoNotDisturbMode(!notificationService.isGlobalDoNotDisturbMode); }); // Commands for Command Palette diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 8a34b5ad6c7..7b1259c3a81 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -39,7 +39,7 @@ export class NotificationsStatus extends Disposable { private registerListeners(): void { this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e))); - this._register(this.notificationService.onDidChangeDoNotDisturbMode(() => this.updateNotificationsCenterStatusItem())); + this._register(this.notificationService.onDidChangeGlobalDoNotDisturbMode(() => this.updateNotificationsCenterStatusItem())); } private onDidChangeNotification(e: INotificationChangeEvent): void { @@ -83,7 +83,7 @@ export class NotificationsStatus extends Disposable { showBeak: this.isNotificationsCenterVisible }; - if (this.notificationService.isDoNotDisturbMode) { + if (this.notificationService.isGlobalDoNotDisturbMode) { statusProperties = { ...statusProperties, text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`, diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index be5256986bb..cf4ba9c5c76 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties, IPromptChoiceWithMenu, NotificationPriority } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter, INotificationProgressProperties, IPromptChoiceWithMenu, NotificationPriority, INotificationSource, isNotificationSource } from 'vs/platform/notification/common/notification'; import { toErrorMessage, isErrorWithActions } from 'vs/base/common/errorMessage'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -495,7 +495,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie if (priority === NotificationPriority.DEFAULT && severity !== Severity.Error) { if (filter.global === NotificationsFilter.ERROR) { priority = NotificationPriority.SILENT; // filtered globally - } else if (notification.source && typeof notification.source !== 'string' && filter.sources.has(notification.source.id)) { + } else if (isNotificationSource(notification.source) && filter.sources.has(notification.source.id)) { priority = NotificationPriority.SILENT; // filtered by source } } @@ -537,7 +537,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie private _sticky: boolean | undefined, private _priority: NotificationPriority, private _message: INotificationMessage, - private _source: string | { label: string; id: string } | undefined, + private _source: string | INotificationSource | undefined, progress: INotificationProgressProperties | undefined, actions?: INotificationActions ) { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts index 938e81aa698..65208f03e52 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts @@ -193,7 +193,7 @@ suite('ConfigurationEditing', () => { test('do not notify error', async () => { instantiationService.stub(ITextFileService, 'isDirty', true); const target = sinon.stub(); - instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, isDoNotDisturbMode: false, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, onDidChangeDoNotDisturbMode: undefined!, notify: null!, error: null!, info: null!, warn: null!, status: null!, setDoNotDisturbMode: null! }); + instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, isGlobalDoNotDisturbMode: false, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, onDidChangeGlobalDoNotDisturbMode: undefined!, onDidChangePerSourceDoNotDisturbMode: null!, notify: null!, error: null!, info: null!, warn: null!, status: null!, setGlobalDoNotDisturbMode: null!, isSourceDoNotDisturb: null!, setSourceDoNotDisturb: null! }); try { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true }); } catch (error) { diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index c7b2f23801f..c2dcc2f758a 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NeverShowAgainScope, NotificationsFilter, INeverShowAgainOptions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NeverShowAgainScope, NotificationsFilter, INeverShowAgainOptions, INotificationSource } from 'vs/platform/notification/common/notification'; import { NotificationsModel, ChoiceAction, NotificationChangeType } from 'vs/workbench/common/notifications'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -12,6 +12,10 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IAction, Action } from 'vs/base/common/actions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +interface INotificationSourceDoNotDisturb extends INotificationSource { + readonly filter: NotificationsFilter; +} + export class NotificationService extends Disposable implements INotificationService { declare readonly _serviceBrand: undefined; @@ -59,33 +63,77 @@ export class NotificationService extends Disposable implements INotificationServ })); } - //#region Do not disturb mode + //#region Do not disturb mode (global) - static readonly DND_SETTINGS_KEY = 'notifications.doNotDisturbMode'; + private static readonly GLOBAL_DND_SETTINGS_KEY = 'notifications.doNotDisturbMode'; - private readonly _onDidChangeDoNotDisturbMode = this._register(new Emitter()); - readonly onDidChangeDoNotDisturbMode = this._onDidChangeDoNotDisturbMode.event; + private readonly _onDidChangeGlobalDoNotDisturbMode = this._register(new Emitter()); + readonly onDidChangeGlobalDoNotDisturbMode = this._onDidChangeGlobalDoNotDisturbMode.event; - private _isDoNotDisturbMode = this.storageService.getBoolean(NotificationService.DND_SETTINGS_KEY, StorageScope.APPLICATION, false); - get isDoNotDisturbMode() { return this._isDoNotDisturbMode; } + private _isGlobalDoNotDisturbMode = this.storageService.getBoolean(NotificationService.GLOBAL_DND_SETTINGS_KEY, StorageScope.APPLICATION, false); + get isGlobalDoNotDisturbMode() { return this._isGlobalDoNotDisturbMode; } - setDoNotDisturbMode(enabled: boolean): void { - if (this._isDoNotDisturbMode === enabled) { + setGlobalDoNotDisturbMode(enabled: boolean): void { + if (this._isGlobalDoNotDisturbMode === enabled) { return; // no change } - this.storageService.store(NotificationService.DND_SETTINGS_KEY, enabled, StorageScope.APPLICATION, StorageTarget.MACHINE); - this._isDoNotDisturbMode = enabled; + // Store into model and persist + this._isGlobalDoNotDisturbMode = enabled; + this.storageService.store(NotificationService.GLOBAL_DND_SETTINGS_KEY, enabled, StorageScope.APPLICATION, StorageTarget.MACHINE); - // Toggle via filter + // Update model this.updateDoNotDisturbFilters(); // Events - this._onDidChangeDoNotDisturbMode.fire(); + this._onDidChangeGlobalDoNotDisturbMode.fire(); + } + + //#endregion + + //#region Do not disturb mode (per-source) + + private static readonly PER_SOURCE_DND_SETTINGS_KEY = 'notifications.perSourceDoNotDisturbMode'; + + private readonly _onDidChangePerSourceDoNotDisturbMode = this._register(new Emitter()); + readonly onDidChangePerSourceDoNotDisturbMode = this._onDidChangePerSourceDoNotDisturbMode.event; + + private readonly mapSourceIdToDoNotDisturb: Map = (() => { + const map = new Map(); + + for (const sourceFilter of this.storageService.getObject(NotificationService.PER_SOURCE_DND_SETTINGS_KEY, StorageScope.APPLICATION, Object.create(null))) { + map.set(sourceFilter.id, sourceFilter); + } + + return map; + })(); + + isSourceDoNotDisturb(source: INotificationSource): boolean { + return this.mapSourceIdToDoNotDisturb.get(source.id)?.filter === NotificationsFilter.ERROR; + } + + setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { + const existing = this.mapSourceIdToDoNotDisturb.get(source.id); + if (existing?.filter === (mode ? NotificationsFilter.ERROR : NotificationsFilter.OFF)) { + return; // no change + } + + // Store into model and persist + this.mapSourceIdToDoNotDisturb.set(source.id, { id: source.id, label: source.label, filter: mode ? NotificationsFilter.ERROR : NotificationsFilter.OFF }); + this.storageService.store(NotificationService.PER_SOURCE_DND_SETTINGS_KEY, JSON.stringify([...this.mapSourceIdToDoNotDisturb.values()]), StorageScope.APPLICATION, StorageTarget.MACHINE); + + // Update model + this.updateDoNotDisturbFilters(); + + // Events + this._onDidChangePerSourceDoNotDisturbMode.fire(source); } private updateDoNotDisturbFilters(): void { - this.model.setFilter({ global: this._isDoNotDisturbMode ? NotificationsFilter.ERROR : NotificationsFilter.OFF }); + this.model.setFilter({ + global: this._isGlobalDoNotDisturbMode ? NotificationsFilter.ERROR : NotificationsFilter.OFF, + sources: new Map([...this.mapSourceIdToDoNotDisturb.values()].map(source => [source.id, source.filter])) + }); } //#endregion diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index e40d382d5fc..c3cc169ec24 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -11,7 +11,7 @@ import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IP import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; import { DeferredPromise, RunOnceScheduler, timeout } from 'vs/base/common/async'; import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { INotificationService, Severity, INotificationHandle, NotificationPriority } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity, INotificationHandle, NotificationPriority, isNotificationSource } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; import { Event, Emitter } from 'vs/base/common/event'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -68,8 +68,16 @@ export class ProgressService extends Disposable implements IProgressService { } switch (location) { - case ProgressLocation.Notification: - return this.withNotificationProgress({ ...options, location, priority: this.notificationService.isDoNotDisturbMode ? NotificationPriority.SILENT : undefined }, task, onDidCancel); + case ProgressLocation.Notification: { + let priority: NotificationPriority | undefined = undefined; + if (this.notificationService.isGlobalDoNotDisturbMode) { + priority = NotificationPriority.SILENT; + } else if (isNotificationSource(options.source) && this.notificationService.isSourceDoNotDisturb(options.source)) { + priority = NotificationPriority.SILENT; + } + + return this.withNotificationProgress({ ...options, location, priority }, task, onDidCancel); + } case ProgressLocation.Window: { const type = (options as IProgressWindowOptions).type; if ((options as IProgressWindowOptions).command) {