@hydranium/glsp-server 1.0.0-next.317 → 1.0.0-next.319
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/lib/command/hydranium-glsp-command-stack.d.ts +13 -2
- package/lib/command/hydranium-glsp-command-stack.d.ts.map +1 -1
- package/lib/command/hydranium-glsp-command-stack.js +49 -2
- package/lib/command/hydranium-glsp-command-stack.js.map +1 -1
- package/lib/command/hydranium-glsp-operation-command.d.ts +21 -2
- package/lib/command/hydranium-glsp-operation-command.d.ts.map +1 -1
- package/lib/command/hydranium-glsp-operation-command.js +29 -3
- package/lib/command/hydranium-glsp-operation-command.js.map +1 -1
- package/lib/command/hydranium-glsp-recording-command.d.ts +7 -0
- package/lib/command/hydranium-glsp-recording-command.d.ts.map +1 -1
- package/lib/command/hydranium-glsp-recording-command.js +12 -1
- package/lib/command/hydranium-glsp-recording-command.js.map +1 -1
- package/lib/command/hydranium-glsp-undo-redo-action-handler.d.ts +58 -1
- package/lib/command/hydranium-glsp-undo-redo-action-handler.d.ts.map +1 -1
- package/lib/command/hydranium-glsp-undo-redo-action-handler.js +116 -1
- package/lib/command/hydranium-glsp-undo-redo-action-handler.js.map +1 -1
- package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.d.ts +11 -1
- package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.d.ts.map +1 -1
- package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.js +22 -3
- package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.js.map +1 -1
- package/lib/messages/index.d.ts +3 -0
- package/lib/messages/index.d.ts.map +1 -1
- package/lib/messages/index.js +3 -0
- package/lib/messages/index.js.map +1 -1
- package/lib/state/abstract-hydranium-glsp-state.d.ts +12 -1
- package/lib/state/abstract-hydranium-glsp-state.d.ts.map +1 -1
- package/lib/state/abstract-hydranium-glsp-state.js +17 -1
- package/lib/state/abstract-hydranium-glsp-state.js.map +1 -1
- package/lib/status/diagram-status-reporter.d.ts +23 -3
- package/lib/status/diagram-status-reporter.d.ts.map +1 -1
- package/lib/status/diagram-status-reporter.js +28 -2
- package/lib/status/diagram-status-reporter.js.map +1 -1
- package/lib/submission/hydranium-glsp-submission-handler.d.ts +12 -1
- package/lib/submission/hydranium-glsp-submission-handler.d.ts.map +1 -1
- package/lib/submission/hydranium-glsp-submission-handler.js +30 -4
- package/lib/submission/hydranium-glsp-submission-handler.js.map +1 -1
- package/package.json +7 -7
- package/src/command/hydranium-glsp-command-stack.ts +52 -2
- package/src/command/hydranium-glsp-operation-command.ts +42 -3
- package/src/command/hydranium-glsp-recording-command.ts +14 -1
- package/src/command/hydranium-glsp-undo-redo-action-handler.ts +146 -1
- package/src/computed-bounds/hydranium-glsp-computed-bounds-action-handler.ts +21 -3
- package/src/messages/index.ts +12 -0
- package/src/state/abstract-hydranium-glsp-state.ts +19 -1
- package/src/status/diagram-status-reporter.ts +37 -4
- package/src/submission/hydranium-glsp-submission-handler.ts +26 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydranium-glsp-submission-handler.js","sourceRoot":"","sources":["../../src/submission/hydranium-glsp-submission-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;;;;;;;;;;AAElF,OAAO,
|
|
1
|
+
{"version":3,"file":"hydranium-glsp-submission-handler.js","sourceRoot":"","sources":["../../src/submission/hydranium-glsp-submission-handler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/glsp-server",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.319",
|
|
4
4
|
"description": "GLSP protocol head for the hydranium framework. Peer of @hydranium/core/lsp and @hydranium/data-server; built on @hydranium/core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"diagram",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@eclipse-glsp/server": "2.7.0",
|
|
73
|
-
"@hydranium/core": "1.0.0-next.
|
|
74
|
-
"@hydranium/langium": "1.0.0-next.
|
|
75
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
73
|
+
"@hydranium/core": "1.0.0-next.319",
|
|
74
|
+
"@hydranium/langium": "1.0.0-next.319",
|
|
75
|
+
"@hydranium/protocol": "1.0.0-next.319",
|
|
76
76
|
"inversify": "6.2.2",
|
|
77
77
|
"reflect-metadata": "0.2.2",
|
|
78
78
|
"rimraf": "^5.0.0",
|
|
@@ -82,9 +82,9 @@
|
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
84
|
"@eclipse-glsp/server": "^2.6.0",
|
|
85
|
-
"@hydranium/core": "1.0.0-next.
|
|
86
|
-
"@hydranium/langium": "1.0.0-next.
|
|
87
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
85
|
+
"@hydranium/core": "1.0.0-next.319",
|
|
86
|
+
"@hydranium/langium": "1.0.0-next.319",
|
|
87
|
+
"@hydranium/protocol": "1.0.0-next.319",
|
|
88
88
|
"inversify": "^6.0.0",
|
|
89
89
|
"reflect-metadata": "^0.2.2",
|
|
90
90
|
"vscode-jsonrpc": "^9.0.0",
|
|
@@ -7,15 +7,18 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import { DefaultCommandStack, ModelState } from '@eclipse-glsp/server';
|
|
10
|
+
import { type Command, CompoundCommand, DefaultCommandStack, ModelState } from '@eclipse-glsp/server';
|
|
11
11
|
import { type ServerSharedServices } from '@hydranium/core';
|
|
12
12
|
import { inject, injectable } from 'inversify';
|
|
13
13
|
import { HydraniumTypes } from '../state/hydranium-shared-core-services.js';
|
|
14
|
+
import { isCommandArray } from './hydranium-glsp-operation-command.js';
|
|
15
|
+
import { HydraniumGlspRecordingCommand } from './hydranium-glsp-recording-command.js';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* GLSP's command stack with the diagram's dirty state read from the text store:
|
|
17
19
|
* dirty while any document the diagram's client session has open differs from
|
|
18
|
-
* its file, as the store last knew the file. Undo and redo are GLSP's
|
|
20
|
+
* its file, as the store last knew the file. Undo and redo are GLSP's; a
|
|
21
|
+
* command whose every write was dropped is not pushed.
|
|
19
22
|
*
|
|
20
23
|
* GLSP's own answer counts the diagram's commands since its last save, so it
|
|
21
24
|
* misses every change the diagram did not make: another client's unsaved edit
|
|
@@ -34,6 +37,43 @@ export class HydraniumGlspCommandStack extends DefaultCommandStack {
|
|
|
34
37
|
return textDocuments.openDocuments().some(open => open.clients.includes(clientId) && textDocuments.isDirty(open.uri));
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Lifted from `@eclipse-glsp/server` 2.7.0's `DefaultCommandStack.execute`,
|
|
42
|
+
* pushing no entry for a recording command executed outside an operation
|
|
43
|
+
* whose write was dropped, or a `CompoundCommand` of only such commands at
|
|
44
|
+
* any depth: its undo would do nothing, and the user's next undo would be
|
|
45
|
+
* spent on it. A compound that also changed something is pushed, warning
|
|
46
|
+
* that its undo step leaves out what was dropped. The accompanying test
|
|
47
|
+
* pins upstream's source.
|
|
48
|
+
*/
|
|
49
|
+
override async execute(command: Command): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
await command.execute();
|
|
52
|
+
} catch (error: unknown) {
|
|
53
|
+
this.handleError(error);
|
|
54
|
+
}
|
|
55
|
+
const leaves = leavesOf(command);
|
|
56
|
+
const dropped = leaves.filter(leaf => leaf instanceof HydraniumGlspRecordingCommand && leaf.dropped).length;
|
|
57
|
+
if (dropped > 0 && dropped === leaves.length) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (dropped > 0) {
|
|
61
|
+
this.logger.warn(
|
|
62
|
+
`${dropped} of ${leaves.length} commands of a compound executed outside an operation had their write dropped, ` +
|
|
63
|
+
'so its undo step leaves them out; execute them in one operation to write and undo them as one'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
// Clear the command list past the top (i.e. the old redo list)
|
|
67
|
+
this.commands = this.commands.slice(0, this.top + 1);
|
|
68
|
+
this.commands.push(command);
|
|
69
|
+
++this.top;
|
|
70
|
+
// if the saveIndex points to the old redo list we can never reach a state again
|
|
71
|
+
// where save is not necessary. => ensure that `isDirty` always returns true
|
|
72
|
+
if (this.saveIndex >= this.top) {
|
|
73
|
+
this.saveIndex = -2;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
37
77
|
/**
|
|
38
78
|
* Lifted from `@eclipse-glsp/server` 2.7.0's `DefaultCommandStack.handleError`
|
|
39
79
|
* without its `error` line: the failure is rethrown, and whoever ends it
|
|
@@ -45,3 +85,13 @@ export class HydraniumGlspCommandStack extends DefaultCommandStack {
|
|
|
45
85
|
throw error;
|
|
46
86
|
}
|
|
47
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The commands `command` executes: itself, or a `CompoundCommand`'s children
|
|
91
|
+
* at any depth, read from GLSP's protected `CompoundCommand.commands`, as the
|
|
92
|
+
* operation reads them.
|
|
93
|
+
*/
|
|
94
|
+
function leavesOf(command: Command): Command[] {
|
|
95
|
+
const children: unknown = command instanceof CompoundCommand ? Reflect.get(command, 'commands') : undefined;
|
|
96
|
+
return isCommandArray(children) ? children.flatMap(leavesOf) : [command];
|
|
97
|
+
}
|
|
@@ -61,6 +61,7 @@ export interface OperationInternals {
|
|
|
61
61
|
recordWrittenRoot(root: AstNode): void;
|
|
62
62
|
recordDropped(current: AstNode | undefined): void;
|
|
63
63
|
recordExecuted(step: OperationSideEffect): void;
|
|
64
|
+
isUndoingOrRedoing(): boolean;
|
|
64
65
|
assertNotDuringUndoRedo(label: string): void;
|
|
65
66
|
amendBefore(update: (before: AnyObject) => AnyObject): void;
|
|
66
67
|
}
|
|
@@ -84,6 +85,39 @@ export function openOperationOf(state: object): OperationInternals | undefined {
|
|
|
84
85
|
return openOperations.get(state);
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* How many operations an undo or redo skipped and applied, as one command-stack
|
|
90
|
+
* entry, a compound executed outside an operation, can undo several.
|
|
91
|
+
*/
|
|
92
|
+
export interface UndoRedoTally {
|
|
93
|
+
skipped: number;
|
|
94
|
+
applied: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The tally of each state's undo or redo, until {@link takeUndoRedoTally} reads it. */
|
|
98
|
+
const undoRedoTallies = new WeakMap<object, UndoRedoTally>();
|
|
99
|
+
|
|
100
|
+
function tallyOf(state: object): UndoRedoTally {
|
|
101
|
+
let tally = undoRedoTallies.get(state);
|
|
102
|
+
if (!tally) {
|
|
103
|
+
tally = { skipped: 0, applied: 0 };
|
|
104
|
+
undoRedoTallies.set(state, tally);
|
|
105
|
+
}
|
|
106
|
+
return tally;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The operations undone or redone on `state` since the last call, skipped and
|
|
111
|
+
* applied, for the handler that ran the undo or redo to tell the client: GLSP's
|
|
112
|
+
* command stack takes no answer from an undo, and moves past a skipped one as
|
|
113
|
+
* past any other.
|
|
114
|
+
*/
|
|
115
|
+
export function takeUndoRedoTally(state: object): UndoRedoTally {
|
|
116
|
+
const tally = undoRedoTallies.get(state) ?? { skipped: 0, applied: 0 };
|
|
117
|
+
undoRedoTallies.delete(state);
|
|
118
|
+
return tally;
|
|
119
|
+
}
|
|
120
|
+
|
|
87
121
|
/** Run `operation` as {@link HydraniumGlspOperationCommand} describes. */
|
|
88
122
|
export function runOperation<TSourceModel extends AnyObject>(
|
|
89
123
|
operation: HydraniumGlspOperationCommand<TSourceModel>,
|
|
@@ -172,6 +206,7 @@ export class HydraniumGlspOperationCommand<TSourceModel extends AnyObject = AnyO
|
|
|
172
206
|
this.before = update(this.before) as TSourceModel;
|
|
173
207
|
}
|
|
174
208
|
},
|
|
209
|
+
isUndoingOrRedoing: () => this.undoingOrRedoing,
|
|
175
210
|
assertNotDuringUndoRedo: label => {
|
|
176
211
|
if (this.undoingOrRedoing) {
|
|
177
212
|
throw new Error(
|
|
@@ -411,8 +446,9 @@ export class HydraniumGlspOperationCommand<TSourceModel extends AnyObject = AnyO
|
|
|
411
446
|
/**
|
|
412
447
|
* Resolve the transition onto the current model, run the commands' side
|
|
413
448
|
* effects, then write once, gated on the versions the model was read at. A
|
|
414
|
-
* collision does nothing but warn; a transition with nothing left to
|
|
415
|
-
* or one the model already holds, still runs the side effects.
|
|
449
|
+
* collision does nothing but warn; a transition with nothing left to
|
|
450
|
+
* apply, or one the model already holds, still runs the side effects. Either
|
|
451
|
+
* way the outcome is counted for {@link takeUndoRedoTally}.
|
|
416
452
|
*
|
|
417
453
|
* The caller holds `runExclusive`, which nothing here takes again.
|
|
418
454
|
*/
|
|
@@ -438,10 +474,12 @@ export class HydraniumGlspOperationCommand<TSourceModel extends AnyObject = AnyO
|
|
|
438
474
|
this.modelState.logger.warn(
|
|
439
475
|
`${direction === 'undo' ? 'Undo' : 'Redo'} of an operation skipped (${outcome.status}); a foreign edit changed a shared field`
|
|
440
476
|
);
|
|
477
|
+
tallyOf(this.modelState).skipped++;
|
|
441
478
|
return;
|
|
442
479
|
}
|
|
443
480
|
}
|
|
444
481
|
await this.undoOrRedoSideEffects(direction, merged);
|
|
482
|
+
tallyOf(this.modelState).applied++;
|
|
445
483
|
}
|
|
446
484
|
|
|
447
485
|
/**
|
|
@@ -597,6 +635,7 @@ export class HydraniumGlspOperationCommand<TSourceModel extends AnyObject = AnyO
|
|
|
597
635
|
}
|
|
598
636
|
}
|
|
599
637
|
|
|
600
|
-
|
|
638
|
+
/** Whether `value` is a list of commands, as a `CompoundCommand` holds its children. */
|
|
639
|
+
export function isCommandArray(value: unknown): value is Command[] {
|
|
601
640
|
return Array.isArray(value) && value.every(element => typeof element === 'object' && element !== null && 'execute' in element);
|
|
602
641
|
}
|
|
@@ -48,6 +48,17 @@ export class HydraniumGlspRecordingCommand<TSourceModel extends AnyObject> exten
|
|
|
48
48
|
/** The operation this command opened, when it executed outside one. */
|
|
49
49
|
protected ownOperation?: HydraniumGlspOperationCommand<TSourceModel>;
|
|
50
50
|
|
|
51
|
+
/** Backing field for {@link dropped}. */
|
|
52
|
+
protected _dropped = false;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether the operation this command opened, executing outside one, had its
|
|
56
|
+
* write dropped: the command changed nothing, and its undo does nothing.
|
|
57
|
+
*/
|
|
58
|
+
get dropped(): boolean {
|
|
59
|
+
return this._dropped;
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
constructor(
|
|
52
63
|
modelState: HydraniumGlspRecordingState<TSourceModel>,
|
|
53
64
|
/** Human-readable label for the operation; appears in the timing-pair log line. */
|
|
@@ -66,7 +77,9 @@ export class HydraniumGlspRecordingCommand<TSourceModel extends AnyObject> exten
|
|
|
66
77
|
const open = openOperationOf(this.modelState);
|
|
67
78
|
if (!open) {
|
|
68
79
|
const operation = new HydraniumGlspOperationCommand<TSourceModel>(this.modelState);
|
|
69
|
-
|
|
80
|
+
const outcome = await this.modelState.runExclusive(() => runOperation(operation, () => this));
|
|
81
|
+
this._dropped = outcome === 'dropped';
|
|
82
|
+
if (outcome === 'executed') {
|
|
70
83
|
this.ownOperation = operation;
|
|
71
84
|
}
|
|
72
85
|
return;
|
|
@@ -7,10 +7,75 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import { type Action, ModelState, UndoRedoActionHandler } from '@eclipse-glsp/server';
|
|
10
|
+
import { type Action, Logger, MessageAction, ModelState, RedoAction, UndoAction, UndoRedoActionHandler } from '@eclipse-glsp/server';
|
|
11
|
+
import { type ServerSharedServices } from '@hydranium/core';
|
|
11
12
|
import { type AstNode } from '@hydranium/langium';
|
|
13
|
+
import { type MessageDefinition, defineMessage, describeError, isConflictError } from '@hydranium/protocol';
|
|
12
14
|
import { inject, injectable } from 'inversify';
|
|
13
15
|
import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
|
|
16
|
+
import { HydraniumTypes } from '../state/hydranium-shared-core-services.js';
|
|
17
|
+
import { isDiagramEditDroppedError, logActionFailure } from './diagram-edit-dropped-error.js';
|
|
18
|
+
import { type UndoRedoDirection, takeUndoRedoTally } from './hydranium-glsp-operation-command.js';
|
|
19
|
+
|
|
20
|
+
/** An undo did nothing: another editor has since changed a field it would restore. */
|
|
21
|
+
export const DIAGRAM_UNDO_SKIPPED = defineMessage(
|
|
22
|
+
'hydranium/glsp-server/diagram-undo-skipped',
|
|
23
|
+
'Nothing was undone: another editor has since changed what this undo would restore.'
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
/** A redo did nothing: another editor has since changed a field it would reapply. */
|
|
27
|
+
export const DIAGRAM_REDO_SKIPPED = defineMessage(
|
|
28
|
+
'hydranium/glsp-server/diagram-redo-skipped',
|
|
29
|
+
'Nothing was redone: another editor has since changed what this redo would reapply.'
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Part of an undo did nothing: of the operations one undo step undoes, as a
|
|
34
|
+
* compound executed outside an operation does, another editor has since
|
|
35
|
+
* changed a field some would restore, and the rest were undone.
|
|
36
|
+
*/
|
|
37
|
+
export const DIAGRAM_UNDO_PARTLY_SKIPPED = defineMessage(
|
|
38
|
+
'hydranium/glsp-server/diagram-undo-partly-skipped',
|
|
39
|
+
'Part of this undo was not applied: another editor has since changed some of what it would restore.'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/** Part of a redo did nothing, as {@link DIAGRAM_UNDO_PARTLY_SKIPPED} describes for an undo. */
|
|
43
|
+
export const DIAGRAM_REDO_PARTLY_SKIPPED = defineMessage(
|
|
44
|
+
'hydranium/glsp-server/diagram-redo-partly-skipped',
|
|
45
|
+
'Part of this redo was not applied: another editor has since changed some of what it would reapply.'
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* An undo failed because another editor changed a field it writes while it
|
|
50
|
+
* ran. GLSP clears the undo history on every failed undo or redo, so the
|
|
51
|
+
* message says so: the next undo otherwise does nothing, unexplained.
|
|
52
|
+
*/
|
|
53
|
+
export const DIAGRAM_UNDO_FAILED = defineMessage(
|
|
54
|
+
'hydranium/glsp-server/diagram-undo-failed',
|
|
55
|
+
'Could not undo: another editor changed the same element meanwhile. The undo and redo history of this diagram was cleared.'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/** A redo failed as {@link DIAGRAM_UNDO_FAILED} describes for an undo. */
|
|
59
|
+
export const DIAGRAM_REDO_FAILED = defineMessage(
|
|
60
|
+
'hydranium/glsp-server/diagram-redo-failed',
|
|
61
|
+
'Could not redo: another editor changed the same element meanwhile. The undo and redo history of this diagram was cleared.'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* An undo failed for a reason of its own, a side effect that threw, say, and
|
|
66
|
+
* GLSP cleared the history as it does on every failed undo or redo. The
|
|
67
|
+
* reason goes in the message's details; the log has it in full.
|
|
68
|
+
*/
|
|
69
|
+
export const DIAGRAM_UNDO_ERROR = defineMessage(
|
|
70
|
+
'hydranium/glsp-server/diagram-undo-error',
|
|
71
|
+
'Could not undo: an error occurred. The undo and redo history of this diagram was cleared.'
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/** A redo failed as {@link DIAGRAM_UNDO_ERROR} describes for an undo. */
|
|
75
|
+
export const DIAGRAM_REDO_ERROR = defineMessage(
|
|
76
|
+
'hydranium/glsp-server/diagram-redo-error',
|
|
77
|
+
'Could not redo: an error occurred. The undo and redo history of this diagram was cleared.'
|
|
78
|
+
);
|
|
14
79
|
|
|
15
80
|
/**
|
|
16
81
|
* GLSP's undo and redo handler inside the state's `runExclusive`. GLSP's
|
|
@@ -18,12 +83,92 @@ import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-gls
|
|
|
18
83
|
* the boundary covers the whole action: taken around the undo or redo alone,
|
|
19
84
|
* an undo arriving while an operation runs would move the position past an
|
|
20
85
|
* entry it then cannot undo.
|
|
86
|
+
*
|
|
87
|
+
* An undo or redo a foreign edit skipped, whole or in part, tells the client,
|
|
88
|
+
* which otherwise sees nothing happen. One that fails ends here, as a message saying the
|
|
89
|
+
* history was cleared in place of GLSP's generic notice for a failed action:
|
|
90
|
+
* a dropped or conflicting one says another editor changed the element, and
|
|
91
|
+
* any but a dropped one, which its reconcile has logged, is logged here.
|
|
21
92
|
*/
|
|
22
93
|
@injectable()
|
|
23
94
|
export class HydraniumGlspUndoRedoActionHandler extends UndoRedoActionHandler {
|
|
24
95
|
@inject(ModelState) protected readonly modelState!: AbstractHydraniumGlspState<AstNode, unknown>;
|
|
96
|
+
@inject(HydraniumTypes.SharedCoreServices) protected readonly sharedServices!: ServerSharedServices;
|
|
97
|
+
@inject(Logger) protected readonly logger!: Logger;
|
|
25
98
|
|
|
26
99
|
override execute(action: Action): Promise<Action[]> {
|
|
27
100
|
return this.modelState.runExclusive(() => super.execute(action));
|
|
28
101
|
}
|
|
102
|
+
|
|
103
|
+
protected override undo(): Promise<Action[]> {
|
|
104
|
+
return this.undoOrRedo('undo');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected override redo(): Promise<Action[]> {
|
|
108
|
+
return this.undoOrRedo('redo');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Lifted from `@eclipse-glsp/server` 2.7.0's `UndoRedoActionHandler.undo`
|
|
113
|
+
* and `redo`, telling the client of a skip and ending a failure of the
|
|
114
|
+
* command stack's undo or redo, which flushes the history on any failure.
|
|
115
|
+
* A failed submit after it leaves the history whole and fails as upstream.
|
|
116
|
+
* The accompanying test pins upstream's source.
|
|
117
|
+
*/
|
|
118
|
+
protected async undoOrRedo(direction: UndoRedoDirection): Promise<Action[]> {
|
|
119
|
+
const undo = direction === 'undo';
|
|
120
|
+
if (!(undo ? this.commandStack.canUndo() : this.commandStack.canRedo())) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
// A tally no handler read, as of an undo whose submit failed or that ran outside this handler, is not this one's.
|
|
124
|
+
takeUndoRedoTally(this.modelState);
|
|
125
|
+
try {
|
|
126
|
+
await (undo ? this.commandStack.undo() : this.commandStack.redo());
|
|
127
|
+
} catch (error: unknown) {
|
|
128
|
+
return [this.failure(direction, error)];
|
|
129
|
+
}
|
|
130
|
+
const actions = await this.modelSubmissionHandler.submitModel(direction);
|
|
131
|
+
const { skipped, applied } = takeUndoRedoTally(this.modelState);
|
|
132
|
+
if (skipped === 0) {
|
|
133
|
+
return actions;
|
|
134
|
+
}
|
|
135
|
+
const message =
|
|
136
|
+
applied === 0
|
|
137
|
+
? undo
|
|
138
|
+
? DIAGRAM_UNDO_SKIPPED
|
|
139
|
+
: DIAGRAM_REDO_SKIPPED
|
|
140
|
+
: undo
|
|
141
|
+
? DIAGRAM_UNDO_PARTLY_SKIPPED
|
|
142
|
+
: DIAGRAM_REDO_PARTLY_SKIPPED;
|
|
143
|
+
return [MessageAction.create(this.render(message), { severity: 'WARNING' }), ...actions];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The message for an undo or redo that failed, logging a failure its
|
|
148
|
+
* reconcile has not. A conflict thrown rather than dropped, by a state that
|
|
149
|
+
* does not reconcile or a reconcile that could not read the text, is the
|
|
150
|
+
* same foreign edit as a dropped write and is told as one.
|
|
151
|
+
*/
|
|
152
|
+
protected failure(direction: UndoRedoDirection, error: unknown): MessageAction {
|
|
153
|
+
const undo = direction === 'undo';
|
|
154
|
+
if (isDiagramEditDroppedError(error)) {
|
|
155
|
+
return MessageAction.create(this.render(undo ? DIAGRAM_UNDO_FAILED : DIAGRAM_REDO_FAILED), { severity: 'ERROR' });
|
|
156
|
+
}
|
|
157
|
+
logActionFailure(this.logger, undo ? UndoAction.create() : RedoAction.create(), error);
|
|
158
|
+
if (isConflictError(error)) {
|
|
159
|
+
// The conflict's own message names the document, which the details carry.
|
|
160
|
+
return MessageAction.create(this.render(undo ? DIAGRAM_UNDO_FAILED : DIAGRAM_REDO_FAILED), {
|
|
161
|
+
severity: 'ERROR',
|
|
162
|
+
details: describeError(error)
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return MessageAction.create(this.render(undo ? DIAGRAM_UNDO_ERROR : DIAGRAM_REDO_ERROR), {
|
|
166
|
+
severity: 'ERROR',
|
|
167
|
+
details: describeError(error)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
protected render(message: MessageDefinition<string>): string {
|
|
172
|
+
return this.sharedServices.MessageRenderer.renderMessage(message);
|
|
173
|
+
}
|
|
29
174
|
}
|
|
@@ -12,13 +12,28 @@ import {
|
|
|
12
12
|
type ComputedBoundsAction,
|
|
13
13
|
ComputedBoundsActionHandler,
|
|
14
14
|
type GModelRoot,
|
|
15
|
-
type MaybePromise
|
|
15
|
+
type MaybePromise,
|
|
16
|
+
MessageAction
|
|
16
17
|
} from '@eclipse-glsp/server';
|
|
17
18
|
import { type AstNode } from '@hydranium/langium';
|
|
18
|
-
import {
|
|
19
|
+
import { type ServerSharedServices } from '@hydranium/core';
|
|
20
|
+
import { defineMessage } from '@hydranium/protocol';
|
|
21
|
+
import { inject, injectable } from 'inversify';
|
|
19
22
|
import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
|
|
23
|
+
import { HydraniumTypes } from '../state/hydranium-shared-core-services.js';
|
|
20
24
|
import { type HydraniumGlspSubmissionHandler } from '../submission/hydranium-glsp-submission-handler.js';
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The first layout of a diagram arrived for a model the server has since
|
|
28
|
+
* replaced, so the diagram stays empty. Rendered at the raise site, since
|
|
29
|
+
* GLSP's message action has no slot for its identity. It names the recovery:
|
|
30
|
+
* a reopened diagram requests its model again.
|
|
31
|
+
*/
|
|
32
|
+
export const DIAGRAM_LAYOUT_STALE = defineMessage(
|
|
33
|
+
'hydranium/glsp-server/diagram-layout-stale',
|
|
34
|
+
'Could not show this diagram: the document changed while it was opening. Close the diagram and open it again.'
|
|
35
|
+
);
|
|
36
|
+
|
|
22
37
|
/**
|
|
23
38
|
* GLSP {@link ComputedBoundsActionHandler} base shared by all hydranium
|
|
24
39
|
* adopters. Adds handshake-aware revision-mismatch logging on top of the
|
|
@@ -32,7 +47,8 @@ import { type HydraniumGlspSubmissionHandler } from '../submission/hydranium-gls
|
|
|
32
47
|
* very first `computedBounds` arrives with a revision the server has already
|
|
33
48
|
* bumped, the subsequent `SetModelAction` response never fires and the diagram
|
|
34
49
|
* stays stuck at "model loading…". Adopters need this initial-handshake case
|
|
35
|
-
* logged as an error
|
|
50
|
+
* logged as an error, and the client told with {@link DIAGRAM_LAYOUT_STALE}:
|
|
51
|
+
* the diagram stays empty, so the requested load failed.
|
|
36
52
|
*
|
|
37
53
|
* **Handshake detection** uses
|
|
38
54
|
* {@link HydraniumGlspSubmissionHandler.hasPendingInitialRequest} — true
|
|
@@ -49,6 +65,7 @@ import { type HydraniumGlspSubmissionHandler } from '../submission/hydranium-gls
|
|
|
49
65
|
export class HydraniumGlspComputedBoundsActionHandler extends ComputedBoundsActionHandler {
|
|
50
66
|
declare protected readonly modelState: AbstractHydraniumGlspState<AstNode, unknown>;
|
|
51
67
|
declare protected readonly submissionHandler: HydraniumGlspSubmissionHandler<AstNode, unknown>;
|
|
68
|
+
@inject(HydraniumTypes.SharedCoreServices) protected readonly sharedServices!: ServerSharedServices;
|
|
52
69
|
|
|
53
70
|
override execute(action: ComputedBoundsAction): MaybePromise<Action[]> {
|
|
54
71
|
this.modelState.logger.debug(`ComputedBoundsActionHandler.execute entered: action.revision=${action.revision}`);
|
|
@@ -59,6 +76,7 @@ export class HydraniumGlspComputedBoundsActionHandler extends ComputedBoundsActi
|
|
|
59
76
|
`ComputedBounds rejected (initial handshake): action.revision=${action.revision} model.revision=${model.revision} — ` +
|
|
60
77
|
'initial SetModelAction will not fire (empty diagram)'
|
|
61
78
|
);
|
|
79
|
+
return [MessageAction.create(this.sharedServices.MessageRenderer.renderMessage(DIAGRAM_LAYOUT_STALE), { severity: 'ERROR' })];
|
|
62
80
|
} else {
|
|
63
81
|
this.modelState.logger.debug(
|
|
64
82
|
`ComputedBounds dropped (stale): action.revision=${action.revision} model.revision=${model.revision} — ` +
|
package/src/messages/index.ts
CHANGED
|
@@ -22,6 +22,18 @@
|
|
|
22
22
|
* discovers the codes exist.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
export {
|
|
26
|
+
DIAGRAM_REDO_ERROR,
|
|
27
|
+
DIAGRAM_REDO_FAILED,
|
|
28
|
+
DIAGRAM_REDO_PARTLY_SKIPPED,
|
|
29
|
+
DIAGRAM_REDO_SKIPPED,
|
|
30
|
+
DIAGRAM_UNDO_ERROR,
|
|
31
|
+
DIAGRAM_UNDO_FAILED,
|
|
32
|
+
DIAGRAM_UNDO_PARTLY_SKIPPED,
|
|
33
|
+
DIAGRAM_UNDO_SKIPPED
|
|
34
|
+
} from '../command/hydranium-glsp-undo-redo-action-handler.js';
|
|
35
|
+
export { DIAGRAM_LAYOUT_STALE } from '../computed-bounds/hydranium-glsp-computed-bounds-action-handler.js';
|
|
36
|
+
export { DIAGRAM_EDIT_DROPPED } from '../status/diagram-status-reporter.js';
|
|
25
37
|
export {
|
|
26
38
|
DIAGRAM_READONLY_PARSE_ERROR,
|
|
27
39
|
DIAGRAM_SESSION_REFUSED,
|
|
@@ -409,7 +409,9 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
409
409
|
/**
|
|
410
410
|
* Resync after a write whose reconcile dropped the edit: during an
|
|
411
411
|
* operation the operation undoes its side effects, pushes nothing, and
|
|
412
|
-
* captures the document's current root when it ends.
|
|
412
|
+
* captures the document's current root when it ends. Announced on
|
|
413
|
+
* {@link onEditDropped}, except during an undo or redo, which fails and
|
|
414
|
+
* reports that itself.
|
|
413
415
|
*/
|
|
414
416
|
protected writeDropped(): void {
|
|
415
417
|
const operation = openOperationOf(this);
|
|
@@ -418,6 +420,22 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
418
420
|
} else {
|
|
419
421
|
this.refreshSourceRoot();
|
|
420
422
|
}
|
|
423
|
+
if (!operation?.isUndoingOrRedoing()) {
|
|
424
|
+
this.editDroppedEmitter.fire();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Backing emitter for {@link onEditDropped}. */
|
|
429
|
+
protected readonly editDroppedEmitter = new Emitter<void>();
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Fires when a write's reconcile dropped a diagram edit, whether an
|
|
433
|
+
* operation or code outside one wrote it, so that one listener tells the
|
|
434
|
+
* client: the diagram returning to the document's state otherwise reads as
|
|
435
|
+
* the edit never having been made.
|
|
436
|
+
*/
|
|
437
|
+
get onEditDropped(): Event<void> {
|
|
438
|
+
return this.editDroppedEmitter.event;
|
|
421
439
|
}
|
|
422
440
|
|
|
423
441
|
/**
|
|
@@ -15,23 +15,41 @@ import {
|
|
|
15
15
|
type ClientSessionListener,
|
|
16
16
|
ClientSessionManager,
|
|
17
17
|
EditMode,
|
|
18
|
+
MessageAction,
|
|
18
19
|
ModelState,
|
|
19
20
|
SetEditModeAction,
|
|
20
21
|
StatusAction
|
|
21
22
|
} from '@eclipse-glsp/server';
|
|
23
|
+
import { type ServerSharedServices } from '@hydranium/core';
|
|
22
24
|
import { type AstNode } from '@hydranium/langium';
|
|
23
|
-
import { DisposableCollection, describeErrorWithStack } from '@hydranium/protocol';
|
|
25
|
+
import { DisposableCollection, defineMessage, describeErrorWithStack } from '@hydranium/protocol';
|
|
24
26
|
import { inject, injectable } from 'inversify';
|
|
25
27
|
import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
|
|
26
28
|
import { type DiagramStatusEntry } from '../state/diagram-status.js';
|
|
29
|
+
import { HydraniumTypes } from '../state/hydranium-shared-core-services.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A diagram edit was dropped because another editor changed the same field
|
|
33
|
+
* first. The diagram returns to the document's state, which without this reads
|
|
34
|
+
* as the edit never having been made. It names the recovery: the edit made
|
|
35
|
+
* again applies to the other editor's change.
|
|
36
|
+
*/
|
|
37
|
+
export const DIAGRAM_EDIT_DROPPED = defineMessage(
|
|
38
|
+
'hydranium/glsp-server/diagram-edit-dropped',
|
|
39
|
+
'Your diagram change was not applied: another editor changed the same element first. Make the change again if you still want it.'
|
|
40
|
+
);
|
|
27
41
|
|
|
28
42
|
/**
|
|
29
43
|
* Sends the client what {@link AbstractHydraniumGlspState.onStatusChanged}
|
|
30
|
-
* announces: the current status for its status overlay and the edit mode
|
|
44
|
+
* announces: the current status for its status overlay and the edit mode; and
|
|
45
|
+
* a warning for each edit {@link AbstractHydraniumGlspState.onEditDropped}
|
|
46
|
+
* announces.
|
|
31
47
|
*
|
|
32
|
-
* The only sender of
|
|
48
|
+
* The only sender of the status and the edit mode. Either dispatched from
|
|
33
49
|
* anywhere else is overwritten by the next change, and the state's account of
|
|
34
|
-
* why the diagram is read-only no longer matches the client's.
|
|
50
|
+
* why the diagram is read-only no longer matches the client's. An
|
|
51
|
+
* implementation bound in place of {@link DefaultDiagramStatusReporter} takes
|
|
52
|
+
* over the dropped-edit warning too, or the client no longer hears of one.
|
|
35
53
|
*/
|
|
36
54
|
export interface DiagramStatusReporter extends ClientSessionInitializer {
|
|
37
55
|
/**
|
|
@@ -47,6 +65,7 @@ export class DefaultDiagramStatusReporter implements DiagramStatusReporter, Clie
|
|
|
47
65
|
@inject(ModelState) protected readonly state!: AbstractHydraniumGlspState<AstNode, unknown>;
|
|
48
66
|
@inject(ActionDispatcher) protected readonly actionDispatcher!: ActionDispatcher;
|
|
49
67
|
@inject(ClientSessionManager) protected readonly sessionManager!: ClientSessionManager;
|
|
68
|
+
@inject(HydraniumTypes.SharedCoreServices) protected readonly sharedServices!: ServerSharedServices;
|
|
50
69
|
|
|
51
70
|
protected readonly toDispose = new DisposableCollection();
|
|
52
71
|
/** What the client shows; it starts editable with an empty overlay. */
|
|
@@ -56,6 +75,7 @@ export class DefaultDiagramStatusReporter implements DiagramStatusReporter, Clie
|
|
|
56
75
|
initialize(): void {
|
|
57
76
|
this.sessionManager.addListener(this, this.state.clientId);
|
|
58
77
|
this.toDispose.push(this.state.onStatusChanged(() => this.send()));
|
|
78
|
+
this.toDispose.push(this.state.onEditDropped(() => this.sendEditDropped()));
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
clientEditModeChanged(editMode: EditMode): void {
|
|
@@ -69,6 +89,19 @@ export class DefaultDiagramStatusReporter implements DiagramStatusReporter, Clie
|
|
|
69
89
|
.catch((error: unknown) => this.state.logger.warn(`Could not send the diagram status: ${describeErrorWithStack(error)}`));
|
|
70
90
|
}
|
|
71
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Warn the client of a dropped edit. Not awaited: the drop is announced
|
|
94
|
+
* inside the diagram's exclusive section, and a dispatch from outside any
|
|
95
|
+
* action queues behind the action being processed, which may be waiting
|
|
96
|
+
* for that section.
|
|
97
|
+
*/
|
|
98
|
+
protected sendEditDropped(): void {
|
|
99
|
+
const message = this.sharedServices.MessageRenderer.renderMessage(DIAGRAM_EDIT_DROPPED);
|
|
100
|
+
this.actionDispatcher
|
|
101
|
+
.dispatch(MessageAction.create(message, { severity: 'WARNING' }))
|
|
102
|
+
.catch((error: unknown) => this.state.logger.warn(`Could not warn of a dropped edit: ${describeErrorWithStack(error)}`));
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
/** The actions that bring the client up to date, recording them as sent. */
|
|
73
106
|
protected changedActions(): Action[] {
|
|
74
107
|
const actions: Action[] = [];
|