Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
7
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
8
import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
9
import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
10
import { INotificationViewItem, isNotificationViewItem, NotificationsModel } from '../../../common/notifications.js';
11
import { MenuRegistry, MenuId } from '../../../../platform/actions/common/actions.js';
12
import { localize, localize2 } from '../../../../nls.js';
13
import { IListService, WorkbenchList } from '../../../../platform/list/browser/listService.js';
14
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
15
import { NotificationFocusedContext, NotificationsCenterVisibleContext, NotificationsToastsVisibleContext } from '../../../common/contextkeys.js';
16
import { INotificationService, INotificationSourceFilter, NotificationsFilter } from '../../../../platform/notification/common/notification.js';
17
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
18
import { ActionRunner, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from '../../../../base/common/actions.js';
19
import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';
20
import { DisposableStore } from '../../../../base/common/lifecycle.js';
21
import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
22
23
// Center
24
export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList';
25
export const HIDE_NOTIFICATIONS_CENTER = 'notifications.hideList';
26
const TOGGLE_NOTIFICATIONS_CENTER = 'notifications.toggleList';
27
28
// Toasts
29
export const HIDE_NOTIFICATION_TOAST = 'notifications.hideToasts';
30
const FOCUS_NOTIFICATION_TOAST = 'notifications.focusToasts';
31
const FOCUS_NEXT_NOTIFICATION_TOAST = 'notifications.focusNextToast';
32
const FOCUS_PREVIOUS_NOTIFICATION_TOAST = 'notifications.focusPreviousToast';
33
const FOCUS_FIRST_NOTIFICATION_TOAST = 'notifications.focusFirstToast';
34
const FOCUS_LAST_NOTIFICATION_TOAST = 'notifications.focusLastToast';
35
36
// Notification
37
export const COLLAPSE_NOTIFICATION = 'notification.collapse';
38
export const EXPAND_NOTIFICATION = 'notification.expand';
39
export const ACCEPT_PRIMARY_ACTION_NOTIFICATION = 'notification.acceptPrimaryAction';
40
const TOGGLE_NOTIFICATION = 'notification.toggle';
41
export const CLEAR_NOTIFICATION = 'notification.clear';
42
export const CLEAR_ALL_NOTIFICATIONS = 'notifications.clearAll';
43
export const TOGGLE_DO_NOT_DISTURB_MODE = 'notifications.toggleDoNotDisturbMode';
44
export const TOGGLE_DO_NOT_DISTURB_MODE_BY_SOURCE = 'notifications.toggleDoNotDisturbModeBySource';
45
46
export interface INotificationsCenterController {
47
readonly isVisible: boolean;
48
49
show(): void;
50
hide(): void;
51
52
clearAll(): void;
53
}
54
55
export interface INotificationsToastController {
56
focus(): void;
57
focusNext(): void;
58
focusPrevious(): void;
59
focusFirst(): void;
60
focusLast(): void;
61
62
hide(): void;
63
}
64
65
export function getNotificationFromContext(listService: IListService, context?: unknown): INotificationViewItem | undefined {
66
if (isNotificationViewItem(context)) {
67
return context;
68
}
69
70
const list = listService.lastFocusedList;
71
if (list instanceof WorkbenchList) {
72
let element = list.getFocusedElements()[0];
73
if (!isNotificationViewItem(element)) {
74
if (list.isDOMFocused()) {
75
// the notification list might have received focus
76
// via keyboard and might not have a focused element.
77
// in that case just return the first element
78
// https://github.com/microsoft/vscode/issues/191705
79
element = list.element(0);
80
}
81
}
82
83
if (isNotificationViewItem(element)) {
84
return element;
85
}
86
}
87
88
return undefined;
89
}
90
91
export function registerNotificationCommands(center: INotificationsCenterController, toasts: INotificationsToastController, model: NotificationsModel): void {
92
93
// Show Notifications Cneter
94
KeybindingsRegistry.registerCommandAndKeybindingRule({
95
id: SHOW_NOTIFICATIONS_CENTER,
96
weight: KeybindingWeight.WorkbenchContrib,
97
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyN),
98
handler: () => {
99
toasts.hide();
100
center.show();
101
}
102
});
103
104
// Hide Notifications Center
105
KeybindingsRegistry.registerCommandAndKeybindingRule({
106
id: HIDE_NOTIFICATIONS_CENTER,
107
weight: KeybindingWeight.WorkbenchContrib + 50,
108
when: NotificationsCenterVisibleContext,
109
primary: KeyCode.Escape,
110
handler: () => center.hide()
111
});
112
113
// Toggle Notifications Center
114
CommandsRegistry.registerCommand(TOGGLE_NOTIFICATIONS_CENTER, () => {
115
if (center.isVisible) {
116
center.hide();
117
} else {
118
toasts.hide();
119
center.show();
120
}
121
});
122
123
// Clear Notification
124
KeybindingsRegistry.registerCommandAndKeybindingRule({
125
id: CLEAR_NOTIFICATION,
126
weight: KeybindingWeight.WorkbenchContrib,
127
when: NotificationFocusedContext,
128
primary: KeyCode.Delete,
129
mac: {
130
primary: KeyMod.CtrlCmd | KeyCode.Backspace
131
},
132
handler: (accessor, args?) => {
133
const accessibilitySignalService = accessor.get(IAccessibilitySignalService);
134
const notification = getNotificationFromContext(accessor.get(IListService), args);
135
if (notification && !notification.hasProgress) {
136
notification.close();
137
accessibilitySignalService.playSignal(AccessibilitySignal.clear);
138
}
139
}
140
});
141
142
// Expand Notification
143
KeybindingsRegistry.registerCommandAndKeybindingRule({
144
id: EXPAND_NOTIFICATION,
145
weight: KeybindingWeight.WorkbenchContrib,
146
when: NotificationFocusedContext,
147
primary: KeyCode.RightArrow,
148
handler: (accessor, args?) => {
149
const notification = getNotificationFromContext(accessor.get(IListService), args);
150
notification?.expand();
151
}
152
});
153
154
// Accept Primary Action
155
KeybindingsRegistry.registerCommandAndKeybindingRule({
156
id: ACCEPT_PRIMARY_ACTION_NOTIFICATION,
157
weight: KeybindingWeight.WorkbenchContrib,
158
when: ContextKeyExpr.or(NotificationFocusedContext, NotificationsToastsVisibleContext),
159
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyA,
160
handler: (accessor) => {
161
const actionRunner = accessor.get(IInstantiationService).createInstance(NotificationActionRunner);
162
const notification = getNotificationFromContext(accessor.get(IListService)) || model.notifications.at(0);
163
if (!notification) {
164
return;
165
}
166
const primaryAction = notification.actions?.primary ? notification.actions.primary.at(0) : undefined;
167
if (!primaryAction) {
168
return;
169
}
170
actionRunner.run(primaryAction, notification);
171
notification.close();
172
actionRunner.dispose();
173
}
174
});
175
176
// Collapse Notification
177
KeybindingsRegistry.registerCommandAndKeybindingRule({
178
id: COLLAPSE_NOTIFICATION,
179
weight: KeybindingWeight.WorkbenchContrib,
180
when: NotificationFocusedContext,
181
primary: KeyCode.LeftArrow,
182
handler: (accessor, args?) => {
183
const notification = getNotificationFromContext(accessor.get(IListService), args);
184
notification?.collapse();
185
}
186
});
187
188
// Toggle Notification
189
KeybindingsRegistry.registerCommandAndKeybindingRule({
190
id: TOGGLE_NOTIFICATION,
191
weight: KeybindingWeight.WorkbenchContrib,
192
when: NotificationFocusedContext,
193
primary: KeyCode.Space,
194
secondary: [KeyCode.Enter],
195
handler: accessor => {
196
const notification = getNotificationFromContext(accessor.get(IListService));
197
notification?.toggle();
198
}
199
});
200
201
// Hide Toasts
202
CommandsRegistry.registerCommand(HIDE_NOTIFICATION_TOAST, accessor => {
203
toasts.hide();
204
});
205
206
KeybindingsRegistry.registerKeybindingRule({
207
id: HIDE_NOTIFICATION_TOAST,
208
weight: KeybindingWeight.WorkbenchContrib - 50, // lower when not focused (e.g. let editor suggest win over this command)
209
when: NotificationsToastsVisibleContext,
210
primary: KeyCode.Escape
211
});
212
213
KeybindingsRegistry.registerKeybindingRule({
214
id: HIDE_NOTIFICATION_TOAST,
215
weight: KeybindingWeight.WorkbenchContrib + 100, // higher when focused
216
when: ContextKeyExpr.and(NotificationsToastsVisibleContext, NotificationFocusedContext),
217
primary: KeyCode.Escape
218
});
219
220
// Focus Toasts
221
CommandsRegistry.registerCommand(FOCUS_NOTIFICATION_TOAST, () => toasts.focus());
222
223
// Focus Next Toast
224
KeybindingsRegistry.registerCommandAndKeybindingRule({
225
id: FOCUS_NEXT_NOTIFICATION_TOAST,
226
weight: KeybindingWeight.WorkbenchContrib,
227
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
228
primary: KeyCode.DownArrow,
229
handler: () => {
230
toasts.focusNext();
231
}
232
});
233
234
// Focus Previous Toast
235
KeybindingsRegistry.registerCommandAndKeybindingRule({
236
id: FOCUS_PREVIOUS_NOTIFICATION_TOAST,
237
weight: KeybindingWeight.WorkbenchContrib,
238
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
239
primary: KeyCode.UpArrow,
240
handler: () => {
241
toasts.focusPrevious();
242
}
243
});
244
245
// Focus First Toast
246
KeybindingsRegistry.registerCommandAndKeybindingRule({
247
id: FOCUS_FIRST_NOTIFICATION_TOAST,
248
weight: KeybindingWeight.WorkbenchContrib,
249
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
250
primary: KeyCode.PageUp,
251
secondary: [KeyCode.Home],
252
handler: () => {
253
toasts.focusFirst();
254
}
255
});
256
257
// Focus Last Toast
258
KeybindingsRegistry.registerCommandAndKeybindingRule({
259
id: FOCUS_LAST_NOTIFICATION_TOAST,
260
weight: KeybindingWeight.WorkbenchContrib,
261
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
262
primary: KeyCode.PageDown,
263
secondary: [KeyCode.End],
264
handler: () => {
265
toasts.focusLast();
266
}
267
});
268
269
// Clear All Notifications
270
CommandsRegistry.registerCommand(CLEAR_ALL_NOTIFICATIONS, () => center.clearAll());
271
272
// Toggle Do Not Disturb Mode
273
CommandsRegistry.registerCommand(TOGGLE_DO_NOT_DISTURB_MODE, accessor => {
274
const notificationService = accessor.get(INotificationService);
275
276
notificationService.setFilter(notificationService.getFilter() === NotificationsFilter.ERROR ? NotificationsFilter.OFF : NotificationsFilter.ERROR);
277
});
278
279
// Configure Do Not Disturb by Source
280
CommandsRegistry.registerCommand(TOGGLE_DO_NOT_DISTURB_MODE_BY_SOURCE, accessor => {
281
const notificationService = accessor.get(INotificationService);
282
const quickInputService = accessor.get(IQuickInputService);
283
284
const sortedFilters = notificationService.getFilters().sort((a, b) => a.label.localeCompare(b.label));
285
286
const disposables = new DisposableStore();
287
const picker = disposables.add(quickInputService.createQuickPick<IQuickPickItem & INotificationSourceFilter>());
288
289
picker.items = sortedFilters.map(source => ({
290
id: source.id,
291
label: source.label,
292
tooltip: `${source.label} (${source.id})`,
293
filter: source.filter
294
}));
295
296
picker.canSelectMany = true;
297
picker.placeholder = localize('selectSources', "Select sources to enable all notifications from");
298
picker.selectedItems = picker.items.filter(item => item.filter === NotificationsFilter.OFF);
299
300
picker.show();
301
302
disposables.add(picker.onDidAccept(async () => {
303
for (const item of picker.items) {
304
notificationService.setFilter({
305
id: item.id,
306
label: item.label,
307
filter: picker.selectedItems.includes(item) ? NotificationsFilter.OFF : NotificationsFilter.ERROR
308
});
309
}
310
311
picker.hide();
312
}));
313
314
disposables.add(picker.onDidHide(() => disposables.dispose()));
315
});
316
317
// Commands for Command Palette
318
const category = localize2('notifications', 'Notifications');
319
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: localize2('showNotifications', 'Show Notifications'), category } });
320
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: localize2('hideNotifications', 'Hide Notifications'), category }, when: NotificationsCenterVisibleContext });
321
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: localize2('clearAllNotifications', 'Clear All Notifications'), category } });
322
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ACCEPT_PRIMARY_ACTION_NOTIFICATION, title: localize2('acceptNotificationPrimaryAction', 'Accept Notification Primary Action'), category } });
323
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE, title: localize2('toggleDoNotDisturbMode', 'Toggle Do Not Disturb Mode'), category } });
324
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE_BY_SOURCE, title: localize2('toggleDoNotDisturbModeBySource', 'Toggle Do Not Disturb Mode By Source...'), category } });
325
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: FOCUS_NOTIFICATION_TOAST, title: localize2('focusNotificationToasts', 'Focus Notification Toast'), category }, when: NotificationsToastsVisibleContext });
326
}
327
328
329
export class NotificationActionRunner extends ActionRunner {
330
331
constructor(
332
@ITelemetryService private readonly telemetryService: ITelemetryService,
333
@INotificationService private readonly notificationService: INotificationService
334
) {
335
super();
336
}
337
338
protected override async runAction(action: IAction, context: unknown): Promise<void> {
339
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: action.id, from: 'message' });
340
341
// Run and make sure to notify on any error again
342
try {
343
await super.runAction(action, context);
344
} catch (error) {
345
this.notificationService.error(error);
346
}
347
}
348
}
349
350