@theia/core 1.75.0-next.3 → 1.75.0-next.34
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 +15 -15
- package/lib/browser/authentication-service.js +1 -1
- package/lib/browser/authentication-service.js.map +1 -1
- package/lib/browser/browser-clipboard-service.js +1 -0
- package/lib/browser/browser-clipboard-service.js.map +1 -1
- package/lib/browser/catalog.json +130 -38
- package/lib/browser/common-frontend-contribution.d.ts +3 -0
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +15 -3
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/common-frontend-contribution.spec.d.ts +2 -0
- package/lib/browser/common-frontend-contribution.spec.d.ts.map +1 -0
- package/lib/browser/common-frontend-contribution.spec.js +71 -0
- package/lib/browser/common-frontend-contribution.spec.js.map +1 -0
- package/lib/browser/connection-status-service.d.ts +1 -0
- package/lib/browser/connection-status-service.d.ts.map +1 -1
- package/lib/browser/connection-status-service.js +7 -0
- package/lib/browser/connection-status-service.js.map +1 -1
- package/lib/browser/frontend-application-contribution.d.ts +1 -1
- package/lib/browser/frontend-application-state.js +1 -0
- package/lib/browser/frontend-application-state.js.map +1 -1
- package/lib/browser/icon-theme-service.d.ts +5 -3
- package/lib/browser/icon-theme-service.d.ts.map +1 -1
- package/lib/browser/icon-theme-service.js +7 -1
- package/lib/browser/icon-theme-service.js.map +1 -1
- package/lib/browser/keyboard/browser-keyboard-layout-provider.js +1 -0
- package/lib/browser/keyboard/browser-keyboard-layout-provider.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown-link-handler.d.ts +11 -0
- package/lib/browser/markdown-rendering/markdown-link-handler.d.ts.map +1 -1
- package/lib/browser/markdown-rendering/markdown-link-handler.js +23 -1
- package/lib/browser/markdown-rendering/markdown-link-handler.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown-renderer.d.ts +11 -0
- package/lib/browser/markdown-rendering/markdown-renderer.d.ts.map +1 -1
- package/lib/browser/markdown-rendering/markdown-renderer.js +41 -0
- package/lib/browser/markdown-rendering/markdown-renderer.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown-renderer.spec.d.ts +2 -0
- package/lib/browser/markdown-rendering/markdown-renderer.spec.d.ts.map +1 -0
- package/lib/browser/markdown-rendering/markdown-renderer.spec.js +79 -0
- package/lib/browser/markdown-rendering/markdown-renderer.spec.js.map +1 -0
- package/lib/browser/open-with-service.d.ts +2 -1
- package/lib/browser/open-with-service.d.ts.map +1 -1
- package/lib/browser/open-with-service.js +6 -1
- package/lib/browser/open-with-service.js.map +1 -1
- package/lib/browser/performance/frontend-stopwatch.d.ts +2 -1
- package/lib/browser/performance/frontend-stopwatch.d.ts.map +1 -1
- package/lib/browser/performance/frontend-stopwatch.js +6 -1
- package/lib/browser/performance/frontend-stopwatch.js.map +1 -1
- package/lib/browser/perspective-service.js +1 -1
- package/lib/browser/perspective-service.js.map +1 -1
- package/lib/browser/preferences/preference-proxy.spec.js +3 -0
- package/lib/browser/preferences/preference-proxy.spec.js.map +1 -1
- package/lib/browser/preferences/preference-service.spec.js +3 -0
- package/lib/browser/preferences/preference-service.spec.js.map +1 -1
- package/lib/browser/preferences/preference-validation-service.js +1 -1
- package/lib/browser/preferences/preference-validation-service.js.map +1 -1
- package/lib/browser/saveable-service.d.ts +2 -1
- package/lib/browser/saveable-service.d.ts.map +1 -1
- package/lib/browser/saveable-service.js +6 -1
- package/lib/browser/saveable-service.js.map +1 -1
- package/lib/browser/shell/application-shell.js +3 -3
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/application-shell.spec.d.ts +2 -0
- package/lib/browser/shell/application-shell.spec.d.ts.map +1 -0
- package/lib/browser/shell/application-shell.spec.js +57 -0
- package/lib/browser/shell/application-shell.spec.js.map +1 -0
- package/lib/browser/shell/shell-layout-restorer.d.ts +1 -1
- package/lib/browser/shell/shell-layout-restorer.d.ts.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.js +5 -4
- package/lib/browser/shell/shell-layout-restorer.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +3 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +2 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +6 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
- package/lib/browser/status-bar/status-bar-view-model.d.ts +2 -1
- package/lib/browser/status-bar/status-bar-view-model.d.ts.map +1 -1
- package/lib/browser/status-bar/status-bar-view-model.js +6 -1
- package/lib/browser/status-bar/status-bar-view-model.js.map +1 -1
- package/lib/browser/storage-service.d.ts +1 -1
- package/lib/browser/storage-service.d.ts.map +1 -1
- package/lib/browser/storage-service.js +1 -0
- package/lib/browser/storage-service.js.map +1 -1
- package/lib/browser/tree/tree-consistency.spec.js +103 -0
- package/lib/browser/tree/tree-consistency.spec.js.map +1 -1
- package/lib/browser/tree/tree-model.d.ts.map +1 -1
- package/lib/browser/tree/tree-model.js +1 -0
- package/lib/browser/tree/tree-model.js.map +1 -1
- package/lib/browser/tree/tree.d.ts.map +1 -1
- package/lib/browser/tree/tree.js +6 -0
- package/lib/browser/tree/tree.js.map +1 -1
- package/lib/browser/widget-manager.js +1 -0
- package/lib/browser/widget-manager.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts +13 -0
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js +44 -12
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/browser/window/default-secondary-window-service.d.ts +4 -1
- package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-secondary-window-service.js +17 -6
- package/lib/browser/window/default-secondary-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.d.ts +19 -0
- package/lib/browser/window/default-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-window-service.js +34 -5
- package/lib/browser/window/default-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.spec.js +22 -0
- package/lib/browser/window/default-window-service.spec.js.map +1 -1
- package/lib/browser/window/secondary-window-service.d.ts +7 -0
- package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/window-service.d.ts +1 -1
- package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
- package/lib/browser-only/frontend-only-application-module.js +25 -1
- package/lib/browser-only/frontend-only-application-module.js.map +1 -1
- package/lib/common/command.d.ts +37 -0
- package/lib/common/command.d.ts.map +1 -1
- package/lib/common/command.js +74 -4
- package/lib/common/command.js.map +1 -1
- package/lib/common/command.spec.js +109 -0
- package/lib/common/command.spec.js.map +1 -1
- package/lib/common/message-service-protocol.d.ts +2 -0
- package/lib/common/message-service-protocol.d.ts.map +1 -1
- package/lib/common/message-service-protocol.js +8 -2
- package/lib/common/message-service-protocol.js.map +1 -1
- package/lib/common/performance/stopwatch.js +1 -0
- package/lib/common/performance/stopwatch.js.map +1 -1
- package/lib/common/preferences/preference-schema-service.d.ts +2 -0
- package/lib/common/preferences/preference-schema-service.d.ts.map +1 -1
- package/lib/common/preferences/preference-schema-service.js +11 -5
- package/lib/common/preferences/preference-schema-service.js.map +1 -1
- package/lib/common/preferences/preference-service.d.ts +2 -0
- package/lib/common/preferences/preference-service.d.ts.map +1 -1
- package/lib/common/preferences/preference-service.js +7 -1
- package/lib/common/preferences/preference-service.js.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
- package/lib/electron-browser/window/electron-window-service.js +3 -3
- package/lib/electron-browser/window/electron-window-service.js.map +1 -1
- package/lib/node/backend-application.js +5 -5
- package/lib/node/backend-application.js.map +1 -1
- package/lib/node/backend-application.spec.js +6 -3
- package/lib/node/backend-application.spec.js.map +1 -1
- package/lib/node/env-variables/env-variables-server.d.ts +2 -0
- package/lib/node/env-variables/env-variables-server.d.ts.map +1 -1
- package/lib/node/env-variables/env-variables-server.js +7 -1
- package/lib/node/env-variables/env-variables-server.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.d.ts +2 -0
- package/lib/node/hosting/browser-connection-token.d.ts.map +1 -1
- package/lib/node/hosting/browser-connection-token.js +14 -1
- package/lib/node/hosting/browser-connection-token.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.spec.js +25 -2
- package/lib/node/hosting/browser-connection-token.spec.js.map +1 -1
- package/lib/node/key-store-server.d.ts +2 -0
- package/lib/node/key-store-server.d.ts.map +1 -1
- package/lib/node/key-store-server.js +6 -1
- package/lib/node/key-store-server.js.map +1 -1
- package/lib/node/messaging/default-messaging-service.js +1 -1
- package/lib/node/messaging/default-messaging-service.js.map +1 -1
- package/lib/node/messaging/ipc-connection-provider.js +1 -0
- package/lib/node/messaging/ipc-connection-provider.js.map +1 -1
- package/lib/node/messaging/websocket-endpoint.js +1 -1
- package/lib/node/messaging/websocket-endpoint.js.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts +2 -1
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.js +11 -6
- package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
- package/lib/node/setting-service.js +1 -0
- package/lib/node/setting-service.js.map +1 -1
- package/package.json +19 -19
- package/src/browser/authentication-service.ts +1 -1
- package/src/browser/browser-clipboard-service.ts +2 -2
- package/src/browser/common-frontend-contribution.spec.ts +84 -0
- package/src/browser/common-frontend-contribution.ts +15 -3
- package/src/browser/connection-status-service.ts +7 -3
- package/src/browser/frontend-application-contribution.ts +1 -1
- package/src/browser/frontend-application-state.ts +2 -2
- package/src/browser/icon-theme-service.ts +6 -2
- package/src/browser/keyboard/browser-keyboard-layout-provider.ts +2 -2
- package/src/browser/markdown-rendering/markdown-link-handler.ts +23 -1
- package/src/browser/markdown-rendering/markdown-renderer.spec.ts +88 -0
- package/src/browser/markdown-rendering/markdown-renderer.ts +43 -0
- package/src/browser/open-with-service.ts +6 -3
- package/src/browser/performance/frontend-stopwatch.ts +6 -3
- package/src/browser/perspective-service.ts +1 -1
- package/src/browser/preferences/preference-proxy.spec.ts +3 -0
- package/src/browser/preferences/preference-service.spec.ts +3 -0
- package/src/browser/preferences/preference-validation-service.ts +1 -1
- package/src/browser/saveable-service.ts +5 -2
- package/src/browser/shell/application-shell.spec.ts +62 -0
- package/src/browser/shell/application-shell.ts +4 -4
- package/src/browser/shell/shell-layout-restorer.ts +6 -5
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +3 -2
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +5 -2
- package/src/browser/status-bar/status-bar-view-model.ts +7 -3
- package/src/browser/storage-service.ts +4 -2
- package/src/browser/tree/tree-consistency.spec.ts +119 -0
- package/src/browser/tree/tree-model.ts +4 -2
- package/src/browser/tree/tree.ts +6 -0
- package/src/browser/widget-manager.ts +1 -1
- package/src/browser/widgets/select-component.tsx +49 -12
- package/src/browser/window/default-secondary-window-service.ts +17 -8
- package/src/browser/window/default-window-service.spec.ts +22 -0
- package/src/browser/window/default-window-service.ts +37 -5
- package/src/browser/window/secondary-window-service.ts +7 -0
- package/src/browser/window/window-service.ts +1 -1
- package/src/browser-only/frontend-only-application-module.ts +25 -1
- package/src/common/command.spec.ts +125 -0
- package/src/common/command.ts +77 -4
- package/src/common/message-service-protocol.ts +7 -3
- package/src/common/performance/stopwatch.ts +2 -2
- package/src/common/preferences/preference-schema-service.ts +9 -5
- package/src/common/preferences/preference-service.ts +6 -2
- package/src/electron-browser/menu/electron-menu-contribution.ts +1 -1
- package/src/electron-browser/window/electron-secondary-window-service.ts +1 -1
- package/src/electron-browser/window/electron-window-service.ts +3 -3
- package/src/node/backend-application.spec.ts +6 -3
- package/src/node/backend-application.ts +5 -5
- package/src/node/env-variables/env-variables-server.ts +6 -2
- package/src/node/hosting/browser-connection-token.spec.ts +30 -3
- package/src/node/hosting/browser-connection-token.ts +13 -2
- package/src/node/key-store-server.ts +6 -3
- package/src/node/messaging/default-messaging-service.ts +1 -1
- package/src/node/messaging/ipc-connection-provider.ts +2 -2
- package/src/node/messaging/websocket-endpoint.ts +1 -1
- package/src/node/messaging/websocket-frontend-connection-service.ts +11 -8
- package/src/node/setting-service.ts +2 -2
|
@@ -61,6 +61,10 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
|
|
|
61
61
|
protected optimalWidth = 0;
|
|
62
62
|
protected optimalHeight = 0;
|
|
63
63
|
protected resizeObserver: ResizeObserver | undefined;
|
|
64
|
+
/** Window the scroll/wheel/resize listeners are attached to; may differ from the main window in secondary windows. */
|
|
65
|
+
protected listenersWindow: Window | undefined;
|
|
66
|
+
/** Perfect-scrollbar ancestors the `ps-scroll-y` listener was attached to. The field may have moved to another window since, so the elements are remembered. */
|
|
67
|
+
protected psScrollListenerTargets: HTMLElement[] = [];
|
|
64
68
|
|
|
65
69
|
constructor(props: SelectComponentProps) {
|
|
66
70
|
super(props);
|
|
@@ -71,14 +75,28 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
|
|
|
71
75
|
hover: selected
|
|
72
76
|
};
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
this.dropdownElement = this.resolveDropdownContainer(document);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The document that currently hosts this component. Falls back to the main document before
|
|
83
|
+
* the first render. Widgets can be moved to secondary windows, so this must be re-queried
|
|
84
|
+
* instead of captured once.
|
|
85
|
+
*/
|
|
86
|
+
protected getHostDocument(): Document {
|
|
87
|
+
return this.fieldRef.current?.ownerDocument ?? document;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Finds or creates the dropdown portal container in the given document. */
|
|
91
|
+
protected resolveDropdownContainer(hostDocument: Document): HTMLElement {
|
|
92
|
+
let list = hostDocument.getElementById(SELECT_COMPONENT_CONTAINER);
|
|
75
93
|
if (!list) {
|
|
76
|
-
list =
|
|
94
|
+
list = hostDocument.createElement('div');
|
|
77
95
|
list.id = SELECT_COMPONENT_CONTAINER;
|
|
78
96
|
list.className = 'theia-select-component-container';
|
|
79
|
-
|
|
97
|
+
hostDocument.body.appendChild(list);
|
|
80
98
|
}
|
|
81
|
-
|
|
99
|
+
return list;
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
protected getInitialSelectedIndex(props: SelectComponentProps): number {
|
|
@@ -179,12 +197,14 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
|
|
|
179
197
|
// neither triggers the `scroll`, `wheel` nor `blur` event
|
|
180
198
|
if (parent.classList.contains('ps')) {
|
|
181
199
|
parent.addEventListener('ps-scroll-y', hide);
|
|
200
|
+
this.psScrollListenerTargets.push(parent);
|
|
182
201
|
}
|
|
183
202
|
parent = parent.parentElement;
|
|
184
203
|
}
|
|
185
204
|
|
|
205
|
+
this.listenersWindow = this.getHostDocument().defaultView ?? window;
|
|
186
206
|
for (const [key, listener] of this.mountedListeners.entries()) {
|
|
187
|
-
|
|
207
|
+
this.listenersWindow.addEventListener(key, listener);
|
|
188
208
|
}
|
|
189
209
|
|
|
190
210
|
// Catch Lumino sash drags globally - observe the closest lm-Widget panel
|
|
@@ -222,18 +242,24 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
|
|
|
222
242
|
}
|
|
223
243
|
|
|
224
244
|
override componentWillUnmount(): void {
|
|
245
|
+
this.detachListeners();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
protected detachListeners(): void {
|
|
225
249
|
this.resizeObserver?.disconnect();
|
|
250
|
+
this.resizeObserver = undefined;
|
|
226
251
|
if (this.mountedListeners.size > 0) {
|
|
227
252
|
const eventListener = this.mountedListeners.get('scroll')!;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
parent.removeEventListener('ps-scroll-y', eventListener);
|
|
231
|
-
parent = parent.parentElement;
|
|
253
|
+
for (const target of this.psScrollListenerTargets) {
|
|
254
|
+
target.removeEventListener('ps-scroll-y', eventListener);
|
|
232
255
|
}
|
|
233
256
|
for (const [key, listener] of this.mountedListeners.entries()) {
|
|
234
|
-
window.removeEventListener(key, listener);
|
|
257
|
+
(this.listenersWindow ?? window).removeEventListener(key, listener);
|
|
235
258
|
}
|
|
259
|
+
this.mountedListeners.clear();
|
|
236
260
|
}
|
|
261
|
+
this.psScrollListenerTargets = [];
|
|
262
|
+
this.listenersWindow = undefined;
|
|
237
263
|
}
|
|
238
264
|
|
|
239
265
|
override render(): React.ReactNode {
|
|
@@ -345,6 +371,14 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
|
|
|
345
371
|
return;
|
|
346
372
|
}
|
|
347
373
|
if (!this.state.dimensions) {
|
|
374
|
+
// Re-resolve the portal container and listener window against the document that
|
|
375
|
+
// currently hosts the field: the widget may have been moved to a secondary window
|
|
376
|
+
// (or back to the main window) since the last time the dropdown was opened.
|
|
377
|
+
const hostDocument = this.getHostDocument();
|
|
378
|
+
this.dropdownElement = this.resolveDropdownContainer(hostDocument);
|
|
379
|
+
if (this.listenersWindow && this.listenersWindow !== hostDocument.defaultView) {
|
|
380
|
+
this.detachListeners();
|
|
381
|
+
}
|
|
348
382
|
const rect = this.fieldRef.current.getBoundingClientRect();
|
|
349
383
|
this.setState({ dimensions: rect });
|
|
350
384
|
} else {
|
|
@@ -361,7 +395,8 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
|
|
|
361
395
|
hover: selectedIndex
|
|
362
396
|
});
|
|
363
397
|
// Force releasing focus of the select element to allow closing via escape later on
|
|
364
|
-
|
|
398
|
+
const hostDocument = this.getHostDocument();
|
|
399
|
+
if (releaseFocus && hostDocument.activeElement && hostDocument.activeElement === this.fieldRef.current) {
|
|
365
400
|
this.fieldRef.current.blur();
|
|
366
401
|
}
|
|
367
402
|
}
|
|
@@ -371,7 +406,9 @@ export class SelectComponent extends React.Component<SelectComponentProps, Selec
|
|
|
371
406
|
return;
|
|
372
407
|
}
|
|
373
408
|
|
|
374
|
-
|
|
409
|
+
// Secondary windows have no 'theia-app-shell' element; use the document element as the boundary there.
|
|
410
|
+
const hostDocument = this.getHostDocument();
|
|
411
|
+
const shellArea = (hostDocument.getElementById('theia-app-shell') ?? hostDocument.documentElement).getBoundingClientRect();
|
|
375
412
|
const maxWidth = this.alignLeft ? shellArea.width - this.state.dimensions.left : this.state.dimensions.right;
|
|
376
413
|
if (this.mountedListeners.size === 0) {
|
|
377
414
|
// Only attach our listeners once we render our dropdown menu
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
//
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
|
-
import { inject, injectable, postConstruct } from 'inversify';
|
|
16
|
+
import { inject, injectable, postConstruct, named } from 'inversify';
|
|
17
17
|
import { SecondaryWindow, SecondaryWindowService } from './secondary-window-service';
|
|
18
18
|
import { WindowService } from './window-service';
|
|
19
19
|
import { ExtractableWidget, Widget } from '../widgets';
|
|
20
20
|
import { ApplicationShell } from '../shell';
|
|
21
21
|
import { Saveable } from '../saveable';
|
|
22
|
-
import { Emitter, environment, Event, PreferenceService } from '../../common';
|
|
22
|
+
import { Emitter, environment, Event, PreferenceService, ILogger } from '../../common';
|
|
23
23
|
import { SaveableService } from '../saveable-service';
|
|
24
24
|
import { getAllWidgetsFromSecondaryWindow, getDefaultRestoreArea } from '../secondary-window-handler';
|
|
25
25
|
import { WindowFocusService } from './window-focus-service';
|
|
@@ -28,6 +28,8 @@ import { WindowFocusService } from './window-focus-service';
|
|
|
28
28
|
export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
29
29
|
protected readonly onWindowOpenedEmitter = new Emitter<Window>;
|
|
30
30
|
readonly onWindowOpened: Event<Window> = this.onWindowOpenedEmitter.event;
|
|
31
|
+
protected readonly onWindowLoadedEmitter = new Emitter<Window>;
|
|
32
|
+
readonly onWindowLoaded: Event<Window> = this.onWindowLoadedEmitter.event;
|
|
31
33
|
protected readonly onWindowClosedEmitter = new Emitter<Window>;
|
|
32
34
|
readonly onWindowClosed: Event<Window> = this.onWindowClosedEmitter.event;
|
|
33
35
|
protected readonly beforeWidgetRestoreEmitter = new Emitter<[Widget, Window]>;
|
|
@@ -57,17 +59,20 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
57
59
|
@inject(WindowFocusService)
|
|
58
60
|
protected readonly windowFocusService: WindowFocusService;
|
|
59
61
|
|
|
62
|
+
@inject(ILogger) @named('core:DefaultSecondaryWindowService')
|
|
63
|
+
protected readonly logger: ILogger;
|
|
64
|
+
|
|
60
65
|
@postConstruct()
|
|
61
66
|
init(): void {
|
|
62
67
|
// Set up messaging with secondary windows
|
|
63
68
|
window.addEventListener('message', (event: MessageEvent) => {
|
|
64
|
-
|
|
69
|
+
this.logger.trace('Message on main window', event);
|
|
65
70
|
if (event.data.fromSecondary) {
|
|
66
|
-
|
|
71
|
+
this.logger.trace('Message comes from secondary window');
|
|
67
72
|
return;
|
|
68
73
|
}
|
|
69
74
|
if (event.data.fromMain) {
|
|
70
|
-
|
|
75
|
+
this.logger.trace('Message has mainWindow marker, therefore ignore it');
|
|
71
76
|
return;
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -78,7 +83,7 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
this.logger.trace('Delegate main window message to secondary windows', event);
|
|
82
87
|
this.secondaryWindows.forEach(secondaryWindow => {
|
|
83
88
|
if (!secondaryWindow.window.closed) {
|
|
84
89
|
secondaryWindow.window.postMessage({ ...event.data, fromMain: true }, '*');
|
|
@@ -110,6 +115,10 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
110
115
|
this.secondaryWindows.push(newWindow);
|
|
111
116
|
this.onWindowOpenedEmitter.fire(newWindow);
|
|
112
117
|
newWindow.addEventListener('DOMContentLoaded', () => {
|
|
118
|
+
// This fires for the final secondary-window.html document, not for the initial
|
|
119
|
+
// about:blank document the window starts out with, so onWindowLoaded listeners
|
|
120
|
+
// can safely modify the document.
|
|
121
|
+
this.onWindowLoadedEmitter.fire(newWindow);
|
|
113
122
|
const focusRegistration = this.windowFocusService.registerWindow(newWindow);
|
|
114
123
|
newWindow.addEventListener('beforeunload', evt => {
|
|
115
124
|
const widgets = getAllWidgetsFromSecondaryWindow(newWindow) ?? [widget];
|
|
@@ -124,7 +133,7 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
124
133
|
}
|
|
125
134
|
}, { capture: true });
|
|
126
135
|
|
|
127
|
-
newWindow.addEventListener('
|
|
136
|
+
newWindow.addEventListener('pagehide', () => {
|
|
128
137
|
focusRegistration.dispose();
|
|
129
138
|
const extIndex = this.secondaryWindows.indexOf(newWindow);
|
|
130
139
|
if (extIndex > -1) {
|
|
@@ -140,7 +149,7 @@ export class DefaultSecondaryWindowService implements SecondaryWindowService {
|
|
|
140
149
|
}
|
|
141
150
|
|
|
142
151
|
protected windowCreated(newWindow: Window, widget: ExtractableWidget, shell: ApplicationShell): void {
|
|
143
|
-
newWindow.addEventListener('
|
|
152
|
+
newWindow.addEventListener('pagehide', () => {
|
|
144
153
|
this.restoreWidgets(newWindow, widget, shell);
|
|
145
154
|
});
|
|
146
155
|
}
|
|
@@ -77,4 +77,26 @@ describe('DefaultWindowService', () => {
|
|
|
77
77
|
assert(windowService['collectContributionUnloadVetoes']().length > 0, 'there should be vetoes');
|
|
78
78
|
assert(frontendContributions.every(contribution => contribution.onWillStopCalled), 'contributions should have been called');
|
|
79
79
|
});
|
|
80
|
+
it('should reload unconditionally when restored from the back/forward cache', () => {
|
|
81
|
+
const frontendContributions: TestFrontendApplicationContribution[] = [
|
|
82
|
+
new TestFrontendApplicationContribution(true),
|
|
83
|
+
];
|
|
84
|
+
const windowService = setupWindowService('always', frontendContributions);
|
|
85
|
+
let reloaded = 0;
|
|
86
|
+
windowService.reload = () => { reloaded++; };
|
|
87
|
+
windowService['handlePageShow']({ persisted: false } as PageTransitionEvent);
|
|
88
|
+
const reloadsBeforeRestore = reloaded;
|
|
89
|
+
windowService['handlePageShow']({ persisted: true } as PageTransitionEvent);
|
|
90
|
+
assert(reloadsBeforeRestore === 0, 'a regular page show should not reload');
|
|
91
|
+
assert(reloaded === 1, 'a restored page should reload');
|
|
92
|
+
assert(windowService['collectContributionUnloadVetoes']().length === 0, 'the recovery reload should not be vetoed');
|
|
93
|
+
});
|
|
94
|
+
it('onUnload should fire at most once, even if `pagehide` fires repeatedly', () => {
|
|
95
|
+
const windowService = setupWindowService('never', []);
|
|
96
|
+
let fired = 0;
|
|
97
|
+
windowService.onUnload(() => fired++);
|
|
98
|
+
windowService['handlePageHide']();
|
|
99
|
+
windowService['handlePageHide']();
|
|
100
|
+
assert(fired === 1, `onUnload should have fired once, but fired ${fired} time(s)`);
|
|
101
|
+
});
|
|
80
102
|
});
|
|
@@ -30,6 +30,7 @@ export class DefaultWindowService implements WindowService, FrontendApplicationC
|
|
|
30
30
|
|
|
31
31
|
protected frontendApplication: FrontendApplication;
|
|
32
32
|
protected allowVetoes = true;
|
|
33
|
+
protected unloaded = false;
|
|
33
34
|
|
|
34
35
|
protected onUnloadEmitter = new Emitter<void>();
|
|
35
36
|
get onUnload(): Event<void> {
|
|
@@ -103,12 +104,43 @@ export class DefaultWindowService implements WindowService, FrontendApplicationC
|
|
|
103
104
|
* Implement the mechanism to detect unloading of the page.
|
|
104
105
|
*/
|
|
105
106
|
protected registerUnloadListeners(): void {
|
|
107
|
+
// If `beforeunload` is cancelled and the user stays, the page is not unloaded, so `pagehide` is not fired.
|
|
106
108
|
window.addEventListener('beforeunload', event => this.handleBeforeUnloadEvent(event));
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
this.registerPageHideListener();
|
|
110
|
+
window.addEventListener('pageshow', event => this.handlePageShow(event));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* `pagehide` is used instead of the deprecated `unload` event, which Chrome may block
|
|
115
|
+
* via permissions policy (https://developer.chrome.com/docs/web-platform/deprecating-unload),
|
|
116
|
+
* silently skipping the handler and thus e.g. losing the layout.
|
|
117
|
+
*/
|
|
118
|
+
protected registerPageHideListener(): void {
|
|
119
|
+
window.addEventListener('pagehide', () => this.handlePageHide());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `pagehide` also fires when the page enters the back/forward cache, in which case the frontend
|
|
124
|
+
* has already shut down (state saved, connections closed). Reload to get a working application
|
|
125
|
+
* again if the page is restored from the cache. That shutdown already happened, so the reload
|
|
126
|
+
* must not be vetoed by the contributions.
|
|
127
|
+
*/
|
|
128
|
+
protected handlePageShow(event: PageTransitionEvent): void {
|
|
129
|
+
if (event.persisted) {
|
|
130
|
+
this.setSafeToShutDown();
|
|
131
|
+
this.reload();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Fires {@link onUnload} at most once per document: `pagehide` fires again for the reload that
|
|
137
|
+
* recovers a page restored from the back/forward cache, but the application is already stopped.
|
|
138
|
+
*/
|
|
139
|
+
protected handlePageHide(): void {
|
|
140
|
+
if (!this.unloaded) {
|
|
141
|
+
this.unloaded = true;
|
|
142
|
+
this.onUnloadEmitter.fire();
|
|
143
|
+
}
|
|
112
144
|
}
|
|
113
145
|
|
|
114
146
|
async isSafeToShutDown(stopReason: StopReason): Promise<boolean> {
|
|
@@ -55,6 +55,13 @@ export interface SecondaryWindowService {
|
|
|
55
55
|
*/
|
|
56
56
|
createSecondaryWindow(widget: ExtractableWidget, shell: ApplicationShell): SecondaryWindow | Window | undefined;
|
|
57
57
|
readonly onWindowOpened: Event<Window>;
|
|
58
|
+
/**
|
|
59
|
+
* Emitted once a secondary window's final document has finished loading. Listeners can safely
|
|
60
|
+
* modify the window's document, unlike with {@link onWindowOpened}: at that point the window
|
|
61
|
+
* still shows the initial `about:blank` document, and anything added to it is discarded when
|
|
62
|
+
* the final document replaces it.
|
|
63
|
+
*/
|
|
64
|
+
readonly onWindowLoaded: Event<Window>;
|
|
58
65
|
readonly onWindowClosed: Event<Window>;
|
|
59
66
|
readonly beforeWidgetRestore: Event<[Widget, Window]>;
|
|
60
67
|
|
|
@@ -56,7 +56,7 @@ export interface WindowService {
|
|
|
56
56
|
focus(): void;
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Fires when the
|
|
59
|
+
* Fires when the application is shutting down. On this event, the frontend application can save its state and release resource.
|
|
60
60
|
* Saving the state and releasing any resources must be a synchronous call. Any asynchronous calls invoked after emitting this event might be ignored.
|
|
61
61
|
*/
|
|
62
62
|
readonly onUnload: Event<void>;
|
|
@@ -52,8 +52,32 @@ export const frontendOnlyApplicationModule = new ContainerModule((bind, unbind,
|
|
|
52
52
|
bind(QuickPickService).to(QuickPickServiceImpl).inSingletonScope();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
let extensionsCache: Promise<ExtensionInfo[]> | undefined;
|
|
56
|
+
|
|
55
57
|
const mockedApplicationServer: ApplicationServer = {
|
|
56
|
-
getExtensionsInfos: async (): Promise<ExtensionInfo[]> =>
|
|
58
|
+
getExtensionsInfos: async (): Promise<ExtensionInfo[]> => {
|
|
59
|
+
if (extensionsCache === undefined) {
|
|
60
|
+
extensionsCache = (async () => {
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch('./extensions.json');
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
extensionsCache = undefined;
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const raw = await res.json();
|
|
68
|
+
if (!Array.isArray(raw)) {
|
|
69
|
+
extensionsCache = undefined;
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
return raw as ExtensionInfo[];
|
|
73
|
+
} catch {
|
|
74
|
+
extensionsCache = undefined;
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
}
|
|
79
|
+
return extensionsCache;
|
|
80
|
+
},
|
|
57
81
|
getApplicationInfo: async (): Promise<ApplicationInfo | undefined> => undefined,
|
|
58
82
|
getApplicationRoot: async (): Promise<string> => '',
|
|
59
83
|
getApplicationPlatform: () => Promise.resolve('web'),
|
|
@@ -139,6 +139,131 @@ describe('Commands', () => {
|
|
|
139
139
|
expect(commandRegistry.getAllHandlers('id').length).to.be.equal(2);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
describe('registerAlias', () => {
|
|
143
|
+
|
|
144
|
+
it('onDidExecuteCommand fires for alias when target is executed', async () => {
|
|
145
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
146
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
147
|
+
const events: string[] = [];
|
|
148
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
149
|
+
await commandRegistry.executeCommand('target');
|
|
150
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('onDidExecuteCommand fires only for alias when alias is executed (no reverse)', async () => {
|
|
154
|
+
commandRegistry.registerCommand({ id: 'alias' }, new StubCommandHandler());
|
|
155
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
156
|
+
const events: string[] = [];
|
|
157
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
158
|
+
await commandRegistry.executeCommand('alias');
|
|
159
|
+
// Unidirectional: an alias fires for its target, but not the other way round
|
|
160
|
+
expect(events).to.deep.equal(['alias']);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('onWillExecuteCommand fires for alias when target is executed', async () => {
|
|
164
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
165
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
166
|
+
const events: string[] = [];
|
|
167
|
+
commandRegistry.onWillExecuteCommand(e => events.push(e.commandId));
|
|
168
|
+
await commandRegistry.executeCommand('target');
|
|
169
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('disposing the alias removes the event link', async () => {
|
|
173
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
174
|
+
const disposable = commandRegistry.registerAlias('alias', 'target');
|
|
175
|
+
disposable.dispose();
|
|
176
|
+
const events: string[] = [];
|
|
177
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
178
|
+
await commandRegistry.executeCommand('target');
|
|
179
|
+
expect(events).to.deep.equal(['target']);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('getAliases returns the aliases registered for a target', () => {
|
|
183
|
+
commandRegistry.registerAlias('a', 'b');
|
|
184
|
+
expect(commandRegistry.getAliases('b')).to.deep.equal(['a']);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('getAliases returns nothing for the alias direction (unidirectional)', () => {
|
|
188
|
+
commandRegistry.registerAlias('a', 'b');
|
|
189
|
+
expect(commandRegistry.getAliases('a')).to.be.empty;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('getAliases returns nothing for unaliased commands', () => {
|
|
193
|
+
expect(commandRegistry.getAliases('nonexistent')).to.be.empty;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('a target can carry several aliases', async () => {
|
|
197
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
198
|
+
commandRegistry.registerAlias('alias1', 'target');
|
|
199
|
+
commandRegistry.registerAlias('alias2', 'target');
|
|
200
|
+
const events: string[] = [];
|
|
201
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
202
|
+
await commandRegistry.executeCommand('target');
|
|
203
|
+
expect(events).to.deep.equal(['target', 'alias1', 'alias2']);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('disposing one alias keeps the others', async () => {
|
|
207
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
208
|
+
const first = commandRegistry.registerAlias('alias1', 'target');
|
|
209
|
+
commandRegistry.registerAlias('alias2', 'target');
|
|
210
|
+
first.dispose();
|
|
211
|
+
expect(commandRegistry.getAliases('target')).to.deep.equal(['alias2']);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('alias events include correct args', async () => {
|
|
215
|
+
commandRegistry.registerCommand({ id: 'target' }, new ConcatCommandHandler());
|
|
216
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
+
const capturedArgs: any[][] = [];
|
|
219
|
+
commandRegistry.onDidExecuteCommand(e => capturedArgs.push(e.args));
|
|
220
|
+
await commandRegistry.executeCommand('target', 'x', 'y');
|
|
221
|
+
expect(capturedArgs.length).to.equal(2);
|
|
222
|
+
expect(capturedArgs[0]).to.deep.equal(['x', 'y']);
|
|
223
|
+
expect(capturedArgs[1]).to.deep.equal(['x', 'y']);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('nested execution: alias handler delegating to target fires target and alias events', async () => {
|
|
227
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
228
|
+
commandRegistry.registerCommand({ id: 'alias' }, {
|
|
229
|
+
execute: () => commandRegistry.executeCommand('target')
|
|
230
|
+
});
|
|
231
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
232
|
+
const events: string[] = [];
|
|
233
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
234
|
+
await commandRegistry.executeCommand('alias');
|
|
235
|
+
// The alias is on the stack while the target runs, so its event is only reported once.
|
|
236
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('reports the alias once for the will execute event as well', async () => {
|
|
240
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
241
|
+
commandRegistry.registerCommand({ id: 'alias' }, {
|
|
242
|
+
execute: () => commandRegistry.executeCommand('target')
|
|
243
|
+
});
|
|
244
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
245
|
+
const events: string[] = [];
|
|
246
|
+
commandRegistry.onWillExecuteCommand(e => events.push(e.commandId));
|
|
247
|
+
await commandRegistry.executeCommand('alias');
|
|
248
|
+
expect(events).to.deep.equal(['alias', 'target']);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('reports the alias again once the delegating execution finished', async () => {
|
|
252
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
253
|
+
commandRegistry.registerCommand({ id: 'alias' }, {
|
|
254
|
+
execute: () => commandRegistry.executeCommand('target')
|
|
255
|
+
});
|
|
256
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
257
|
+
await commandRegistry.executeCommand('alias');
|
|
258
|
+
|
|
259
|
+
const events: string[] = [];
|
|
260
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
261
|
+
await commandRegistry.executeCommand('target');
|
|
262
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
});
|
|
266
|
+
|
|
142
267
|
describe('compareCommands', () => {
|
|
143
268
|
|
|
144
269
|
it('should sort command \'a\' before command \'b\' with categories', () => {
|
package/src/common/command.ts
CHANGED
|
@@ -202,6 +202,19 @@ export class CommandRegistry implements CommandService {
|
|
|
202
202
|
// List of recently used commands.
|
|
203
203
|
protected _recent: string[] = [];
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Unidirectional alias map from a target command id to the alias command ids registered for it.
|
|
207
|
+
* When the target (for instance a native Theia command) is executed, events also fire for its
|
|
208
|
+
* aliases (for instance the VS Code command ids).
|
|
209
|
+
*/
|
|
210
|
+
protected readonly _aliases = new Map<string, Set<string>>();
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* How often each command is currently executing, so that an alias event can be skipped while the
|
|
214
|
+
* alias command itself is on the stack.
|
|
215
|
+
*/
|
|
216
|
+
protected readonly executionDepth = new Map<string, number>();
|
|
217
|
+
|
|
205
218
|
protected readonly onWillExecuteCommandEmitter = new Emitter<WillExecuteCommandEvent>();
|
|
206
219
|
readonly onWillExecuteCommand = this.onWillExecuteCommandEmitter.event;
|
|
207
220
|
|
|
@@ -277,6 +290,42 @@ export class CommandRegistry implements CommandService {
|
|
|
277
290
|
}
|
|
278
291
|
}
|
|
279
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Register a unidirectional alias from a target command to an alias command.
|
|
295
|
+
* When the target command is executed, {@link onDidExecuteCommand} and {@link onWillExecuteCommand}
|
|
296
|
+
* fire for the alias id as well.
|
|
297
|
+
*
|
|
298
|
+
* This does not affect command handlers, enablement or visibility, only event emission. A target may
|
|
299
|
+
* carry several aliases.
|
|
300
|
+
*
|
|
301
|
+
* No event is fired for an alias while that alias is executing, which keeps an alias command that
|
|
302
|
+
* delegates to its target from reporting itself twice. The check is by command id rather than by call
|
|
303
|
+
* stack, so an unrelated execution of the alias that happens to overlap suppresses the event as well.
|
|
304
|
+
*
|
|
305
|
+
* @param aliasId the alias command id, for instance a VS Code command id
|
|
306
|
+
* @param targetId the target command id, for instance the native Theia command id
|
|
307
|
+
* @returns a `Disposable` that removes this alias again
|
|
308
|
+
*/
|
|
309
|
+
registerAlias(aliasId: string, targetId: string): Disposable {
|
|
310
|
+
const aliases = this._aliases.get(targetId) ?? new Set<string>();
|
|
311
|
+
aliases.add(aliasId);
|
|
312
|
+
this._aliases.set(targetId, aliases);
|
|
313
|
+
return Disposable.create(() => {
|
|
314
|
+
const registered = this._aliases.get(targetId);
|
|
315
|
+
if (registered?.delete(aliasId) && registered.size === 0) {
|
|
316
|
+
this._aliases.delete(targetId);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Returns the alias ids registered for the given command id.
|
|
323
|
+
*/
|
|
324
|
+
getAliases(commandId: string): readonly string[] {
|
|
325
|
+
const aliases = this._aliases.get(commandId);
|
|
326
|
+
return aliases ? Array.from(aliases) : [];
|
|
327
|
+
}
|
|
328
|
+
|
|
280
329
|
/**
|
|
281
330
|
* Register the given handler for the given command identifier.
|
|
282
331
|
*
|
|
@@ -341,10 +390,23 @@ export class CommandRegistry implements CommandService {
|
|
|
341
390
|
async executeCommand<T>(commandId: string, ...args: any[]): Promise<T | undefined> {
|
|
342
391
|
const handler = this.getActiveHandler(commandId, ...args);
|
|
343
392
|
if (handler) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
393
|
+
this.executionDepth.set(commandId, (this.executionDepth.get(commandId) ?? 0) + 1);
|
|
394
|
+
try {
|
|
395
|
+
await this.fireWillExecuteCommand(commandId, args);
|
|
396
|
+
const result = await handler.execute(...args);
|
|
397
|
+
this.onDidExecuteCommandEmitter.fire({ commandId, args });
|
|
398
|
+
for (const alias of this.getPendingAliases(commandId)) {
|
|
399
|
+
this.onDidExecuteCommandEmitter.fire({ commandId: alias, args });
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
402
|
+
} finally {
|
|
403
|
+
const depth = this.executionDepth.get(commandId) ?? 1;
|
|
404
|
+
if (depth > 1) {
|
|
405
|
+
this.executionDepth.set(commandId, depth - 1);
|
|
406
|
+
} else {
|
|
407
|
+
this.executionDepth.delete(commandId);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
348
410
|
}
|
|
349
411
|
throw Object.assign(new Error(`The command '${commandId}' cannot be executed. There are no active handlers available for the command.`), { code: 'NO_ACTIVE_HANDLER' });
|
|
350
412
|
}
|
|
@@ -352,6 +414,17 @@ export class CommandRegistry implements CommandService {
|
|
|
352
414
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
353
415
|
protected async fireWillExecuteCommand(commandId: string, args: any[] = []): Promise<void> {
|
|
354
416
|
await WaitUntilEvent.fire(this.onWillExecuteCommandEmitter, { commandId, args }, 30000);
|
|
417
|
+
for (const alias of this.getPendingAliases(commandId)) {
|
|
418
|
+
await WaitUntilEvent.fire(this.onWillExecuteCommandEmitter, { commandId: alias, args }, 30000);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The aliases of the given command that are not executing themselves, and for which events are
|
|
424
|
+
* therefore still owed.
|
|
425
|
+
*/
|
|
426
|
+
protected getPendingAliases(commandId: string): readonly string[] {
|
|
427
|
+
return this.getAliases(commandId).filter(alias => !this.executionDepth.has(alias));
|
|
355
428
|
}
|
|
356
429
|
|
|
357
430
|
/**
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { injectable } from 'inversify';
|
|
17
|
+
import { injectable, inject, named } from 'inversify';
|
|
18
18
|
import { CancellationToken } from './cancellation';
|
|
19
19
|
import { nls } from './nls';
|
|
20
|
+
import { ILogger } from './logger';
|
|
20
21
|
|
|
21
22
|
export const messageServicePath = '/services/messageService';
|
|
22
23
|
|
|
@@ -115,6 +116,9 @@ export interface ProgressUpdate {
|
|
|
115
116
|
@injectable()
|
|
116
117
|
export class MessageClient {
|
|
117
118
|
|
|
119
|
+
@inject(ILogger) @named('core:MessageClient')
|
|
120
|
+
protected readonly logger: ILogger;
|
|
121
|
+
|
|
118
122
|
/**
|
|
119
123
|
* Show a message of the given type and possible actions to the user.
|
|
120
124
|
* Resolve to a chosen action.
|
|
@@ -123,7 +127,7 @@ export class MessageClient {
|
|
|
123
127
|
* To be implemented by an extension, e.g. by the messages extension.
|
|
124
128
|
*/
|
|
125
129
|
showMessage(message: Message): Promise<string | undefined> {
|
|
126
|
-
|
|
130
|
+
this.logger.info(message.text);
|
|
127
131
|
return Promise.resolve(undefined);
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -133,7 +137,7 @@ export class MessageClient {
|
|
|
133
137
|
* To be implemented by an extension, e.g. by the messages extension.
|
|
134
138
|
*/
|
|
135
139
|
showProgress(progressId: string, message: ProgressMessage, cancellationToken: CancellationToken): Promise<string | undefined> {
|
|
136
|
-
|
|
140
|
+
this.logger.info(message.text);
|
|
137
141
|
return Promise.resolve(undefined);
|
|
138
142
|
}
|
|
139
143
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
18
18
|
|
|
19
|
-
import { inject, injectable, unmanaged } from 'inversify';
|
|
19
|
+
import { inject, injectable, unmanaged, named } from 'inversify';
|
|
20
20
|
import { ILogger, LogLevel } from '../logger';
|
|
21
21
|
import { MaybePromise } from '../types';
|
|
22
22
|
import { Measurement, MeasurementOptions, MeasurementResult } from './measurement';
|
|
@@ -48,7 +48,7 @@ interface LogOptions extends MeasurementOptions {
|
|
|
48
48
|
@injectable()
|
|
49
49
|
export abstract class Stopwatch {
|
|
50
50
|
|
|
51
|
-
@inject(ILogger)
|
|
51
|
+
@inject(ILogger) @named('core:Stopwatch')
|
|
52
52
|
protected readonly logger: ILogger;
|
|
53
53
|
|
|
54
54
|
protected _storedMeasurements: MeasurementResult[] = [];
|