10. Reference Contributor
引用是实现自定义语言支持中最重要和最棘手的部分之一.
解析引用意味着能够从元素的使用转变为声明,完成,重命名重构,查找用法等.
每个可以重命名或引用的元素都需要实现com.intellij.psi.PsiNamedElement接口.
10.1.
定义一个名为element的基类
package com.simpleplugin.psi;
import com.intellij.psi.PsiNameIdentifierOwner;
public interface SimpleNamedElement extends PsiNameIdentifierOwner {
}
package com.simpleplugin.psi.impl;
import com.intellij.extapi.psi.ASTWrapperPsiElement;
import com.intellij.lang.ASTNode;
import com.simpleplugin.psi.SimpleNamedElement;
import org.jetbrains.annotations.NotNull;
public abstract class SimpleNamedElementImpl extends ASTWrapperPsiElement implements SimpleNamedElement {
  public SimpleNamedElementImpl(@NotNull ASTNode node) {
    super(node);
  }
}
10.2.
为生成的PSI元素定义辅助方法
由于我们需要在PSI类中实现新方法,我们应该在SimplePsiImplUtil类中定义它们:
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);
      if (keyNode != null) {
          return keyNode.getPsi();
      } else {
          return null;
      }
  }
  // ...
}
请注意,SimpleElementFactory类将显示为错误.
我们接下来会创建它.
10.3.
定义元素工厂
package com.simpleplugin.psi;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.simpleplugin.SimpleFileType;
public class SimpleElementFactory {
  public static SimpleProperty createProperty(Project project, String name) {
    final 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);
  }
}
10.4.
更新语法并重新生成解析器
现在我们需要对语法文件进行相应的更改并重新生成解析器和PSI类.
property ::= (KEY? SEPARATOR VALUE?) | KEY {mixin="com.simpleplugin.psi.impl.SimpleNamedElementImpl"
  implements="com.simpleplugin.psi.SimpleNamedElement" methods=[getKey getValue getName setName getNameIdentifier]}
不要忘记重新生成解析器!
右键单击Simple.bnf文件并选择_Generate Parser Code_.
10.5.
定义参考
现在我们需要定义一个引用类来解析它的用法属性.
package com.simpleplugin;
import com.intellij.codeInsight.lookup.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.simpleplugin.psi.SimpleProperty;
import org.jetbrains.annotations.*;
import java.util.*;
public class SimpleReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference {
  private String key;
  public SimpleReference(@NotNull PsiElement element, TextRange textRange) {
    super(element, textRange);
    key = element.getText().substring(textRange.getStartOffset(), textRange.getEndOffset());
  }
  @NotNull
  @Override
  public ResolveResult[] multiResolve(boolean incompleteCode) {
    Project project = myElement.getProject();
    final List<SimpleProperty> properties = SimpleUtil.findProperties(project, key);
    List<ResolveResult> results = new ArrayList<ResolveResult>();
    for (SimpleProperty 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<SimpleProperty> properties = SimpleUtil.findProperties(project);
    List<LookupElement> variants = new ArrayList<LookupElement>();
    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();
  }
}
10.6.
定义参考贡献者
引用参与者允许您提供从其他语言(如Java)中的元素到您所用语言中的元素的引用.
让我们为每个属性的使用贡献一个参考.
package com.simpleplugin;
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;
public class SimpleReferenceContributor 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 && value.startsWith("simple" + ":")) {
                                              return new PsiReference[]{
                                                  new SimpleReference(element, new TextRange(8, value.length() + 1))};
                                            }
                                            return PsiReference.EMPTY_ARRAY;
                                          }
                                        });
  }
}
10.7.
注册参考贡献者
<psi.referenceContributor implementation="com.simpleplugin.SimpleReferenceContributor"/>
10.8.
运行该项目
如您所见,IDE现在解析了该属性并提供了完成功能.

根据定义和用法重命名重构.

10.9.
定义重构支持提供程序
为了允许就地重构,我们应该在重构支持提供程序中明确指定它.
package com.simpleplugin;
import com.intellij.lang.refactoring.RefactoringSupportProvider;
import com.intellij.psi.PsiElement;
import com.simpleplugin.psi.SimpleProperty;
public class SimpleRefactoringSupportProvider extends RefactoringSupportProvider {
  @Override
  public boolean isMemberInplaceRenameAvailable(PsiElement element, PsiElement context) {
    return element instanceof SimpleProperty;
  }
}
10.10.
注册重构支持提供程序
<lang.refactoringSupport language="Simple" implementationClass="com.simpleplugin.SimpleRefactoringSupportProvider"/>
10.11.
运行该项目

                        Last modified: 11 May 2019