notifications - add service methods to DND per source

This commit is contained in:
Benjamin Pasero
2023-12-18 10:51:34 +01:00
committed by Benjamin Pasero
parent 968af29545
commit 0ff10eb8f3
15 changed files with 191 additions and 61 deletions

View File

@@ -42,7 +42,7 @@ import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/com
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent, Verbosity } from 'vs/platform/label/common/label'; 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 { 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 { 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'; 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<INotification> = Event.None; readonly onDidRemoveNotification: Event<INotification> = Event.None;
readonly onDidChangeDoNotDisturbMode: Event<void> = Event.None; readonly onDidChangeGlobalDoNotDisturbMode: Event<void> = Event.None;
readonly onDidChangePerSourceDoNotDisturbMode = Event.None;
public _serviceBrand: undefined; public _serviceBrand: undefined;
public isDoNotDisturbMode: boolean = false; public isGlobalDoNotDisturbMode: boolean = false;
private static readonly NO_OP: INotificationHandle = new NoOpNotification(); private static readonly NO_OP: INotificationHandle = new NoOpNotification();
@@ -350,9 +352,14 @@ export class StandaloneNotificationService implements INotificationService {
return Disposable.None; return Disposable.None;
} }
public setDoNotDisturbMode(mode: boolean): void { public setGlobalDoNotDisturbMode(mode: boolean): void {
this.isDoNotDisturbMode = mode; this.isGlobalDoNotDisturbMode = mode;
} }
public isSourceDoNotDisturb(source: INotificationSource): boolean {
return false;
}
public setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { }
} }
export class StandaloneCommandService implements ICommandService { export class StandaloneCommandService implements ICommandService {

View File

@@ -17,7 +17,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { createUSLayoutResolvedKeybinding } from 'vs/platform/keybinding/test/common/keybindingsTestUtils'; import { createUSLayoutResolvedKeybinding } from 'vs/platform/keybinding/test/common/keybindingsTestUtils';
import { NullLogService } from 'vs/platform/log/common/log'; 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'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
function createContext(ctx: any) { function createContext(ctx: any) {
@@ -142,10 +142,11 @@ suite('AbstractKeybindingService', () => {
const notificationService: INotificationService = { const notificationService: INotificationService = {
_serviceBrand: undefined, _serviceBrand: undefined,
isDoNotDisturbMode: false, isGlobalDoNotDisturbMode: false,
onDidAddNotification: undefined!, onDidAddNotification: undefined!,
onDidRemoveNotification: undefined!, onDidRemoveNotification: undefined!,
onDidChangeDoNotDisturbMode: undefined!, onDidChangeGlobalDoNotDisturbMode: undefined!,
onDidChangePerSourceDoNotDisturbMode: undefined!,
notify: (notification: INotification) => { notify: (notification: INotification) => {
showMessageCalls.push({ sev: notification.severity, message: notification.message }); showMessageCalls.push({ sev: notification.severity, message: notification.message });
return new NoOpNotification(); return new NoOpNotification();
@@ -173,8 +174,14 @@ suite('AbstractKeybindingService', () => {
} }
}; };
}, },
setDoNotDisturbMode(mode: boolean) { setGlobalDoNotDisturbMode(mode: boolean) {
throw new Error('not implemented'); 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.');
} }
}; };

View File

@@ -97,6 +97,29 @@ export interface INeverShowAgainOptions {
readonly scope?: NeverShowAgainScope; 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 { export interface INotification extends INotificationProperties {
/** /**
@@ -120,7 +143,7 @@ export interface INotification extends INotificationProperties {
/** /**
* The source of the notification appears as additional information. * 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 * Actions to show as part of the notification. Primary actions show up as
@@ -343,19 +366,34 @@ export interface INotificationService {
readonly onDidRemoveNotification: Event<INotification>; readonly onDidRemoveNotification: Event<INotification>;
/** /**
* Emitted when a do not disturb mode has changed. * Emitted when the global do not disturb mode has changed.
*/ */
readonly onDidChangeDoNotDisturbMode: Event<void>; readonly onDidChangeGlobalDoNotDisturbMode: Event<void>;
/** /**
* If enabled, only error messages will show as toasts. * 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<INotificationSource>;
/**
* 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` * Show the provided notification to the user. The returned `INotificationHandle`

View File

@@ -5,7 +5,7 @@
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; 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 { export class TestNotificationService implements INotificationService {
@@ -13,11 +13,13 @@ export class TestNotificationService implements INotificationService {
readonly onDidRemoveNotification: Event<INotification> = Event.None; readonly onDidRemoveNotification: Event<INotification> = Event.None;
readonly onDidChangeDoNotDisturbMode: Event<void> = Event.None; readonly onDidChangeGlobalDoNotDisturbMode: Event<void> = Event.None;
readonly onDidChangePerSourceDoNotDisturbMode = Event.None;
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
isDoNotDisturbMode: boolean = false; isGlobalDoNotDisturbMode: boolean = false;
private static readonly NO_OP: INotificationHandle = new NoOpNotification(); private static readonly NO_OP: INotificationHandle = new NoOpNotification();
@@ -45,7 +47,13 @@ export class TestNotificationService implements INotificationService {
return Disposable.None; return Disposable.None;
} }
setDoNotDisturbMode(mode: boolean): void { setGlobalDoNotDisturbMode(mode: boolean): void {
this.isDoNotDisturbMode = mode; this.isGlobalDoNotDisturbMode = mode;
} }
isSourceDoNotDisturb(source: INotificationSource): boolean {
return false;
}
setSourceDoNotDisturb(source: INotificationSource, mode: boolean): void { }
} }

View File

@@ -8,7 +8,7 @@ import { DeferredPromise } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; 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<IProgressService>('progressService'); export const IProgressService = createDecorator<IProgressService>('progressService');
@@ -53,7 +53,7 @@ export const enum ProgressLocation {
export interface IProgressOptions { export interface IProgressOptions {
readonly location: ProgressLocation | string; readonly location: ProgressLocation | string;
readonly title?: string; readonly title?: string;
readonly source?: string | { label: string; id: string }; readonly source?: string | INotificationSource;
readonly total?: number; readonly total?: number;
readonly cancellable?: boolean; readonly cancellable?: boolean;
readonly buttons?: string[]; readonly buttons?: string[];

View File

@@ -9,7 +9,7 @@ import { IAction, toAction } from 'vs/base/common/actions';
import { MainThreadMessageServiceShape, MainContext, MainThreadMessageOptions } from '../common/extHost.protocol'; import { MainThreadMessageServiceShape, MainContext, MainThreadMessageOptions } from '../common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; 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 { Event } from 'vs/base/common/event';
import { ICommandService } from 'vs/platform/commands/common/commands'; 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) { if (options.source) {
source = { source = {
label: nls.localize('extensionSource', "{0} (Extension)", options.source.label), label: nls.localize('extensionSource', "{0} (Extension)", options.source.label),

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService'; import { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService';
import { IDialogService, IPrompt, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; 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 { ICommandService } from 'vs/platform/commands/common/commands';
import { mock } from 'vs/base/test/common/mock'; import { mock } from 'vs/base/test/common/mock';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
@@ -25,10 +25,11 @@ const emptyCommandService: ICommandService = {
const emptyNotificationService = new class implements INotificationService { const emptyNotificationService = new class implements INotificationService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
isDoNotDisturbMode: boolean = false; isGlobalDoNotDisturbMode: boolean = false;
onDidAddNotification: Event<INotification> = Event.None; onDidAddNotification: Event<INotification> = Event.None;
onDidRemoveNotification: Event<INotification> = Event.None; onDidRemoveNotification: Event<INotification> = Event.None;
onDidChangeDoNotDisturbMode: Event<void> = Event.None; onDidChangeGlobalDoNotDisturbMode: Event<void> = Event.None;
onDidChangePerSourceDoNotDisturbMode = Event.None;
notify(...args: any[]): never { notify(...args: any[]): never {
throw new Error('not implemented'); throw new Error('not implemented');
} }
@@ -47,20 +48,27 @@ const emptyNotificationService = new class implements INotificationService {
status(message: string | Error, options?: IStatusMessageOptions): IDisposable { status(message: string | Error, options?: IStatusMessageOptions): IDisposable {
return Disposable.None; return Disposable.None;
} }
setDoNotDisturbMode(mode: boolean): void { setGlobalDoNotDisturbMode(mode: boolean): void {
throw new Error('not implemented'); 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 { class EmptyNotificationService implements INotificationService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
isDoNotDisturbMode: boolean = false; isGlobalDoNotDisturbMode: boolean = false;
constructor(private withNotify: (notification: INotification) => void) { constructor(private withNotify: (notification: INotification) => void) {
} }
onDidAddNotification: Event<INotification> = Event.None; onDidAddNotification: Event<INotification> = Event.None;
onDidRemoveNotification: Event<INotification> = Event.None; onDidRemoveNotification: Event<INotification> = Event.None;
onDidChangeDoNotDisturbMode: Event<void> = Event.None; onDidChangeGlobalDoNotDisturbMode: Event<void> = Event.None;
onDidChangePerSourceDoNotDisturbMode = Event.None;
notify(notification: INotification): INotificationHandle { notify(notification: INotification): INotificationHandle {
this.withNotify(notification); this.withNotify(notification);
@@ -81,7 +89,13 @@ class EmptyNotificationService implements INotificationService {
status(message: string, options?: IStatusMessageOptions): IDisposable { status(message: string, options?: IStatusMessageOptions): IDisposable {
return Disposable.None; 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.'); throw new Error('Method not implemented.');
} }
} }

View File

@@ -1338,7 +1338,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (!restoring) { if (!restoring) {
zenModeExitInfo.transitionedToFullScreen = toggleFullScreen; zenModeExitInfo.transitionedToFullScreen = toggleFullScreen;
zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isMainEditorLayoutCentered() && config.centerLayout; 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.sideBar = this.isVisible(Parts.SIDEBAR_PART);
zenModeExitInfo.wasVisible.panel = this.isVisible(Parts.PANEL_PART); zenModeExitInfo.wasVisible.panel = this.isVisible(Parts.PANEL_PART);
zenModeExitInfo.wasVisible.auxiliaryBar = this.isVisible(Parts.AUXILIARYBAR_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) { if (config.silentNotifications && zenModeExitInfo.handleNotificationsDoNotDisturbMode) {
this.notificationService.setDoNotDisturbMode(true); this.notificationService.setGlobalDoNotDisturbMode(true);
} }
if (config.centerLayout) { if (config.centerLayout) {
@@ -1404,7 +1404,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (e.affectsConfiguration(ZenModeSettings.SILENT_NOTIFICATIONS)) { if (e.affectsConfiguration(ZenModeSettings.SILENT_NOTIFICATIONS)) {
const zenModeSilentNotifications = !!this.configurationService.getValue(ZenModeSettings.SILENT_NOTIFICATIONS); const zenModeSilentNotifications = !!this.configurationService.getValue(ZenModeSettings.SILENT_NOTIFICATIONS);
if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) { 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) { if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) {
this.notificationService.setDoNotDisturbMode(false); this.notificationService.setGlobalDoNotDisturbMode(false);
} }
setLineNumbers(); setLineNumbers();

View File

@@ -67,10 +67,10 @@ export class NotificationsCenter extends Themable implements INotificationsCente
private registerListeners(): void { private registerListeners(): void {
this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));
this._register(this.layoutService.onDidLayoutMainContainer(dimension => this.layout(Dimension.lift(dimension)))); 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 this.hide(); // hide the notification center when do not disturb is toggled
} }

View File

@@ -287,7 +287,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
CommandsRegistry.registerCommand(TOGGLE_DO_NOT_DISTURB_MODE, accessor => { CommandsRegistry.registerCommand(TOGGLE_DO_NOT_DISTURB_MODE, accessor => {
const notificationService = accessor.get(INotificationService); const notificationService = accessor.get(INotificationService);
notificationService.setDoNotDisturbMode(!notificationService.isDoNotDisturbMode); notificationService.setGlobalDoNotDisturbMode(!notificationService.isGlobalDoNotDisturbMode);
}); });
// Commands for Command Palette // Commands for Command Palette

View File

@@ -39,7 +39,7 @@ export class NotificationsStatus extends Disposable {
private registerListeners(): void { private registerListeners(): void {
this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));
this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(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 { private onDidChangeNotification(e: INotificationChangeEvent): void {
@@ -83,7 +83,7 @@ export class NotificationsStatus extends Disposable {
showBeak: this.isNotificationsCenterVisible showBeak: this.isNotificationsCenterVisible
}; };
if (this.notificationService.isDoNotDisturbMode) { if (this.notificationService.isGlobalDoNotDisturbMode) {
statusProperties = { statusProperties = {
...statusProperties, ...statusProperties,
text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`, text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`,

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { toErrorMessage, isErrorWithActions } from 'vs/base/common/errorMessage';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; 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 (priority === NotificationPriority.DEFAULT && severity !== Severity.Error) {
if (filter.global === NotificationsFilter.ERROR) { if (filter.global === NotificationsFilter.ERROR) {
priority = NotificationPriority.SILENT; // filtered globally 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 priority = NotificationPriority.SILENT; // filtered by source
} }
} }
@@ -537,7 +537,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
private _sticky: boolean | undefined, private _sticky: boolean | undefined,
private _priority: NotificationPriority, private _priority: NotificationPriority,
private _message: INotificationMessage, private _message: INotificationMessage,
private _source: string | { label: string; id: string } | undefined, private _source: string | INotificationSource | undefined,
progress: INotificationProgressProperties | undefined, progress: INotificationProgressProperties | undefined,
actions?: INotificationActions actions?: INotificationActions
) { ) {

View File

@@ -193,7 +193,7 @@ suite('ConfigurationEditing', () => {
test('do not notify error', async () => { test('do not notify error', async () => {
instantiationService.stub(ITextFileService, 'isDirty', true); instantiationService.stub(ITextFileService, 'isDirty', true);
const target = sinon.stub(); const target = sinon.stub();
instantiationService.stub(INotificationService, <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, <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 { try {
await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true }); await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true });
} catch (error) { } catch (error) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls'; 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 { NotificationsModel, ChoiceAction, NotificationChangeType } from 'vs/workbench/common/notifications';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event'; 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 { IAction, Action } from 'vs/base/common/actions';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
interface INotificationSourceDoNotDisturb extends INotificationSource {
readonly filter: NotificationsFilter;
}
export class NotificationService extends Disposable implements INotificationService { export class NotificationService extends Disposable implements INotificationService {
declare readonly _serviceBrand: undefined; 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<void>()); private readonly _onDidChangeGlobalDoNotDisturbMode = this._register(new Emitter<void>());
readonly onDidChangeDoNotDisturbMode = this._onDidChangeDoNotDisturbMode.event; readonly onDidChangeGlobalDoNotDisturbMode = this._onDidChangeGlobalDoNotDisturbMode.event;
private _isDoNotDisturbMode = this.storageService.getBoolean(NotificationService.DND_SETTINGS_KEY, StorageScope.APPLICATION, false); private _isGlobalDoNotDisturbMode = this.storageService.getBoolean(NotificationService.GLOBAL_DND_SETTINGS_KEY, StorageScope.APPLICATION, false);
get isDoNotDisturbMode() { return this._isDoNotDisturbMode; } get isGlobalDoNotDisturbMode() { return this._isGlobalDoNotDisturbMode; }
setDoNotDisturbMode(enabled: boolean): void { setGlobalDoNotDisturbMode(enabled: boolean): void {
if (this._isDoNotDisturbMode === enabled) { if (this._isGlobalDoNotDisturbMode === enabled) {
return; // no change return; // no change
} }
this.storageService.store(NotificationService.DND_SETTINGS_KEY, enabled, StorageScope.APPLICATION, StorageTarget.MACHINE); // Store into model and persist
this._isDoNotDisturbMode = enabled; this._isGlobalDoNotDisturbMode = enabled;
this.storageService.store(NotificationService.GLOBAL_DND_SETTINGS_KEY, enabled, StorageScope.APPLICATION, StorageTarget.MACHINE);
// Toggle via filter // Update model
this.updateDoNotDisturbFilters(); this.updateDoNotDisturbFilters();
// Events // 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<INotificationSource>());
readonly onDidChangePerSourceDoNotDisturbMode = this._onDidChangePerSourceDoNotDisturbMode.event;
private readonly mapSourceIdToDoNotDisturb: Map<string /** source id */, INotificationSourceDoNotDisturb> = (() => {
const map = new Map<string, INotificationSourceDoNotDisturb>();
for (const sourceFilter of this.storageService.getObject<INotificationSourceDoNotDisturb[]>(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 { 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 //#endregion

View File

@@ -11,7 +11,7 @@ import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IP
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar';
import { DeferredPromise, RunOnceScheduler, timeout } from 'vs/base/common/async'; import { DeferredPromise, RunOnceScheduler, timeout } from 'vs/base/common/async';
import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; 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 { Action } from 'vs/base/common/actions';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -68,8 +68,16 @@ export class ProgressService extends Disposable implements IProgressService {
} }
switch (location) { switch (location) {
case ProgressLocation.Notification: case ProgressLocation.Notification: {
return this.withNotificationProgress({ ...options, location, priority: this.notificationService.isDoNotDisturbMode ? NotificationPriority.SILENT : undefined }, task, onDidCancel); 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: { case ProgressLocation.Window: {
const type = (options as IProgressWindowOptions).type; const type = (options as IProgressWindowOptions).type;
if ((options as IProgressWindowOptions).command) { if ((options as IProgressWindowOptions).command) {