idea插件开发-自定义语言5-References and Resolve

news2024/11/25 4:40:54

        实现自定义语言 PSI 中最重要和最棘手的部分之一是解析References。解析References使用户能够从 PSI 元素的使用(访问变量、调用方法等)导航到该元素的声明(变量的定义、方法声明等)。其实就是实现Navigate | Declaration or Usages功能,这个功能是实现Find Usages、Rename、Code Completion的先决条件。

引用功能是自定义语言支持实现中最重要的部分之一。解析引用意味着能够从元素的使用到它的声明、完成、重命名重构、查找用法等。

        View | Quick Definition 也需要依赖此功能,如果需要知道文档的确切的范围,需要实现com.intellij.lang.implementationTextSelectioner扩展点。

一、References and Resolve

1、Psi References

        所有的PSI elements如果想实现引用功能都需要持有PsiReference属性,这个属性是由PsiElement.getReference()方法返回的。一个Element可以拥有多个PsiReference(例如,一个字符串文字可以包含多个子字符串)PsiElement.getReferences(),  上述这个方法的性能如果有问题的话,可以使用HintedReferenceHost工具类进行优化。

        PsiReference的主要接口是resolve(),它返回引用指向的元素,或者null如果无法解析对有效元素的引用,已解析的元素应该实现PsiNamedElement接口或PsiNameIdentifierOwner接口。

