@eclipse-glsp/server 1.1.0-next.3d7585d.42 → 1.1.0-next.44aae0c.44

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.
Files changed (175) hide show
  1. package/lib/common/command/command-stack.d.ts +45 -14
  2. package/lib/common/command/command-stack.d.ts.map +1 -1
  3. package/lib/common/command/command-stack.js +78 -9
  4. package/lib/common/command/command-stack.js.map +1 -1
  5. package/lib/common/command/command-stack.spec.d.ts +2 -0
  6. package/lib/common/command/command-stack.spec.d.ts.map +1 -0
  7. package/lib/common/command/command-stack.spec.js +207 -0
  8. package/lib/common/command/command-stack.spec.js.map +1 -0
  9. package/lib/common/command/command.d.ts +34 -7
  10. package/lib/common/command/command.d.ts.map +1 -1
  11. package/lib/common/command/command.js +73 -0
  12. package/lib/common/command/command.js.map +1 -1
  13. package/lib/common/command/command.spec.d.ts +2 -0
  14. package/lib/common/command/command.spec.d.ts.map +1 -0
  15. package/lib/common/command/command.spec.js +86 -0
  16. package/lib/common/command/command.spec.js.map +1 -0
  17. package/lib/common/command/recording-command.d.ts +75 -0
  18. package/lib/common/command/recording-command.d.ts.map +1 -0
  19. package/lib/common/command/recording-command.js +99 -0
  20. package/lib/common/command/recording-command.js.map +1 -0
  21. package/lib/common/command/recording-command.spec.d.ts +17 -0
  22. package/lib/common/command/recording-command.spec.d.ts.map +1 -0
  23. package/lib/common/command/recording-command.spec.js +62 -0
  24. package/lib/common/command/recording-command.spec.js.map +1 -0
  25. package/lib/common/command/undo-redo-action-handler.d.ts +28 -0
  26. package/lib/common/command/undo-redo-action-handler.d.ts.map +1 -0
  27. package/lib/common/command/undo-redo-action-handler.js +72 -0
  28. package/lib/common/command/undo-redo-action-handler.js.map +1 -0
  29. package/lib/common/di/diagram-module.d.ts +3 -0
  30. package/lib/common/di/diagram-module.d.ts.map +1 -1
  31. package/lib/common/di/diagram-module.js +12 -0
  32. package/lib/common/di/diagram-module.js.map +1 -1
  33. package/lib/common/features/contextactions/tool-palette-item-provider.d.ts +1 -1
  34. package/lib/common/features/contextactions/tool-palette-item-provider.d.ts.map +1 -1
  35. package/lib/common/features/contextactions/tool-palette-item-provider.js +2 -4
  36. package/lib/common/features/contextactions/tool-palette-item-provider.js.map +1 -1
  37. package/lib/common/{gmodel → features/layout}/computed-bounds-action-handler.d.ts +8 -3
  38. package/lib/common/features/layout/computed-bounds-action-handler.d.ts.map +1 -0
  39. package/lib/common/{gmodel → features/layout}/computed-bounds-action-handler.js +8 -3
  40. package/lib/common/features/layout/computed-bounds-action-handler.js.map +1 -0
  41. package/lib/common/features/layout/layout-operation-handler.d.ts +12 -3
  42. package/lib/common/features/layout/layout-operation-handler.d.ts.map +1 -1
  43. package/lib/common/features/layout/layout-operation-handler.js +26 -10
  44. package/lib/common/features/layout/layout-operation-handler.js.map +1 -1
  45. package/lib/common/features/model/model-state.d.ts +0 -2
  46. package/lib/common/features/model/model-state.d.ts.map +1 -1
  47. package/lib/common/features/model/model-state.js +0 -1
  48. package/lib/common/features/model/model-state.js.map +1 -1
  49. package/lib/common/features/model/model-submission-handler.d.ts +2 -0
  50. package/lib/common/features/model/model-submission-handler.d.ts.map +1 -1
  51. package/lib/common/features/model/model-submission-handler.js +7 -2
  52. package/lib/common/features/model/model-submission-handler.js.map +1 -1
  53. package/lib/common/features/model/save-model-action-handler.d.ts +2 -2
  54. package/lib/common/features/model/save-model-action-handler.d.ts.map +1 -1
  55. package/lib/common/features/model/save-model-action-handler.js +5 -5
  56. package/lib/common/features/model/save-model-action-handler.js.map +1 -1
  57. package/lib/common/gmodel/apply-label-edit-operation-handler.d.ts +4 -5
  58. package/lib/common/gmodel/apply-label-edit-operation-handler.d.ts.map +1 -1
  59. package/lib/common/gmodel/apply-label-edit-operation-handler.js +12 -21
  60. package/lib/common/gmodel/apply-label-edit-operation-handler.js.map +1 -1
  61. package/lib/common/gmodel/change-bounds-operation-handler.d.ts +9 -6
  62. package/lib/common/gmodel/change-bounds-operation-handler.d.ts.map +1 -1
  63. package/lib/common/gmodel/change-bounds-operation-handler.js +14 -14
  64. package/lib/common/gmodel/change-bounds-operation-handler.js.map +1 -1
  65. package/lib/common/gmodel/change-routing-points-operation-handler.d.ts +5 -5
  66. package/lib/common/gmodel/change-routing-points-operation-handler.d.ts.map +1 -1
  67. package/lib/common/gmodel/change-routing-points-operation-handler.js +11 -14
  68. package/lib/common/gmodel/change-routing-points-operation-handler.js.map +1 -1
  69. package/lib/common/{operations → gmodel}/cut-operation-handler.d.ts +5 -4
  70. package/lib/common/gmodel/cut-operation-handler.d.ts.map +1 -0
  71. package/lib/common/{operations → gmodel}/cut-operation-handler.js +13 -11
  72. package/lib/common/gmodel/cut-operation-handler.js.map +1 -0
  73. package/lib/common/gmodel/delete-operation-handler.d.ts +6 -6
  74. package/lib/common/gmodel/delete-operation-handler.d.ts.map +1 -1
  75. package/lib/common/gmodel/delete-operation-handler.js +13 -10
  76. package/lib/common/gmodel/delete-operation-handler.js.map +1 -1
  77. package/lib/common/gmodel/gmodel-create-edge-operation-handler.d.ts +11 -7
  78. package/lib/common/gmodel/gmodel-create-edge-operation-handler.d.ts.map +1 -1
  79. package/lib/common/gmodel/gmodel-create-edge-operation-handler.js +12 -13
  80. package/lib/common/gmodel/gmodel-create-edge-operation-handler.js.map +1 -1
  81. package/lib/common/gmodel/gmodel-create-node-operation-handler.d.ts +32 -5
  82. package/lib/common/gmodel/gmodel-create-node-operation-handler.d.ts.map +1 -1
  83. package/lib/common/gmodel/gmodel-create-node-operation-handler.js +45 -3
  84. package/lib/common/gmodel/gmodel-create-node-operation-handler.js.map +1 -1
  85. package/lib/common/gmodel/gmodel-diagram-module.d.ts +8 -8
  86. package/lib/common/gmodel/gmodel-diagram-module.d.ts.map +1 -1
  87. package/lib/common/gmodel/gmodel-diagram-module.js +15 -21
  88. package/lib/common/gmodel/gmodel-diagram-module.js.map +1 -1
  89. package/lib/common/gmodel/gmodel-operation-handler.d.ts +23 -0
  90. package/lib/common/gmodel/gmodel-operation-handler.d.ts.map +1 -0
  91. package/lib/common/gmodel/gmodel-operation-handler.js +43 -0
  92. package/lib/common/gmodel/gmodel-operation-handler.js.map +1 -0
  93. package/lib/common/gmodel/index.d.ts +2 -1
  94. package/lib/common/gmodel/index.d.ts.map +1 -1
  95. package/lib/common/gmodel/index.js +2 -1
  96. package/lib/common/gmodel/index.js.map +1 -1
  97. package/lib/common/gmodel/paste-operation-handler.d.ts +7 -6
  98. package/lib/common/gmodel/paste-operation-handler.d.ts.map +1 -1
  99. package/lib/common/gmodel/paste-operation-handler.js +20 -15
  100. package/lib/common/gmodel/paste-operation-handler.js.map +1 -1
  101. package/lib/common/gmodel/reconnect-edge-operation-handler.d.ts +5 -5
  102. package/lib/common/gmodel/reconnect-edge-operation-handler.d.ts.map +1 -1
  103. package/lib/common/gmodel/reconnect-edge-operation-handler.js +11 -14
  104. package/lib/common/gmodel/reconnect-edge-operation-handler.js.map +1 -1
  105. package/lib/common/index.d.ts +2 -1
  106. package/lib/common/index.d.ts.map +1 -1
  107. package/lib/common/index.js +2 -1
  108. package/lib/common/index.js.map +1 -1
  109. package/lib/common/operations/compound-operation-handler.d.ts +5 -5
  110. package/lib/common/operations/compound-operation-handler.d.ts.map +1 -1
  111. package/lib/common/operations/compound-operation-handler.js +37 -9
  112. package/lib/common/operations/compound-operation-handler.js.map +1 -1
  113. package/lib/common/operations/create-operation-handler.d.ts +8 -40
  114. package/lib/common/operations/create-operation-handler.d.ts.map +1 -1
  115. package/lib/common/operations/create-operation-handler.js +26 -85
  116. package/lib/common/operations/create-operation-handler.js.map +1 -1
  117. package/lib/common/operations/operation-action-handler.d.ts +10 -1
  118. package/lib/common/operations/operation-action-handler.d.ts.map +1 -1
  119. package/lib/common/operations/operation-action-handler.js +37 -22
  120. package/lib/common/operations/operation-action-handler.js.map +1 -1
  121. package/lib/common/operations/operation-handler-registry.d.ts +11 -2
  122. package/lib/common/operations/operation-handler-registry.d.ts.map +1 -1
  123. package/lib/common/operations/operation-handler-registry.js +31 -2
  124. package/lib/common/operations/operation-handler-registry.js.map +1 -1
  125. package/lib/common/operations/operation-handler.d.ts +28 -11
  126. package/lib/common/operations/operation-handler.d.ts.map +1 -1
  127. package/lib/common/operations/operation-handler.js +50 -1
  128. package/lib/common/operations/operation-handler.js.map +1 -1
  129. package/lib/common/test/mock-util.d.ts +14 -1
  130. package/lib/common/test/mock-util.d.ts.map +1 -1
  131. package/lib/common/test/mock-util.js +42 -37
  132. package/lib/common/test/mock-util.js.map +1 -1
  133. package/package.json +4 -3
  134. package/src/common/command/command-stack.spec.ts +239 -0
  135. package/src/common/command/command-stack.ts +106 -20
  136. package/src/common/command/command.spec.ts +92 -0
  137. package/src/common/command/command.ts +96 -7
  138. package/src/common/command/recording-command.spec.ts +74 -0
  139. package/src/common/command/recording-command.ts +119 -0
  140. package/src/common/command/undo-redo-action-handler.ts +57 -0
  141. package/src/common/di/diagram-module.ts +13 -0
  142. package/src/common/features/contextactions/tool-palette-item-provider.ts +2 -4
  143. package/src/common/{gmodel → features/layout}/computed-bounds-action-handler.ts +9 -4
  144. package/src/common/features/layout/layout-operation-handler.ts +26 -11
  145. package/src/common/features/model/model-state.ts +0 -3
  146. package/src/common/features/model/model-submission-handler.ts +6 -2
  147. package/src/common/features/model/save-model-action-handler.ts +5 -6
  148. package/src/common/gmodel/apply-label-edit-operation-handler.ts +13 -15
  149. package/src/common/gmodel/change-bounds-operation-handler.ts +12 -8
  150. package/src/common/gmodel/change-routing-points-operation-handler.ts +8 -7
  151. package/src/common/{operations → gmodel}/cut-operation-handler.ts +9 -8
  152. package/src/common/gmodel/delete-operation-handler.ts +16 -10
  153. package/src/common/gmodel/gmodel-create-edge-operation-handler.ts +16 -10
  154. package/src/common/gmodel/gmodel-create-node-operation-handler.ts +54 -6
  155. package/src/common/gmodel/gmodel-diagram-module.ts +22 -27
  156. package/src/common/gmodel/gmodel-operation-handler.ts +31 -0
  157. package/src/common/gmodel/index.ts +2 -1
  158. package/src/common/gmodel/paste-operation-handler.ts +19 -11
  159. package/src/common/gmodel/reconnect-edge-operation-handler.ts +8 -7
  160. package/src/common/index.ts +2 -1
  161. package/src/common/operations/compound-operation-handler.ts +14 -12
  162. package/src/common/operations/create-operation-handler.ts +17 -74
  163. package/src/common/operations/operation-action-handler.ts +51 -27
  164. package/src/common/operations/operation-handler-registry.ts +18 -3
  165. package/src/common/operations/operation-handler.ts +38 -13
  166. package/src/common/test/mock-util.ts +42 -36
  167. package/lib/common/gmodel/apply-label-edit-operation-handler.spec.d.ts +0 -2
  168. package/lib/common/gmodel/apply-label-edit-operation-handler.spec.d.ts.map +0 -1
  169. package/lib/common/gmodel/apply-label-edit-operation-handler.spec.js +0 -38
  170. package/lib/common/gmodel/apply-label-edit-operation-handler.spec.js.map +0 -1
  171. package/lib/common/gmodel/computed-bounds-action-handler.d.ts.map +0 -1
  172. package/lib/common/gmodel/computed-bounds-action-handler.js.map +0 -1
  173. package/lib/common/operations/cut-operation-handler.d.ts.map +0 -1
  174. package/lib/common/operations/cut-operation-handler.js.map +0 -1
  175. package/src/common/gmodel/apply-label-edit-operation-handler.spec.ts +0 -40
