diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-11-04 17:41:52 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-11-04 17:41:52 +0100 |
commit | 432ff3aaee8d45025f309436db541d0ec1b76485 (patch) | |
tree | 0d49c583f71f9972c6f5050540ac50e78992eaf2 | |
parent | fix(web): fix autocomplete prefix behavior (diff) | |
download | refinery-432ff3aaee8d45025f309436db541d0ec1b76485.tar.gz refinery-432ff3aaee8d45025f309436db541d0ec1b76485.tar.zst refinery-432ff3aaee8d45025f309436db541d0ec1b76485.zip |
fix(language): hide current implicit proposal
Content assist proposals should not display the object that is only
added to the model because the current context assist input refers to
it (e.g., an implicit node or variable that is only referenced in the
currently edited context).
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 | } | ||