diff options
4 files changed, 148 insertions, 1 deletions
diff --git a/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java b/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java index 3502c29f..51cecf06 100644 --- a/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java +++ b/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java | |||
@@ -4,9 +4,11 @@ | |||
4 | package tools.refinery.language.ide; | 4 | package tools.refinery.language.ide; |
5 | 5 | ||
6 | import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher; | 6 | import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher; |
7 | import org.eclipse.xtext.ide.editor.contentassist.IdeCrossrefProposalProvider; | ||
7 | import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator; | 8 | import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator; |
8 | 9 | ||
9 | import tools.refinery.language.ide.contentassist.FuzzyMatcher; | 10 | import tools.refinery.language.ide.contentassist.FuzzyMatcher; |
11 | import tools.refinery.language.ide.contentassist.ProblemCrossrefProposalProvider; | ||
10 | import tools.refinery.language.ide.syntaxcoloring.ProblemSemanticHighlightingCalculator; | 12 | import tools.refinery.language.ide.syntaxcoloring.ProblemSemanticHighlightingCalculator; |
11 | 13 | ||
12 | /** | 14 | /** |
@@ -16,9 +18,13 @@ public class ProblemIdeModule extends AbstractProblemIdeModule { | |||
16 | public Class<? extends ISemanticHighlightingCalculator> bindISemanticHighlightingCalculator() { | 18 | public Class<? extends ISemanticHighlightingCalculator> bindISemanticHighlightingCalculator() { |
17 | return ProblemSemanticHighlightingCalculator.class; | 19 | return ProblemSemanticHighlightingCalculator.class; |
18 | } | 20 | } |
19 | 21 | ||
20 | @Override | 22 | @Override |
21 | public Class<? extends IPrefixMatcher> bindIPrefixMatcher() { | 23 | public Class<? extends IPrefixMatcher> bindIPrefixMatcher() { |
22 | return FuzzyMatcher.class; | 24 | return FuzzyMatcher.class; |
23 | } | 25 | } |
26 | |||
27 | public Class<? extends IdeCrossrefProposalProvider> bindIdeCrossrefProposalProvider() { | ||
28 | return ProblemCrossrefProposalProvider.class; | ||
29 | } | ||
24 | } | 30 | } |
diff --git a/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java b/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java new file mode 100644 index 00000000..416535ce --- /dev/null +++ b/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java | |||
@@ -0,0 +1,79 @@ | |||
1 | package tools.refinery.language.ide.contentassist; | ||
2 | |||
3 | import java.util.Objects; | ||
4 | |||
5 | import org.eclipse.emf.ecore.EObject; | ||
6 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
7 | import org.eclipse.xtext.CrossReference; | ||
8 | import org.eclipse.xtext.GrammarUtil; | ||
9 | import org.eclipse.xtext.ide.editor.contentassist.ContentAssistContext; | ||
10 | import org.eclipse.xtext.ide.editor.contentassist.ContentAssistEntry; | ||
11 | import org.eclipse.xtext.ide.editor.contentassist.IdeCrossrefProposalProvider; | ||
12 | import org.eclipse.xtext.nodemodel.util.NodeModelUtils; | ||
13 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
14 | |||
15 | import com.google.inject.Inject; | ||
16 | |||
17 | import tools.refinery.language.model.ProblemUtil; | ||
18 | import tools.refinery.language.model.problem.Problem; | ||
19 | import tools.refinery.language.resource.ReferenceCounter; | ||
20 | |||
21 | public class ProblemCrossrefProposalProvider extends IdeCrossrefProposalProvider { | ||
22 | @Inject | ||
23 | private ReferenceCounter referenceCounter; | ||
24 | |||
25 | @Override | ||
26 | protected ContentAssistEntry createProposal(IEObjectDescription candidate, CrossReference crossRef, | ||
27 | ContentAssistContext context) { | ||
28 | if (!shouldCreateProposal(candidate, crossRef, context)) { | ||
29 | return null; | ||
30 | } | ||
31 | return super.createProposal(candidate, crossRef, context); | ||
32 | } | ||
33 | |||
34 | protected boolean shouldCreateProposal(IEObjectDescription candidate, CrossReference crossRef, | ||
35 | ContentAssistContext context) { | ||
36 | var rootModel = context.getRootModel(); | ||
37 | var eObjectOrProxy = candidate.getEObjectOrProxy(); | ||
38 | if (!Objects.equals(rootModel.eResource(), eObjectOrProxy.eResource())) { | ||
39 | return true; | ||
40 | } | ||
41 | var currentValue = getCurrentValue(crossRef, context); | ||
42 | if (currentValue == null) { | ||
43 | return true; | ||
44 | } | ||
45 | var eObject = EcoreUtil.resolve(eObjectOrProxy, rootModel); | ||
46 | if (!Objects.equals(currentValue, eObject)) { | ||
47 | return true; | ||
48 | } | ||
49 | if (!ProblemUtil.isImplicit(eObject)) { | ||
50 | return true; | ||
51 | } | ||
52 | if (rootModel instanceof Problem problem) { | ||
53 | var referenceCounts = referenceCounter.getReferenceCounts(problem); | ||
54 | var count = referenceCounts.get(eObject); | ||
55 | return count != null && count >= 2; | ||
56 | } | ||
57 | return true; | ||
58 | } | ||
59 | |||
60 | protected EObject getCurrentValue(CrossReference crossRef, ContentAssistContext context) { | ||
61 | var value = getCurrentValue(crossRef, context.getCurrentModel()); | ||
62 | if (value != null) { | ||
63 | return value; | ||
64 | } | ||
65 | var currentNodeSemanticObject = NodeModelUtils.findActualSemanticObjectFor(context.getCurrentNode()); | ||
66 | return getCurrentValue(crossRef, currentNodeSemanticObject); | ||
67 | } | ||
68 | |||
69 | protected EObject getCurrentValue(CrossReference crossRef, EObject context) { | ||
70 | if (context == null) { | ||
71 | return null; | ||
72 | } | ||
73 | var eReference = GrammarUtil.getReference(crossRef, context.eClass()); | ||
74 | if (eReference == null || eReference.isMany()) { | ||
75 | return null; | ||
76 | } | ||
77 | return (EObject) context.eGet(eReference); | ||
78 | } | ||
79 | } | ||
diff --git a/language-model/src/main/java/tools/refinery/language/model/ProblemUtil.java b/language-model/src/main/java/tools/refinery/language/model/ProblemUtil.java index b6b199f8..5f8641bf 100644 --- a/language-model/src/main/java/tools/refinery/language/model/ProblemUtil.java +++ b/language-model/src/main/java/tools/refinery/language/model/ProblemUtil.java | |||
@@ -12,6 +12,7 @@ import org.eclipse.emf.ecore.EObject; | |||
12 | import org.eclipse.emf.ecore.resource.Resource; | 12 | import org.eclipse.emf.ecore.resource.Resource; |
13 | 13 | ||
14 | import tools.refinery.language.model.problem.ClassDeclaration; | 14 | import tools.refinery.language.model.problem.ClassDeclaration; |
15 | import tools.refinery.language.model.problem.ImplicitVariable; | ||
15 | import tools.refinery.language.model.problem.Node; | 16 | import tools.refinery.language.model.problem.Node; |
16 | import tools.refinery.language.model.problem.Problem; | 17 | import tools.refinery.language.model.problem.Problem; |
17 | import tools.refinery.language.model.problem.ProblemPackage; | 18 | import tools.refinery.language.model.problem.ProblemPackage; |
@@ -33,6 +34,24 @@ public final class ProblemUtil { | |||
33 | public static boolean isSingletonVariable(Variable variable) { | 34 | public static boolean isSingletonVariable(Variable variable) { |
34 | return variable.eContainingFeature() == ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__SINGLETON_VARIABLE; | 35 | return variable.eContainingFeature() == ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__SINGLETON_VARIABLE; |
35 | } | 36 | } |
37 | |||
38 | public static boolean isImplicitVariable(Variable variable) { | ||
39 | return variable instanceof ImplicitVariable; | ||
40 | } | ||
41 | |||
42 | public static boolean isImplicitNode(Node node) { | ||
43 | return node.eContainingFeature() == ProblemPackage.Literals.PROBLEM__NODES; | ||
44 | } | ||
45 | |||
46 | public static boolean isImplicit(EObject eObject) { | ||
47 | if (eObject instanceof Node node) { | ||
48 | return isImplicitNode(node); | ||
49 | } else if (eObject instanceof Variable variable) { | ||
50 | return isImplicitVariable(variable); | ||
51 | } else { | ||
52 | return false; | ||
53 | } | ||
54 | } | ||
36 | 55 | ||
37 | public static boolean isUniqueNode(Node node) { | 56 | public static boolean isUniqueNode(Node node) { |
38 | var containingFeature = node.eContainingFeature(); | 57 | var containingFeature = node.eContainingFeature(); |
diff --git a/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java b/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java new file mode 100644 index 00000000..56186bc9 --- /dev/null +++ b/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java | |||
@@ -0,0 +1,43 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.Map; | ||
5 | |||
6 | import org.eclipse.emf.ecore.EObject; | ||
7 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
8 | |||
9 | import com.google.inject.Inject; | ||
10 | import com.google.inject.Singleton; | ||
11 | |||
12 | import tools.refinery.language.model.problem.Problem; | ||
13 | |||
14 | @Singleton | ||
15 | public class ReferenceCounter { | ||
16 | @Inject | ||
17 | private IResourceScopeCache cache; | ||
18 | |||
19 | public Map<EObject, Integer> getReferenceCounts(Problem problem) { | ||
20 | var resource = problem.eResource(); | ||
21 | if (resource == null) { | ||
22 | return doGetReferenceCounts(problem); | ||
23 | } | ||
24 | return cache.get(problem, resource, () -> doGetReferenceCounts(problem)); | ||
25 | } | ||
26 | |||
27 | protected Map<EObject, Integer> doGetReferenceCounts(Problem problem) { | ||
28 | var map = new HashMap<EObject, Integer>(); | ||
29 | countCrossReferences(problem, map); | ||
30 | var iterator = problem.eAllContents(); | ||
31 | while (iterator.hasNext()) { | ||
32 | var eObject = iterator.next(); | ||
33 | countCrossReferences(eObject, map); | ||
34 | } | ||
35 | return map; | ||
36 | } | ||
37 | |||
38 | protected void countCrossReferences(EObject eObject, Map<EObject, Integer> map) { | ||
39 | for (var referencedObject : eObject.eCrossReferences()) { | ||
40 | map.compute(referencedObject, (key, currentValue) -> currentValue == null ? 1 : currentValue + 1); | ||
41 | } | ||
42 | } | ||
43 | } | ||