ReferenceContributor unable to Resolve PsiLiteralExpression References Contained Inside Spring Value Annotation
Hello,
This is my first time developing a plugin, so I'm somewhat lost.
I'm currently trying to implement a ReferenceContributor that will be able to resolve from a YAML path specified in a Java file to its corresponding value in the containing YAML file.
The contributor works fine for expressions that are in string literals i.e.
String value = "some.path.to.value"
but is unable to do the exact same thing when given in Spring's @Value annotation, i.e.:
@Value("some.path.to.value")
String value
I notice that it is never calling the resolve method in the Reference class whenever I attempt to ctrl + click the value contained in the @Value annotation.
For reference, I was able to successfully create a LineMarkerProvider that worked in both situations.
I'm not quite sure what I could be missing since I essentially copied and pasted from the Custom Language Support Tutorial.
Here is the ReferenceContributor:
package com.yamlplugin;
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.*;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.meta.model.YamlReferenceType;
import org.jetbrains.yaml.psi.YAMLPsiElement;
import org.jetbrains.yaml.psi.impl.YAMLScalarTextImpl;
import java.util.List;
public class YamlReferenceContributor extends PsiReferenceContributor {
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression.class),
new PsiReferenceProvider() {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element,
@NotNull ProcessingContext
context) {
PsiLiteralExpression literalExpression = (PsiLiteralExpression) element;
String value = literalExpression.getValue() instanceof String ?
(String) literalExpression.getValue() : null;
if (value != null) {
YamlReference yamlReference = new YamlReference(element, new TextRange(1, value.length() + 1));
PsiReference[] psiReferences = new PsiReference[]{yamlReference};
return psiReferences;
}
return PsiReference.EMPTY_ARRAY;
}
});
}
}
Here is the Reference Class:
package com.yamlplugin;
import com.intellij.codeInsight.lookup.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import org.jetbrains.annotations.*;
import org.jetbrains.yaml.psi.YAMLPsiElement;
import java.util.*;
public class YamlReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference {
private String key;
private String [] keyPath;
public YamlReference(@NotNull PsiElement element, TextRange textRange) {
super(element, textRange);
key = element.getText().substring(textRange.getStartOffset(), textRange.getEndOffset());
keyPath = FNMYamlPluginUtility.processStringLiteral(key);
}
@NotNull
@Override
public ResolveResult[] multiResolve(boolean incompleteCode) {
Project project = myElement.getProject();
final List<YAMLPsiElement> properties = FNMYamlPluginUtility.findProperties(project, keyPath);
List<ResolveResult> results = new ArrayList<ResolveResult>();
for (YAMLPsiElement property : properties) {
results.add(new PsiElementResolveResult(property));
}
return results.toArray(new ResolveResult[results.size()]);
}
@Nullable
@Override
public PsiElement resolve() {
ResolveResult[] resolveResults = multiResolve(false);
return resolveResults.length == 1 ? resolveResults[0].getElement() : null;
}
@NotNull
@Override
public Object[] getVariants() {
Project project = myElement.getProject();
List<YAMLPsiElement> properties = FNMYamlPluginUtility.findProperties(project);
List<LookupElement> variants = new ArrayList<LookupElement>();
for (final YAMLPsiElement property : properties) {
if (property.getText() != null && property.getText().length() > 0) {
variants.add(LookupElementBuilder.create(property).
withIcon(FNMYamlIcons.FILE).
withTypeText(property.getContainingFile().getName())
);
}
}
return variants.toArray();
}
}
For good measure, here is the Util Class I use:
package com.yamlplugin;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.FileTypeIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.yaml.YAMLFileType;
import org.jetbrains.yaml.YAMLUtil;
import org.jetbrains.yaml.psi.YAMLFile;
import org.jetbrains.yaml.psi.YAMLKeyValue;
import org.jetbrains.yaml.psi.YAMLPsiElement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class FNMYamlPluginUtility {
public static List<YAMLPsiElement> findProperties(Project project, String[] keys) {
List<YAMLPsiElement> result= null;
Collection<VirtualFile> virtualFiles =
FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, YAMLFileType.YML,
GlobalSearchScope.allScope(project));
for (VirtualFile virtualFile : virtualFiles) {
YAMLFile yamlFile = (YAMLFile) PsiManager.getInstance(project).findFile(virtualFile);
if (yamlFile != null) {
YAMLKeyValue keyValue = YAMLUtil.getQualifiedKeyInFile(yamlFile, keys);
if (keyValue != null) {
if(result == null)
{
result = new ArrayList<>();
}
result.add(keyValue);
}
}
}
return result != null ? result : Collections.<YAMLPsiElement>emptyList();
}
public static List<YAMLPsiElement> findProperties(Project project) {
List<YAMLPsiElement> result = new ArrayList<YAMLPsiElement>();
Collection<VirtualFile> virtualFiles =
FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, YAMLFileType.YML,
GlobalSearchScope.allScope(project));
for (VirtualFile virtualFile : virtualFiles) {
YAMLFile yamlFile = (YAMLFile) PsiManager.getInstance(project).findFile(virtualFile);
if (yamlFile != null) {
YAMLPsiElement[] properties = PsiTreeUtil.getChildrenOfType(yamlFile, YAMLPsiElement.class);
if (properties != null) {
Collections.addAll(result, properties);
}
}
}
return result;
}
public static String[] processStringLiteral(String path) {
String[] finalResult;
if(path.contains("$")) {
path = path.replace("$", "");
}
if(path.contains("{")) {
path = path.replace("{", "");
}
if(path.contains("}")) {
path = path.replace("}", "");
}
if(path.contains("."))
{
finalResult = path.split("\\.");
} else{
finalResult = new String[]{path};
}
return finalResult;
}
}
I've seen that many times people get the text range incorrect, but I haven't been able to modify it to get it to work.
Any help or direction would be greatly appreciated.
Thank you,
Coleman
Please sign in to leave a comment.
> This is my first time developing a plugin, so I'm somewhat lost.
I can't count how many times I was lost (completely) while trying to make sense of IntelliJ platform :)
By any chance, @... have you been able to resolve this issue?
Apparently I get emails when someone comments on these! I was not able to resolve the issue, but I found a work around. Instead of implementing the Ctrl+click functionality I instead had intellij display an icon whenever it was able to resolve a path. If you clicked the icon it would list all the YAML files where a reference to that string could be found. I don't remember how I did it and I've since left the company where the code is stored. I believe if you look in their tutorial guides they go over how to add something similar. Sorry I couldn't be of more help!