@@ -14,26 +14,115 @@
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
16
 
17
+ import { flatPush, MaybeArray, MaybePromise } from '@eclipse-glsp/protocol';
18
+
17
19
  /**
18
- * An interface that every command is expected to support.
19
- * A command can be executed, it can be undone, and can then be redone.
20
+ * A command implements a specific modification of the source model, which can be applied by invoking `execute()`.
21
+ * Commands may also provide implementations to `undo()` and `redo()` the effect they had.
22
+ * To maintain an order in which commands can be undone and redone, they are managed in the context of a {@link CommandStack}.
23
+ *
24
+ * Each command is self contained and stores all the information it needs to execute, undo or redo itself.
25
+ *
26
+ * In theory commands can be used to execute arbitrary activities and not only modify the source model.
27
+ * However, it is mostly used to record and manage changes made to the source model via GLSP operations.
20
28
  */
21
29
  export interface Command {
22
30
  /**
23
- * Performs the command activity required for the effect.
31
+ * Performs the command activity required for the effect (e.g. source model change).
24
32
  */
25
- execute(): void;
33
+ execute(): MaybePromise<void>;
26
34
 
27
35
  /**
28
36
  * Performs the command activity required to `undo` the effects of a preceding `execute` (or `redo`).
29
37
  * The effect, if any, of calling `undo` before `execute` or `redo` have been called, is undefined.
30
38
  */
31
- undo(): void;
39
+ undo(): MaybePromise<void>;
32
40
 
33
41
  /**
34
42
  * Performs the command activity required to `redo` the effect after undoing the effect.
35
43
  * The effect, if any, of calling `redo` before `undo` is called is undefined.
36
- * Note that if you implement `redo` to call `execute` then any derived class will be restricted by that decision also.
37
44
  */
38
- redo(): void;
45
+ redo(): MaybePromise<void>;
46
+
47
+ /**
48
+ * Returns whether the command can be undone.
49
+ * This function is optional. If not implemented, it is assumed that the command can be undone.
50
+ * @returns whether the command can be undone
51
+ */
52
+ canUndo?(): boolean;
53
+ }
54
+
55
+ export namespace Command {
56
+ export function canUndo(command: Command): boolean {
57
+ return command.canUndo?.() ?? true;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * A command that comprises a sequence of subcommands.
63
+ */
64
+ export class CompoundCommand implements Command {
65
+ protected readonly commands: Command[];
66
+
67
+ constructor(commands: Command[]);
68
+ constructor(...commands: Command[]);
69
+ constructor(...commands: MaybeArray<Command>[]) {
70
+ this.commands = [];
71
+ flatPush(this.commands, commands);
72
+ }
73
+
74
+ async execute(): Promise<void> {
75
+ const alreadyExecuted: Command[] = [];
76
+
77
+ try {
78
+ for (const command of this.commands) {
79
+ await command.execute();
80
+ alreadyExecuted.unshift(command);
81
+ }
82
+ } catch (err) {
83
+ for (const command of alreadyExecuted) {
84
+ if (Command.canUndo(command)) {
85
+ await command.undo();
86
+ } else {
87
+ break;
88
+ }
89
+ }
90
+ throw err;
91
+ }
92
+ }
93
+
94
+ async undo(): Promise<void> {
95
+ const alreadyUndone: Command[] = [];
96
+ try {
97
+ for (const command of [...this.commands].reverse()) {
98
+ await command.undo();
99
+ alreadyUndone.unshift(command);
100
+ }
101
+ } catch (err) {
102
+ for (const command of alreadyUndone) {
103
+ await command.redo();
104
+ }
105
+ throw err;
106
+ }
107
+ }
108
+
109
+ async redo(): Promise<void> {
110
+ const alreadyRedone: Command[] = [];
111
+
112
+ try {
113
+ for (const command of this.commands) {
114
+ await command.redo();
115
+ alreadyRedone.unshift(command);
116
+ }
117
+ } catch (err) {
118
+ for (const command of alreadyRedone) {
119
+ await command.undo();
120
+ }
121
+ throw err;
122
+ }
123
+ }
124
+
125
+ canUndo(): boolean {
126
+ return this.commands.every(Command.canUndo);
127
+ }
39
128
  }
@@ -0,0 +1,74 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2023 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ import { AnyObject } from '@eclipse-glsp/protocol';
18
+ import { expect } from 'chai';
19
+ import { RecordingCommand } from './recording-command';
20
+
21
+ interface TestModel {
22
+ string: string;
23
+ number: number;
24
+ flag: boolean;
25
+ maybe?: AnyObject;
26
+ }
27
+
28
+ let jsonObject: TestModel;
29
+
30
+ describe('RecordingCommand', () => {
31
+ let beforeState: TestModel;
32
+
33
+ beforeEach(() => {
34
+ jsonObject = {
35
+ string: 'foo',
36
+ number: 0,
37
+ flag: true
38
+ };
39
+ beforeState = JSON.parse(JSON.stringify(jsonObject));
40
+ });
41
+
42
+ it('should be undoable after execution', async () => {
43
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
44
+ const command = new RecordingCommand(jsonObject, () => {});
45
+ expect(command.canUndo()).to.be.false;
46
+ await command.execute();
47
+ expect(command.canUndo()).to.be.true;
48
+ });
49
+
50
+ it('should restore the pre execution state when undo is called', async () => {
51
+ const command = new RecordingCommand(jsonObject, () => {
52
+ jsonObject.string = 'bar';
53
+ jsonObject.flag = false;
54
+ jsonObject.maybe = { hello: 'world' };
55
+ });
56
+ await command.execute();
57
+ expect(jsonObject).to.not.be.deep.equals(beforeState);
58
+ await command.undo();
59
+ expect(jsonObject).to.be.deep.equals(beforeState);
60
+ });
61
+
62
+ it('should restore the post execution state when redo is called', async () => {
63
+ const command = new RecordingCommand(jsonObject, () => {
64
+ jsonObject.string = 'bar';
65
+ jsonObject.flag = false;
66
+ jsonObject.maybe = { hello: 'world' };
67
+ });
68
+ await command.execute();
69
+ const afterState = JSON.parse(JSON.stringify(jsonObject));
70
+ jsonObject = JSON.parse(JSON.stringify(afterState));
71
+ await command.redo();
72
+ expect(jsonObject).to.be.deep.equals(afterState);
73
+ });
74
+ });
@@ -0,0 +1,119 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2023 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ import { GModelRootSchema } from '@eclipse-glsp/graph';
18
+ import { AnyObject, MaybePromise } from '@eclipse-glsp/protocol';
19
+ import * as jsonPatch from 'fast-json-patch';
20
+ import { GModelSerializer } from '../features/model/gmodel-serializer';
21
+ import { ModelState } from '../features/model/model-state';
22
+ import { Command } from './command';
23
+ /**
24
+ * An abstract implementation for {@link Command} that should records changes made to a JSON object during execution.
25
+ * These changes are recorded in the form of json patches.
26
+ */
27
+ export abstract class AbstractRecordingCommand<JsonObject extends AnyObject> implements Command {
28
+ protected undoPatch?: jsonPatch.Operation[];
29
+ protected redoPatch?: jsonPatch.Operation[];
30
+
31
+ async execute(): Promise<void> {
32
+ const beforeState = this.deepClone(this.getJsonObject());
33
+ await this.doExecute();
34
+ const afterState = this.getJsonObject();
35
+ this.undoPatch = jsonPatch.compare(afterState, beforeState);
36
+ this.redoPatch = jsonPatch.compare(beforeState, afterState);
37
+ }
38
+
39
+ /**
40
+ * Retrieves the current state of the JSON object. Is called right before and
41
+ * right after {@link AbstractRecordingCommand.doExecute} to derive the undo & redo patches.
42
+ * @returns The current state of the JSON object
43
+ */
44
+ protected abstract getJsonObject(): JsonObject;
45
+
46
+ /**
47
+ * The actual execution i.e. series of changes applied to the JSOn object that should be captured.
48
+ */
49
+ protected abstract doExecute(): MaybePromise<void>;
50
+
51
+ protected applyPatch(object: JsonObject, patch: jsonPatch.Operation[]): void {
52
+ jsonPatch.applyPatch(object, patch, false, true);
53
+ }
54
+
55
+ /**
56
+ * Captures the original state of the JSON object before executing the command routine.
57
+ * This implementation cannot clone circular structures as it relies on `JSON.stringify`.
58
+ * @param object The object that should be cloned
59
+ * @returns A deep clone of the given object
60
+ */
61
+ protected deepClone(object: JsonObject): JsonObject {
62
+ return jsonPatch.deepClone(object);
63
+ }
64
+
65
+ undo(): void {
66
+ if (this.undoPatch) {
67
+ this.applyPatch(this.getJsonObject(), this.undoPatch);
68
+ }
69
+ }
70
+
71
+ redo(): void {
72
+ if (this.redoPatch) {
73
+ this.applyPatch(this.getJsonObject(), this.redoPatch);
74
+ }
75
+ }
76
+
77
+ canUndo(): boolean {
78
+ return !!this.undoPatch && !!this.redoPatch;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Simple base implementation of {@link AbstractRecordingCommand} that records the changes made to the given JSON object during
84
+ * the the given `doExecute` function.
85
+ */
86
+ export class RecordingCommand<JsonObject extends AnyObject = AnyObject> extends AbstractRecordingCommand<JsonObject> {
87
+ constructor(protected jsonObject: JsonObject, protected doExecute: () => MaybePromise<void>) {
88
+ super();
89
+ }
90
+
91
+ protected getJsonObject(): JsonObject {
92
+ return this.jsonObject;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Simple base implementation of {@link AbstractRecordingCommand} that allows recording of changes made
98
+ * to the `GModelRoot` ({@link ModelState.root}) during the given `doExecute` function
99
+ */
100
+ export class GModelRecordingCommand extends AbstractRecordingCommand<GModelRootSchema> {
101
+ constructor(protected modelState: ModelState, protected serializer: GModelSerializer, protected doExecute: () => MaybePromise<void>) {
102
+ super();
103
+ }
104
+
105
+ override async execute(): Promise<void> {
106
+ await super.execute();
107
+ this.modelState.index.indexRoot(this.modelState.root);
108
+ }
109
+
110
+ protected getJsonObject(): GModelRootSchema {
111
+ return this.serializer.createSchema(this.modelState.root);
112
+ }
113
+
114
+ protected override applyPatch(rootSchema: GModelRootSchema, patch: jsonPatch.Operation[]): void {
115
+ super.applyPatch(rootSchema, patch);
116
+ const newRoot = this.serializer.createRoot(rootSchema);
117
+ this.modelState.updateRoot(newRoot);
118
+ }
119
+ }
@@ -0,0 +1,57 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2023 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ import { Action, MaybePromise, RedoAction, UndoAction } from '@eclipse-glsp/protocol';
18
+ import { inject, injectable } from 'inversify';
19
+ import { ActionHandler } from '../actions/action-handler';
20
+ import { ModelSubmissionHandler } from '../features/model/model-submission-handler';
21
+ import { CommandStack } from './command-stack';
22
+
23
+ @injectable()
24
+ export class UndoRedoActionHandler implements ActionHandler {
25
+ @inject(CommandStack)
26
+ protected commandStack: CommandStack;
27
+
28
+ @inject(ModelSubmissionHandler)
29
+ protected modelSubmissionHandler: ModelSubmissionHandler;
30
+
31
+ readonly actionKinds = [UndoAction.KIND, RedoAction.KIND];
32
+
33
+ execute(action: Action): MaybePromise<Action[]> {
34
+ if (UndoAction.is(action)) {
35
+ return this.undo();
36
+ } else if (RedoAction.is(action)) {
37
+ return this.redo();
38
+ }
39
+ return [];
40
+ }
41
+
42
+ protected async undo(): Promise<Action[]> {
43
+ if (this.commandStack.canUndo()) {
44
+ await this.commandStack.undo();
45
+ return this.modelSubmissionHandler.submitModel('undo');
46
+ }
47
+ return [];
48
+ }
49
+
50
+ protected async redo(): Promise<Action[]> {
51
+ if (this.commandStack.canRedo()) {
52
+ await this.commandStack.redo();
53
+ return this.modelSubmissionHandler.submitModel('redo');
54
+ }
55
+ return [];
56
+ }
57
+ }
@@ -48,6 +48,8 @@ import { ActionDispatcher, DefaultActionDispatcher } from '../actions/action-dis
48
48
  import { ActionHandlerConstructor, ActionHandlerFactory } from '../actions/action-handler';
49
49
  import { ActionHandlerRegistry, ActionHandlerRegistryInitializer } from '../actions/action-handler-registry';
50
50
  import { ClientActionHandler } from '../actions/client-action-handler';
51
+ import { CommandStack, DefaultCommandStack } from '../command/command-stack';
52
+ import { UndoRedoActionHandler } from '../command/undo-redo-action-handler';
51
53
  import { DiagramConfiguration } from '../diagram/diagram-configuration';
52
54
  import { RequestTypeHintsActionHandler } from '../diagram/request-type-hints-action-handler';
53
55
  import { CommandPaletteActionProvider } from '../features/contextactions/command-palette-action-provider';
@@ -63,7 +65,9 @@ import {
63
65
  } from '../features/directediting/context-edit-validator-registry';
64
66
  import { LabelEditValidator } from '../features/directediting/label-edit-validator';
65
67
  import { RequestEditValidationHandler } from '../features/directediting/request-edit-validation-handler';
68
+ import { ComputedBoundsActionHandler } from '../features/layout/computed-bounds-action-handler';
66
69
  import { LayoutEngine } from '../features/layout/layout-engine';
70
+ import { LayoutOperationHandler } from '../features/layout/layout-operation-handler';
67
71
  import { GModelFactory } from '../features/model/gmodel-factory';
68
72
  import { GModelIndex } from '../features/model/gmodel-index';
69
73
  import { DefaultGModelSerializer, GModelSerializer } from '../features/model/gmodel-serializer';
@@ -137,6 +141,7 @@ import {
137
141
  * - {@link OperationHandlerFactory}
138
142
  * - {@link OperationHandlerRegistry}
139
143
  * - {@link Operations}
144
+ * - {@link CommandStack}
140
145
  * - {@link NavigationTargetResolver} as optional binding
141
146
  * {@link NavigationTargetProvider} as {@link ClassMultiBinding<NavigationTargetProvider>} (empty)
142
147
  * - {@link NavigationTargetProviderRegistry}
@@ -193,6 +198,7 @@ export abstract class DiagramModule extends GLSPModule {
193
198
  applyBindingTarget(context, OperationHandlerRegistry, this.bindOperationHandlerRegistry()).inSingletonScope();
194
199
  applyBindingTarget(context, OperationHandlerFactory, this.bindOperationHandlerFactory());
195
200
  applyBindingTarget(context, Operations, this.bindOperations()).inSingletonScope();
201
+ applyBindingTarget(context, CommandStack, this.bindCommandStack()).inSingletonScope();
196
202
 
197
203
  // Navigation
198
204
  applyOptionalBindingTarget(context, NavigationTargetResolver, this.bindNavigationTargetResolver());
@@ -232,6 +238,8 @@ export abstract class DiagramModule extends GLSPModule {
232
238
  binding.add(RequestNavigationTargetsActionHandler);
233
239
  binding.add(ResolveNavigationTargetsActionHandler);
234
240
  binding.add(SaveModelActionHandler);
241
+ binding.add(UndoRedoActionHandler);
242
+ binding.add(ComputedBoundsActionHandler);
235
243
  }
236
244
 
237
245
  protected bindDiagramType(): BindingTarget<string> {
@@ -291,8 +299,13 @@ export abstract class DiagramModule extends GLSPModule {
291
299
  };
292
300
  }
293
301
 
302
+ protected bindCommandStack(): BindingTarget<CommandStack> {
303
+ return DefaultCommandStack;
304
+ }
305
+
294
306
  protected configureOperationHandlers(binding: InstanceMultiBinding<OperationHandlerConstructor>): void {
295
307
  binding.add(CompoundOperationHandler);
308
+ binding.add(LayoutOperationHandler);
296
309
  }
297
310
 
298
311
  protected configureContextActionProviders(binding: MultiBinding<ContextActionsProvider>): void {
@@ -63,10 +63,8 @@ export class DefaultToolPaletteItemProvider extends ToolPaletteItemProvider {
63
63
  @inject(OperationHandlerRegistry) operationHandlerRegistry: OperationHandlerRegistry;
64
64
  protected counter: number;
65
65
 
66
- getItems(args?: Args): PaletteItem[] {
67
- const handlers = this.operationHandlerRegistry
68
- .getAll()
69
- .filter(handler => handler instanceof CreateOperationHandler) as CreateOperationHandler[];
66
+ getItems(_args?: Args): PaletteItem[] {
67
+ const handlers = this.operationHandlerRegistry.getAll().filter(CreateOperationHandler.is) as CreateOperationHandler[];
70
68
  this.counter = 0;
71
69
  const nodes = this.createPaletteItem(handlers, CreateNodeOperation.KIND);
72
70
  const edges = this.createPaletteItem(handlers, CreateEdgeOperation.KIND);
@@ -16,11 +16,16 @@
16
16
  import { GModelRoot } from '@eclipse-glsp/graph';
17
17
  import { Action, ComputedBoundsAction, MaybePromise } from '@eclipse-glsp/protocol';
18
18
  import { inject, injectable } from 'inversify';
19
- import { ActionHandler } from '../actions/action-handler';
20
- import { ModelState } from '../features/model/model-state';
21
- import { ModelSubmissionHandler } from '../features/model/model-submission-handler';
22
- import { applyAlignment, applyBounds, applyRoute } from '../utils/layout-util';
19
+ import { ActionHandler } from '../../actions/action-handler';
20
+ import { applyAlignment, applyBounds, applyRoute } from '../../utils/layout-util';
21
+ import { ModelState } from '../model/model-state';
22
+ import { ModelSubmissionHandler } from '../model/model-submission-handler';
23
23
 
24
+ /**
25
+ * Syncs the bounds computed by the client (i.e. the actual bounds after applying CSS styles) back to the `GModel`.
26
+ * In this default implementation the updated bounds are stored transient. This means they are applied to the graphical model but
27
+ * are not persisted to the source model.
28
+ */
24
29
  @injectable()
25
30
  export class ComputedBoundsActionHandler implements ActionHandler {
26
31
  @inject(ModelSubmissionHandler)
@@ -13,15 +13,23 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
- import { LayoutOperation, Operation } from '@eclipse-glsp/protocol';
16
+ import { LayoutOperation, MaybePromise, Operation } from '@eclipse-glsp/protocol';
17
17
  import { inject, injectable, optional } from 'inversify';
18
+ import { Command } from '../../command/command';
19
+ import { GModelRecordingCommand } from '../../command/recording-command';
18
20
  import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration';
19
21
  import { OperationHandler } from '../../operations/operation-handler';
20
22
  import { Logger } from '../../utils/logger';
23
+ import { GModelSerializer } from '../model/gmodel-serializer';
21
24
  import { LayoutEngine } from './layout-engine';
22
25
 
26
+ /**
27
+ * The default handler for `{@link LayoutOperation}s. Does invoke the (optional) layout engine
28
+ * if the server is configured for manual layouting. Changes are stored transient in the graphical model and are not
29
+ * persisted in the source model.
30
+ */
23
31
  @injectable()
24
- export class LayoutOperationHandler implements OperationHandler {
32
+ export class LayoutOperationHandler extends OperationHandler {
25
33
  @inject(Logger)
26
34
  protected logger: Logger;
27
35
 
@@ -32,16 +40,23 @@ export class LayoutOperationHandler implements OperationHandler {
32
40
  @inject(DiagramConfiguration)
33
41
  protected diagramConfiguration: DiagramConfiguration;
34
42
 
43
+ @inject(GModelSerializer)
44
+ protected serializer: GModelSerializer;
45
+
35
46
  readonly operationType = LayoutOperation.KIND;
36
- async execute(operation: Operation): Promise<void> {
37
- if (operation.kind === LayoutOperation.KIND) {
38
- if (this.diagramConfiguration.layoutKind === ServerLayoutKind.MANUAL) {
39
- if (this.layoutEngine) {
40
- await this.layoutEngine.layout();
41
- return;
42
- }
43
- this.logger.warn('Could not execute layout operation. No `LayoutEngine` is bound!');
44
- }
47
+
48
+ createCommand(operation: LayoutOperation): MaybePromise<Command | undefined> {
49
+ if (this.diagramConfiguration.layoutKind !== ServerLayoutKind.MANUAL) {
50
+ return undefined;
51
+ }
52
+ if (!this.layoutEngine) {
53
+ this.logger.warn('Could not execute layout operation. No `LayoutEngine` is bound!');
54
+ return undefined;
45
55
  }
56
+ return new GModelRecordingCommand(this.modelState, this.serializer, () => this.executeOperation(operation));
57
+ }
58
+
59
+ protected async executeOperation(_operation: Operation): Promise<void> {
60
+ await this.layoutEngine?.layout();
46
61
  }
47
62
  }
@@ -30,7 +30,6 @@ export interface ModelState {
30
30
  readonly root: GModelRoot;
31
31
  updateRoot(newRoot: GModelRoot): void;
32
32
  editMode: string;
33
- isDirty: boolean;
34
33
  sourceUri?: string;
35
34
  clientId: string;
36
35
  readonly isReadonly: boolean;
@@ -56,8 +55,6 @@ export class DefaultModelState implements ModelState {
56
55
 
57
56
  editMode = EditMode.EDITABLE;
58
57
 
59
- isDirty = false;
60
-
61
58
  set<P>(key: string, property: P): void {
62
59
  this.properties.set(key, property);
63
60
  }
@@ -24,6 +24,7 @@ import {
24
24
  UpdateModelAction
25
25
  } from '@eclipse-glsp/protocol';
26
26
  import { inject, injectable, optional } from 'inversify';
27
+ import { CommandStack } from '../../command/command-stack';
27
28
  import { DiagramConfiguration, ServerLayoutKind } from '../../diagram/diagram-configuration';
28
29
  import { LayoutEngine } from '../layout/layout-engine';
29
30
  import { GModelFactory } from './gmodel-factory';
@@ -48,6 +49,9 @@ export class ModelSubmissionHandler {
48
49
  @optional()
49
50
  protected layoutEngine: LayoutEngine;
50
51
 
52
+ @inject(CommandStack)
53
+ protected commandStack: CommandStack;
54
+
51
55
  /**
52
56
  * Returns a list of actions to update the client-side model, based on the specified <code>modelState</code>.
53
57
  *
@@ -63,7 +67,7 @@ export class ModelSubmissionHandler {
63
67
  const root = this.serializeGModel();
64
68
 
65
69
  if (this.diagramConfiguration.needsClientLayout) {
66
- return [RequestBoundsAction.create(root), SetDirtyStateAction.create(this.modelState.isDirty, { reason })];
70
+ return [RequestBoundsAction.create(root), SetDirtyStateAction.create(this.commandStack.isDirty, { reason })];
67
71
  }
68
72
  return [SetModelAction.create(root)];
69
73
  }
@@ -95,7 +99,7 @@ export class ModelSubmissionHandler {
95
99
  : UpdateModelAction.create(root, { animate: this.diagramConfiguration.animatedUpdate })
96
100
  );
97
101
  if (!this.diagramConfiguration.needsClientLayout) {
98
- result.push(SetDirtyStateAction.create(this.modelState.isDirty, { reason }));
102
+ result.push(SetDirtyStateAction.create(this.commandStack.isDirty, { reason }));
99
103
  }
100
104
  return result;
101
105
  }
@@ -16,23 +16,22 @@
16
16
  import { Action, SaveModelAction, SetDirtyStateAction } from '@eclipse-glsp/protocol';
17
17
  import { inject, injectable } from 'inversify';
18
18
  import { ActionHandler } from '../../actions/action-handler';
19
- import { ModelState } from './model-state';
19
+ import { CommandStack } from '../../command/command-stack';
20
20
  import { SourceModelStorage } from './source-model-storage';
21
21
 
22
22
  @injectable()
23
23
  export class SaveModelActionHandler implements ActionHandler {
24
24
  actionKinds = [SaveModelAction.KIND];
25
25
 
26
- @inject(ModelState)
27
- protected modelState: ModelState;
26
+ @inject(CommandStack)
27
+ protected commandStack: CommandStack;
28
28
 
29
29
  @inject(SourceModelStorage)
30
30
  protected sourceModelStorage: SourceModelStorage;
31
31
 
32
32
  async execute(action: SaveModelAction): Promise<Action[]> {
33
33
  await this.sourceModelStorage.saveSourceModel(action);
34
- this.modelState.isDirty = false; // TODO: call save is done when available
35
-
36
- return [SetDirtyStateAction.create(this.modelState.isDirty, { reason: 'save' })];
34
+ this.commandStack.saveIsDone();
35
+ return [SetDirtyStateAction.create(this.commandStack.isDirty, { reason: 'save' })];
37
36
  }
38
37
  }
@@ -15,24 +15,22 @@
15
15
  ********************************************************************************/
16
16
  import { GLabel } from '@eclipse-glsp/graph';
17
17
  import { ApplyLabelEditOperation, MaybePromise } from '@eclipse-glsp/protocol';
18
- import { inject, injectable } from 'inversify';
19
- import { ModelState } from '../features/model/model-state';
20
- import { OperationHandler } from '../operations/operation-handler';
21
- import { GLSPServerError } from '../utils/glsp-server-error';
18
+ import { injectable } from 'inversify';
19
+ import { Command } from '../command/command';
20
+ import { getOrThrow } from '../utils/glsp-server-error';
21
+ import { GModelOperationHandler } from './gmodel-operation-handler';
22
22
 
23
23
  @injectable()
24
- export class ApplyLabelEditOperationHandler implements OperationHandler {
24
+ export class GModelApplyLabelEditOperationHandler extends GModelOperationHandler {
25
25
  readonly operationType = ApplyLabelEditOperation.KIND;
26
26
 
27
- @inject(ModelState)
28
- protected readonly modelState: ModelState;
29
-
30
- execute(operation: ApplyLabelEditOperation): MaybePromise<void> {
31
- const element = this.modelState.index.findByClass(operation.labelId, GLabel);
32
- if (element) {
33
- element.text = operation.text;
34
- } else {
35
- throw new GLSPServerError('Element with provided ID cannot be found or is not a GLabel');
36
- }
27
+ createCommand(operation: ApplyLabelEditOperation): MaybePromise<Command | undefined> {
28
+ const label = getOrThrow(
29
+ this.modelState.index.findByClass(operation.labelId, GLabel),
30
+ 'Element with provided ID cannot be found or is not a GLabel'
31
+ );
32
+ return label.text !== operation.text //
33
+ ? this.commandOf(() => (label.text = operation.text))
34
+ : undefined;
37
35
  }
38
36
  }