Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jep/utility #50

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
25 changes: 24 additions & 1 deletion extension/src-language-server/diagram-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* SPDX-License-Identifier: EPL-2.0
*/

import { Action, DiagramServices, JsonMap, RequestAction, RequestModelAction, ResponseAction } from "sprotty-protocol";
import { Action, CollapseExpandAction, CollapseExpandAllAction, DiagramServices, JsonMap, RequestAction, RequestModelAction, ResponseAction } from "sprotty-protocol";
import { Connection } from "vscode-languageserver";
import { FtaServices } from "./fta/fta-module.js";
import { SetSynthesisOptionsAction, UpdateOptionsAction } from "./options/actions.js";
Expand Down Expand Up @@ -53,6 +53,9 @@
import { StpaServices } from "./stpa/stpa-module.js";
import { SynthesisOptions } from "./synthesis-options.js";

// matches id of a node to its expansion state. True means expanded, false and undefined means collapsed
export let expansionState = new Map<string, boolean>();

Check warning on line 57 in extension/src-language-server/diagram-server.ts

View workflow job for this annotation

GitHub Actions / Run eslint and test and build

'expansionState' is never reassigned. Use 'const' instead

export class PastaDiagramServer extends SnippetDiagramServer {
protected synthesisOptions: SynthesisOptions | undefined;
protected stpaSnippets: StpaDiagramSnippets | undefined;
Expand Down Expand Up @@ -103,10 +106,30 @@
return this.handleGenerateSVGDiagrams(action as GenerateSVGsAction);
case UpdateDiagramAction.KIND:
return this.updateView(this.state.options);
case CollapseExpandAction.KIND:
return this.collapseExpand(action as CollapseExpandAction);
case CollapseExpandAllAction.KIND:
// TODO: create buttons in sidepanel to send this action and implement the reaction to it
console.log("received collapse/expand all action");
}
return super.handleAction(action);
}

/**
* Collapses and expands the nodes with the given ids and updates the view.
* @param action The CollapseExpandAction that triggered this method.
* @returns
*/
protected collapseExpand(action: CollapseExpandAction): Promise<void> {
for (const id of action.expandIds) {
expansionState.set(id, true);
}
for (const id of action.collapseIds) {
expansionState.set(id, false);
}
return this.updateView(this.state.options);
}

