@hydranium/glsp-client-theia 1.0.0-next.123 → 1.0.0-next.127
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 +19 -7
- package/lib/browser/diagram-widget.d.ts +13 -1
- package/lib/browser/diagram-widget.d.ts.map +1 -1
- package/lib/browser/diagram-widget.js +19 -0
- package/lib/browser/diagram-widget.js.map +1 -1
- package/lib/browser/glsp-saveable.d.ts +66 -0
- package/lib/browser/glsp-saveable.d.ts.map +1 -0
- package/lib/browser/glsp-saveable.js +109 -0
- package/lib/browser/glsp-saveable.js.map +1 -0
- package/lib/browser/hidden-bounds-updater.d.ts +2 -4
- package/lib/browser/hidden-bounds-updater.d.ts.map +1 -1
- package/lib/browser/hidden-bounds-updater.js +7 -3
- package/lib/browser/hidden-bounds-updater.js.map +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +1 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/node/glsp-server-connection-handler.d.ts +6 -10
- package/lib/node/glsp-server-connection-handler.d.ts.map +1 -1
- package/lib/node/glsp-server-connection-handler.js +8 -14
- package/lib/node/glsp-server-connection-handler.js.map +1 -1
- package/package.json +9 -5
- package/src/browser/diagram-widget.ts +24 -3
- package/src/browser/glsp-saveable.ts +110 -0
- package/src/browser/hidden-bounds-updater.ts +5 -6
- package/src/browser/index.ts +1 -0
- package/src/node/glsp-server-connection-handler.ts +6 -14
package/README.md
CHANGED
|
@@ -51,8 +51,20 @@ if you are mounting a hydranium GLSP diagram in a Theia application.
|
|
|
51
51
|
- **`HydraniumGlspClientContribution`** — for a server that starts late: it tails
|
|
52
52
|
the Output channel for a server-printed ready marker and defers `start` until a
|
|
53
53
|
workspace is open.
|
|
54
|
+
- **`HydraniumGlspSaveable`** — the diagram widget's saveable. Each save is a
|
|
55
|
+
`RequestSaveModelAction` the server answers: the save resolves on its own
|
|
56
|
+
response, and rejects on its own rejection or after 10 s rather than GLSP's
|
|
57
|
+
2 s. It stays dirty while a save is pending, so Theia's exit check still
|
|
58
|
+
counts it. The server answers through
|
|
59
|
+
`HydraniumGlspRequestSaveModelActionHandler`, which
|
|
60
|
+
`AbstractHydraniumGlspDiagramModule` registers; against a server that does
|
|
61
|
+
not advertise the request, the saveable behaves as GLSP's does. The dirty
|
|
62
|
+
flag follows the server, so an edit whose dirty state has not arrived when
|
|
63
|
+
Save All runs is not saved. Under the `fire-and-forget` `SaveDeliveryPolicy`
|
|
64
|
+
the server answers before the disk write finishes, and answers a failed
|
|
65
|
+
write as saved.
|
|
54
66
|
- **Backend (`./node`)** — `GlspServerConnectionHandler` (the socket bridge,
|
|
55
|
-
|
|
67
|
+
over `@hydranium/client-theia`'s `SocketChannelForwarder`) and
|
|
56
68
|
`createGlspConnectionContainerModule(handler)` for the frontend-scoped module
|
|
57
69
|
boilerplate.
|
|
58
70
|
|
|
@@ -107,12 +119,12 @@ stylesheet loaded afterwards.
|
|
|
107
119
|
|
|
108
120
|
## Entry points
|
|
109
121
|
|
|
110
|
-
| Subpath | Holds
|
|
111
|
-
| ----------- |
|
|
112
|
-
| `.` | Re-exports `./browser` — the larger of the two tiers and safe to load anywhere.
|
|
113
|
-
| `./browser` | The frontend module bases, `createGlspClientTheiaModule`, the dispatcher, loader, widget, bounds updater, message service, diagram manager and configuration bases, `NoOpExternalMarkerManager`
|
|
114
|
-
| `./node` | `GlspServerConnectionHandler`, `createGlspConnectionContainerModule`
|
|
115
|
-
| `./testing` | `makeBindRecorder`, the GLSP-module-specific Inversify double (the cross-head doubles live in `@hydranium/client-theia/testing`)
|
|
122
|
+
| Subpath | Holds | Environment |
|
|
123
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
124
|
+
| `.` | Re-exports `./browser` — the larger of the two tiers and safe to load anywhere. | browser-neutral (gated) |
|
|
125
|
+
| `./browser` | The frontend module bases, `createGlspClientTheiaModule`, the dispatcher, loader, widget and its saveable, bounds updater, message service, diagram manager and configuration bases, `NoOpExternalMarkerManager` | browser / Theia frontend (gated) |
|
|
126
|
+
| `./node` | `GlspServerConnectionHandler`, `createGlspConnectionContainerModule` | Node / Theia backend |
|
|
127
|
+
| `./testing` | `makeBindRecorder`, the GLSP-module-specific Inversify double (the cross-head doubles live in `@hydranium/client-theia/testing`) | browser-neutral (gated) |
|
|
116
128
|
|
|
117
129
|
Every subpath also has a `./lib/<name>` twin for consumers on
|
|
118
130
|
`moduleResolution: "Node"`. "Gated" means the repository's neutral-bundle check
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
|
-
import { GLSPDiagramWidget } from '@eclipse-glsp/theia-integration';
|
|
9
|
+
import { GLSPDiagramWidget, type GLSPDiagramWidgetOptions } from '@eclipse-glsp/theia-integration';
|
|
10
10
|
import { type Message } from '@theia/core/lib/browser';
|
|
11
|
+
import { type Container } from '@theia/core/shared/inversify';
|
|
11
12
|
import { type DiagramLoadOutcome, HydraniumDiagramLoader } from './diagram-loader';
|
|
12
13
|
import '../../style/diagram-loading.css';
|
|
13
14
|
/** Class on the overlay root. Adopters style / override via this contract. */
|
|
@@ -45,9 +46,20 @@ export declare const DIAGRAM_LOADING_FAILED_CLASS = "hydranium-diagram-loading-f
|
|
|
45
46
|
* Bound unconditionally by `AbstractHydraniumGlspTheiaFrontendModule`. To opt out,
|
|
46
47
|
* override {@link showLoadingOverlay} to a no-op; to change what is rendered,
|
|
47
48
|
* override {@link createLoadingOverlay} or {@link loadingLabel}.
|
|
49
|
+
*
|
|
50
|
+
* Its saveable is a {@link HydraniumGlspSaveable}, which keeps a pending save
|
|
51
|
+
* dirty, so Theia's exit check still counts it; see {@link createSaveable}.
|
|
48
52
|
*/
|
|
49
53
|
export declare class HydraniumGlspDiagramWidget extends GLSPDiagramWidget {
|
|
50
54
|
protected loadingOverlay?: HTMLElement;
|
|
55
|
+
/**
|
|
56
|
+
* Replaces the saveable GLSP's `configure` builds inline, with no factory
|
|
57
|
+
* seam. The widget factory calls `configure` before the widget reaches the
|
|
58
|
+
* shell, so Theia only ever tracks this one.
|
|
59
|
+
*/
|
|
60
|
+
configure(options: GLSPDiagramWidgetOptions, diContainer: Container): void;
|
|
61
|
+
/** The widget's saveable. Override to change how saves and dirty state behave. */
|
|
62
|
+
protected createSaveable(): GLSPDiagramWidget['saveable'];
|
|
51
63
|
protected onAfterAttach(msg: Message): void;
|
|
52
64
|
dispose(): void;
|
|
53
65
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagram-widget.d.ts","sourceRoot":"","sources":["../../src/browser/diagram-widget.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAGlF,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"diagram-widget.d.ts","sourceRoot":"","sources":["../../src/browser/diagram-widget.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAGlF,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGnG,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,KAAK,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAKnF,OAAO,iCAAiC,CAAC;AAEzC,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AAEjE;kFACkF;AAClF,eAAO,MAAM,4BAA4B,qCAAoC,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBACa,0BAA2B,SAAQ,iBAAiB;IAC9D,SAAS,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC;IAEvC;;;;OAIG;IACM,SAAS,CAAC,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,SAAS,GAAG,IAAI;IAOnF,kFAAkF;IAClF,SAAS,CAAC,cAAc,IAAI,iBAAiB,CAAC,UAAU,CAAC;cAItC,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAO3C,OAAO,IAAI,IAAI;IAKxB;;;;;;;OAOG;IACH,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAmBpC;;;;;;OAMG;IACH,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAQ1D;;;;;;;;;OASG;IACH,SAAS,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAa/C,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAKpC;;oFAEgF;IAChF,SAAS,CAAC,oBAAoB,IAAI,WAAW;IAa7C;sEACkE;IAClE,SAAS,KAAK,YAAY,IAAI,MAAM,CAEnC;IAED;;;;;OAKG;IACH,SAAS,KAAK,sBAAsB,IAAI,sBAAsB,GAAG,SAAS,CAGzE;CACH"}
|
|
@@ -19,6 +19,7 @@ const client_1 = require("@eclipse-glsp/client");
|
|
|
19
19
|
const theia_integration_1 = require("@eclipse-glsp/theia-integration");
|
|
20
20
|
const inversify_1 = require("@theia/core/shared/inversify");
|
|
21
21
|
const diagram_loader_1 = require("./diagram-loader");
|
|
22
|
+
const glsp_saveable_1 = require("./glsp-saveable");
|
|
22
23
|
// Shipped by this package rather than left to adopters: an overlay whose
|
|
23
24
|
// stylesheet was forgotten is an unstyled div in normal flow, which is a silent
|
|
24
25
|
// failure. Adopters override individual rules from their own stylesheet.
|
|
@@ -58,9 +59,27 @@ exports.DIAGRAM_LOADING_FAILED_CLASS = `${exports.DIAGRAM_LOADING_CLASS}-failed`
|
|
|
58
59
|
* Bound unconditionally by `AbstractHydraniumGlspTheiaFrontendModule`. To opt out,
|
|
59
60
|
* override {@link showLoadingOverlay} to a no-op; to change what is rendered,
|
|
60
61
|
* override {@link createLoadingOverlay} or {@link loadingLabel}.
|
|
62
|
+
*
|
|
63
|
+
* Its saveable is a {@link HydraniumGlspSaveable}, which keeps a pending save
|
|
64
|
+
* dirty, so Theia's exit check still counts it; see {@link createSaveable}.
|
|
61
65
|
*/
|
|
62
66
|
let HydraniumGlspDiagramWidget = class HydraniumGlspDiagramWidget extends theia_integration_1.GLSPDiagramWidget {
|
|
63
67
|
loadingOverlay;
|
|
68
|
+
/**
|
|
69
|
+
* Replaces the saveable GLSP's `configure` builds inline, with no factory
|
|
70
|
+
* seam. The widget factory calls `configure` before the widget reaches the
|
|
71
|
+
* shell, so Theia only ever tracks this one.
|
|
72
|
+
*/
|
|
73
|
+
configure(options, diContainer) {
|
|
74
|
+
super.configure(options, diContainer);
|
|
75
|
+
this.saveable.dispose();
|
|
76
|
+
this.saveable = this.createSaveable();
|
|
77
|
+
this.toDispose.push(this.saveable);
|
|
78
|
+
}
|
|
79
|
+
/** The widget's saveable. Override to change how saves and dirty state behave. */
|
|
80
|
+
createSaveable() {
|
|
81
|
+
return new glsp_saveable_1.HydraniumGlspSaveable(this.actionDispatcher, this.diContainer.get(client_1.EditorContextService));
|
|
82
|
+
}
|
|
64
83
|
onAfterAttach(msg) {
|
|
65
84
|
// Before `super`, which is what starts the load on first attach — so the
|
|
66
85
|
// overlay is already up when the round trip begins.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagram-widget.js","sourceRoot":"","sources":["../../src/browser/diagram-widget.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;AAElF,
|
|
1
|
+
{"version":3,"file":"diagram-widget.js","sourceRoot":"","sources":["../../src/browser/diagram-widget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
import { GLSPSaveable } from '@eclipse-glsp/theia-integration/lib/browser/diagram/glsp-saveable';
|
|
10
|
+
/**
|
|
11
|
+
* A `GLSPSaveable` whose every save is a request the server answers, and which
|
|
12
|
+
* stays dirty until that answer arrives.
|
|
13
|
+
*
|
|
14
|
+
* GLSP's sends a `SaveModelAction`, which the server does not answer. It takes
|
|
15
|
+
* the next dirty-state change to clean as the answer, but the editor context
|
|
16
|
+
* reports a change only when the dirty flag changes: the second of two saves
|
|
17
|
+
* gets no answer, and a failed save gets none either. Here each save sends a
|
|
18
|
+
* `RequestSaveModelAction` and settles on the response to that request only:
|
|
19
|
+
* it resolves when the server has saved, rejects when the server rejects it,
|
|
20
|
+
* and rejects when {@link saveTimeout} runs out. A save sent while another is
|
|
21
|
+
* pending is a request of its own, so an edit made between the two is saved.
|
|
22
|
+
*
|
|
23
|
+
* GLSP's also reports not dirty from the moment it sends a save. Theia's exit
|
|
24
|
+
* check asks only whether anything is dirty, so the window closes over a save
|
|
25
|
+
* the server has not finished, and a page reloaded right after Ctrl+S gives no
|
|
26
|
+
* warning. Here the saveable is dirty while any save is pending. GLSP's also
|
|
27
|
+
* gives up after 2 s, and Theia's Save All then counts the save as failed and
|
|
28
|
+
* closes the window anyway, so a slow save of a large model is cut off.
|
|
29
|
+
*
|
|
30
|
+
* The dirty flag still comes from the server. An edit whose dirty-state change
|
|
31
|
+
* has not arrived when Save All runs does not make the saveable dirty, so that
|
|
32
|
+
* edit is not saved.
|
|
33
|
+
*
|
|
34
|
+
* The server answers only where the diagram module registers the request's
|
|
35
|
+
* handler; `AbstractHydraniumGlspDiagramModule` does. A server without it does
|
|
36
|
+
* not advertise the request, and against such a server the saveable is GLSP's:
|
|
37
|
+
* it sends a `SaveModelAction` and reports dirty as GLSP's does. It does not
|
|
38
|
+
* keep only the dirty-while-pending half there, because GLSP's tracks one
|
|
39
|
+
* pending save and a second save overwrites it, so the first would never
|
|
40
|
+
* report clean.
|
|
41
|
+
*
|
|
42
|
+
* The answer means the server has done what its save does, which is not always
|
|
43
|
+
* the disk write: under the `fire-and-forget` `SaveDeliveryPolicy` the server
|
|
44
|
+
* answers before the write finishes and answers a failed write as saved, so a
|
|
45
|
+
* window closed on that answer can still lose the write.
|
|
46
|
+
*/
|
|
47
|
+
export declare class HydraniumGlspSaveable extends GLSPSaveable {
|
|
48
|
+
/**
|
|
49
|
+
* How long a save waits for the server's response before it rejects.
|
|
50
|
+
* Long, because the wait only runs out when the server hangs, and a save
|
|
51
|
+
* that rejects in Theia's Save All lets the window close over it.
|
|
52
|
+
*/
|
|
53
|
+
protected saveTimeout: number;
|
|
54
|
+
/** The saves sent and not yet settled. */
|
|
55
|
+
protected readonly pendingSaves: Set<Promise<void>>;
|
|
56
|
+
get dirty(): boolean;
|
|
57
|
+
save(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the server answers a {@link RequestSaveModelAction}. GLSP's model
|
|
60
|
+
* source registers a handler for each kind the server advertises before it
|
|
61
|
+
* requests the model, so the dispatcher knows it from the first save on.
|
|
62
|
+
*/
|
|
63
|
+
protected serverAnswersSaves(): boolean;
|
|
64
|
+
protected sendSave(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=glsp-saveable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glsp-saveable.d.ts","sourceRoot":"","sources":["../../src/browser/glsp-saveable.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAMlF,OAAO,EAAE,YAAY,EAAE,MAAM,mEAAmE,CAAC;AAEjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;IACpD;;;;OAIG;IACH,UAAmB,WAAW,SAAU;IACxC,0CAA0C;IAC1C,SAAS,CAAC,QAAQ,CAAC,YAAY,qBAA4B;IAE3D,IAAa,KAAK,IAAI,OAAO,CAK5B;IAEQ,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,IAAI,OAAO;IAIvC,SAAS,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAoBrC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/********************************************************************************
|
|
3
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
4
|
+
*
|
|
5
|
+
* This program and the accompanying materials are made available under the
|
|
6
|
+
* terms of the MIT License which is available in the project root.
|
|
7
|
+
*
|
|
8
|
+
* SPDX-License-Identifier: MIT
|
|
9
|
+
********************************************************************************/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.HydraniumGlspSaveable = void 0;
|
|
12
|
+
const protocol_1 = require("@hydranium/protocol");
|
|
13
|
+
// The module path, not the package root: the root also loads GLSP's diagram
|
|
14
|
+
// widget, which touches DOM globals at module load, and this module's unit tests
|
|
15
|
+
// load it in Node.
|
|
16
|
+
const glsp_saveable_1 = require("@eclipse-glsp/theia-integration/lib/browser/diagram/glsp-saveable");
|
|
17
|
+
/**
|
|
18
|
+
* A `GLSPSaveable` whose every save is a request the server answers, and which
|
|
19
|
+
* stays dirty until that answer arrives.
|
|
20
|
+
*
|
|
21
|
+
* GLSP's sends a `SaveModelAction`, which the server does not answer. It takes
|
|
22
|
+
* the next dirty-state change to clean as the answer, but the editor context
|
|
23
|
+
* reports a change only when the dirty flag changes: the second of two saves
|
|
24
|
+
* gets no answer, and a failed save gets none either. Here each save sends a
|
|
25
|
+
* `RequestSaveModelAction` and settles on the response to that request only:
|
|
26
|
+
* it resolves when the server has saved, rejects when the server rejects it,
|
|
27
|
+
* and rejects when {@link saveTimeout} runs out. A save sent while another is
|
|
28
|
+
* pending is a request of its own, so an edit made between the two is saved.
|
|
29
|
+
*
|
|
30
|
+
* GLSP's also reports not dirty from the moment it sends a save. Theia's exit
|
|
31
|
+
* check asks only whether anything is dirty, so the window closes over a save
|
|
32
|
+
* the server has not finished, and a page reloaded right after Ctrl+S gives no
|
|
33
|
+
* warning. Here the saveable is dirty while any save is pending. GLSP's also
|
|
34
|
+
* gives up after 2 s, and Theia's Save All then counts the save as failed and
|
|
35
|
+
* closes the window anyway, so a slow save of a large model is cut off.
|
|
36
|
+
*
|
|
37
|
+
* The dirty flag still comes from the server. An edit whose dirty-state change
|
|
38
|
+
* has not arrived when Save All runs does not make the saveable dirty, so that
|
|
39
|
+
* edit is not saved.
|
|
40
|
+
*
|
|
41
|
+
* The server answers only where the diagram module registers the request's
|
|
42
|
+
* handler; `AbstractHydraniumGlspDiagramModule` does. A server without it does
|
|
43
|
+
* not advertise the request, and against such a server the saveable is GLSP's:
|
|
44
|
+
* it sends a `SaveModelAction` and reports dirty as GLSP's does. It does not
|
|
45
|
+
* keep only the dirty-while-pending half there, because GLSP's tracks one
|
|
46
|
+
* pending save and a second save overwrites it, so the first would never
|
|
47
|
+
* report clean.
|
|
48
|
+
*
|
|
49
|
+
* The answer means the server has done what its save does, which is not always
|
|
50
|
+
* the disk write: under the `fire-and-forget` `SaveDeliveryPolicy` the server
|
|
51
|
+
* answers before the write finishes and answers a failed write as saved, so a
|
|
52
|
+
* window closed on that answer can still lose the write.
|
|
53
|
+
*/
|
|
54
|
+
class HydraniumGlspSaveable extends glsp_saveable_1.GLSPSaveable {
|
|
55
|
+
/**
|
|
56
|
+
* How long a save waits for the server's response before it rejects.
|
|
57
|
+
* Long, because the wait only runs out when the server hangs, and a save
|
|
58
|
+
* that rejects in Theia's Save All lets the window close over it.
|
|
59
|
+
*/
|
|
60
|
+
saveTimeout = 10_000;
|
|
61
|
+
/** The saves sent and not yet settled. */
|
|
62
|
+
pendingSaves = new Set();
|
|
63
|
+
get dirty() {
|
|
64
|
+
if (!this.serverAnswersSaves()) {
|
|
65
|
+
return super.dirty;
|
|
66
|
+
}
|
|
67
|
+
return this.editorContextService.isDirty || this.pendingSaves.size > 0;
|
|
68
|
+
}
|
|
69
|
+
save() {
|
|
70
|
+
if (!this.serverAnswersSaves()) {
|
|
71
|
+
return super.save();
|
|
72
|
+
}
|
|
73
|
+
if (this.editorContextService.isDirty) {
|
|
74
|
+
return this.sendSave();
|
|
75
|
+
}
|
|
76
|
+
// Nothing new to save, but a save still pending has not finished.
|
|
77
|
+
return Promise.all(this.pendingSaves).then(() => undefined);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether the server answers a {@link RequestSaveModelAction}. GLSP's model
|
|
81
|
+
* source registers a handler for each kind the server advertises before it
|
|
82
|
+
* requests the model, so the dispatcher knows it from the first save on.
|
|
83
|
+
*/
|
|
84
|
+
serverAnswersSaves() {
|
|
85
|
+
return this.actionDispatcher.hasHandler(protocol_1.RequestSaveModelAction.create());
|
|
86
|
+
}
|
|
87
|
+
sendSave() {
|
|
88
|
+
// `request` with a bound of our own rather than GLSP's `requestUntil`:
|
|
89
|
+
// that one forgets a request when it times out, so a late response is
|
|
90
|
+
// dispatched as an action no client handler takes, which rejects.
|
|
91
|
+
// Here a late response settles the forgotten request and nothing else.
|
|
92
|
+
const response = this.actionDispatcher.request(protocol_1.RequestSaveModelAction.create());
|
|
93
|
+
let timer;
|
|
94
|
+
const timedOut = new Promise((_resolve, reject) => {
|
|
95
|
+
timer = setTimeout(() => reject(new Error('Save operation timed out')), this.saveTimeout);
|
|
96
|
+
});
|
|
97
|
+
const saving = Promise.race([response, timedOut])
|
|
98
|
+
.then(() => undefined)
|
|
99
|
+
.finally(() => {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
this.pendingSaves.delete(saving);
|
|
102
|
+
this.onDirtyChangedEmitter.fire(undefined);
|
|
103
|
+
});
|
|
104
|
+
this.pendingSaves.add(saving);
|
|
105
|
+
return saving;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.HydraniumGlspSaveable = HydraniumGlspSaveable;
|
|
109
|
+
//# sourceMappingURL=glsp-saveable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glsp-saveable.js","sourceRoot":"","sources":["../../src/browser/glsp-saveable.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAElF,kDAA6D;AAC7D,4EAA4E;AAC5E,iFAAiF;AACjF,mBAAmB;AACnB,qGAAiG;AAEjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAa,qBAAsB,SAAQ,4BAAY;IACpD;;;;OAIG;IACgB,WAAW,GAAG,MAAM,CAAC;IACxC,0CAA0C;IACvB,YAAY,GAAG,IAAI,GAAG,EAAiB,CAAC;IAE3D,IAAa,KAAK;QACf,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC,KAAK,CAAC;QACtB,CAAC;QACD,OAAO,IAAI,CAAC,oBAAoB,CAAC,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC;IAC1E,CAAC;IAEQ,IAAI;QACV,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC;QACD,IAAI,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC1B,CAAC;QACD,kEAAkE;QAClE,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/D,CAAC;IAED;;;;OAIG;IACO,kBAAkB;QACzB,OAAO,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,iCAAsB,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5E,CAAC;IAES,QAAQ;QACf,uEAAuE;QACvE,sEAAsE;QACtE,kEAAkE;QAClE,uEAAuE;QACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,iCAAsB,CAAC,MAAM,EAAE,CAAC,CAAC;QAChF,IAAI,KAAgD,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;YACtD,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7F,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAkB,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;aAC5D,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;aACrB,OAAO,CAAC,GAAG,EAAE;YACX,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACN,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9B,OAAO,MAAM,CAAC;IACjB,CAAC;CACH;AAzDD,sDAyDC"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
import { type Action, type ElementAndRoutingPoints, type GModelElement, type GRoutableElement, GLSPHiddenBoundsUpdater, RequestBoundsAction } from '@eclipse-glsp/client';
|
|
10
10
|
import { ChannelLogger } from '@hydranium/client-theia/lib/browser';
|
|
11
|
-
import {
|
|
11
|
+
import { Clock } from '@hydranium/client-theia/lib/common';
|
|
12
12
|
import { type VNode } from 'snabbdom';
|
|
13
13
|
/**
|
|
14
14
|
* Instruments sprotty's hidden-bounds pass and repairs degenerate routes before
|
|
@@ -50,9 +50,7 @@ import { type VNode } from 'snabbdom';
|
|
|
50
50
|
*/
|
|
51
51
|
export declare class HydraniumHiddenBoundsUpdater extends GLSPHiddenBoundsUpdater {
|
|
52
52
|
protected readonly channel: ChannelLogger;
|
|
53
|
-
/**
|
|
54
|
-
* measures — no logic is gated on time, so there is nothing to fake in
|
|
55
|
-
* production. Overridden in tests for a deterministic elapsed value. */
|
|
53
|
+
/** The diagram container's {@link Clock}, or a `SystemClock`; it only measures. */
|
|
56
54
|
protected readonly clock: Clock;
|
|
57
55
|
/**
|
|
58
56
|
* Scope description for the in-flight request, read from the `cause` in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hidden-bounds-updater.d.ts","sourceRoot":"","sources":["../../src/browser/hidden-bounds-updater.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,KAAK,MAAM,EACX,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,uBAAuB,EAGvB,mBAAmB,EAGrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"hidden-bounds-updater.d.ts","sourceRoot":"","sources":["../../src/browser/hidden-bounds-updater.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,KAAK,MAAM,EACX,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,uBAAuB,EAGvB,mBAAmB,EAGrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,KAAK,EAAE,MAAM,oCAAoC,CAAC;AAG3D,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAOtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBACa,4BAA6B,SAAQ,uBAAuB;IAC/C,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAG,aAAa,CAAC;IAElE,mFAAmF;IACxD,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAqB;IAE/E;;;;OAIG;IACH,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK;IAQ9D;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CAAC,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAQ1D,2EAA2E;IAC3E,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAIhE;uEACmE;IACnE,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,uBAAuB;cAO9D,gBAAgB,IAAI,IAAI;IAa3C,mEAAmE;IACnE,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAIhF,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAazC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAM5C;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS;CAcvE"}
|
|
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.HydraniumHiddenBoundsUpdater = void 0;
|
|
21
21
|
const client_1 = require("@eclipse-glsp/client");
|
|
22
22
|
const browser_1 = require("@hydranium/client-theia/lib/browser");
|
|
23
|
+
const common_1 = require("@hydranium/client-theia/lib/common");
|
|
23
24
|
const protocol_1 = require("@hydranium/protocol");
|
|
24
25
|
const inversify_1 = require("@theia/core/shared/inversify");
|
|
25
26
|
/** How many scoped element ids the log line names before summarising the rest.
|
|
@@ -66,9 +67,7 @@ const SCOPE_ID_CAP = 3;
|
|
|
66
67
|
*/
|
|
67
68
|
let HydraniumHiddenBoundsUpdater = class HydraniumHiddenBoundsUpdater extends client_1.GLSPHiddenBoundsUpdater {
|
|
68
69
|
channel;
|
|
69
|
-
/**
|
|
70
|
-
* measures — no logic is gated on time, so there is nothing to fake in
|
|
71
|
-
* production. Overridden in tests for a deterministic elapsed value. */
|
|
70
|
+
/** The diagram container's {@link Clock}, or a `SystemClock`; it only measures. */
|
|
72
71
|
clock = new protocol_1.SystemClock();
|
|
73
72
|
/**
|
|
74
73
|
* Scope description for the in-flight request, read from the `cause` in
|
|
@@ -201,6 +200,11 @@ __decorate([
|
|
|
201
200
|
(0, inversify_1.inject)(browser_1.ChannelLogger),
|
|
202
201
|
__metadata("design:type", browser_1.ChannelLogger)
|
|
203
202
|
], HydraniumHiddenBoundsUpdater.prototype, "channel", void 0);
|
|
203
|
+
__decorate([
|
|
204
|
+
(0, inversify_1.inject)(common_1.Clock),
|
|
205
|
+
(0, inversify_1.optional)(),
|
|
206
|
+
__metadata("design:type", Object)
|
|
207
|
+
], HydraniumHiddenBoundsUpdater.prototype, "clock", void 0);
|
|
204
208
|
exports.HydraniumHiddenBoundsUpdater = HydraniumHiddenBoundsUpdater = __decorate([
|
|
205
209
|
(0, inversify_1.injectable)()
|
|
206
210
|
], HydraniumHiddenBoundsUpdater);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hidden-bounds-updater.js","sourceRoot":"","sources":["../../src/browser/hidden-bounds-updater.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;AAElF,iDAW8B;AAC9B,iEAAoE;AACpE,
|
|
1
|
+
{"version":3,"file":"hidden-bounds-updater.js","sourceRoot":"","sources":["../../src/browser/hidden-bounds-updater.ts"],"names":[],"mappings":"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"}
|
package/lib/browser/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from './diagram-widget';
|
|
|
14
14
|
export * from './glsp-client-theia-module';
|
|
15
15
|
export * from './glsp-diagram-manager';
|
|
16
16
|
export * from './glsp-message-service';
|
|
17
|
+
export * from './glsp-saveable';
|
|
17
18
|
export * from './glsp-theia-frontend-module';
|
|
18
19
|
export * from './hidden-bounds-updater';
|
|
19
20
|
export * from './hydranium-glsp-diagram-configuration';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAIlF,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wCAAwC,CAAC;AAKvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAIlF,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wCAAwC,CAAC;AAKvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,4CAA4C,CAAC"}
|
package/lib/browser/index.js
CHANGED
|
@@ -32,6 +32,7 @@ __exportStar(require("./diagram-widget"), exports);
|
|
|
32
32
|
__exportStar(require("./glsp-client-theia-module"), exports);
|
|
33
33
|
__exportStar(require("./glsp-diagram-manager"), exports);
|
|
34
34
|
__exportStar(require("./glsp-message-service"), exports);
|
|
35
|
+
__exportStar(require("./glsp-saveable"), exports);
|
|
35
36
|
__exportStar(require("./glsp-theia-frontend-module"), exports);
|
|
36
37
|
__exportStar(require("./hidden-bounds-updater"), exports);
|
|
37
38
|
__exportStar(require("./hydranium-glsp-diagram-configuration"), exports);
|
package/lib/browser/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,gEAAgE;AAChE,mFAAmF;AACnF,sDAAoC;AACpC,wDAAsC;AACtC,mDAAiC;AACjC,gEAA8C;AAC9C,mDAAiC;AACjC,6DAA2C;AAC3C,yDAAuC;AACvC,yDAAuC;AACvC,+DAA6C;AAC7C,0DAAwC;AACxC,yEAAuD"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,gEAAgE;AAChE,mFAAmF;AACnF,sDAAoC;AACpC,wDAAsC;AACtC,mDAAiC;AACjC,gEAA8C;AAC9C,mDAAiC;AACjC,6DAA2C;AAC3C,yDAAuC;AACvC,yDAAuC;AACvC,kDAAgC;AAChC,+DAA6C;AAC7C,0DAAwC;AACxC,yEAAuD"}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
import { AbstractSocketForwardingConnectionHandler } from '@hydranium/client-theia/lib/node';
|
|
10
|
-
import { type Channel, type Disposable } from '@theia/core';
|
|
11
10
|
import type * as net from 'net';
|
|
12
11
|
/** Options for `GlspServerConnectionHandler`. The language contribution
|
|
13
12
|
* id determines the per-language servicePath that Theia routes browser-frontend
|
|
@@ -42,18 +41,15 @@ export interface GlspServerConnectionHandlerOptions {
|
|
|
42
41
|
/**
|
|
43
42
|
* Bridges a Theia browser-frontend channel to a GLSP server's TCP socket.
|
|
44
43
|
*
|
|
45
|
-
* The port
|
|
46
|
-
* on the cross-head
|
|
47
|
-
*
|
|
44
|
+
* The port discovery, the buffer-and-replay race fix, the connect orchestration
|
|
45
|
+
* and the byte forwarder live on the cross-head
|
|
46
|
+
* {@link AbstractSocketForwardingConnectionHandler} base; this subclass supplies
|
|
47
|
+
* only the GLSP specifics — the per-language servicePath
|
|
48
48
|
* (`GLSPContribution.servicePath + '/' + languageContributionId`), the port
|
|
49
|
-
* command, the log labels
|
|
50
|
-
*
|
|
51
|
-
* language ids or command names subclass with a one-line `super({...})` call.
|
|
52
|
-
* Sibling of `@hydranium/data-client-theia`'s `DataServerConnectionHandler`,
|
|
53
|
-
* which subclasses the same base with its own GLSP-free forwarder.
|
|
49
|
+
* command, the log labels. Adopters with different language ids or command
|
|
50
|
+
* names subclass with a one-line `super({...})` call.
|
|
54
51
|
*/
|
|
55
52
|
export declare class GlspServerConnectionHandler extends AbstractSocketForwardingConnectionHandler {
|
|
56
53
|
constructor(options: GlspServerConnectionHandlerOptions);
|
|
57
|
-
protected forwardToSocketConnection(clientChannel: Channel, socket: net.Socket): Disposable;
|
|
58
54
|
}
|
|
59
55
|
//# sourceMappingURL=glsp-server-connection-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glsp-server-connection-handler.d.ts","sourceRoot":"","sources":["../../src/node/glsp-server-connection-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;
|
|
1
|
+
{"version":3,"file":"glsp-server-connection-handler.d.ts","sourceRoot":"","sources":["../../src/node/glsp-server-connection-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAGlF,OAAO,EAAE,yCAAyC,EAAE,MAAM,kCAAkC,CAAC;AAE7F,OAAO,KAAK,KAAK,GAAG,MAAM,KAAK,CAAC;AAEhC;;;iDAGiD;AACjD,MAAM,WAAW,kCAAkC;IAChD,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;GAUG;AACH,qBACa,2BAA4B,SAAQ,yCAAyC;gBAC9D,OAAO,EAAE,kCAAkC;CAYtE"}
|
|
@@ -22,23 +22,20 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.GlspServerConnectionHandler = void 0;
|
|
24
24
|
const common_1 = require("@eclipse-glsp/theia-integration/lib/common");
|
|
25
|
-
const node_1 = require("@
|
|
26
|
-
const node_2 = require("@hydranium/client-theia/lib/node");
|
|
25
|
+
const node_1 = require("@hydranium/client-theia/lib/node");
|
|
27
26
|
const inversify_1 = require("@theia/core/shared/inversify");
|
|
28
27
|
/**
|
|
29
28
|
* Bridges a Theia browser-frontend channel to a GLSP server's TCP socket.
|
|
30
29
|
*
|
|
31
|
-
* The port
|
|
32
|
-
* on the cross-head
|
|
33
|
-
*
|
|
30
|
+
* The port discovery, the buffer-and-replay race fix, the connect orchestration
|
|
31
|
+
* and the byte forwarder live on the cross-head
|
|
32
|
+
* {@link AbstractSocketForwardingConnectionHandler} base; this subclass supplies
|
|
33
|
+
* only the GLSP specifics — the per-language servicePath
|
|
34
34
|
* (`GLSPContribution.servicePath + '/' + languageContributionId`), the port
|
|
35
|
-
* command, the log labels
|
|
36
|
-
*
|
|
37
|
-
* language ids or command names subclass with a one-line `super({...})` call.
|
|
38
|
-
* Sibling of `@hydranium/data-client-theia`'s `DataServerConnectionHandler`,
|
|
39
|
-
* which subclasses the same base with its own GLSP-free forwarder.
|
|
35
|
+
* command, the log labels. Adopters with different language ids or command
|
|
36
|
+
* names subclass with a one-line `super({...})` call.
|
|
40
37
|
*/
|
|
41
|
-
let GlspServerConnectionHandler = class GlspServerConnectionHandler extends
|
|
38
|
+
let GlspServerConnectionHandler = class GlspServerConnectionHandler extends node_1.AbstractSocketForwardingConnectionHandler {
|
|
42
39
|
constructor(options) {
|
|
43
40
|
super({
|
|
44
41
|
path: common_1.GLSPContribution.servicePath + '/' + options.languageContributionId,
|
|
@@ -51,9 +48,6 @@ let GlspServerConnectionHandler = class GlspServerConnectionHandler extends node
|
|
|
51
48
|
onSocketCreated: options.onSocketCreated
|
|
52
49
|
});
|
|
53
50
|
}
|
|
54
|
-
forwardToSocketConnection(clientChannel, socket) {
|
|
55
|
-
return new node_1.SocketConnectionForwarder(clientChannel, socket);
|
|
56
|
-
}
|
|
57
51
|
};
|
|
58
52
|
exports.GlspServerConnectionHandler = GlspServerConnectionHandler;
|
|
59
53
|
exports.GlspServerConnectionHandler = GlspServerConnectionHandler = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glsp-server-connection-handler.js","sourceRoot":"","sources":["../../src/node/glsp-server-connection-handler.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;AAElF,uEAA8E;AAC9E,
|
|
1
|
+
{"version":3,"file":"glsp-server-connection-handler.js","sourceRoot":"","sources":["../../src/node/glsp-server-connection-handler.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;AAElF,uEAA8E;AAC9E,2DAA6F;AAC7F,4DAAqE;AAkCrE;;;;;;;;;;GAUG;AAEI,IAAM,2BAA2B,GAAjC,MAAM,2BAA4B,SAAQ,gDAAyC;IACvF,YAAyB,OAA2C;QACjE,KAAK,CAAC;YACH,IAAI,EAAE,yBAAgB,CAAC,WAAW,GAAG,GAAG,GAAG,OAAO,CAAC,sBAAsB;YACzE,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,YAAY,EAAE,MAAM;YACpB,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,kBAAkB;YACpD,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,eAAe,EAAE,OAAO,CAAC,eAAe;SAC1C,CAAC,CAAC;IACN,CAAC;CACH,CAAA;AAbY,kEAA2B;sCAA3B,2BAA2B;IADvC,IAAA,sBAAU,GAAE;IAEG,WAAA,IAAA,qBAAS,GAAE,CAAA;;GADd,2BAA2B,CAavC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/glsp-client-theia",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.127",
|
|
4
4
|
"description": "Theia-coupled client primitives for GLSP heads built on @hydranium/glsp-server. Provides the action dispatcher, channel logger, and connection-handler bases that Theia-based adopters extend; companion of @hydranium/glsp-server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hydranium",
|
|
@@ -77,15 +77,19 @@
|
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@eclipse-glsp/client": "2.7.0",
|
|
80
|
+
"@eclipse-glsp/server": "2.7.0",
|
|
80
81
|
"@eclipse-glsp/theia-integration": "2.7.0",
|
|
81
|
-
"@hydranium/client-theia": "1.0.0-next.
|
|
82
|
-
"@hydranium/
|
|
82
|
+
"@hydranium/client-theia": "1.0.0-next.127",
|
|
83
|
+
"@hydranium/core": "1.0.0-next.127",
|
|
84
|
+
"@hydranium/glsp-server": "1.0.0-next.127",
|
|
85
|
+
"@hydranium/protocol": "1.0.0-next.127",
|
|
83
86
|
"@theia/core": "^1.70.0",
|
|
84
87
|
"@theia/output": "^1.70.0",
|
|
85
88
|
"@theia/process": "^1.70.0",
|
|
86
89
|
"@theia/workspace": "^1.70.0",
|
|
87
90
|
"@types/node": "^22.0.0",
|
|
88
91
|
"inversify": "6.2.2",
|
|
92
|
+
"reflect-metadata": "0.2.2",
|
|
89
93
|
"rimraf": "^5.0.0",
|
|
90
94
|
"snabbdom": "3.5.1",
|
|
91
95
|
"typescript": "^5.8.0"
|
|
@@ -93,8 +97,8 @@
|
|
|
93
97
|
"peerDependencies": {
|
|
94
98
|
"@eclipse-glsp/client": "^2.6.0",
|
|
95
99
|
"@eclipse-glsp/theia-integration": "^2.6.0",
|
|
96
|
-
"@hydranium/client-theia": "1.0.0-next.
|
|
97
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
100
|
+
"@hydranium/client-theia": "1.0.0-next.127",
|
|
101
|
+
"@hydranium/protocol": "1.0.0-next.127",
|
|
98
102
|
"@theia/core": "^1.70.0",
|
|
99
103
|
"@theia/output": "^1.70.0",
|
|
100
104
|
"@theia/process": "^1.70.0",
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import { DiagramLoader } from '@eclipse-glsp/client';
|
|
11
|
-
import { GLSPDiagramWidget } from '@eclipse-glsp/theia-integration';
|
|
10
|
+
import { DiagramLoader, EditorContextService } from '@eclipse-glsp/client';
|
|
11
|
+
import { GLSPDiagramWidget, type GLSPDiagramWidgetOptions } from '@eclipse-glsp/theia-integration';
|
|
12
12
|
// Type-only: the `@theia/core/lib/browser` barrel touches DOM globals at module
|
|
13
13
|
// load, which the node-environment unit tests cannot provide.
|
|
14
14
|
import { type Message } from '@theia/core/lib/browser';
|
|
15
|
-
import { injectable } from '@theia/core/shared/inversify';
|
|
15
|
+
import { type Container, injectable } from '@theia/core/shared/inversify';
|
|
16
16
|
import { type DiagramLoadOutcome, HydraniumDiagramLoader } from './diagram-loader';
|
|
17
|
+
import { HydraniumGlspSaveable } from './glsp-saveable';
|
|
17
18
|
// Shipped by this package rather than left to adopters: an overlay whose
|
|
18
19
|
// stylesheet was forgotten is an unstyled div in normal flow, which is a silent
|
|
19
20
|
// failure. Adopters override individual rules from their own stylesheet.
|
|
@@ -56,11 +57,31 @@ export const DIAGRAM_LOADING_FAILED_CLASS = `${DIAGRAM_LOADING_CLASS}-failed`;
|
|
|
56
57
|
* Bound unconditionally by `AbstractHydraniumGlspTheiaFrontendModule`. To opt out,
|
|
57
58
|
* override {@link showLoadingOverlay} to a no-op; to change what is rendered,
|
|
58
59
|
* override {@link createLoadingOverlay} or {@link loadingLabel}.
|
|
60
|
+
*
|
|
61
|
+
* Its saveable is a {@link HydraniumGlspSaveable}, which keeps a pending save
|
|
62
|
+
* dirty, so Theia's exit check still counts it; see {@link createSaveable}.
|
|
59
63
|
*/
|
|
60
64
|
@injectable()
|
|
61
65
|
export class HydraniumGlspDiagramWidget extends GLSPDiagramWidget {
|
|
62
66
|
protected loadingOverlay?: HTMLElement;
|
|
63
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Replaces the saveable GLSP's `configure` builds inline, with no factory
|
|
70
|
+
* seam. The widget factory calls `configure` before the widget reaches the
|
|
71
|
+
* shell, so Theia only ever tracks this one.
|
|
72
|
+
*/
|
|
73
|
+
override configure(options: GLSPDiagramWidgetOptions, diContainer: Container): void {
|
|
74
|
+
super.configure(options, diContainer);
|
|
75
|
+
this.saveable.dispose();
|
|
76
|
+
this.saveable = this.createSaveable();
|
|
77
|
+
this.toDispose.push(this.saveable);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The widget's saveable. Override to change how saves and dirty state behave. */
|
|
81
|
+
protected createSaveable(): GLSPDiagramWidget['saveable'] {
|
|
82
|
+
return new HydraniumGlspSaveable(this.actionDispatcher, this.diContainer.get(EditorContextService));
|
|
83
|
+
}
|
|
84
|
+
|
|
64
85
|
protected override onAfterAttach(msg: Message): void {
|
|
65
86
|
// Before `super`, which is what starts the load on first attach — so the
|
|
66
87
|
// overlay is already up when the round trip begins.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { RequestSaveModelAction } from '@hydranium/protocol';
|
|
11
|
+
// The module path, not the package root: the root also loads GLSP's diagram
|
|
12
|
+
// widget, which touches DOM globals at module load, and this module's unit tests
|
|
13
|
+
// load it in Node.
|
|
14
|
+
import { GLSPSaveable } from '@eclipse-glsp/theia-integration/lib/browser/diagram/glsp-saveable';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A `GLSPSaveable` whose every save is a request the server answers, and which
|
|
18
|
+
* stays dirty until that answer arrives.
|
|
19
|
+
*
|
|
20
|
+
* GLSP's sends a `SaveModelAction`, which the server does not answer. It takes
|
|
21
|
+
* the next dirty-state change to clean as the answer, but the editor context
|
|
22
|
+
* reports a change only when the dirty flag changes: the second of two saves
|
|
23
|
+
* gets no answer, and a failed save gets none either. Here each save sends a
|
|
24
|
+
* `RequestSaveModelAction` and settles on the response to that request only:
|
|
25
|
+
* it resolves when the server has saved, rejects when the server rejects it,
|
|
26
|
+
* and rejects when {@link saveTimeout} runs out. A save sent while another is
|
|
27
|
+
* pending is a request of its own, so an edit made between the two is saved.
|
|
28
|
+
*
|
|
29
|
+
* GLSP's also reports not dirty from the moment it sends a save. Theia's exit
|
|
30
|
+
* check asks only whether anything is dirty, so the window closes over a save
|
|
31
|
+
* the server has not finished, and a page reloaded right after Ctrl+S gives no
|
|
32
|
+
* warning. Here the saveable is dirty while any save is pending. GLSP's also
|
|
33
|
+
* gives up after 2 s, and Theia's Save All then counts the save as failed and
|
|
34
|
+
* closes the window anyway, so a slow save of a large model is cut off.
|
|
35
|
+
*
|
|
36
|
+
* The dirty flag still comes from the server. An edit whose dirty-state change
|
|
37
|
+
* has not arrived when Save All runs does not make the saveable dirty, so that
|
|
38
|
+
* edit is not saved.
|
|
39
|
+
*
|
|
40
|
+
* The server answers only where the diagram module registers the request's
|
|
41
|
+
* handler; `AbstractHydraniumGlspDiagramModule` does. A server without it does
|
|
42
|
+
* not advertise the request, and against such a server the saveable is GLSP's:
|
|
43
|
+
* it sends a `SaveModelAction` and reports dirty as GLSP's does. It does not
|
|
44
|
+
* keep only the dirty-while-pending half there, because GLSP's tracks one
|
|
45
|
+
* pending save and a second save overwrites it, so the first would never
|
|
46
|
+
* report clean.
|
|
47
|
+
*
|
|
48
|
+
* The answer means the server has done what its save does, which is not always
|
|
49
|
+
* the disk write: under the `fire-and-forget` `SaveDeliveryPolicy` the server
|
|
50
|
+
* answers before the write finishes and answers a failed write as saved, so a
|
|
51
|
+
* window closed on that answer can still lose the write.
|
|
52
|
+
*/
|
|
53
|
+
export class HydraniumGlspSaveable extends GLSPSaveable {
|
|
54
|
+
/**
|
|
55
|
+
* How long a save waits for the server's response before it rejects.
|
|
56
|
+
* Long, because the wait only runs out when the server hangs, and a save
|
|
57
|
+
* that rejects in Theia's Save All lets the window close over it.
|
|
58
|
+
*/
|
|
59
|
+
protected override saveTimeout = 10_000;
|
|
60
|
+
/** The saves sent and not yet settled. */
|
|
61
|
+
protected readonly pendingSaves = new Set<Promise<void>>();
|
|
62
|
+
|
|
63
|
+
override get dirty(): boolean {
|
|
64
|
+
if (!this.serverAnswersSaves()) {
|
|
65
|
+
return super.dirty;
|
|
66
|
+
}
|
|
67
|
+
return this.editorContextService.isDirty || this.pendingSaves.size > 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override save(): Promise<void> {
|
|
71
|
+
if (!this.serverAnswersSaves()) {
|
|
72
|
+
return super.save();
|
|
73
|
+
}
|
|
74
|
+
if (this.editorContextService.isDirty) {
|
|
75
|
+
return this.sendSave();
|
|
76
|
+
}
|
|
77
|
+
// Nothing new to save, but a save still pending has not finished.
|
|
78
|
+
return Promise.all(this.pendingSaves).then(() => undefined);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether the server answers a {@link RequestSaveModelAction}. GLSP's model
|
|
83
|
+
* source registers a handler for each kind the server advertises before it
|
|
84
|
+
* requests the model, so the dispatcher knows it from the first save on.
|
|
85
|
+
*/
|
|
86
|
+
protected serverAnswersSaves(): boolean {
|
|
87
|
+
return this.actionDispatcher.hasHandler(RequestSaveModelAction.create());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected sendSave(): Promise<void> {
|
|
91
|
+
// `request` with a bound of our own rather than GLSP's `requestUntil`:
|
|
92
|
+
// that one forgets a request when it times out, so a late response is
|
|
93
|
+
// dispatched as an action no client handler takes, which rejects.
|
|
94
|
+
// Here a late response settles the forgotten request and nothing else.
|
|
95
|
+
const response = this.actionDispatcher.request(RequestSaveModelAction.create());
|
|
96
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
97
|
+
const timedOut = new Promise<never>((_resolve, reject) => {
|
|
98
|
+
timer = setTimeout(() => reject(new Error('Save operation timed out')), this.saveTimeout);
|
|
99
|
+
});
|
|
100
|
+
const saving: Promise<void> = Promise.race([response, timedOut])
|
|
101
|
+
.then(() => undefined)
|
|
102
|
+
.finally(() => {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
this.pendingSaves.delete(saving);
|
|
105
|
+
this.onDirtyChangedEmitter.fire(undefined);
|
|
106
|
+
});
|
|
107
|
+
this.pendingSaves.add(saving);
|
|
108
|
+
return saving;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -20,8 +20,9 @@ import {
|
|
|
20
20
|
isRoutable
|
|
21
21
|
} from '@eclipse-glsp/client';
|
|
22
22
|
import { ChannelLogger } from '@hydranium/client-theia/lib/browser';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { Clock } from '@hydranium/client-theia/lib/common';
|
|
24
|
+
import { SystemClock } from '@hydranium/protocol';
|
|
25
|
+
import { inject, injectable, optional } from '@theia/core/shared/inversify';
|
|
25
26
|
import { type VNode } from 'snabbdom';
|
|
26
27
|
|
|
27
28
|
/** How many scoped element ids the log line names before summarising the rest.
|
|
@@ -71,10 +72,8 @@ const SCOPE_ID_CAP = 3;
|
|
|
71
72
|
export class HydraniumHiddenBoundsUpdater extends GLSPHiddenBoundsUpdater {
|
|
72
73
|
@inject(ChannelLogger) protected readonly channel!: ChannelLogger;
|
|
73
74
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
* production. Overridden in tests for a deterministic elapsed value. */
|
|
77
|
-
protected readonly clock: Clock = new SystemClock();
|
|
75
|
+
/** The diagram container's {@link Clock}, or a `SystemClock`; it only measures. */
|
|
76
|
+
@inject(Clock) @optional() protected readonly clock: Clock = new SystemClock();
|
|
78
77
|
|
|
79
78
|
/**
|
|
80
79
|
* Scope description for the in-flight request, read from the `cause` in
|
package/src/browser/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from './diagram-widget';
|
|
|
17
17
|
export * from './glsp-client-theia-module';
|
|
18
18
|
export * from './glsp-diagram-manager';
|
|
19
19
|
export * from './glsp-message-service';
|
|
20
|
+
export * from './glsp-saveable';
|
|
20
21
|
export * from './glsp-theia-frontend-module';
|
|
21
22
|
export * from './hidden-bounds-updater';
|
|
22
23
|
export * from './hydranium-glsp-diagram-configuration';
|
|
@@ -8,9 +8,7 @@
|
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
10
|
import { GLSPContribution } from '@eclipse-glsp/theia-integration/lib/common';
|
|
11
|
-
import { SocketConnectionForwarder } from '@eclipse-glsp/theia-integration/lib/node';
|
|
12
11
|
import { AbstractSocketForwardingConnectionHandler } from '@hydranium/client-theia/lib/node';
|
|
13
|
-
import { type Channel, type Disposable } from '@theia/core';
|
|
14
12
|
import { injectable, unmanaged } from '@theia/core/shared/inversify';
|
|
15
13
|
import type * as net from 'net';
|
|
16
14
|
|
|
@@ -48,15 +46,13 @@ export interface GlspServerConnectionHandlerOptions {
|
|
|
48
46
|
/**
|
|
49
47
|
* Bridges a Theia browser-frontend channel to a GLSP server's TCP socket.
|
|
50
48
|
*
|
|
51
|
-
* The port
|
|
52
|
-
* on the cross-head
|
|
53
|
-
*
|
|
49
|
+
* The port discovery, the buffer-and-replay race fix, the connect orchestration
|
|
50
|
+
* and the byte forwarder live on the cross-head
|
|
51
|
+
* {@link AbstractSocketForwardingConnectionHandler} base; this subclass supplies
|
|
52
|
+
* only the GLSP specifics — the per-language servicePath
|
|
54
53
|
* (`GLSPContribution.servicePath + '/' + languageContributionId`), the port
|
|
55
|
-
* command, the log labels
|
|
56
|
-
*
|
|
57
|
-
* language ids or command names subclass with a one-line `super({...})` call.
|
|
58
|
-
* Sibling of `@hydranium/data-client-theia`'s `DataServerConnectionHandler`,
|
|
59
|
-
* which subclasses the same base with its own GLSP-free forwarder.
|
|
54
|
+
* command, the log labels. Adopters with different language ids or command
|
|
55
|
+
* names subclass with a one-line `super({...})` call.
|
|
60
56
|
*/
|
|
61
57
|
@injectable()
|
|
62
58
|
export class GlspServerConnectionHandler extends AbstractSocketForwardingConnectionHandler {
|
|
@@ -72,8 +68,4 @@ export class GlspServerConnectionHandler extends AbstractSocketForwardingConnect
|
|
|
72
68
|
onSocketCreated: options.onSocketCreated
|
|
73
69
|
});
|
|
74
70
|
}
|
|
75
|
-
|
|
76
|
-
protected forwardToSocketConnection(clientChannel: Channel, socket: net.Socket): Disposable {
|
|
77
|
-
return new SocketConnectionForwarder(clientChannel, socket);
|
|
78
|
-
}
|
|
79
71
|
}
|