虽然引用元素和被引用元素都可能有名称,但只有引入名称的元素(例如,定义int x = 42)需要实现PsiNamedElement。使用点的引用元素(例如,表达式中的 the x不应x + 1实现PsiNamedElement,因为它没有名称。

        与resolve()方法对应方法isReferenceTo(),它检查引用是否解析为指定的元素。后一种方法可以通过调用resolve()并将结果与​​传递的 PSI 元素进行比较来实现。可参考示例:

public class ResourceBundleReference extends PsiReferenceBase<PsiElement>
  implements PsiPolyVariantReference, BundleNameEvaluator, ResolvingHint {
  private static final Function<PropertiesFile, PsiElement> PROPERTIES_FILE_PSI_ELEMENT_FUNCTION =
    PropertiesFile::getContainingFile;
  private final String myBundleName;

  public ResourceBundleReference(final PsiElement element) {
    this(element, false);
  }

  public ResourceBundleReference(final PsiElement element, boolean soft) {
    super(element, soft);
    myBundleName = getValue().replace('/', '.');
  }

  public ResourceBundleReference(final PsiElement element, TextRange textRange, boolean soft) {
    super(element, textRange, soft);
    myBundleName = getValue().replace('/', '.');
  }

  @Override
  public boolean canResolveTo(Class<? extends PsiElement> elementClass) {
    return ReflectionUtil.isAssignable(PsiFile.class, elementClass);
  }

  @Override
  @Nullable
  public PsiElement resolve() {
    ResolveResult[] resolveResults = multiResolve(false);
    return resolveResults.length == 1 ? resolveResults[0].getElement() : null;
  }

  @Override
  public ResolveResult @NotNull [] multiResolve(final boolean incompleteCode) {
    PropertiesReferenceManager referenceManager = PropertiesReferenceManager.getInstance(myElement.getProject());
    List<PropertiesFile> propertiesFiles = referenceManager.findPropertiesFiles(myElement.getResolveScope(), myBundleName, this);
    return PsiElementResolveResult.createResults(ContainerUtil.map(propertiesFiles, PROPERTIES_FILE_PSI_ELEMENT_FUNCTION));
  }

  @Override
  @NotNull
  public String getCanonicalText() {
    return myBundleName;
  }

  @Override
  public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
    if (newElementName.endsWith(PropertiesFileType.DOT_DEFAULT_EXTENSION)) {
      newElementName = newElementName.substring(0, newElementName.lastIndexOf(PropertiesFileType.DOT_DEFAULT_EXTENSION));
    }

    final String currentValue = getValue();
    final char packageDelimiter = getPackageDelimiter();
    final int index = currentValue.lastIndexOf(packageDelimiter);
    if (index != -1) {
      newElementName = currentValue.substring(0, index) + packageDelimiter + newElementName;
    }

    return super.handleElementRename(newElementName);
  }

  private char getPackageDelimiter() {
    return getValue().indexOf('/') != -1 ? '/' : '.';
  }

  @Override
  public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException {
    if (!(element instanceof PropertiesFile)) {
      throw new IncorrectOperationException();
    }
    final String name = ResourceBundleManager.getInstance(element.getProject()).getFullName((PropertiesFile)element);
    return name != null ? super.handleElementRename(name) : element;
  }


  @Override
  public boolean isReferenceTo(@NotNull PsiElement element) {
    if (element instanceof PropertiesFile) {
      final String name = ResourceBundleManager.getInstance(element.getProject()).getFullName((PropertiesFile)element);
      if (name != null && name.equals(myBundleName)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public Object @NotNull [] getVariants() {
    final ProjectFileIndex projectFileIndex = ProjectFileIndex.getInstance(getElement().getProject());
    final PropertiesReferenceManager referenceManager = PropertiesReferenceManager.getInstance(getElement().getProject());

    final Set<String> bundleNames = new HashSet<>();
    final List<LookupElement> variants = new SmartList<>();
    PropertiesFileProcessor processor = (baseName, propertiesFile) -> {
      if (!bundleNames.add(baseName)) return true;

      final LookupElementBuilder builder =
        LookupElementBuilder.create(baseName)
          .withIcon(AllIcons.Nodes.ResourceBundle);
      boolean isInContent = projectFileIndex.isInContent(propertiesFile.getVirtualFile());
      variants.add(isInContent ? PrioritizedLookupElement.withPriority(builder, Double.MAX_VALUE) : builder);
      return true;
    };

    referenceManager.processPropertiesFiles(myElement.getResolveScope(), processor, this);
    return variants.toArray(LookupElement.EMPTY_ARRAY);
  }

  @Override
  public String evaluateBundleName(final PsiFile psiFile) {
    return BundleNameEvaluator.DEFAULT.evaluateBundleName(psiFile);
  }
}

2、Resolve实现逻辑

        PsiScopeProcessor接口和PsiElement.processDeclarations()方法可以作为实现解析支持的基础。插件可以放弃标准接口并提供自己的、不同的 resolve 实现。resolve 实现包含以下组件:

1、一个PsiScopeProcessor接口实现类,它收集引用的可能声明,并在成功完成时停止解析过程。需要实现的主要方法是execute(),调用它来处理解析过程中遇到的每一个声明,true如果需要继续解析或者false找到声明则返回。方法getHint()handleEvent()用于内部优化,在PsiScopeProcessor自定义语言的实现中可以留空;

2、一个函数,它从参考位置向上遍历 PSI 树,直到解析成功完成或到达解析范围的末尾。如果引用的目标位于不同的文件中,则可以定位该文件;

3、在 PSI 树遍历时,每个PSI 元素都会调用processDeclarations(),如果 PSI 元素是一个声明,它会将自身传递给传递给它的execute()方法PsiScopeProcessor。此外,如果需要,根据语言范围规则,PSI 元素可以将PsiScopeProcessor传递给它的子元素。

3、多个目标References

        PsiReference(PsiPolyVariantReference)接口允许Reference解析到多个目标上,通过multiResolve()方法返回这些目标。multiResolve() 也可以基于PsiScopeProcessor实现。

4、额外突出显示

        Settings | Editor | Color Scheme | Language Defaults中的设置是通过HighlightedReference来实现的。

 二、示例

1、定义命名元素类

public interface SimpleNamedElement extends PsiNameIdentifierOwner {

}
public abstract class SimpleNamedElementImpl extends ASTWrapperPsiElement implements SimpleNamedElement {

  public SimpleNamedElementImpl(@NotNull ASTNode node) {
    super(node);
  }

}

为生成的 PSI 元素定义辅助方法

        修改SimplePsiImplUtil以支持添加到简单语言的 PSI 类的新方法。

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;
  }

  // ...

}

定义一个元素工厂

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);
  }
}

更新语法并重新生成解析器

        现在通过用下面的行替换定义来对Simple.bnf语法文件进行相应的更改。property不要忘记在更新文件后重新生成解析器!右键单击Simple.bnf文件并选择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]
}

2、定义Reference

public class SimpleReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference {

