Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/workbench.ts
3294 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 './style.js';
7
import { runWhenWindowIdle } from '../../base/browser/dom.js';
8
import { Event, Emitter, setGlobalLeakWarningThreshold } from '../../base/common/event.js';
9
import { RunOnceScheduler, timeout } from '../../base/common/async.js';
10
import { isFirefox, isSafari, isChrome } from '../../base/browser/browser.js';
11
import { mark } from '../../base/common/performance.js';
12
import { onUnexpectedError, setUnexpectedErrorHandler } from '../../base/common/errors.js';
13
import { Registry } from '../../platform/registry/common/platform.js';
14
import { isWindows, isLinux, isWeb, isNative, isMacintosh } from '../../base/common/platform.js';
15
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../common/contributions.js';
16
import { IEditorFactoryRegistry, EditorExtensions } from '../common/editor.js';
17
import { getSingletonServiceDescriptors } from '../../platform/instantiation/common/extensions.js';
18
import { Position, Parts, IWorkbenchLayoutService, positionToString } from '../services/layout/browser/layoutService.js';
19
import { IStorageService, WillSaveStateReason, StorageScope, StorageTarget } from '../../platform/storage/common/storage.js';
20
import { IConfigurationChangeEvent, IConfigurationService } from '../../platform/configuration/common/configuration.js';
21
import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js';
22
import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js';
23
import { LifecyclePhase, ILifecycleService, WillShutdownEvent } from '../services/lifecycle/common/lifecycle.js';
24
import { INotificationService } from '../../platform/notification/common/notification.js';
25
import { NotificationService } from '../services/notification/common/notificationService.js';
26
import { NotificationsCenter } from './parts/notifications/notificationsCenter.js';
27
import { NotificationsAlerts } from './parts/notifications/notificationsAlerts.js';
28
import { NotificationsStatus } from './parts/notifications/notificationsStatus.js';
29
import { registerNotificationCommands } from './parts/notifications/notificationsCommands.js';
30
import { NotificationsToasts } from './parts/notifications/notificationsToasts.js';
31
import { setARIAContainer } from '../../base/browser/ui/aria/aria.js';
32
import { FontMeasurements } from '../../editor/browser/config/fontMeasurements.js';
33
import { BareFontInfo } from '../../editor/common/config/fontInfo.js';
34
import { ILogService } from '../../platform/log/common/log.js';
35
import { toErrorMessage } from '../../base/common/errorMessage.js';
36
import { WorkbenchContextKeysHandler } from './contextkeys.js';
37
import { coalesce } from '../../base/common/arrays.js';
38
import { InstantiationService } from '../../platform/instantiation/common/instantiationService.js';
39
import { Layout } from './layout.js';
40
import { IHostService } from '../services/host/browser/host.js';
41
import { IDialogService } from '../../platform/dialogs/common/dialogs.js';
42
import { mainWindow } from '../../base/browser/window.js';
43
import { PixelRatio } from '../../base/browser/pixelRatio.js';
44
import { IHoverService, WorkbenchHoverDelegate } from '../../platform/hover/browser/hover.js';
45
import { setHoverDelegateFactory } from '../../base/browser/ui/hover/hoverDelegateFactory.js';
46
import { setBaseLayerHoverDelegate } from '../../base/browser/ui/hover/hoverDelegate2.js';
47
import { AccessibilityProgressSignalScheduler } from '../../platform/accessibilitySignal/browser/progressAccessibilitySignalScheduler.js';
48
import { setProgressAcccessibilitySignalScheduler } from '../../base/browser/ui/progressbar/progressAccessibilitySignal.js';
49
import { AccessibleViewRegistry } from '../../platform/accessibility/browser/accessibleViewRegistry.js';
50
import { NotificationAccessibleView } from './parts/notifications/notificationAccessibleView.js';
51
52
export interface IWorkbenchOptions {
53
54
/**
55
* Extra classes to be added to the workbench container.
56
*/
57
extraClasses?: string[];
58
59
/**
60
* Whether to reset the workbench parts layout on startup.
61
*/
62
resetLayout?: boolean;
63
}
64
65
export class Workbench extends Layout {
66
67
private readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
68
readonly onWillShutdown = this._onWillShutdown.event;
69
70
private readonly _onDidShutdown = this._register(new Emitter<void>());
71
readonly onDidShutdown = this._onDidShutdown.event;
72
73
constructor(
74
parent: HTMLElement,
75
private readonly options: IWorkbenchOptions | undefined,
76
private readonly serviceCollection: ServiceCollection,
77
logService: ILogService
78
) {
79
super(parent, { resetLayout: Boolean(options?.resetLayout) });
80
81
// Perf: measure workbench startup time
82
mark('code/willStartWorkbench');
83
84
this.registerErrorHandler(logService);
85
}
86
87
private registerErrorHandler(logService: ILogService): void {
88
89
// Listen on unhandled rejection events
90
// Note: intentionally not registered as disposable to handle
91
// errors that can occur during shutdown phase.
92
mainWindow.addEventListener('unhandledrejection', (event) => {
93
94
// See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
95
onUnexpectedError(event.reason);
96
97
// Prevent the printing of this event to the console
98
event.preventDefault();
99
});
100
101
// Install handler for unexpected errors
102
setUnexpectedErrorHandler(error => this.handleUnexpectedError(error, logService));
103
}
104
105
private previousUnexpectedError: { message: string | undefined; time: number } = { message: undefined, time: 0 };
106
private handleUnexpectedError(error: unknown, logService: ILogService): void {
107
const message = toErrorMessage(error, true);
108
if (!message) {
109
return;
110
}
111
112
const now = Date.now();
113
if (message === this.previousUnexpectedError.message && now - this.previousUnexpectedError.time <= 1000) {
114
return; // Return if error message identical to previous and shorter than 1 second
115
}
116
117
this.previousUnexpectedError.time = now;
118
this.previousUnexpectedError.message = message;
119
120
// Log it
121
logService.error(message);
122
}
123
124
startup(): IInstantiationService {
125
try {
126
127
// Configure emitter leak warning threshold
128
this._register(setGlobalLeakWarningThreshold(175));
129
130
// Services
131
const instantiationService = this.initServices(this.serviceCollection);
132
133
instantiationService.invokeFunction(accessor => {
134
const lifecycleService = accessor.get(ILifecycleService);
135
const storageService = accessor.get(IStorageService);
136
const configurationService = accessor.get(IConfigurationService);
137
const hostService = accessor.get(IHostService);
138
const hoverService = accessor.get(IHoverService);
139
const dialogService = accessor.get(IDialogService);
140
const notificationService = accessor.get(INotificationService) as NotificationService;
141
142
// Default Hover Delegate must be registered before creating any workbench/layout components
143
// as these possibly will use the default hover delegate
144
setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, { instantHover: enableInstantHover }, {}));
145
setBaseLayerHoverDelegate(hoverService);
146
147
// Layout
148
this.initLayout(accessor);
149
150
// Registries
151
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).start(accessor);
152
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).start(accessor);
153
154
// Context Keys
155
this._register(instantiationService.createInstance(WorkbenchContextKeysHandler));
156
157
// Register Listeners
158
this.registerListeners(lifecycleService, storageService, configurationService, hostService, dialogService);
159
160
// Render Workbench
161
this.renderWorkbench(instantiationService, notificationService, storageService, configurationService);
162
163
// Workbench Layout
164
this.createWorkbenchLayout();
165
166
// Layout
167
this.layout();
168
169
// Restore
170
this.restore(lifecycleService);
171
});
172
173
return instantiationService;
174
} catch (error) {
175
onUnexpectedError(error);
176
177
throw error; // rethrow because this is a critical issue we cannot handle properly here
178
}
179
}
180
181
private initServices(serviceCollection: ServiceCollection): IInstantiationService {
182
183
// Layout Service
184
serviceCollection.set(IWorkbenchLayoutService, this);
185
186
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
187
//
188
// NOTE: Please do NOT register services here. Use `registerSingleton()`
189
// from `workbench.common.main.ts` if the service is shared between
190
// desktop and web or `workbench.desktop.main.ts` if the service
191
// is desktop only.
192
//
193
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
194
195
// All Contributed Services
196
const contributedServices = getSingletonServiceDescriptors();
197
for (const [id, descriptor] of contributedServices) {
198
serviceCollection.set(id, descriptor);
199
}
200
201
const instantiationService = new InstantiationService(serviceCollection, true);
202
203
// Wrap up
204
instantiationService.invokeFunction(accessor => {
205
const lifecycleService = accessor.get(ILifecycleService);
206
207
// TODO@Sandeep debt around cyclic dependencies
208
const configurationService = accessor.get(IConfigurationService) as any;
209
if (typeof configurationService.acquireInstantiationService === 'function') {
210
configurationService.acquireInstantiationService(instantiationService);
211
}
212
213
// Signal to lifecycle that services are set
214
lifecycleService.phase = LifecyclePhase.Ready;
215
});
216
217
return instantiationService;
218
}
219
220
private registerListeners(lifecycleService: ILifecycleService, storageService: IStorageService, configurationService: IConfigurationService, hostService: IHostService, dialogService: IDialogService): void {
221
222
// Configuration changes
223
this._register(configurationService.onDidChangeConfiguration(e => this.updateFontAliasing(e, configurationService)));
224
225
// Font Info
226
if (isNative) {
227
this._register(storageService.onWillSaveState(e => {
228
if (e.reason === WillSaveStateReason.SHUTDOWN) {
229
this.storeFontInfo(storageService);
230
}
231
}));
232
} else {
233
this._register(lifecycleService.onWillShutdown(() => this.storeFontInfo(storageService)));
234
}
235
236
// Lifecycle
237
this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event)));
238
this._register(lifecycleService.onDidShutdown(() => {
239
this._onDidShutdown.fire();
240
this.dispose();
241
}));
242
243
// In some environments we do not get enough time to persist state on shutdown.
244
// In other cases, VSCode might crash, so we periodically save state to reduce
245
// the chance of loosing any state.
246
// The window loosing focus is a good indication that the user has stopped working
247
// in that window so we pick that at a time to collect state.
248
this._register(hostService.onDidChangeFocus(focus => {
249
if (!focus) {
250
storageService.flush();
251
}
252
}));
253
254
// Dialogs showing/hiding
255
this._register(dialogService.onWillShowDialog(() => this.mainContainer.classList.add('modal-dialog-visible')));
256
this._register(dialogService.onDidShowDialog(() => this.mainContainer.classList.remove('modal-dialog-visible')));
257
}
258
259
private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined;
260
private updateFontAliasing(e: IConfigurationChangeEvent | undefined, configurationService: IConfigurationService) {
261
if (!isMacintosh) {
262
return; // macOS only
263
}
264
265
if (e && !e.affectsConfiguration('workbench.fontAliasing')) {
266
return;
267
}
268
269
const aliasing = configurationService.getValue<'default' | 'antialiased' | 'none' | 'auto'>('workbench.fontAliasing');
270
if (this.fontAliasing === aliasing) {
271
return;
272
}
273
274
this.fontAliasing = aliasing;
275
276
// Remove all
277
const fontAliasingValues: (typeof aliasing)[] = ['antialiased', 'none', 'auto'];
278
this.mainContainer.classList.remove(...fontAliasingValues.map(value => `monaco-font-aliasing-${value}`));
279
280
// Add specific
281
if (fontAliasingValues.some(option => option === aliasing)) {
282
this.mainContainer.classList.add(`monaco-font-aliasing-${aliasing}`);
283
}
284
}
285
286
private restoreFontInfo(storageService: IStorageService, configurationService: IConfigurationService): void {
287
const storedFontInfoRaw = storageService.get('editorFontInfo', StorageScope.APPLICATION);
288
if (storedFontInfoRaw) {
289
try {
290
const storedFontInfo = JSON.parse(storedFontInfoRaw);
291
if (Array.isArray(storedFontInfo)) {
292
FontMeasurements.restoreFontInfo(mainWindow, storedFontInfo);
293
}
294
} catch (err) {
295
/* ignore */
296
}
297
}
298
299
FontMeasurements.readFontInfo(mainWindow, BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), PixelRatio.getInstance(mainWindow).value));
300
}
301
302
private storeFontInfo(storageService: IStorageService): void {
303
const serializedFontInfo = FontMeasurements.serializeFontInfo(mainWindow);
304
if (serializedFontInfo) {
305
storageService.store('editorFontInfo', JSON.stringify(serializedFontInfo), StorageScope.APPLICATION, StorageTarget.MACHINE);
306
}
307
}
308
309
private renderWorkbench(instantiationService: IInstantiationService, notificationService: NotificationService, storageService: IStorageService, configurationService: IConfigurationService): void {
310
311
// ARIA & Signals
312
setARIAContainer(this.mainContainer);
313
setProgressAcccessibilitySignalScheduler((msDelayTime: number, msLoopTime?: number) => instantiationService.createInstance(AccessibilityProgressSignalScheduler, msDelayTime, msLoopTime));
314
315
// State specific classes
316
const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac';
317
const workbenchClasses = coalesce([
318
'monaco-workbench',
319
platformClass,
320
isWeb ? 'web' : undefined,
321
isChrome ? 'chromium' : isFirefox ? 'firefox' : isSafari ? 'safari' : undefined,
322
...this.getLayoutClasses(),
323
...(this.options?.extraClasses ? this.options.extraClasses : [])
324
]);
325
326
this.mainContainer.classList.add(...workbenchClasses);
327
328
// Apply font aliasing
329
this.updateFontAliasing(undefined, configurationService);
330
331
// Warm up font cache information before building up too many dom elements
332
this.restoreFontInfo(storageService, configurationService);
333
334
// Create Parts
335
for (const { id, role, classes, options } of [
336
{ id: Parts.TITLEBAR_PART, role: 'none', classes: ['titlebar'] },
337
{ id: Parts.BANNER_PART, role: 'banner', classes: ['banner'] },
338
{ id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892
339
{ id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] },
340
{ id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.willRestoreEditors() } },
341
{ id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] },
342
{ id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] },
343
{ id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] }
344
]) {
345
const partContainer = this.createPart(id, role, classes);
346
347
mark(`code/willCreatePart/${id}`);
348
this.getPart(id).create(partContainer, options);
349
mark(`code/didCreatePart/${id}`);
350
}
351
352
// Notification Handlers
353
this.createNotificationsHandlers(instantiationService, notificationService);
354
355
// Add Workbench to DOM
356
this.parent.appendChild(this.mainContainer);
357
}
358
359
private createPart(id: string, role: string, classes: string[]): HTMLElement {
360
const part = document.createElement(role === 'status' ? 'footer' /* Use footer element for status bar #98376 */ : 'div');
361
part.classList.add('part', ...classes);
362
part.id = id;
363
part.setAttribute('role', role);
364
if (role === 'status') {
365
part.setAttribute('aria-live', 'off');
366
}
367
368
return part;
369
}
370
371
private createNotificationsHandlers(instantiationService: IInstantiationService, notificationService: NotificationService): void {
372
373
// Instantiate Notification components
374
const notificationsCenter = this._register(instantiationService.createInstance(NotificationsCenter, this.mainContainer, notificationService.model));
375
const notificationsToasts = this._register(instantiationService.createInstance(NotificationsToasts, this.mainContainer, notificationService.model));
376
this._register(instantiationService.createInstance(NotificationsAlerts, notificationService.model));
377
const notificationsStatus = instantiationService.createInstance(NotificationsStatus, notificationService.model);
378
379
// Visibility
380
this._register(notificationsCenter.onDidChangeVisibility(() => {
381
notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible);
382
notificationsToasts.update(notificationsCenter.isVisible);
383
}));
384
385
this._register(notificationsToasts.onDidChangeVisibility(() => {
386
notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible);
387
}));
388
389
// Register Commands
390
registerNotificationCommands(notificationsCenter, notificationsToasts, notificationService.model);
391
392
// Register notification accessible view
393
AccessibleViewRegistry.register(new NotificationAccessibleView());
394
395
// Register with Layout
396
this.registerNotifications({
397
onDidChangeNotificationsVisibility: Event.map(Event.any(notificationsToasts.onDidChangeVisibility, notificationsCenter.onDidChangeVisibility), () => notificationsToasts.isVisible || notificationsCenter.isVisible)
398
});
399
}
400
401
private restore(lifecycleService: ILifecycleService): void {
402
403
// Ask each part to restore
404
try {
405
this.restoreParts();
406
} catch (error) {
407
onUnexpectedError(error);
408
}
409
410
// Transition into restored phase after layout has restored
411
// but do not wait indefinitely on this to account for slow
412
// editors restoring. Since the workbench is fully functional
413
// even when the visible editors have not resolved, we still
414
// want contributions on the `Restored` phase to work before
415
// slow editors have resolved. But we also do not want fast
416
// editors to resolve slow when too many contributions get
417
// instantiated, so we find a middle ground solution via
418
// `Promise.race`
419
this.whenReady.finally(() =>
420
Promise.race([
421
this.whenRestored,
422
timeout(2000)
423
]).finally(() => {
424
425
// Update perf marks only when the layout is fully
426
// restored. We want the time it takes to restore
427
// editors to be included in these numbers
428
429
function markDidStartWorkbench() {
430
mark('code/didStartWorkbench');
431
performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench');
432
}
433
434
if (this.isRestored()) {
435
markDidStartWorkbench();
436
} else {
437
this.whenRestored.finally(() => markDidStartWorkbench());
438
}
439
440
// Set lifecycle phase to `Restored`
441
lifecycleService.phase = LifecyclePhase.Restored;
442
443
// Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec)
444
const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => {
445
this._register(runWhenWindowIdle(mainWindow, () => lifecycleService.phase = LifecyclePhase.Eventually, 2500));
446
}, 2500));
447
eventuallyPhaseScheduler.schedule();
448
})
449
);
450
}
451
}
452
453