Finding PsiReference to an XML file referenced by an XML tag value in custom PsiReferenceContributor Follow
Answered
Hello!
I am attempting to create a custom PsiReferenceContributor which interprets the value of a tag in an XML file as a reference to another XML file. For example:
<site>
<group-profile-ref>mygroup</group-profile-ref>
</site>
where mygroup is interpreted as a reference to an xml file that lives in the same root as the above xml file and would have a relative path of something like m/mygroup/config/config.xml
I have been able to get a VirtualFile for the mygroup config.xml file, but am having no luck getting a reference to the that file so that "Go To Declaration" works when mygroup is clicked on.
Here is what I have so far:
public class SiteReferencesContributor
extends PsiReferenceContributor {
private static final VirtualFilePattern CONFIG_XML = namedXmlFile("config.xml");
private static final XmlTagPattern.Capture SITE_TAG = xmlTag().withName("site");
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(psiElement(XML_DATA_CHARACTERS)
.withParent(xmlText()
.withParent(siteChildren("profile-ref", "group-profile-ref",
"oem-profile-ref", "country-profile-ref"))),
new ProfileReferenceProvider());
}
private static VirtualFilePattern namedXmlFile(final String name) {
return virtualFile()
.ofType(XmlFileType.INSTANCE)
.withName(name);
}
private static PsiElementPattern<XmlTag, XmlTagPattern.Capture> siteChildren(String... names) {
return xmlTag()
.withName(names)
.withParent(SITE_TAG)
.inVirtualFile(CONFIG_XML);
}
}
public class ProfileReferenceProvider
extends PsiReferenceProvider {
private static final PsiFileReferenceHelper fileReferenceHelper = new PsiFileReferenceHelper();
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull final PsiElement element, @NotNull ProcessingContext context) {
String profile = element.getText();
final Project project = element.getProject();
final VirtualFile referringFile = element.getContainingFile().getVirtualFile();
final PsiFileSystemItem dvsRoot = fileReferenceHelper.findRoot(project, referringFile);
final String relativeProfilePath = profile.toLowerCase().charAt(0) + "/" + profile + "/config/config.xml";
final VirtualFile profileXml = dvsRoot.getVirtualFile().findFileByRelativePath(relativeProfilePath);
if (profileXml != null && profileXml.exists()) {
final PsiManager psiManager = dvsRoot.getManager();
PsiFile file = psiManager.findFile(profileXml);
return new PsiReference[]{file.getReference()};
}
return PsiReference.EMPTY_ARRAY;
}
}
Any help would be greatly appreciated.
Thanks!
Please sign in to leave a comment.
com.intellij.psi.impl.source.PsiFileImpl#getReference returns null always, try returning com.intellij.psi.PsiReferenceBase#createSelfReference(T, com.intellij.psi.PsiElement) with resolveTo=file
You might want to consider using XML DOM API if you have a more complex model/functionality in mind https://www.jetbrains.org/intellij/sdk/docs/reference_guide/frameworks_and_external_apis/xml_dom_api.html
Excellent, that worked! Yann, thank you for your assistance.
We have our own in-house repository where these configuration files are stored and a CLI to fetch them. In the case where the developer does not have the referenced xml configuration file checked out, i would like to offer the option to check out the file by executing the CLI command before going to the declaration.
Is there a way to achieve this so that when the user clicks on the declaration that doesn't exist, the option to check out that file is presented?
Thank you again!
You can provide an inspection that checks references and highlights unresolved ones and provides quickfix via Alt+Enter.