@theia/core 1.71.0-next.6 → 1.71.0-next.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/i18n/nls.cs.json +4 -4
- package/i18n/nls.de.json +4 -4
- package/i18n/nls.es.json +4 -4
- package/i18n/nls.fr.json +4 -4
- package/i18n/nls.hu.json +4 -4
- package/i18n/nls.it.json +4 -4
- package/i18n/nls.ja.json +4 -4
- package/i18n/nls.ko.json +4 -4
- package/i18n/nls.pl.json +4 -4
- package/i18n/nls.pt-br.json +4 -4
- package/i18n/nls.ru.json +4 -4
- package/i18n/nls.tr.json +4 -4
- package/i18n/nls.zh-cn.json +4 -4
- package/i18n/nls.zh-tw.json +4 -4
- package/lib/browser/catalog.json +149 -8
- package/lib/browser/common-frontend-contribution.js +2 -2
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/components/card.d.ts.map +1 -1
- package/lib/browser/components/card.js +11 -3
- package/lib/browser/components/card.js.map +1 -1
- package/lib/browser/connection-status-service.js +1 -1
- package/lib/browser/connection-status-service.js.map +1 -1
- package/lib/browser/keyboard/index.d.ts +1 -0
- package/lib/browser/keyboard/index.d.ts.map +1 -1
- package/lib/browser/keyboard/index.js +1 -0
- package/lib/browser/keyboard/index.js.map +1 -1
- package/lib/browser/keyboard/keyboard-utils.d.ts +17 -0
- package/lib/browser/keyboard/keyboard-utils.d.ts.map +1 -0
- package/lib/browser/keyboard/keyboard-utils.js +40 -0
- package/lib/browser/keyboard/keyboard-utils.js.map +1 -0
- package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
- package/lib/browser/menu/browser-menu-plugin.js +8 -1
- package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
- package/lib/browser/messaging/messaging-frontend-module.d.ts.map +1 -1
- package/lib/browser/messaging/messaging-frontend-module.js +3 -0
- package/lib/browser/messaging/messaging-frontend-module.js.map +1 -1
- package/lib/browser/messaging/ws-connection-source.d.ts +2 -1
- package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
- package/lib/browser/messaging/ws-connection-source.js +5 -2
- package/lib/browser/messaging/ws-connection-source.js.map +1 -1
- package/lib/browser/saveable-service.d.ts +9 -2
- package/lib/browser/saveable-service.d.ts.map +1 -1
- package/lib/browser/saveable-service.js +34 -25
- package/lib/browser/saveable-service.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts +1 -0
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js +10 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts +2 -0
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js +11 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-toolbar-item.js.map +1 -1
- package/lib/browser/window/browser-window-module.d.ts.map +1 -1
- package/lib/browser/window/browser-window-module.js +2 -0
- package/lib/browser/window/browser-window-module.js.map +1 -1
- package/lib/browser/window/default-secondary-window-service.d.ts +2 -0
- package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-secondary-window-service.js +7 -0
- package/lib/browser/window/default-secondary-window-service.js.map +1 -1
- package/lib/browser/window/window-focus-service.d.ts +71 -0
- package/lib/browser/window/window-focus-service.d.ts.map +1 -0
- package/lib/browser/window/window-focus-service.js +173 -0
- package/lib/browser/window/window-focus-service.js.map +1 -0
- package/lib/common/event.d.ts +16 -0
- package/lib/common/event.d.ts.map +1 -1
- package/lib/common/event.js +20 -2
- package/lib/common/event.js.map +1 -1
- package/lib/common/event.spec.js +63 -0
- package/lib/common/event.spec.js.map +1 -1
- package/lib/common/glob.d.ts +2 -0
- package/lib/common/glob.d.ts.map +1 -1
- package/lib/common/glob.js +8 -7
- package/lib/common/glob.js.map +1 -1
- package/lib/common/message-rpc/channel.d.ts +1 -1
- package/lib/common/message-rpc/channel.d.ts.map +1 -1
- package/lib/common/message-rpc/channel.js +20 -12
- package/lib/common/message-rpc/channel.js.map +1 -1
- package/lib/common/message-rpc/channel.spec.d.ts.map +1 -1
- package/lib/common/message-rpc/channel.spec.js +94 -0
- package/lib/common/message-rpc/channel.spec.js.map +1 -1
- package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -1
- package/lib/common/message-rpc/rpc-protocol.js +13 -3
- package/lib/common/message-rpc/rpc-protocol.js.map +1 -1
- package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -1
- package/lib/common/message-rpc/uint8-array-message-buffer.js +1 -1
- package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -1
- package/lib/common/messaging/index.d.ts +1 -0
- package/lib/common/messaging/index.d.ts.map +1 -1
- package/lib/common/messaging/index.js +1 -0
- package/lib/common/messaging/index.js.map +1 -1
- package/lib/common/messaging/socket-write-buffer.d.ts +4 -3
- package/lib/common/messaging/socket-write-buffer.d.ts.map +1 -1
- package/lib/common/messaging/socket-write-buffer.js +14 -4
- package/lib/common/messaging/socket-write-buffer.js.map +1 -1
- package/lib/common/preferences/index.d.ts +1 -0
- package/lib/common/preferences/index.d.ts.map +1 -1
- package/lib/common/preferences/index.js +1 -0
- package/lib/common/preferences/index.js.map +1 -1
- package/lib/common/preferences/preference-utils.d.ts +6 -0
- package/lib/common/preferences/preference-utils.d.ts.map +1 -0
- package/lib/common/preferences/preference-utils.js +29 -0
- package/lib/common/preferences/preference-utils.js.map +1 -0
- package/lib/common/resource.d.ts +2 -0
- package/lib/common/resource.d.ts.map +1 -1
- package/lib/common/resource.js +7 -3
- package/lib/common/resource.js.map +1 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.d.ts.map +1 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.js +5 -1
- package/lib/electron-browser/menu/electron-main-menu-factory.js.map +1 -1
- package/lib/electron-browser/messaging/electron-messaging-frontend-module.d.ts.map +1 -1
- package/lib/electron-browser/messaging/electron-messaging-frontend-module.js +3 -0
- package/lib/electron-browser/messaging/electron-messaging-frontend-module.js.map +1 -1
- package/lib/electron-browser/window/electron-window-module.d.ts.map +1 -1
- package/lib/electron-browser/window/electron-window-module.js +2 -0
- package/lib/electron-browser/window/electron-window-module.js.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +4 -2
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
- package/lib/electron-main/theia-electron-window.js +3 -0
- package/lib/electron-main/theia-electron-window.js.map +1 -1
- package/lib/node/messaging/default-messaging-service.d.ts.map +1 -1
- package/lib/node/messaging/default-messaging-service.js +1 -0
- package/lib/node/messaging/default-messaging-service.js.map +1 -1
- package/lib/node/messaging/index.d.ts +1 -0
- package/lib/node/messaging/index.d.ts.map +1 -1
- package/lib/node/messaging/index.js +1 -0
- package/lib/node/messaging/index.js.map +1 -1
- package/lib/node/messaging/messaging-backend-module.d.ts.map +1 -1
- package/lib/node/messaging/messaging-backend-module.js +4 -0
- package/lib/node/messaging/messaging-backend-module.js.map +1 -1
- package/lib/node/messaging/test/default-messaging-service.spec.d.ts +2 -0
- package/lib/node/messaging/test/default-messaging-service.spec.d.ts.map +1 -0
- package/lib/node/messaging/test/default-messaging-service.spec.js +81 -0
- package/lib/node/messaging/test/default-messaging-service.spec.js.map +1 -0
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts +9 -5
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.js +21 -5
- package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
- package/lib/node/process-utils.d.ts.map +1 -1
- package/lib/node/process-utils.js +9 -1
- package/lib/node/process-utils.js.map +1 -1
- package/package.json +32 -32
- package/src/browser/common-frontend-contribution.ts +2 -2
- package/src/browser/components/card.tsx +13 -2
- package/src/browser/connection-status-service.ts +1 -1
- package/src/browser/keyboard/index.ts +1 -0
- package/src/browser/keyboard/keyboard-utils.ts +37 -0
- package/src/browser/menu/browser-menu-plugin.ts +8 -1
- package/src/browser/messaging/messaging-frontend-module.ts +3 -0
- package/src/browser/messaging/ws-connection-source.ts +3 -2
- package/src/browser/saveable-service.ts +34 -27
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +14 -1
- package/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +13 -2
- package/src/browser/style/card.css +4 -2
- package/src/browser/style/hover-service.css +7 -0
- package/src/browser/window/browser-window-module.ts +2 -0
- package/src/browser/window/default-secondary-window-service.ts +6 -0
- package/src/browser/window/window-focus-service.ts +187 -0
- package/src/common/event.spec.ts +80 -0
- package/src/common/event.ts +31 -2
- package/src/common/glob.ts +2 -2
- package/src/common/i18n/nls.metadata.json +4254 -1058
- package/src/common/message-rpc/channel.spec.ts +116 -0
- package/src/common/message-rpc/channel.ts +15 -11
- package/src/common/message-rpc/rpc-protocol.ts +12 -3
- package/src/common/message-rpc/uint8-array-message-buffer.ts +1 -1
- package/src/common/messaging/index.ts +1 -0
- package/src/common/messaging/socket-write-buffer.ts +10 -4
- package/src/common/preferences/index.ts +1 -0
- package/src/common/preferences/preference-utils.ts +28 -0
- package/src/common/resource.ts +8 -2
- package/src/electron-browser/menu/electron-main-menu-factory.ts +5 -1
- package/src/electron-browser/messaging/electron-messaging-frontend-module.ts +3 -0
- package/src/electron-browser/window/electron-window-module.ts +2 -0
- package/src/electron-main/electron-api-main.ts +4 -2
- package/src/electron-main/theia-electron-window.ts +3 -0
- package/src/node/messaging/default-messaging-service.ts +1 -0
- package/src/node/messaging/index.ts +1 -0
- package/src/node/messaging/messaging-backend-module.ts +5 -1
- package/src/node/messaging/test/default-messaging-service.spec.ts +85 -0
- package/src/node/messaging/websocket-frontend-connection-service.ts +20 -7
- package/src/node/process-utils.ts +9 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
// based on https://github.com/microsoft/vscode/blob/ea6aac971b851ff8675f9ea04f8c0dfc36034a89/src/vs/workbench/services/host/browser/browserHostService.ts
|
|
18
|
+
// and https://github.com/microsoft/vscode/blob/ea6aac971b851ff8675f9ea04f8c0dfc36034a89/src/vs/base/browser/dom.ts#L1319 (FocusTracker)
|
|
19
|
+
/*---------------------------------------------------------------------------------------------
|
|
20
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
21
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
22
|
+
*--------------------------------------------------------------------------------------------*/
|
|
23
|
+
|
|
24
|
+
import { injectable, postConstruct } from 'inversify';
|
|
25
|
+
import { Disposable, DisposableCollection, Emitter, Event } from '../../common';
|
|
26
|
+
|
|
27
|
+
export interface WindowFocusEvent {
|
|
28
|
+
win: Window;
|
|
29
|
+
hasFocus: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tracks focus state for each registered application window independently.
|
|
34
|
+
*
|
|
35
|
+
* The main window is registered automatically. Secondary windows should be
|
|
36
|
+
* registered via {@link registerWindow} — typically by the service responsible
|
|
37
|
+
* for creating them.
|
|
38
|
+
*
|
|
39
|
+
* Uses two complementary signals per window for robust detection:
|
|
40
|
+
* - `window` `focus`/`blur` events (OS-level window focus changes)
|
|
41
|
+
* - `document` `visibilitychange` events (browser tab switches)
|
|
42
|
+
*
|
|
43
|
+
* Blur events are debounced per window with `setTimeout(0)` so that focus
|
|
44
|
+
* moving between elements within the same window does not produce a false
|
|
45
|
+
* blur: the subsequent `focus` event cancels the pending blur before it fires.
|
|
46
|
+
*
|
|
47
|
+
* Each per-window event is latched — it only fires when that window's focus
|
|
48
|
+
* state actually changes.
|
|
49
|
+
*/
|
|
50
|
+
@injectable()
|
|
51
|
+
export class WindowFocusService implements Disposable {
|
|
52
|
+
|
|
53
|
+
protected readonly onDidWindowChangeFocusEmitter = new Emitter<WindowFocusEvent>();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Fires when an individual window's focus state changes.
|
|
57
|
+
* The event payload identifies which window changed and whether it gained or lost focus.
|
|
58
|
+
*
|
|
59
|
+
* A window losing focus to another application window will fire separately
|
|
60
|
+
* for each window involved: one event with `hasFocus: false` for the window
|
|
61
|
+
* that lost focus, and one with `hasFocus: true` for the window that gained it.
|
|
62
|
+
*/
|
|
63
|
+
readonly onDidWindowChangeFocus: Event<WindowFocusEvent> = this.onDidWindowChangeFocusEmitter.event;
|
|
64
|
+
|
|
65
|
+
protected readonly toDispose = new DisposableCollection(this.onDidWindowChangeFocusEmitter);
|
|
66
|
+
|
|
67
|
+
/** Per-window tracking state. */
|
|
68
|
+
protected readonly windowStates = new Map<Window, WindowTrackingState>();
|
|
69
|
+
|
|
70
|
+
@postConstruct()
|
|
71
|
+
protected init(): void {
|
|
72
|
+
this.registerWindow(window);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Register a window for focus tracking. The returned {@link Disposable}
|
|
77
|
+
* removes the window from tracking when disposed.
|
|
78
|
+
*
|
|
79
|
+
* The main window is registered automatically. Call this for secondary
|
|
80
|
+
* windows when they are created.
|
|
81
|
+
*/
|
|
82
|
+
registerWindow(win: Window): Disposable {
|
|
83
|
+
if (this.windowStates.has(win)) {
|
|
84
|
+
return Disposable.NULL;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const state: WindowTrackingState = {
|
|
88
|
+
lastFocusState: this.windowHasFocus(win),
|
|
89
|
+
pendingBlurTimeout: undefined,
|
|
90
|
+
};
|
|
91
|
+
this.windowStates.set(win, state);
|
|
92
|
+
|
|
93
|
+
const onFocus = () => this.handleFocus(win, state);
|
|
94
|
+
const onBlur = () => this.scheduleBlur(win, state);
|
|
95
|
+
const onVisibilityChange = () => this.updateWindowFocusState(win, state);
|
|
96
|
+
|
|
97
|
+
win.addEventListener('focus', onFocus);
|
|
98
|
+
win.addEventListener('blur', onBlur);
|
|
99
|
+
win.document.addEventListener('visibilitychange', onVisibilityChange);
|
|
100
|
+
|
|
101
|
+
const cleanup = Disposable.create(() => {
|
|
102
|
+
this.cancelPendingBlur(state);
|
|
103
|
+
this.windowStates.delete(win);
|
|
104
|
+
win.removeEventListener('focus', onFocus);
|
|
105
|
+
win.removeEventListener('blur', onBlur);
|
|
106
|
+
try {
|
|
107
|
+
win.document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
108
|
+
} catch {
|
|
109
|
+
// The window may already be closed
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.toDispose.push(cleanup);
|
|
114
|
+
return cleanup;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether any registered window currently has focus.
|
|
119
|
+
*/
|
|
120
|
+
get hasFocus(): boolean {
|
|
121
|
+
for (const win of this.windowStates.keys()) {
|
|
122
|
+
if (this.windowHasFocus(win)) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
protected windowHasFocus(win: Window): boolean {
|
|
130
|
+
try {
|
|
131
|
+
return win.document.hasFocus();
|
|
132
|
+
} catch {
|
|
133
|
+
// The window may have been closed
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
protected handleFocus(win: Window, state: WindowTrackingState): void {
|
|
139
|
+
// Cancel this window's pending blur — focus arrived before the timeout,
|
|
140
|
+
// meaning focus just moved between elements within this window.
|
|
141
|
+
this.cancelPendingBlur(state);
|
|
142
|
+
this.updateWindowFocusState(win, state);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Schedule a deferred blur check for this specific window.
|
|
147
|
+
* If no `focus` event arrives on this same window before the timeout fires,
|
|
148
|
+
* we treat it as a real focus loss for that window.
|
|
149
|
+
*/
|
|
150
|
+
protected scheduleBlur(win: Window, state: WindowTrackingState): void {
|
|
151
|
+
this.cancelPendingBlur(state);
|
|
152
|
+
state.pendingBlurTimeout = setTimeout(() => {
|
|
153
|
+
state.pendingBlurTimeout = undefined;
|
|
154
|
+
this.updateWindowFocusState(win, state);
|
|
155
|
+
}, 0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected cancelPendingBlur(state: WindowTrackingState): void {
|
|
159
|
+
if (state.pendingBlurTimeout !== undefined) {
|
|
160
|
+
clearTimeout(state.pendingBlurTimeout);
|
|
161
|
+
state.pendingBlurTimeout = undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Re-evaluate a single window's focus and fire if it changed (latching).
|
|
167
|
+
*/
|
|
168
|
+
protected updateWindowFocusState(win: Window, state: WindowTrackingState): void {
|
|
169
|
+
const focused = this.windowHasFocus(win);
|
|
170
|
+
if (focused !== state.lastFocusState) {
|
|
171
|
+
state.lastFocusState = focused;
|
|
172
|
+
this.onDidWindowChangeFocusEmitter.fire({ win, hasFocus: focused });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
dispose(): void {
|
|
177
|
+
for (const state of this.windowStates.values()) {
|
|
178
|
+
this.cancelPendingBlur(state);
|
|
179
|
+
}
|
|
180
|
+
this.toDispose.dispose();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
interface WindowTrackingState {
|
|
185
|
+
lastFocusState: boolean;
|
|
186
|
+
pendingBlurTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
187
|
+
}
|
package/src/common/event.spec.ts
CHANGED
|
@@ -29,4 +29,84 @@ describe('Event Objects', () => {
|
|
|
29
29
|
expect(counter).eq(1);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
describe('Emitter errorHandling option', () => {
|
|
33
|
+
|
|
34
|
+
it('should log errors by default', () => {
|
|
35
|
+
const emitter = new Emitter<void>();
|
|
36
|
+
const errors: unknown[] = [];
|
|
37
|
+
const originalError = console.error;
|
|
38
|
+
console.error = (e: unknown) => errors.push(e);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
emitter.event(() => { throw new Error('test error'); });
|
|
42
|
+
emitter.fire(undefined);
|
|
43
|
+
|
|
44
|
+
expect(errors).to.have.lengthOf(1);
|
|
45
|
+
expect((errors[0] as Error).message).to.equal('test error');
|
|
46
|
+
} finally {
|
|
47
|
+
console.error = originalError;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should propagate a single error when errorHandling is propagate', () => {
|
|
52
|
+
const emitter = new Emitter<void>({ errorHandling: 'propagate' });
|
|
53
|
+
|
|
54
|
+
emitter.event(() => { throw new Error('boom'); });
|
|
55
|
+
|
|
56
|
+
expect(() => emitter.fire(undefined)).to.throw('boom');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should call all listeners before propagating the error', () => {
|
|
60
|
+
const emitter = new Emitter<void>({ errorHandling: 'propagate' });
|
|
61
|
+
let secondCalled = false;
|
|
62
|
+
|
|
63
|
+
emitter.event(() => { throw new Error('first fails'); });
|
|
64
|
+
emitter.event(() => { secondCalled = true; });
|
|
65
|
+
|
|
66
|
+
expect(() => emitter.fire(undefined)).to.throw('first fails');
|
|
67
|
+
expect(secondCalled).to.be.true;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should throw AggregateError when multiple listeners fail with propagate', () => {
|
|
71
|
+
const emitter = new Emitter<void>({ errorHandling: 'propagate' });
|
|
72
|
+
|
|
73
|
+
emitter.event(() => { throw new Error('error 1'); });
|
|
74
|
+
emitter.event(() => { throw new Error('error 2'); });
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
emitter.fire(undefined);
|
|
78
|
+
expect.fail('Expected an error to be thrown');
|
|
79
|
+
} catch (err) {
|
|
80
|
+
expect(err).to.be.instanceOf(AggregateError);
|
|
81
|
+
const aggregate = err as AggregateError;
|
|
82
|
+
expect(aggregate.errors).to.have.lengthOf(2);
|
|
83
|
+
expect(aggregate.errors[0].message).to.equal('error 1');
|
|
84
|
+
expect(aggregate.errors[1].message).to.equal('error 2');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should invoke custom error handler for each error', () => {
|
|
89
|
+
const errors: unknown[] = [];
|
|
90
|
+
const emitter = new Emitter<void>({ errorHandling: e => errors.push(e) });
|
|
91
|
+
|
|
92
|
+
emitter.event(() => { throw new Error('handled 1'); });
|
|
93
|
+
emitter.event(() => { throw new Error('handled 2'); });
|
|
94
|
+
emitter.fire(undefined);
|
|
95
|
+
|
|
96
|
+
expect(errors).to.have.lengthOf(2);
|
|
97
|
+
expect((errors[0] as Error).message).to.equal('handled 1');
|
|
98
|
+
expect((errors[1] as Error).message).to.equal('handled 2');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should not throw when no listeners fail with propagate', () => {
|
|
102
|
+
const emitter = new Emitter<void>({ errorHandling: 'propagate' });
|
|
103
|
+
let called = false;
|
|
104
|
+
|
|
105
|
+
emitter.event(() => { called = true; });
|
|
106
|
+
emitter.fire(undefined);
|
|
107
|
+
|
|
108
|
+
expect(called).to.be.true;
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
32
112
|
});
|
package/src/common/event.ts
CHANGED
|
@@ -123,6 +123,8 @@ class CallbackList implements Iterable<Callback> {
|
|
|
123
123
|
private _callbacks: Function[] | undefined;
|
|
124
124
|
private _contexts: any[] | undefined;
|
|
125
125
|
|
|
126
|
+
constructor(private readonly errorHandling: ErrorHandlingStrategy = 'log') {}
|
|
127
|
+
|
|
126
128
|
get length(): number {
|
|
127
129
|
return this._callbacks && this._callbacks.length || 0;
|
|
128
130
|
}
|
|
@@ -179,13 +181,25 @@ class CallbackList implements Iterable<Callback> {
|
|
|
179
181
|
|
|
180
182
|
public invoke(...args: any[]): any[] {
|
|
181
183
|
const ret: any[] = [];
|
|
184
|
+
const errors: unknown[] = [];
|
|
182
185
|
for (const callback of this) {
|
|
183
186
|
try {
|
|
184
187
|
ret.push(callback(...args));
|
|
185
188
|
} catch (e) {
|
|
186
|
-
|
|
189
|
+
if (this.errorHandling === 'propagate') {
|
|
190
|
+
errors.push(e);
|
|
191
|
+
} else if (typeof this.errorHandling === 'function') {
|
|
192
|
+
this.errorHandling(e);
|
|
193
|
+
} else {
|
|
194
|
+
console.error(e);
|
|
195
|
+
}
|
|
187
196
|
}
|
|
188
197
|
}
|
|
198
|
+
if (errors.length === 1) {
|
|
199
|
+
throw errors[0];
|
|
200
|
+
} else if (errors.length > 1) {
|
|
201
|
+
throw new AggregateError(errors, 'Multiple event listeners failed');
|
|
202
|
+
}
|
|
189
203
|
return ret;
|
|
190
204
|
}
|
|
191
205
|
|
|
@@ -199,9 +213,24 @@ class CallbackList implements Iterable<Callback> {
|
|
|
199
213
|
}
|
|
200
214
|
}
|
|
201
215
|
|
|
216
|
+
/**
|
|
217
|
+
* A strategy for handling errors in firing an emitter's event, one of
|
|
218
|
+
*
|
|
219
|
+
* - `'log'` (default): errors are caught and logged via `console.error`
|
|
220
|
+
* - `'propagate'`: all listeners are called; if any throw, the errors are collected
|
|
221
|
+
* and re-thrown after all listeners complete (single error re-thrown directly,
|
|
222
|
+
* multiple errors wrapped in an `AggregateError`)
|
|
223
|
+
* - `(error: unknown) => void`: a custom callback invoked for each error
|
|
224
|
+
*/
|
|
225
|
+
export type ErrorHandlingStrategy = 'log' | 'propagate' | ((error: unknown) => void);
|
|
226
|
+
|
|
202
227
|
export interface EmitterOptions {
|
|
203
228
|
onFirstListenerAdd?: Function;
|
|
204
229
|
onLastListenerRemove?: Function;
|
|
230
|
+
/**
|
|
231
|
+
* How errors thrown by event listeners are handled during {@link Emitter.fire}.
|
|
232
|
+
*/
|
|
233
|
+
errorHandling?: ErrorHandlingStrategy;
|
|
205
234
|
}
|
|
206
235
|
|
|
207
236
|
export class Emitter<T = any> {
|
|
@@ -229,7 +258,7 @@ export class Emitter<T = any> {
|
|
|
229
258
|
if (!this._event) {
|
|
230
259
|
this._event = Object.assign((listener: (e: T) => any, thisArgs?: any, disposables?: DisposableGroup) => {
|
|
231
260
|
if (!this._callbacks) {
|
|
232
|
-
this._callbacks = new CallbackList();
|
|
261
|
+
this._callbacks = new CallbackList(this._options?.errorHandling);
|
|
233
262
|
}
|
|
234
263
|
if (this._options && this._options.onFirstListenerAdd && this._callbacks.isEmpty()) {
|
|
235
264
|
this._options.onFirstListenerAdd(this);
|
package/src/common/glob.ts
CHANGED
|
@@ -44,8 +44,8 @@ export interface SiblingClause {
|
|
|
44
44
|
when: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const GLOBSTAR = '**';
|
|
48
|
-
const GLOB_SPLIT = '/';
|
|
47
|
+
export const GLOBSTAR = '**';
|
|
48
|
+
export const GLOB_SPLIT = '/';
|
|
49
49
|
const PATH_REGEX = '[/\\\\]'; // any slash or backslash
|
|
50
50
|
const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash
|
|
51
51
|
const ALL_FORWARD_SLASHES = /\//g;
|