Custom language tutorial: ReferenceContributor

Hey,

I've been following your custom language tutorial, and have been able to get a basic plugin setup for a custom DSL that we use at our company. The syntax highlighting and code completion parts of the tutorial worked as I expected, and I'm pretty happy. But when it comes to defining a ReferenceContributor, I'm having some trouble. I specify a ReferenceContributor in the plugin.xml, like so

  <extensions defaultExtensionNs="com.intellij">
      <fileTypeFactory implementation="com.hulu.beaconspec.plugin.BeaconSpecFileTypeFactory"/>
      <fileTypeFactory implementation="com.hulu.beaconspec.plugin.BeaconSpecFileTypeFactory"/>
      <lang.parserDefinition language="beaconspec" implementationClass="com.hulu.beaconspec.plugin.BeaconSpecParserDefinition"/>
      <lang.syntaxHighlighterFactory key="beaconspec" implementationClass="com.hulu.beaconspec.plugin.BeaconSpecSyntaxHighlighterFactory"/>
      <colorSettingsPage implementation="com.hulu.beaconspec.plugin.BeaconSpecColorSettingsPage"/>
      <completion.contributor language="beaconspec" implementationClass="com.hulu.beaconspec.plugin.BeaconSpecCompletionContributor"/>
      <psi.referenceContributor implementation="com.hulu.beaconspec.plugin.refactoring.BeaconReferenceContributor"/>
  </extensions>


And I have a ReferenceContributor class:



import com.hulu.beaconspec.plugin.refactoring.BeaconReference;
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;

class BeaconPsiReferenceProvider extends PsiReferenceProvider {
    @NotNull
    @Override
    public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext processingContext) {
        PsiLiteralExpression literalExpression = (PsiLiteralExpression) element;
        String text = (String) literalExpression.getValue();
        if (text!=null) {
            return new PsiReference[]{ new BeaconReference(element, new TextRange(0, text.length()))};
        }
        return new PsiReference[0];
    }
}

public class BeaconReferenceContributor extends PsiReferenceContributor {

    @Override
    public void registerReferenceProviders(PsiReferenceRegistrar registrar) {
        registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression.class), new BeaconPsiReferenceProvider());
    }
}

I put a breakpoint in the registerReferenceProviders() function, and when running my plugin it never seems to hit the breakpoint. So it looks like I'm failing to even register the reference provider. Let me know if you see anything wrong.

Thanks!

0
8 comments

What your code does right now is add references to your expressions into a Java string literal. Is that what you mean to do, or do you actually want to have references from one type of element to another type inside your own file?

0
Avatar
Permanently deleted user

The latter, but for now I just want to figure out why it isn't entering the registerReferenceProviders function at all.

I assume to add a different kind of reference provider I'd have to do something like this:

registrar.registerReferenceProvider(PlatformPatterns.psiElement(BeaconSpecDeclaration.class), new BeaconPsiReferenceProvider());


And then subsequently change the BeaconPsiReferenceProvider class as well...

0

There's one important thing missing in the tutorial, I believe. ReferenceContributors are not used automatically, you need to invoke them explicitly via ReferenceProvidersRegistry. I did it in my base MyLangElementImpl class (all PsiElement classes for my language inherit from this one, either directly or indirectly), which is pretty universal. It looks like this:

public class MyLangElementImpl extends ASTWrapperPsiElement implements MyLangElement {
    public MyLangElementImpl(@NotNull ASTNode node) {
        super(node);
    }


    ...


    @Nullable
    public PsiReference getReference() {
        PsiReference[] references = getReferences();
        return references.length == 0 ? null : references[0];
    }


    @NotNull
    public PsiReference[] getReferences() {
        return ReferenceProvidersRegistry.getReferencesFromProviders(this);
    }


    ...
}

1

In your custom language, there is usually no need to go through ReferenceProvidersRegistry to provide references for an element. Instead, you can put all your logic directly into the getReferences() method of your PsiElement.

0

Yes. However:

1. This is not documented anywhere. I lost few hours trying to debug this exact problem, only to find out that ReferenceContributors are not invoked automatically.

2. I kinda like it this way :-) It lets me keep the MyLangPsiImplUtil, which I need because of GrammarKit, as short as possible.

0
Avatar
Permanently deleted user

Thanks for the help Dmitry and Ladislav, I'll try this

0

I've added some notes on that to the javadocs.

0
Avatar
Permanently deleted user

Sorry, not quite clear: what is "MyLanguageElement" interface?


0

Please sign in to leave a comment.