aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
blob: f64d40664337daefc2ce6dcf3a4566e4cf32b59f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package tools.refinery.language.ide.syntaxcoloring;

import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.xtext.ide.editor.syntaxcoloring.DefaultSemanticHighlightingCalculator;
import org.eclipse.xtext.ide.editor.syntaxcoloring.IHighlightedPositionAcceptor;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.service.OperationCanceledManager;
import org.eclipse.xtext.util.CancelIndicator;
import org.jetbrains.annotations.NotNull;
import tools.refinery.language.model.problem.*;
import tools.refinery.language.utils.ProblemDesugarer;
import tools.refinery.language.utils.ProblemUtil;

import java.util.List;

public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighlightingCalculator {
	private static final String BUILTIN_CLASS = "builtin";
	private static final String ABSTRACT_CLASS = "abstract";
	private static final String CONTAINMENT_CLASS = "containment";
	private static final String ERROR_CLASS = "error";
	private static final String NODE_CLASS = "node";
	private static final String INDIVIDUAL_NODE_CLASS = "individual";
	private static final String NEW_NODE_CLASS = "new";

	@Inject
	private OperationCanceledManager operationCanceledManager;

	@Inject
	private ProblemDesugarer desugarer;

	@Inject
	private TypeHashProvider typeHashProvider;

	@Override
	protected boolean highlightElement(EObject object, IHighlightedPositionAcceptor acceptor,
									   CancelIndicator cancelIndicator) {
		highlightName(object, acceptor);
		highlightCrossReferences(object, acceptor, cancelIndicator);
		return false;
	}

	protected void highlightName(EObject object, IHighlightedPositionAcceptor acceptor) {
		if (!(object instanceof NamedElement)) {
			return;
		}
		String[] highlightClass = getHighlightClass(object, null);
		if (highlightClass.length > 0) {
			highlightFeature(acceptor, object, ProblemPackage.Literals.NAMED_ELEMENT__NAME, highlightClass);
		}
	}

	protected void highlightCrossReferences(EObject object, IHighlightedPositionAcceptor acceptor,
											CancelIndicator cancelIndicator) {
		for (EReference reference : object.eClass().getEAllReferences()) {
			if (reference.isContainment()) {
				continue;
			}
			operationCanceledManager.checkCanceled(cancelIndicator);
			if (reference.isMany()) {
				highlightManyValues(object, reference, acceptor);
			} else {
				highlightSingleValue(object, reference, acceptor);
			}
		}
	}

	protected void highlightSingleValue(EObject object, EReference reference, IHighlightedPositionAcceptor acceptor) {
		EObject valueObj = (EObject) object.eGet(reference);
		String[] highlightClass = getHighlightClass(valueObj, reference);
		if (highlightClass.length > 0) {
			highlightFeature(acceptor, object, reference, highlightClass);
		}
	}

	protected void highlightManyValues(EObject object, EReference reference, IHighlightedPositionAcceptor acceptor) {
		@SuppressWarnings("unchecked")
		EList<? extends EObject> values = (EList<? extends EObject>) object.eGet(reference);
		List<INode> nodes = NodeModelUtils.findNodesForFeature(object, reference);
		int size = Math.min(values.size(), nodes.size());
		for (var i = 0; i < size; i++) {
			EObject valueInList = values.get(i);
			INode node = nodes.get(i);
			String[] highlightClass = getHighlightClass(valueInList, reference);
			if (highlightClass.length > 0) {
				highlightNode(acceptor, node, highlightClass);
			}
		}
	}

	protected String[] getHighlightClass(EObject eObject, EReference reference) {
		boolean isError = ProblemUtil.isError(eObject);
		if (ProblemUtil.isBuiltIn(eObject)) {
			var className = isError ? ERROR_CLASS : BUILTIN_CLASS;
			return new String[]{className};
		}
		return getUserDefinedElementHighlightClass(eObject, reference, isError);
	}

	@NotNull
	private String[] getUserDefinedElementHighlightClass(EObject eObject, EReference reference, boolean isError) {
		ImmutableList.Builder<String> classesBuilder = ImmutableList.builder();
		if (eObject instanceof ClassDeclaration classDeclaration && classDeclaration.isAbstract()) {
			classesBuilder.add(ABSTRACT_CLASS);
		}
		if (eObject instanceof ReferenceDeclaration referenceDeclaration
				&& desugarer.isContainmentReference(referenceDeclaration)) {
			classesBuilder.add(CONTAINMENT_CLASS);
		}
		if (isError && reference != null) {
			// References to error patterns should be highlighted as errors, but error pattern definitions shouldn't.
			classesBuilder.add(ERROR_CLASS);
		}
		if (eObject instanceof Node node) {
			highlightNode(node, reference, classesBuilder);
		}
		if (eObject instanceof Relation relation) {
			var typeHash = typeHashProvider.getTypeHash(relation);
			if (typeHash != null) {
				classesBuilder.add("typeHash-" + typeHash);
			}
		}
		List<String> classes = classesBuilder.build();
		return classes.toArray(new String[0]);
	}

	private static void highlightNode(Node node, EReference reference, ImmutableList.Builder<String> classesBuilder) {
		if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE) {
			classesBuilder.add(NODE_CLASS);
		}
		if (ProblemUtil.isIndividualNode(node)) {
			classesBuilder.add(INDIVIDUAL_NODE_CLASS);
		}
		if (ProblemUtil.isNewNode(node)) {
			classesBuilder.add(NEW_NODE_CLASS);
		}
	}
}