  private final String key;

  public 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[results.size()]);
  }

  @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();
  }

}

定义Reference Contributor

public 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;
              }
            });
  }

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

3、定义Refactoring Support Provide

public class SimpleRefactoringSupportProvider extends RefactoringSupportProvider {

  @Override
  public boolean isMemberInplaceRenameAvailable(@NotNull PsiElement elementToRename, @Nullable PsiElement context) {
    return (elementToRename instanceof SimpleProperty);
  }

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

        至此,Simple语言此时支持了重构功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/819868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenCVForUnity(十)扩张与侵蚀效果

文章目录 前言扩张案例展示 侵蚀案例展示 结语&#xff1a; 前言 在这个教程中&#xff0c;您将学习两种常见的图像形态运算符&#xff1a;侵蚀和膨胀。为此&#xff0c;您将使用OpenCV库中的两个函数&#xff1a;erode 和 dilate。 形态操作是一组基于形状的图像处理操作。形态…

java+springboot+mysql校园宿舍报修管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的校园宿舍报修管理系统&#xff0c;系统包含管理员、维修员、学生角色&#xff0c;功能如下&#xff1a; 管理员&#xff1a;楼栋管理、宿舍管理、维修人员管理、学生管理&#xff1b;报修管理&#xff08;派单给维修员&am…

k8s概念-StatefulSet

StatefulSet 是用来管理有状态应用的控制器 StatefulSet 用来管理某Pod集合的部署和扩缩&#xff0c; 并为这些 Pod 提供持久存储和持久标识符StatefulSet | KubernetesStatefulSet 运行一组 Pod&#xff0c;并为每个 Pod 保留一个稳定的标识。 这可用于管理需要持久化存储或稳…

系统架构师---UP统一过程常考概念

前言&#xff1a; 在 计算机诞生的年代&#xff0c;计算机是一种 只有天才才能掌握的工具。人们对软件的 认知仅仅停留在程序的层面上&#xff0c;所谓的软件开发就是那些能够掌握计算机的天才们写的一些只有计算机才能理解的二进制序列 。但是随着技术的发展&#xff0c;软件…

【计算机网络】传输层协议 -- TCP协议

文章目录 1. TCP协议的引入2. TCP协议的特点3. TCP协议格式3.1 序号与确认序号3.2 发送缓冲区与接收缓冲区3.3 窗口大小3.4 六个标志位 4. 确认应答机制5. 超时重传机制6. 连接管理机制6.1 三次握手6.2 四次挥手 7. 流量控制8. 滑动窗口9. 拥塞控制10. 延迟应答11. 捎带应答12.…

手撕SpringBoot的自定义启动器

一. 前言 哈喽&#xff0c;大家好&#xff0c;最近金九银十&#xff0c;又有不少小伙伴私信辉哥&#xff0c;说自己在面试时被问到SpringBoot如何自定义启动器&#xff0c;结果自己不知道该怎么回答。那么今天就手把手地带着大家&#xff0c;去看看在SpringBoot中到底该怎么实…

半导体制造工艺流程

本资料仅用于学习和讨论&#xff0c;如有侵权请反应 1、半导体制造工艺流程-要求 1.1 英特尔50亿纳米的制作工艺 2、第一步 晶圆加工 2.1 第一步 晶圆加工 2.2 第二步 氧化 2.3 第三步 光刻 2.4第四步 刻蚀 2.5 第五步 薄膜沉积 2.6 第六步 互连 2.7 第七步 测试 2.8…

奥威软件SaaS BI系统:一站式数据可视化解决方案

SaaS BI 系统是 Software as a Service 商业智能的缩写&#xff0c;是一种基于云计算的商业智能解决方案。它允许用户通过互联网访问和分析数据&#xff0c;而无需安装和维护昂贵的硬件和软件。如今将BI系统SaaS化已成趋势&#xff0c;越来越多的企业开始寻找SaaS BI系统&#…

IDA分析实例android_crackme/EasyJNI/Transformers/pingan2

文章目录 第一个实例android_crackme将32位的android_server放到手机目录下给android_server赋予root更改root用户组运行android_serverpc端端口转发安装apk&#xff0c;并运行app打开32位IDA并attach到进程先使用jadx看java层逻辑定位要分析的方法IDA 给两个方法打断点 第二个…

无涯教程-jQuery Mock Test函数

本节介绍了与 jQuery Framework 相关的各种模拟测试。您可以在本地计算机上下载这些样本模拟测试,并在方便时离线解决。每个模拟测试均随附一个模拟测试键,可让您验证最终分数并为自己评分。 Mock Test I Mock Test II Mock Test III Mock Test IV Q 1 -关于jQuery,以下哪项是…

【LeetCode 75】第十六题(1004)最大连续1的个数

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码运行结果&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给一个只有0和1的数组&#xff0c;可以将K个0变成1&#xff0c;问最大能得到的全是1的子数组的长度是多少。 这算是很经典的滑动…

领域驱动设计(五) - 战术设计 - 【3/3】聚合与模型的构建方法

这一小章主要阐述下如何组织上述分析后的模型。 使用聚合&#xff08;Aggergate&#xff09;进行建模&#xff0c;并且在设计中结合工厂&#xff08;Factory&#xff09;和资源库&#xff08;Repositiory&#xff0c;注意Orm映射出的持久化对象不是领域模型的范围&#xff0c;在…

深入理解ClickHouse跳数索引教程

跳数索引 影响ClickHouse查询性能的因素很多。在大多数场景中&#xff0c;关键因素是ClickHouse在计算查询WHERE子句条件时是否可以使用主键。因此&#xff0c;选择适用于最常见查询模式的主键对于表的设计至关重要。 然而&#xff0c;无论如何仔细地调优主键&#xff0c;不可…

Java导出数据到Excel

Java导出数据到Excel分3步处理 1、构建Workbook 数据 2、设置Workbook 格式 3、导出到Excel 1、构建Workbook 数据 public static void buildData(Workbook wb, List<Person> list) {Sheet sheetName wb.createSheet("sheetName");Row row sheetName.creat…

macOS 环境变量加载探究

使用 macOS 安装环境&#xff0c;见到过很数种环境变量配置方法&#xff0c;每次也都是按照别人的代码&#xff0c;人家配置在哪 我就配置在哪&#xff0c;其实不太清楚有什么区别&#xff0c;决定记录下。 本机 macOS 13.3&#xff0c;从 macOS Catalina(10.15) 开始&#xf…

软件测试入门基础知识

目录 1.软件测试的定义 2.软件测试的生命周期 3.如何描述一个bug 4.bug的级别如何定义 5.bug生命周期 6.软件测试策略 7.软件测试模型 7.1传统瀑布模型 7.2V模型 7.3W模型&#xff08;双V模型&#xff09; 7.4敏捷模型 7.5X模型 1.软件测试的定义 首先要明确测试的定义…

systemVerilog基础9——类的继承

1、 描述子类继承父类的关键词&#xff1a;extends 之前定义过的类Packet&#xff0c;可以进一步扩展构成一个它的子类LinkedPacket。类Packet的定义如下&#xff1a; class Packet ;//class定义类 类名 packet//类 packet的成员//数据或类属性bit [3:0] command; bit [40:0] …

【css】背景图片附着

属性&#xff1a;background-attachment 属性指定背景图像是应该滚动还是固定的&#xff08;不会随页面的其余部分一起滚动&#xff09;。 background-attachment: fixed&#xff1a;为固定&#xff1b; background-attachment: scroll为滚动 代码&#xff1a; <!DOCTYPE h…

TypeScript基础学习

目录 一、安装 1、下载国内镜像 2、安装 3、查看安装情况 4、使用例子 二、变量声明 1、规则 2、声明的四种方式 3、注意 4、类型断言 5、类型推断 6、变量作用域 三、基础类型&#xff08;共11种&#xff09; 1、Any 类型 2、Null 和 Undefined 3、never 类型…

医药化工企业洁净厂房改造消防防爆安全的重要性

设计 【摘要】&#xff1a;近年来&#xff0c;我国医药化工企业规模不断扩大。医药化工企业的情况复杂&#xff0c;稍有不慎将发生火灾或者爆炸&#xff0c;对人员生命以及财产安全造成巨大的损害&#xff0c;酿成悲剧。所以&#xff0c;“三同时”原则的落实&#xff0c;如何…