IntelliJ Platform Plugin SDK Help

10. Reference Contributor

The references functionality is one of the most important parts in the implementation of custom language support. Resolving references means the ability to go from the usage of an element to its declaration, completion, rename refactoring, find usages, etc.

Define a Named Element Class

The classes below show how the Simple Language fulfills the need to implement PsiNamedElement.

The SimpleNamedElement interface is subclassed from PsiNameIdentifierOwner.

public interface SimpleNamedElement extends PsiNameIdentifierOwner { }

The SimpleNamedElementImpl class implements the SimpleNamedElement interface and extends ASTWrapperPsiElement.

public abstract class SimpleNamedElementImpl extends ASTWrapperPsiElement implements SimpleNamedElement { public SimpleNamedElementImpl(@NotNull ASTNode node) { super(node); } }

Define Helper Methods for Generated PSI Elements

Modify SimplePsiImplUtil to support new methods that get added to the PSI class for Simple Language. Note that SimpleElementFactory isn't defined until the next step, so for now it shows as an error.

public class SimplePsiImplUtil { // ... public static String getName(SimpleProperty element) { return getKey(element); } public static PsiElement setName(SimpleProperty element, String newName) { ASTNode keyNode = element.getNode().findChildByType(SimpleTypes.KEY); if (keyNode != null) { SimpleProperty property = SimpleElementFactory.createProperty(element.getProject(), newName); ASTNode newKeyNode = property.getFirstChild().getNode(); element.getNode().replaceChild(keyNode, newKeyNode); } return element; } public static PsiElement getNameIdentifier(SimpleProperty element) { ASTNode keyNode = element.getNode().findChildByType(SimpleTypes.KEY); return keyNode != null ? keyNode.getPsi() : null; } // ... }

Define an Element Factory

The SimpleElementFactory provides methods for creating SimpleFile.

package org.intellij.sdk.language.psi; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import org.intellij.sdk.language.SimpleFileType; public class SimpleElementFactory { public static SimpleProperty createProperty(Project project, String name) { SimpleFile file = createFile(project, name); return (SimpleProperty) file.getFirstChild(); } public static SimpleFile createFile(Project project, String text) { String name = "dummy.simple"; return (SimpleFile) PsiFileFactory.getInstance(project). createFileFromText(name, SimpleFileType.INSTANCE, text); } }

Update Grammar and Regenerate the Parser

Now make corresponding changes to the Simple.bnf grammar file by replacing the property definition with the lines below. Don't forget to regenerate the parser after updating the file! Right-click on the Simple.bnf file and select Generate Parser Code.

property ::= (KEY? SEPARATOR VALUE?) | KEY { mixin="org.intellij.sdk.language.psi.impl.SimpleNamedElementImpl" implements="org.intellij.sdk.language.psi.SimpleNamedElement" methods=[getKey getValue getName setName getNameIdentifier] }

Define a Reference

Now define a reference class SimpleReference.java to resolve a property from its usage. This requires extending PsiReferenceBase and implementing PsiPolyVariantReference. The latter enables the reference to resolve to more than one element or to resolve result(s) for a superset of valid resolve cases.

final class SimpleReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference { private final String key; SimpleReference(@NotNull PsiElement element, TextRange textRange) { super(element, textRange); key = element.getText().substring(textRange.getStartOffset(), textRange.getEndOffset()); } @Override public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) { Project project = myElement.getProject(); final List<SimpleProperty> properties = SimpleUtil.findProperties(project, key); List<ResolveResult> results = new ArrayList<>(); for (SimpleProperty property : properties) { results.add(new PsiElementResolveResult(property)); } return results.toArray(new ResolveResult[0]); } @Nullable @Override public PsiElement resolve() { ResolveResult[] resolveResults = multiResolve(false); return resolveResults.length == 1 ? resolveResults[0].getElement() : null; } @Override public Object @NotNull [] getVariants() { Project project = myElement.getProject(); List<SimpleProperty> properties = SimpleUtil.findProperties(project); List<LookupElement> variants = new ArrayList<>(); for (final SimpleProperty property : properties) { if (property.getKey() != null && property.getKey().length() > 0) { variants.add(LookupElementBuilder .create(property).withIcon(SimpleIcons.FILE) .withTypeText(property.getContainingFile().getName()) ); } } return variants.toArray(); } }

Define a Reference Contributor

A reference contributor allows the simple_language_plugin to provide references to Simple Language from elements in other languages such as Java. Create SimpleReferenceContributor by subclassing PsiReferenceContributor. Contribute a reference to each usage of a property:

final class SimpleReferenceContributor extends PsiReferenceContributor { @Override public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression.class), new PsiReferenceProvider() { @Override public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { PsiLiteralExpression literalExpression = (PsiLiteralExpression) element; String value = literalExpression.getValue() instanceof String ? (String) literalExpression.getValue() : null; if ((value != null && value.startsWith(SIMPLE_PREFIX_STR + SIMPLE_SEPARATOR_STR))) { TextRange property = new TextRange(SIMPLE_PREFIX_STR.length() + SIMPLE_SEPARATOR_STR.length() + 1, value.length() + 1); return new PsiReference[]{new SimpleReference(element, property)}; } return PsiReference.EMPTY_ARRAY; } }); } }

Register the Reference Contributor

The SimpleReferenceContributor implementation is registered using the com.intellij.psi.referenceContributor extension point and specifying language="JAVA".

<extensions defaultExtensionNs="com.intellij"> <psi.referenceContributor language="JAVA" implementation="org.intellij.sdk.language.SimpleReferenceContributor"/> </extensions>

Run the Project with the Reference Contributor

Run the project by using the Gradle runIde task.

The IDE now resolves the property and provides completion suggestions:

Reference Contributor

The Rename refactoring functionality is now available from definition and usages.

Rename

Define a Refactoring Support Provider

Support for in-place refactoring is specified explicitly in a refactoring support provider. Create SimpleRefactoringSupportProvider by subclassing RefactoringSupportProvider As long as an element is a SimpleProperty it is allowed to be refactored:

final class SimpleRefactoringSupportProvider extends RefactoringSupportProvider { @Override public boolean isMemberInplaceRenameAvailable(@NotNull PsiElement elementToRename, @Nullable PsiElement context) { return (elementToRename instanceof SimpleProperty); } }

Register the Refactoring Support Provider

The SimpleRefactoringSupportProvider implementation is registered with the IntelliJ Platform in the plugin configuration file using the com.intellij.lang.refactoringSupport extension point.

<extensions defaultExtensionNs="com.intellij"> <lang.refactoringSupport language="Simple" implementationClass="org.intellij.sdk.language.SimpleRefactoringSupportProvider"/> </extensions>

Run the Project

Run the project by using the Gradle runIde task.

The IDE now supports refactoring suggestions:

In Place Rename
Last modified: 27 February 2023