/**
* Creates a snippet from a string.
* @param text The text that should be inserted when clicking on the snippet.
Expand Down
5 changes: 3 additions & 2 deletions extension/src-language-server/layout-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ElkLayoutEngine } from "sprotty-elk/lib/elk-layout.js";
import { Point, SEdge, SGraph, SModelIndex } from "sprotty-protocol";
import { FTAEdge } from "./fta/diagram/fta-interfaces.js";
import { FTA_EDGE_TYPE } from "./fta/diagram/fta-model.js";
import { STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa/diagram/stpa-model.js';

export class LayoutEngine extends ElkLayoutEngine {
layout(sgraph: SGraph, index?: SModelIndex): SGraph | Promise<SGraph> {
Expand Down Expand Up @@ -52,10 +53,10 @@ export class LayoutEngine extends ElkLayoutEngine {
});
}

/** Override method to save the junctionpoints in FTAEdges*/
/** Override method to save junctionpoints*/
protected applyEdge(sedge: SEdge, elkEdge: ElkExtendedEdge, index: SModelIndex): void {
const points: Point[] = [];
if (sedge.type === FTA_EDGE_TYPE) {
if (sedge.type === FTA_EDGE_TYPE || sedge.type === STPA_EDGE_TYPE || sedge.type === STPA_INTERMEDIATE_EDGE_TYPE) {
(sedge as any as FTAEdge).junctionPoints = elkEdge.junctionPoints;
}
if (elkEdge.sections && elkEdge.sections.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { AstNode } from "langium";
import { IdCache } from "langium-sprotty";
import { SModelElement, SNode } from "sprotty-protocol";
import { expansionState } from "../../diagram-server.js";
import { Command, Graph, Node, Variable, VerticalEdge } from "../../generated/ast.js";
import { createControlStructureEdge, createDummyNode, createLabel, createPort } from "./diagram-elements.js";
import { CSEdge, CSNode, ParentNode } from "./stpa-interfaces.js";
Expand Down Expand Up @@ -94,8 +95,8 @@ export function createControlStructureNode(
// add nodes representing the process model
children.push(createProcessModelNodes(node.variables, idCache));
}
// add children of the control structure node
if (node.children?.length !== 0) {
// add children of the control structure node if the node is expanded
if (node.children?.length !== 0 && expansionState.get(node.name) === true) {
// add invisible node to group the children in order to be able to lay them out separately from the process model node
const invisibleNode = {
type: CS_INVISIBLE_SUBCOMPONENT_TYPE,
Expand All @@ -120,6 +121,8 @@ export function createControlStructureNode(
id: nodeId,
level: node.level,
children: children,
hasChildren: node.children?.length !== 0,
expanded: expansionState.get(node.name) === true,
layout: "stack",
layoutOptions: {
paddingTop: 10.0,
Expand Down Expand Up @@ -154,6 +157,8 @@ export function createProcessModelNodes(variables: Variable[], idCache: IdCache<
type: CS_NODE_TYPE,
id: nodeId,
children: children,
hasChildren: false,
expanded: expansionState.get(variable.name) === true,
layout: "stack",
layoutOptions: {
paddingTop: 10.0,
Expand Down Expand Up @@ -216,8 +221,12 @@ export function generateVerticalCSEdges(
edges.push(...translateIOToEdgeAndNode(node.inputs, node, EdgeType.INPUT, idToSNode, idCache));
// create edges representing the other outputs
edges.push(...translateIOToEdgeAndNode(node.outputs, node, EdgeType.OUTPUT, idToSNode, idCache));
// create edges for children and add the ones that must be added at the top level
edges.push(...generateVerticalCSEdges(node.children, idToSNode, idCache, addMissing, missingFeedback));

// add edges of the children of the node if the node is expanded
if (expansionState.get(node.name) === true) {
// create edges for children and add the ones that must be added at the top level
edges.push(...generateVerticalCSEdges(node.children, idToSNode, idCache, addMissing, missingFeedback));
}
}
return edges;
}
Expand Down Expand Up @@ -520,7 +529,11 @@ export function generatePortsForCSHierarchy(
const nodes: SNode[] = [];
while (current && (!ancestor || current !== ancestor)) {
const currentId = idCache.getId(current);
if (currentId) {
if (!currentId) {
// if the current node is collapsed, the ID is not set
// we may still want to draw the edge so far as possible to indicate the connection
current = current?.$container;
} else {
const currentNode = idToSNode.get(currentId);
if (currentNode) {
// current node could have an invisible child that was skipped while going up the hierarchy because it does not exist in the AST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ export function createDummyNode(name: string, level: number | undefined, idCache
type: DUMMY_NODE_TYPE,
id: idCache.uniqueId("dummy" + name),
layout: "stack",
hasChildren: false,
expanded: false,
layoutOptions: {
paddingTop: 10.0,
paddingBottom: 10.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,13 @@ export function createRelationshipGraphChildren(
.flat(1),
...sysCons
.map(systemConstraint =>
generateAspectWithEdges(systemConstraint, showSystemConstraintDescription, idToSNode, options, idCache)
generateAspectWithEdges(
systemConstraint,
showSystemConstraintDescription,
idToSNode,
options,
idCache
)
)
.flat(1),
]);
Expand All @@ -140,13 +146,19 @@ export function createRelationshipGraphChildren(
.flat(1),
...filteredModel.systemLevelConstraints
?.map(systemConstraint =>
generateAspectWithEdges(systemConstraint, showSystemConstraintDescription, idToSNode, options, idCache)
generateAspectWithEdges(
systemConstraint,
showSystemConstraintDescription,
idToSNode,
options,
idCache
)
)
.flat(1),
...filteredModel.systemLevelConstraints
?.map(systemConstraint =>
systemConstraint.subComponents?.map(subsystemConstraint =>
generateEdgesForSTPANode(subsystemConstraint, idToSNode, options, idCache)
generateEdgesForSTPANode(subsystemConstraint, undefined, idToSNode, options, idCache)
)
)
.flat(2),
Expand All @@ -158,7 +170,12 @@ export function createRelationshipGraphChildren(
showLabels,
labelManagement
);
const showUCAsDescription = showLabelOfAspect(STPAAspect.UCA, aspectsToShowDescriptions, showLabels, labelManagement);
const showUCAsDescription = showLabelOfAspect(
STPAAspect.UCA,
aspectsToShowDescriptions,
showLabels,
labelManagement
);
const showControllerConstraintDescription = showLabelOfAspect(
STPAAspect.CONTROLLERCONSTRAINT,
aspectsToShowDescriptions,
Expand Down Expand Up @@ -300,7 +317,15 @@ export function generateAspectWithEdges(
if ((isUCA(node) || isContext(node)) && node.$container.system.ref) {
stpaNode.controlAction = node.$container.system.ref.name + "." + node.$container.action.ref?.name;
}
const elements: SModelElement[] = generateEdgesForSTPANode(node, idToSNode, options, idCache);

let nodePort: SModelElement | undefined;
if (options.getUseHyperEdges()) {
const nodeId = idCache.getId(node);
nodePort = createPort(idCache.uniqueId(nodeId + "_outPort"), PortSide.NORTH);
stpaNode?.children?.push(nodePort);
}

const elements: SModelElement[] = generateEdgesForSTPANode(node, nodePort, idToSNode, options, idCache);
elements.push(stpaNode);
return elements;
}
Expand Down Expand Up @@ -366,11 +391,15 @@ export function generateSTPANode(
/**
* Generates the edges for {@code node}.
* @param node STPA component for which the edges should be created.
* @param nodePort The port of the node.
* @param idToSNode The map of the generated IDs to their generated SNodes.
* @param options The synthesis options of the STPA model.
* @param idCache The ID cache of the STPA model.
* @returns Edges representing the references {@code node} contains.
*/
export function generateEdgesForSTPANode(
node: AstNode,
nodePort: SModelElement | undefined,
idToSNode: Map<string, SNode>,
options: StpaSynthesisOptions,
idCache: IdCache<AstNode>
Expand All @@ -379,8 +408,9 @@ export function generateEdgesForSTPANode(
// for every reference an edge is created
// if hierarchy option is false, edges from subcomponents to parents are created too
const targets = getTargets(node, options.getHierarchy());

for (const target of targets) {
const edge = generateSTPAEdge(node, target, "", idToSNode, idCache);
const edge = generateSTPAEdge(node, nodePort?.id, target, "", idToSNode, idCache);
if (edge) {
elements.push(edge);
}
Expand All @@ -391,13 +421,16 @@ export function generateEdgesForSTPANode(
/**
* Generates a single STPAEdge based on the given arguments.
* @param source The source of the edge.
* @param sourcePortId The ID of the source port of the edge.
* @param target The target of the edge.
* @param label The label of the edge.
* @param param4 GeneratorContext of the STPA model.
* @param idToSNode The map of the generated IDs to their generated SNodes.
* @param idCache The ID cache of the STPA model.
* @returns An STPAEdge.
*/
export function generateSTPAEdge(
source: AstNode,
sourcePortId: string | undefined,
target: AstNode,
label: string,
idToSNode: Map<string, SNode>,
Expand All @@ -421,32 +454,36 @@ export function generateSTPAEdge(
target,
source,
sourceId,
sourcePortId,
edgeId,
children,
idToSNode,
idCache
);
} else {
// otherwise it is sufficient to add ports for source and target
const portIds = createPortsForSTPAEdge(
sourceId,
PortSide.NORTH,
targetId,
PortSide.SOUTH,
edgeId,
idToSNode,
idCache
);
let targetPortId: string | undefined;
if (sourcePortId) {
// if hyperedges are used, the source port is already given
const targetNode = idToSNode.get(targetId!);
targetPortId = idCache.uniqueId(edgeId + "_newTransition");
targetNode?.children?.push(createPort(targetPortId, PortSide.SOUTH));
} else {
const portIds = createPortsForSTPAEdge(
sourceId,
PortSide.NORTH,
targetId,
PortSide.SOUTH,
edgeId,
idToSNode,
idCache
);
sourcePortId = portIds.sourcePortId;
targetPortId = portIds.targetPortId;
}

// add edge between the two ports
return createSTPAEdge(
edgeId,
portIds.sourcePortId,
portIds.targetPortId,
children,
STPA_EDGE_TYPE,
getAspect(source)
);
return createSTPAEdge(edgeId, sourcePortId, targetPortId, children, STPA_EDGE_TYPE, getAspect(source));
}
}
}
Expand All @@ -458,6 +495,7 @@ export function generateSTPAEdge(
* @param targetId The id of the target node.
* @param targetSide The side of the target node the edge should be connected to.
* @param edgeId The id of the edge.
* @param idToSNode The map of the generated IDs to their generated SNodes.
* @param idCache The id cache of the STPA model.
* @returns the ids of the source and target port the edge should be connected to.
*/
Expand Down Expand Up @@ -487,15 +525,18 @@ export function createPortsForSTPAEdge(
* @param target The target of the edge.
* @param source The source of the edge.
* @param sourceId The ID of the source of the edge.
* @param sourcePortId The ID of the source port of the edge.
* @param edgeId The ID of the original edge.
* @param children The children of the original edge.
* @param idToSNode The map of the generated IDs to their generated SNodes.
* @param idCache The ID cache of the STPA model.
* @returns an STPAEdge to connect the {@code source} (or its top parent) with the top parent of the {@code target}.
*/
export function generateIntermediateIncomingSTPAEdges(
target: AstNode,
source: AstNode,
sourceId: string,
sourcePortId: string | undefined,
edgeId: string,
children: SModelElement[],
idToSNode: Map<string, SNode>,
Expand Down Expand Up @@ -533,10 +574,12 @@ export function generateIntermediateIncomingSTPAEdges(
idCache
);
} else {
// add port for source node
const sourceNode = idToSNode.get(sourceId);
const sourcePortId = idCache.uniqueId(edgeId + "_newTransition");
sourceNode?.children?.push(createPort(sourcePortId, PortSide.NORTH));
if (!sourcePortId) {
// add port for source node
const sourceNode = idToSNode.get(sourceId);
sourcePortId = idCache.uniqueId(edgeId + "_newTransition");
sourceNode?.children?.push(createPort(sourcePortId, PortSide.NORTH));
}

// add edge from source to top parent of the target
return createSTPAEdge(
Expand All @@ -556,6 +599,7 @@ export function generateIntermediateIncomingSTPAEdges(
* @param edgeId The ID of the original edge.
* @param children The children of the original edge.
* @param targetPortId The ID of the target port.
* @param idToSNode The map of the generated IDs to their generated SNodes.
* @param idCache The ID cache of the STPA model.
* @returns the STPAEdge to connect the top parent of the {@code source} with the {@code targetPortId}.
*/
Expand Down Expand Up @@ -602,6 +646,7 @@ export function generateIntermediateOutgoingSTPAEdges(
* @param current The current node.
* @param edgeId The ID of the original edge for which the ports are created.
* @param side The side of the ports.
* @param idToSNode The map of the generated IDs to their generated SNodes.
* @param idCache The ID cache of the STPA model.
* @returns the IDs of the created ports.
*/
Expand Down
6 changes: 3 additions & 3 deletions extension/src-language-server/stpa/diagram/layout-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator {
options["org.eclipse.elk.separateConnectedComponents"] = "false";
}
if (csParent) {
options["org.eclipse.elk.layered.considerModelOrder.strategy"] = "PREFER_NODES";
options["org.eclipse.elk.layered.considerModelOrder.strategy"] = "NODES_AND_EDGES";
options["org.eclipse.elk.layered.crossingMinimization.forceNodeModelOrder"] = "true";
options["org.eclipse.elk.layered.cycleBreaking.strategy"] = "MODEL_ORDER";
}
Expand Down Expand Up @@ -122,7 +122,7 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator {
"org.eclipse.elk.partitioning.activate": "true",
"org.eclipse.elk.direction": "DOWN",
"org.eclipse.elk.portConstraints": "FIXED_SIDE",
"org.eclipse.elk.layered.considerModelOrder.strategy": "PREFER_NODES",
"org.eclipse.elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
"org.eclipse.elk.layered.crossingMinimization.forceNodeModelOrder": "true",
"org.eclipse.elk.layered.cycleBreaking.strategy": "MODEL_ORDER",
// nodes with many edges are streched
Expand Down Expand Up @@ -191,7 +191,7 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator {
// nodes with many edges are streched
"org.eclipse.elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
"org.eclipse.elk.layered.nodePlacement.networkSimplex.nodeFlexibility.default": "NODE_SIZE",
"org.eclipse.elk.layered.considerModelOrder.strategy": "PREFER_NODES",
"org.eclipse.elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
"org.eclipse.elk.layered.crossingMinimization.forceNodeModelOrder": "true",
"org.eclipse.elk.layered.cycleBreaking.strategy": "MODEL_ORDER"
};
Expand Down
Loading
Loading