@meltemi/diagrams-edit 1.9.0 → 1.10.0
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/dist/attachments.d.ts +30 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +85 -0
- package/dist/attachments.js.map +1 -0
- package/dist/c4.d.ts +26 -1
- package/dist/c4.d.ts.map +1 -1
- package/dist/c4.js +321 -27
- package/dist/c4.js.map +1 -1
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +31 -9
- package/dist/create.js.map +1 -1
- package/dist/free.js +8 -6
- package/dist/free.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lines.d.ts +16 -0
- package/dist/lines.d.ts.map +1 -0
- package/dist/lines.js +130 -0
- package/dist/lines.js.map +1 -0
- package/dist/messages/en.d.ts +35 -1
- package/dist/messages/en.d.ts.map +1 -1
- package/dist/messages/en.js +36 -2
- package/dist/messages/en.js.map +1 -1
- package/dist/messages/es.d.ts.map +1 -1
- package/dist/messages/es.js +21 -2
- package/dist/messages/es.js.map +1 -1
- package/dist/messages/index.d.ts +20 -1
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +47 -6
- package/dist/plan.js.map +1 -1
- package/dist/relations.d.ts +1 -1
- package/dist/relations.d.ts.map +1 -1
- package/dist/relations.js +3 -2
- package/dist/relations.js.map +1 -1
- package/dist/types.d.ts +96 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Arg, TextEdit } from "@meltemi/diagrams-language";
|
|
2
|
+
import type { Element } from "@meltemi/diagrams-model";
|
|
3
|
+
import { type EditContext, type TextContext } from "./context.js";
|
|
4
|
+
import type { OperationOf } from "./plan.js";
|
|
5
|
+
import type { Dependent } from "./types.js";
|
|
6
|
+
/** The words after the text of a note's declaration: `on` and the target that follows it. */
|
|
7
|
+
export interface AttachmentArgs {
|
|
8
|
+
/** The argument before `on`: the note's text (or its id when it has none). */
|
|
9
|
+
readonly before: Arg;
|
|
10
|
+
readonly word: Arg;
|
|
11
|
+
readonly target: Arg;
|
|
12
|
+
}
|
|
13
|
+
/** The `on <target>` clause of a note's statement, or undefined when it has none. */
|
|
14
|
+
export declare function attachmentArgs(note: Element): AttachmentArgs | undefined;
|
|
15
|
+
/** The edit that takes ` on <target>` out of a note's statement, keeping its block and comments. */
|
|
16
|
+
export declare function stripAttachment(note: Element): TextEdit | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* A note attached to something about to go, as a dependent of the removal and the edit that
|
|
19
|
+
* leaves the note without its clause: a note's text is the person's words, so it stays.
|
|
20
|
+
*/
|
|
21
|
+
export declare function attachmentDependent(ctx: TextContext, note: Element): {
|
|
22
|
+
dependent: Dependent;
|
|
23
|
+
edit: TextEdit;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Writes, replaces or removes the `on <element>` clause of a note, in the same declaration and
|
|
27
|
+
* with the smallest edit: the clause follows the note's text and the block stays where it is.
|
|
28
|
+
*/
|
|
29
|
+
export declare function attachNote(ctx: EditContext, op: OperationOf<"attachNote">): TextEdit[];
|
|
30
|
+
//# sourceMappingURL=attachments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAoB,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAA0B,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAG1F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;CACtB;AAID,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAOxE;AAED,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAGnE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,OAAO,GACZ;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,SAAS,CAatD;AAUD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,QAAQ,EAAE,CA+BtF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { reject, requireElement } from "./context.js";
|
|
2
|
+
import { verifyNoNewErrors } from "./insert.js";
|
|
3
|
+
import { messages, reasons } from "./messages/index.js";
|
|
4
|
+
const isOn = (arg) => arg?.kind === "QualifiedRef" && arg.text === "on";
|
|
5
|
+
/** The `on <target>` clause of a note's statement, or undefined when it has none. */
|
|
6
|
+
export function attachmentArgs(note) {
|
|
7
|
+
const node = note.node;
|
|
8
|
+
if (note.kind !== "note" || node?.kind !== "KeywordStatement")
|
|
9
|
+
return undefined;
|
|
10
|
+
const args = node.args;
|
|
11
|
+
const at = args.findIndex((arg, i) => i >= 2 && isOn(arg));
|
|
12
|
+
const target = args[at + 1];
|
|
13
|
+
return at < 0 || !target ? undefined : { before: args[at - 1], word: args[at], target };
|
|
14
|
+
}
|
|
15
|
+
/** The edit that takes ` on <target>` out of a note's statement, keeping its block and comments. */
|
|
16
|
+
export function stripAttachment(note) {
|
|
17
|
+
const clause = attachmentArgs(note);
|
|
18
|
+
return clause && { start: clause.before.end, end: clause.target.end, newText: "" };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A note attached to something about to go, as a dependent of the removal and the edit that
|
|
22
|
+
* leaves the note without its clause: a note's text is the person's words, so it stays.
|
|
23
|
+
*/
|
|
24
|
+
export function attachmentDependent(ctx, note) {
|
|
25
|
+
const edit = stripAttachment(note);
|
|
26
|
+
if (!edit || !note.node)
|
|
27
|
+
return undefined;
|
|
28
|
+
const lines = ctx.compilation.parse.lines;
|
|
29
|
+
return {
|
|
30
|
+
edit,
|
|
31
|
+
dependent: {
|
|
32
|
+
kind: "attachment",
|
|
33
|
+
name: note.id,
|
|
34
|
+
description: messages().dependents.attachment(note.id, lines.location(note.node.start).line),
|
|
35
|
+
range: lines.range(note.node),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Whether `path` is an element a note of the diagram may be attached to. */
|
|
40
|
+
function attachable(ctx, note, path) {
|
|
41
|
+
if (path === note.id)
|
|
42
|
+
return false;
|
|
43
|
+
const element = ctx.diagram.elementsById.get(path);
|
|
44
|
+
// A view draws entities, and notes attach to what it draws.
|
|
45
|
+
return ctx.diagram.type === "c4" ? element?.entity != null : element !== undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Writes, replaces or removes the `on <element>` clause of a note, in the same declaration and
|
|
49
|
+
* with the smallest edit: the clause follows the note's text and the block stays where it is.
|
|
50
|
+
*/
|
|
51
|
+
export function attachNote(ctx, op) {
|
|
52
|
+
const note = requireElement(ctx, op.note);
|
|
53
|
+
const r = reasons().attach;
|
|
54
|
+
if (note.kind !== "note" || note.node?.kind !== "KeywordStatement") {
|
|
55
|
+
reject("edit.invalid-value", r.onlyNotes(note.id, note.kind));
|
|
56
|
+
}
|
|
57
|
+
const clause = attachmentArgs(note);
|
|
58
|
+
const node = note.node;
|
|
59
|
+
if (op.element === null) {
|
|
60
|
+
const edit = stripAttachment(note);
|
|
61
|
+
return edit ? [edit] : [];
|
|
62
|
+
}
|
|
63
|
+
if (op.element === note.id)
|
|
64
|
+
reject("edit.invalid-value", r.self(note.id));
|
|
65
|
+
if (!attachable(ctx, note, op.element)) {
|
|
66
|
+
if (ctx.diagram.type === "c4") {
|
|
67
|
+
reject("edit.unsafe-target", r.notDrawn(op.element, ctx.diagram.id));
|
|
68
|
+
}
|
|
69
|
+
requireElement(ctx, op.element);
|
|
70
|
+
}
|
|
71
|
+
if (clause?.target.kind === "QualifiedRef" && clause.target.text === op.element)
|
|
72
|
+
return [];
|
|
73
|
+
const edits = clause
|
|
74
|
+
? [{ start: clause.target.start, end: clause.target.end, newText: op.element }]
|
|
75
|
+
: [
|
|
76
|
+
{
|
|
77
|
+
start: (node.args[1] ?? node.args[0]).end,
|
|
78
|
+
end: (node.args[1] ?? node.args[0]).end,
|
|
79
|
+
newText: ` on ${op.element}`,
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
verifyNoNewErrors(ctx, edits);
|
|
83
|
+
return edits;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
|
package/dist/c4.d.ts
CHANGED
|
@@ -2,10 +2,22 @@ import { type Statement, type TextEdit } from "@meltemi/diagrams-language";
|
|
|
2
2
|
import { type Compilation, type EntityReference, type FlowDiagram, type ModelDefinition } from "@meltemi/diagrams-model";
|
|
3
3
|
import { type EditContext, type TextContext } from "./context.js";
|
|
4
4
|
import { type OperationOf } from "./plan.js";
|
|
5
|
+
/** The operation fields that name the element it acts on. */
|
|
6
|
+
interface ElementTarget {
|
|
7
|
+
readonly kind: string;
|
|
8
|
+
readonly element?: string | null;
|
|
9
|
+
readonly target?: string;
|
|
10
|
+
readonly targetKind?: string;
|
|
11
|
+
}
|
|
5
12
|
/** Refuses an operation that does not apply to a C4 view, naming the ones that do. */
|
|
6
|
-
export declare function refuseInView(ctx: EditContext,
|
|
13
|
+
export declare function refuseInView(ctx: EditContext, operation: ElementTarget): void;
|
|
7
14
|
/** Relations of the view that are combinations written in it, not model relations. */
|
|
8
15
|
export declare function isCombination(diagram: FlowDiagram, key: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Declares a note in a view: after its last note, else after the lists and labels and before the
|
|
18
|
+
* blocks, where the language keeps what a view says about what it draws.
|
|
19
|
+
*/
|
|
20
|
+
export declare function insertViewNote(ctx: EditContext, text: string): TextEdit;
|
|
9
21
|
export declare function includeEntity(ctx: EditContext, op: OperationOf<"includeEntity">): TextEdit[];
|
|
10
22
|
export declare function excludeEntity(ctx: EditContext, op: OperationOf<"excludeEntity">): TextEdit[];
|
|
11
23
|
export declare function setLocalLabel(ctx: EditContext, op: OperationOf<"setLocalLabel">): TextEdit[];
|
|
@@ -24,7 +36,20 @@ export declare function removeEntity(ctx: ModelContext, op: OperationOf<"removeE
|
|
|
24
36
|
export declare function setEntityProperty(ctx: ModelContext, op: OperationOf<"setEntityProperty">): TextEdit[];
|
|
25
37
|
export declare function createEntity(ctx: ModelContext, op: OperationOf<"createEntity">): TextEdit[];
|
|
26
38
|
export declare function createModelRelation(ctx: ModelContext, op: OperationOf<"createModelRelation">): TextEdit[];
|
|
39
|
+
/** Moves one end of a model relation to another entity; the label, id and block stay. */
|
|
40
|
+
export declare function reconnectModelRelation(ctx: ModelContext, op: OperationOf<"reconnectModelRelation">): TextEdit[];
|
|
41
|
+
/**
|
|
42
|
+
* The routes of a relation in a view of a file other than the model's, once the model moved one of
|
|
43
|
+
* its ends (the compilation already resolves the moved model): they follow the end the view
|
|
44
|
+
* draws now, as `reconnectModelRelation` makes them follow in the model's own file.
|
|
45
|
+
*/
|
|
46
|
+
export declare function rewriteRouteEnd(ctx: EditContext, op: OperationOf<"rewriteRouteEnd">): TextEdit[];
|
|
27
47
|
export declare function removeModelRelation(ctx: ModelContext, op: OperationOf<"removeModelRelation">): TextEdit[];
|
|
48
|
+
/**
|
|
49
|
+
* Takes a model relation out of a view whose model is in another file: the statements that name
|
|
50
|
+
* it go (with `cascade`), or are listed as dependents, with the view as drawing it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function forgetModelRelation(ctx: EditContext, op: OperationOf<"forgetModelRelation">): TextEdit[];
|
|
28
53
|
export declare function setModelRelationProperty(ctx: ModelContext, op: OperationOf<"setModelRelationProperty">): TextEdit[];
|
|
29
54
|
export type { Statement };
|
|
30
55
|
//# sourceMappingURL=c4.d.ts.map
|
package/dist/c4.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"c4.d.ts","sourceRoot":"","sources":["../src/c4.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"c4.d.ts","sourceRoot":"","sources":["../src/c4.ts"],"names":[],"mappings":"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"}
|
package/dist/c4.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { isIdentifierText, isToken, } from "@meltemi/diagrams-language";
|
|
2
|
-
import { ENTITY_CHILDREN, ENTITY_KINDS, isSafeUrl, } from "@meltemi/diagrams-model";
|
|
1
|
+
import { applyTextEdits, isIdentifierText, isToken, } from "@meltemi/diagrams-language";
|
|
2
|
+
import { ENTITY_CHILDREN, ENTITY_KINDS, defaultEntities, isSafeUrl, compile, singleIdent, } from "@meltemi/diagrams-model";
|
|
3
3
|
import { insertStatement, reject } from "./context.js";
|
|
4
4
|
import { deleteStatements } from "./delete.js";
|
|
5
|
+
import { attachmentDependent } from "./attachments.js";
|
|
5
6
|
import { insertAfter, insertBefore, verifyNoNewErrors } from "./insert.js";
|
|
6
7
|
import { messages, reasons } from "./messages/index.js";
|
|
7
8
|
import { removeListItems } from "./plan.js";
|
|
9
|
+
import { endpointText, requireEndPosition, routeParts } from "./routes.js";
|
|
8
10
|
import { storyDependents } from "./stories.js";
|
|
9
11
|
import { quoteString } from "./text.js";
|
|
10
12
|
// ---------------------------------------------------------------------- views
|
|
11
13
|
/** Operations a C4 view accepts; the others write declarations the view does not have. */
|
|
12
14
|
const VIEW_OPERATIONS = new Set([
|
|
15
|
+
// A view declares notes: `createElement` refuses every other kind there.
|
|
16
|
+
"createElement",
|
|
17
|
+
"attachNote",
|
|
13
18
|
"setPosition",
|
|
14
19
|
"setSize",
|
|
15
20
|
"setRoute",
|
|
@@ -25,11 +30,32 @@ const VIEW_OPERATIONS = new Set([
|
|
|
25
30
|
"includeEntity",
|
|
26
31
|
"excludeEntity",
|
|
27
32
|
"setLocalLabel",
|
|
33
|
+
"forgetModelRelation",
|
|
34
|
+
"rewriteRouteEnd",
|
|
35
|
+
"materializeRelationId",
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Operations a note of a view takes as any element does, since the note is a declaration of the
|
|
39
|
+
* view: its text, id, classes and url, and its removal with the placement and styles it has.
|
|
40
|
+
*/
|
|
41
|
+
const NOTE_OPERATIONS = new Set([
|
|
42
|
+
"renameElement",
|
|
43
|
+
"removeElement",
|
|
44
|
+
"setElementLabel",
|
|
45
|
+
"setClasses",
|
|
46
|
+
"setElementProperty",
|
|
28
47
|
]);
|
|
29
48
|
/** Refuses an operation that does not apply to a C4 view, naming the ones that do. */
|
|
30
|
-
export function refuseInView(ctx,
|
|
49
|
+
export function refuseInView(ctx, operation) {
|
|
50
|
+
const { kind } = operation;
|
|
31
51
|
if (ctx.diagram.type !== "c4" || VIEW_OPERATIONS.has(kind))
|
|
32
52
|
return;
|
|
53
|
+
if (NOTE_OPERATIONS.has(kind) && operation.targetKind !== "relation") {
|
|
54
|
+
const id = operation.element ?? operation.target;
|
|
55
|
+
const element = id === undefined ? undefined : ctx.diagram.elementsById.get(id);
|
|
56
|
+
if (element?.kind === "note" && element.entity === null)
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
33
59
|
reject("edit.invalid-value", reasons().c4.notInView(kind, ctx.diagram.c4?.model ?? ""));
|
|
34
60
|
}
|
|
35
61
|
function requireView(ctx) {
|
|
@@ -69,6 +95,13 @@ function insertViewStatement(ctx, text, after) {
|
|
|
69
95
|
? insertBefore(ctx, first, () => text)
|
|
70
96
|
: insertStatement(ctx, body, ctx.diagram.node.start, () => text);
|
|
71
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Declares a note in a view: after its last note, else after the lists and labels and before the
|
|
100
|
+
* blocks, where the language keeps what a view says about what it draws.
|
|
101
|
+
*/
|
|
102
|
+
export function insertViewNote(ctx, text) {
|
|
103
|
+
return insertViewStatement(ctx, text, ["level", "scope", "include", "exclude", "label", "note"]);
|
|
104
|
+
}
|
|
72
105
|
function requireEntity(ctx, path) {
|
|
73
106
|
const model = requireView(ctx).resolved?.definition;
|
|
74
107
|
if (!model)
|
|
@@ -106,8 +139,7 @@ export function includeEntity(ctx, op) {
|
|
|
106
139
|
return edits;
|
|
107
140
|
}
|
|
108
141
|
/** The view statements that only target an element, and edits that drop it from shared ones. */
|
|
109
|
-
function viewDependents(ctx, id) {
|
|
110
|
-
const diagram = ctx.diagram;
|
|
142
|
+
function viewDependents(ctx, diagram, id) {
|
|
111
143
|
const lines = ctx.compilation.parse.lines;
|
|
112
144
|
const statements = [];
|
|
113
145
|
const edits = [];
|
|
@@ -122,6 +154,16 @@ function viewDependents(ctx, id) {
|
|
|
122
154
|
});
|
|
123
155
|
};
|
|
124
156
|
const gone = new Set(diagram.relations.filter((r) => r.from === id || r.to === id).map((r) => r.key));
|
|
157
|
+
// A note of the view attached to the entity stays, without its `on` clause.
|
|
158
|
+
for (const note of diagram.elements) {
|
|
159
|
+
if (note.attachedTo !== id)
|
|
160
|
+
continue;
|
|
161
|
+
const attached = attachmentDependent(ctx, note);
|
|
162
|
+
if (!attached)
|
|
163
|
+
continue;
|
|
164
|
+
dependents.push(attached.dependent);
|
|
165
|
+
edits.push(attached.edit);
|
|
166
|
+
}
|
|
125
167
|
for (const entry of diagram.placement) {
|
|
126
168
|
if (entry.target === id || gone.has(entry.target))
|
|
127
169
|
depend(entry.statement, entry.target, entry.node);
|
|
@@ -176,7 +218,7 @@ export function excludeEntity(ctx, op) {
|
|
|
176
218
|
if (element.kind === "group") {
|
|
177
219
|
reject("edit.unsafe-target", reasons().c4.isScope(op.entity));
|
|
178
220
|
}
|
|
179
|
-
const dependents = viewDependents(ctx, op.entity);
|
|
221
|
+
const dependents = viewDependents(ctx, ctx.diagram, op.entity);
|
|
180
222
|
if (!op.cascade && dependents.dependents.length > 0) {
|
|
181
223
|
reject("edit.has-dependents", reasons().c4.removingFromView(op.entity, dependents.dependents.length), dependents.dependents);
|
|
182
224
|
}
|
|
@@ -222,7 +264,7 @@ export function setLocalLabel(ctx, op) {
|
|
|
222
264
|
else {
|
|
223
265
|
const relation = ctx.diagram.relationsByKey.get(op.target);
|
|
224
266
|
if (relation && relation.id === null) {
|
|
225
|
-
reject("edit.anonymous-relation", reasons().
|
|
267
|
+
reject("edit.anonymous-relation", reasons().relations.unnamed(op.target));
|
|
226
268
|
}
|
|
227
269
|
edits = [
|
|
228
270
|
insertViewStatement(ctx, `label ${op.target} ${quoteString(op.label)}`, [
|
|
@@ -378,6 +420,16 @@ export function removeEntity(ctx, op) {
|
|
|
378
420
|
if (removed.has(label.target) || relationKeys.has(label.target))
|
|
379
421
|
depend("label", label.target, label.node);
|
|
380
422
|
}
|
|
423
|
+
// The notes of the view attached to a removed entity stay, without their clause.
|
|
424
|
+
for (const note of diagram.elements) {
|
|
425
|
+
if (note.attachedTo === null || !removed.has(note.attachedTo))
|
|
426
|
+
continue;
|
|
427
|
+
const attached = attachmentDependent(ctx, note);
|
|
428
|
+
if (!attached)
|
|
429
|
+
continue;
|
|
430
|
+
dependents.push(attached.dependent);
|
|
431
|
+
edits.push(attached.edit);
|
|
432
|
+
}
|
|
381
433
|
for (const entry of diagram.placement) {
|
|
382
434
|
const target = entry.target;
|
|
383
435
|
const relation = diagram.relationsByKey.get(target);
|
|
@@ -578,34 +630,276 @@ export function createModelRelation(ctx, op) {
|
|
|
578
630
|
verifyNoNewErrors(ctx, edits);
|
|
579
631
|
return edits;
|
|
580
632
|
}
|
|
633
|
+
/** Moves one end of a model relation to another entity; the label, id and block stay. */
|
|
634
|
+
export function reconnectModelRelation(ctx, op) {
|
|
635
|
+
const relation = requireModelRelation(ctx, op.relation);
|
|
636
|
+
const entity = requireModelEntity(ctx, op.entity);
|
|
637
|
+
const current = op.end === "from" ? relation.from : relation.to;
|
|
638
|
+
if (current === entity.path)
|
|
639
|
+
return [];
|
|
640
|
+
const ref = op.end === "from" ? relation.node.source : relation.node.target;
|
|
641
|
+
if (ref.kind !== "QualifiedRef") {
|
|
642
|
+
reject("edit.unsafe-target", reasons().c4.endUnreadable(relation.key));
|
|
643
|
+
}
|
|
644
|
+
if (op.position !== undefined)
|
|
645
|
+
requireEndPosition(op.port, op.position);
|
|
646
|
+
// The path from the root resolves wherever the statement sits: the lookup from the enclosing
|
|
647
|
+
// entity outwards ends at the root.
|
|
648
|
+
const moved = { start: ref.start, end: ref.end, newText: entity.path };
|
|
649
|
+
const edits = [moved];
|
|
650
|
+
// The routes of the file's views follow the end the views now draw.
|
|
651
|
+
if (relation.id !== null) {
|
|
652
|
+
const after = compile(applyTextEdits(ctx.text, [moved]), ctx.compilation.options);
|
|
653
|
+
const removed = [];
|
|
654
|
+
for (const diagram of ctx.compilation.document.diagrams) {
|
|
655
|
+
const c4 = diagram.c4;
|
|
656
|
+
if (!c4 || c4.resolved?.file !== null || c4.resolved.definition.id !== ctx.model.id)
|
|
657
|
+
continue;
|
|
658
|
+
const routes = routeStatements(diagram, relation.id);
|
|
659
|
+
if (routes.length === 0)
|
|
660
|
+
continue;
|
|
661
|
+
const now = after.document.diagrams.find((d) => d.id === diagram.id);
|
|
662
|
+
const drawn = now?.relations.find((r) => r.id === relation.id);
|
|
663
|
+
const pinned = diagram.id === op.view && op.port !== undefined
|
|
664
|
+
? { port: op.port, ...(op.position !== undefined ? { position: op.position } : {}) }
|
|
665
|
+
: undefined;
|
|
666
|
+
const followed = followRoutes(ctx.text, ctx.model, routes, drawn, op.end, pinned);
|
|
667
|
+
edits.push(...followed.edits);
|
|
668
|
+
removed.push(...followed.removed);
|
|
669
|
+
}
|
|
670
|
+
edits.push(...deleteStatements(ctx.text, ctx.compilation.parse.root, removed));
|
|
671
|
+
}
|
|
672
|
+
verifyNoNewErrors(ctx, edits);
|
|
673
|
+
return edits;
|
|
674
|
+
}
|
|
675
|
+
/** The `route <id>` statements of a view, whether the analyser took them or not. */
|
|
676
|
+
function routeStatements(diagram, id) {
|
|
677
|
+
const found = [];
|
|
678
|
+
for (const owner of diagram.placementBlocks) {
|
|
679
|
+
for (const stmt of owner.block?.statements ?? []) {
|
|
680
|
+
if (stmt.kind !== "KeywordStatement" || stmt.keyword.text !== "route")
|
|
681
|
+
continue;
|
|
682
|
+
if (singleIdent(stmt.args[0])?.text === id)
|
|
683
|
+
found.push(stmt);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return found;
|
|
687
|
+
}
|
|
688
|
+
/** The compass sides and `auto`: the ports every entity has, which carry over to another. */
|
|
689
|
+
const COMPASS = new Set(["north", "south", "east", "west", "auto"]);
|
|
690
|
+
/** A route end as written: the entity it names, and the port after it when it has one. */
|
|
691
|
+
function writtenEnd(model, text) {
|
|
692
|
+
if (model.entitiesByPath.has(text))
|
|
693
|
+
return { element: text, port: null };
|
|
694
|
+
const dot = text.lastIndexOf(".");
|
|
695
|
+
if (dot > 0 && model.entitiesByPath.has(text.slice(0, dot))) {
|
|
696
|
+
return { element: text.slice(0, dot), port: text.slice(dot + 1) };
|
|
697
|
+
}
|
|
698
|
+
return { element: text, port: null };
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* The edits that make a view's routes of a relation end where the view now draws its moved end:
|
|
702
|
+
* the end rewritten with the drawn entity, a compass port carried over, a named port and a point
|
|
703
|
+
* dropped (or `pinned`, the port and point of a drop on this view), the other end and the
|
|
704
|
+
* waypoints kept; a route the view no longer draws the relation for goes; a route whose end
|
|
705
|
+
* already meets the drawn one stays as it is.
|
|
706
|
+
*/
|
|
707
|
+
function followRoutes(text, model, routes, drawn, end, pinned) {
|
|
708
|
+
const edits = [];
|
|
709
|
+
const removed = [];
|
|
710
|
+
for (const route of routes) {
|
|
711
|
+
if (!drawn) {
|
|
712
|
+
removed.push(route);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
const parts = routeParts(route);
|
|
716
|
+
const arg = end === "from" ? parts.fromRef : parts.toRef;
|
|
717
|
+
const at = end === "from" ? parts.fromAt : parts.toAt;
|
|
718
|
+
if (arg?.kind !== "QualifiedRef") {
|
|
719
|
+
reject("edit.unsafe-target", reasons().relations.routeUnreadable);
|
|
720
|
+
}
|
|
721
|
+
const target = end === "from" ? drawn.from : drawn.to;
|
|
722
|
+
const written = writtenEnd(model, text.slice(arg.start, arg.end));
|
|
723
|
+
let newText;
|
|
724
|
+
if (pinned)
|
|
725
|
+
newText = endpointText(target, pinned.port, pinned.position);
|
|
726
|
+
else if (written.element === target)
|
|
727
|
+
continue;
|
|
728
|
+
else {
|
|
729
|
+
const port = written.port !== null && COMPASS.has(written.port) ? written.port : null;
|
|
730
|
+
newText = endpointText(target, port);
|
|
731
|
+
}
|
|
732
|
+
const stop = (at ?? arg).end;
|
|
733
|
+
if (newText !== text.slice(arg.start, stop)) {
|
|
734
|
+
edits.push({ start: arg.start, end: stop, newText });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return { edits, removed };
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* The routes of a relation in a view of a file other than the model's, once the model moved one of
|
|
741
|
+
* its ends (the compilation already resolves the moved model): they follow the end the view
|
|
742
|
+
* draws now, as `reconnectModelRelation` makes them follow in the model's own file.
|
|
743
|
+
*/
|
|
744
|
+
export function rewriteRouteEnd(ctx, op) {
|
|
745
|
+
const c4 = requireView(ctx);
|
|
746
|
+
const model = c4.resolved?.definition;
|
|
747
|
+
if (!model)
|
|
748
|
+
reject("edit.unsafe-target", reasons().c4.unresolved);
|
|
749
|
+
const routes = routeStatements(ctx.diagram, op.relation);
|
|
750
|
+
if (routes.length === 0)
|
|
751
|
+
return [];
|
|
752
|
+
const drawn = ctx.diagram.relations.find((r) => r.id === op.relation);
|
|
753
|
+
const followed = followRoutes(ctx.text, model, routes, drawn, op.end, undefined);
|
|
754
|
+
const edits = [
|
|
755
|
+
...followed.edits,
|
|
756
|
+
...deleteStatements(ctx.text, ctx.compilation.parse.root, followed.removed),
|
|
757
|
+
];
|
|
758
|
+
verifyNoNewErrors(ctx, edits);
|
|
759
|
+
return edits;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Removes a model relation with the statements of the file's views that name it. Without
|
|
763
|
+
* `cascade`, those statements and the views that draw the relation (other than `op.view`) are
|
|
764
|
+
* listed as dependents.
|
|
765
|
+
*/
|
|
766
|
+
/**
|
|
767
|
+
* What a view holds about a model relation: the statements that name it by its key, or by a
|
|
768
|
+
* combination standing for it alone, as statements to delete, edits that take it out of shared
|
|
769
|
+
* lists, and dependents; with the view itself as drawing it when `listDrawn`.
|
|
770
|
+
*/
|
|
771
|
+
function relationStatements(ctx, diagram, key, listDrawn) {
|
|
772
|
+
const c4 = diagram.c4;
|
|
773
|
+
const lines = ctx.compilation.parse.lines;
|
|
774
|
+
const statements = [];
|
|
775
|
+
const edits = [];
|
|
776
|
+
const dependents = [];
|
|
777
|
+
if (!c4)
|
|
778
|
+
return { statements, edits, dependents };
|
|
779
|
+
const depend = (kind, name, node) => {
|
|
780
|
+
statements.push(node);
|
|
781
|
+
dependents.push({
|
|
782
|
+
kind,
|
|
783
|
+
name,
|
|
784
|
+
description: messages().dependents.statement(kind, name, lines.location(node.start).line),
|
|
785
|
+
range: lines.range(node),
|
|
786
|
+
});
|
|
787
|
+
};
|
|
788
|
+
const keys = new Set([key]);
|
|
789
|
+
let drawn = false;
|
|
790
|
+
for (const shown of diagram.relations) {
|
|
791
|
+
if (!shown.sources.includes(key))
|
|
792
|
+
continue;
|
|
793
|
+
drawn = true;
|
|
794
|
+
if (isCombination(diagram, shown.key) && shown.sources.every((k) => k === key)) {
|
|
795
|
+
keys.add(shown.key);
|
|
796
|
+
depend("relation", shown.key, shown.node);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (drawn && listDrawn) {
|
|
800
|
+
dependents.push({
|
|
801
|
+
kind: "relation",
|
|
802
|
+
name: diagram.id,
|
|
803
|
+
description: messages().dependents.drawnIn(key, diagram.id, lines.location(diagram.node.start).line),
|
|
804
|
+
range: lines.range(diagram.node),
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
for (const entry of diagram.placement) {
|
|
808
|
+
if (keys.has(entry.target))
|
|
809
|
+
depend(entry.statement, entry.target, entry.node);
|
|
810
|
+
}
|
|
811
|
+
for (const label of c4.labels) {
|
|
812
|
+
if (keys.has(label.target))
|
|
813
|
+
depend("label", label.target, label.node);
|
|
814
|
+
}
|
|
815
|
+
// A style or an animation of several targets loses this one; of this one alone, it goes.
|
|
816
|
+
const partial = new Map();
|
|
817
|
+
for (const entry of [...diagram.styles, ...diagram.animations]) {
|
|
818
|
+
if (!keys.has(entry.target))
|
|
819
|
+
continue;
|
|
820
|
+
const index = entry.node.args.findIndex((a) => a.start === entry.targetRange.start.offset);
|
|
821
|
+
const set = partial.get(entry.node) ?? new Set();
|
|
822
|
+
if (index >= 0)
|
|
823
|
+
set.add(index);
|
|
824
|
+
partial.set(entry.node, set);
|
|
825
|
+
const kind = "properties" in entry ? "style" : "animate";
|
|
826
|
+
dependents.push({
|
|
827
|
+
kind,
|
|
828
|
+
name: entry.target,
|
|
829
|
+
description: messages().dependents.statement(kind, entry.target, lines.location(entry.node.start).line),
|
|
830
|
+
range: lines.range(entry.node),
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
for (const [node, indices] of partial) {
|
|
834
|
+
if (indices.size >= node.args.length)
|
|
835
|
+
statements.push(node);
|
|
836
|
+
else
|
|
837
|
+
edits.push(...removeListItems(node.args, indices));
|
|
838
|
+
}
|
|
839
|
+
const stories = storyDependents(ctx.compilation, diagram.id, keys);
|
|
840
|
+
statements.push(...stories.statements);
|
|
841
|
+
edits.push(...stories.edits);
|
|
842
|
+
dependents.push(...stories.dependents);
|
|
843
|
+
// An entity the view drew only for this relation leaves its default set, with what names it.
|
|
844
|
+
const model = c4.resolved?.definition;
|
|
845
|
+
if (model && c4.includes === null) {
|
|
846
|
+
const without = {
|
|
847
|
+
...model,
|
|
848
|
+
relations: model.relations.filter((r) => r.key !== key),
|
|
849
|
+
};
|
|
850
|
+
const after = defaultEntities(without, c4.level, c4.scope);
|
|
851
|
+
for (const path of defaultEntities(model, c4.level, c4.scope)) {
|
|
852
|
+
if (after.has(path) || !diagram.elementsById.has(path))
|
|
853
|
+
continue;
|
|
854
|
+
dependents.push({
|
|
855
|
+
kind: "element",
|
|
856
|
+
name: path,
|
|
857
|
+
description: messages().dependents.leavesView(path, diagram.id),
|
|
858
|
+
range: lines.range(diagram.node),
|
|
859
|
+
});
|
|
860
|
+
// What the entity's own removal would take, less what the relation's already takes.
|
|
861
|
+
const leaving = viewDependents(ctx, diagram, path);
|
|
862
|
+
const taken = new Set(statements.map((s) => s.start));
|
|
863
|
+
const listed = new Set(dependents.map((d) => `${d.range.start.offset}:${d.description}`));
|
|
864
|
+
statements.push(...leaving.statements.filter((s) => !taken.has(s.start)));
|
|
865
|
+
edits.push(...leaving.edits);
|
|
866
|
+
dependents.push(...leaving.dependents.filter((d) => !listed.has(`${d.range.start.offset}:${d.description}`)));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return { statements, edits, dependents };
|
|
870
|
+
}
|
|
581
871
|
export function removeModelRelation(ctx, op) {
|
|
582
872
|
const relation = requireModelRelation(ctx, op.relation);
|
|
583
873
|
const statements = [relation.node];
|
|
584
|
-
|
|
874
|
+
const edits = [];
|
|
875
|
+
const dependents = [];
|
|
585
876
|
for (const diagram of ctx.compilation.document.diagrams) {
|
|
586
877
|
const c4 = diagram.c4;
|
|
587
878
|
if (!c4 || c4.resolved?.file !== null || c4.resolved.definition.id !== ctx.model.id)
|
|
588
879
|
continue;
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
keys.add(drawn.key);
|
|
594
|
-
statements.push(drawn.node);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
for (const entry of diagram.placement)
|
|
598
|
-
if (keys.has(entry.target))
|
|
599
|
-
statements.push(entry.node);
|
|
600
|
-
for (const label of c4.labels)
|
|
601
|
-
if (keys.has(label.target))
|
|
602
|
-
statements.push(label.node);
|
|
603
|
-
for (const entry of [...diagram.styles, ...diagram.animations]) {
|
|
604
|
-
if (keys.has(entry.target))
|
|
605
|
-
statements.push(entry.node);
|
|
606
|
-
}
|
|
880
|
+
const found = relationStatements(ctx, diagram, relation.key, diagram.id !== op.view);
|
|
881
|
+
statements.push(...found.statements);
|
|
882
|
+
edits.push(...found.edits);
|
|
883
|
+
dependents.push(...found.dependents);
|
|
607
884
|
}
|
|
608
|
-
|
|
885
|
+
if (!op.cascade && dependents.length > 0) {
|
|
886
|
+
reject("edit.has-dependents", reasons().c4.removingRelation(relation.key, dependents.length), dependents);
|
|
887
|
+
}
|
|
888
|
+
edits.push(...deleteStatements(ctx.text, ctx.compilation.parse.root, statements));
|
|
889
|
+
verifyNoNewErrors(ctx, edits);
|
|
890
|
+
return edits;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Takes a model relation out of a view whose model is in another file: the statements that name
|
|
894
|
+
* it go (with `cascade`), or are listed as dependents, with the view as drawing it.
|
|
895
|
+
*/
|
|
896
|
+
export function forgetModelRelation(ctx, op) {
|
|
897
|
+
requireView(ctx);
|
|
898
|
+
const { statements, edits, dependents } = relationStatements(ctx, ctx.diagram, op.relation, op.listDrawn ?? true);
|
|
899
|
+
if (!op.cascade && dependents.length > 0) {
|
|
900
|
+
reject("edit.has-dependents", reasons().c4.removingRelation(op.relation, dependents.length), dependents);
|
|
901
|
+
}
|
|
902
|
+
edits.push(...deleteStatements(ctx.text, ctx.compilation.parse.root, statements));
|
|
609
903
|
verifyNoNewErrors(ctx, edits);
|
|
610
904
|
return edits;
|
|
611
905
|
}
|