From 2fe65e414ff3194cdddde01bea6818bbab5290e9 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Wed, 3 Jan 2024 02:13:15 +0100 Subject: feat(web): color identifiers and nodes We use a palette-based coloring strategy, where each class and enum gets a color from --- .../ProblemSemanticHighlightingCalculator.java | 11 ++- .../ide/syntaxcoloring/TypeHashProvider.java | 93 ++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java (limited to 'subprojects/language-ide') diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java index ae8c70e0..4c775fc6 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors * * SPDX-License-Identifier: EPL-2.0 */ @@ -38,6 +38,9 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli @Inject private ProblemDesugarer desugarer; + @Inject + private TypeHashProvider typeHashProvider; + @Override protected boolean highlightElement(EObject object, IHighlightedPositionAcceptor acceptor, CancelIndicator cancelIndicator) { @@ -127,6 +130,12 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli classesBuilder.add(NEW_NODE_CLASS); } } + if (eObject instanceof Relation relation) { + var typeHash = typeHashProvider.getTypeHash(relation); + if (typeHash != null) { + classesBuilder.add("typeHash-" + typeHash); + } + } List classes = classesBuilder.build(); return classes.toArray(new String[0]); } diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java new file mode 100644 index 00000000..f75ecdb2 --- /dev/null +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2024 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.language.ide.syntaxcoloring; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.naming.IQualifiedNameProvider; +import org.eclipse.xtext.scoping.IScopeProvider; +import org.eclipse.xtext.util.IResourceScopeCache; +import tools.refinery.language.model.problem.*; +import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; +import tools.refinery.language.utils.ProblemUtil; + +import java.util.*; + +@Singleton +public class TypeHashProvider { + private static final String CACHE_KEY = "tools.refinery.language.ide.syntaxcoloring.TypeHashProvider"; + private static final int COLOR_COUNT = 10; + + @Inject + private IResourceScopeCache resourceScopeCache; + + @Inject + private IScopeProvider scopeProvider; + + @Inject + private IQualifiedNameProvider qualifiedNameProvider; + + @Inject + private IQualifiedNameConverter qualifiedNameConverter; + + public String getTypeHash(Relation relation) { + if (!(relation instanceof ClassDeclaration || relation instanceof EnumDeclaration) || + ProblemUtil.isBuiltIn(relation)) { + return null; + } + var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(relation); + if (qualifiedName == null) { + return null; + } + var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); + var problem = EcoreUtil2.getContainerOfType(relation, Problem.class); + if (problem == null) { + return null; + } + var cache = resourceScopeCache.get(CACHE_KEY, problem.eResource(), () -> computeHashes(problem)); + return cache.get(qualifiedNameString); + } + + private Map computeHashes(Problem problem) { + var qualifiedNameStrings = new TreeSet(); + var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); + for (var description : scope.getAllElements()) { + if (ProblemResourceDescriptionStrategy.COLOR_RELATION_TRUE.equals( + description.getUserData(ProblemResourceDescriptionStrategy.COLOR_RELATION))) { + var qualifiedNameString = qualifiedNameConverter.toString(description.getQualifiedName()); + qualifiedNameStrings.add(qualifiedNameString); + } + } + var stringList = new ArrayList<>(qualifiedNameStrings); + int size = stringList.size(); + if (size == 0) { + return Map.of(); + } + // The use of a non-cryptographic random generator is safe here, because we only use it to shuffle the color + // IDs in a pseudo-random way. The shuffle depends on the size of the list of identifiers before padding to + // make sure that adding a new class randomizes all color IDs. + @SuppressWarnings("squid:S2245") + var random = new Random(size); + int padding = COLOR_COUNT - (size % COLOR_COUNT); + for (int i = 0; i < padding; i++) { + stringList.add(null); + } + size += padding; + Collections.shuffle(stringList, random); + var mapBuilder = ImmutableMap.builder(); + for (int i = 0; i < size; i++) { + var key = stringList.get(i); + if (key != null) { + int colorId = i % COLOR_COUNT; + mapBuilder.put(key, Integer.toString(colorId)); + } + } + return mapBuilder.build(); + } +} -- cgit v1.2.3-54-g00ecf