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