0
点赞
收藏
分享

微信扫一扫

idea插件开发(3)-插件开发基础

waaagh 2024-01-21 阅读 29


        上两篇文章主要描述了idea插件工程创建和idea插件的配置,本文继承上述两篇文章,详细描述下插件开发基础的第三块内容。也是开发具体插件功能前必须要了解的内容,否则开发过程中有可能会经常卡壳。 

因大部分配置相关的内容在前两篇文章中都已描述过了,所以本文中会着重说明下与配置相关的程序代码,配置相关内容可查看前两篇文章。

术语

  •  AST:Abstract Syntax Tree
  • UAST:Unified Abstract Syntax Tree,An abstraction layer on the different AST
  • DOM:Document Object Model
  • EDT:Event Dispatch Thread,handles all Swing events
  • EP:Extension Point
  • ES:External System,allows integrating external project management systems.
  • FBI:File Based Index,allows storing key-value information based on the project's files.
  • LVCS:Local History
  • LaF:Look and Feel,Defines the visual appearance and behavior of the user interface by use Swing
  • JPS:JetBrains Project System
  • JBR:JetBrains Runtime
  • PSI:Program Structure Interface 
  • RA:Read Action
  • WA:Write Action
  • RC:Run Configuration 
  • SSR:Structural Search and Replace,Allows searching and replacing code
  • VCS:Version Control System
  • VF:Virtual File
  • VFS:Virtual File System 

一、插件打包

        正式的插件包会有三种形式如下:

1、无依赖的jar包

        插件.jar文件与所有必需的捆绑库一起位于插件“根”文件夹下的/lib文件夹中。/lib文件夹中的所有 jar都会自动添加到类路径中:

.IntelliJIDEAx0/
└── plugins
    └── sample.jar
        ├── com/company/sample/SamplePluginService.class
        │   ...
        │   ...
        └── META-INF
            ├── plugin.xml
            ├── pluginIcon.svg
            └── pluginIcon_dark.svg

idea插件开发(3)-插件开发基础_intellij

2、有依赖的jar包

        插件.jar文件与所有必需的捆绑库一起位于插件“根”文件夹下的/lib文件夹中。/lib文件夹中的所有 jar都会自动添加到类路径中:

.IntelliJIDEAx0/
└── plugins
    └── sample
        └── lib
            ├── lib_foo.jar
            ├── lib_bar.jar
            │   ...
            │   ...
            └── sample.jar
                ├── com/company/sample/SamplePluginService.class
                │   ...
                │   ...
                └── META-INF
                    ├── plugin.xml
                    ├── pluginIcon.svg
                    └── pluginIcon_dark.svg

idea插件开发(3)-插件开发基础_ide_02

3、包含源码的jar包

        这种形式主要是为了其它人扩展用,比如定义两个模块API和Implement,需要把API公开时,这样就可以打包在一起供其它人来引用,配置在build.gradle中进行配置:

tasks {
  val createOpenApiSourceJar by registering(Jar::class) {
    // Java sources
    from(sourceSets.main.get().java) {
      include("**/com/example/plugin/openapi/**/*.java")
    }
    destinationDirectory.set(layout.buildDirectory.dir("libs"))
    archiveClassifier.set("src")
  }

  buildPlugin {
    dependsOn(createOpenApiSourceJar)
    from(createOpenApiSourceJar) { into("lib/src") }
  }
}

idea插件开发(3)-插件开发基础_jar_03

        上述配置将创建一个源JAR,其中包含com.example.plugin.openapi包中的Java文件,并将其添加到所需 example-plugin.zip 中的最终插件 ZIP 分发中!/example-plugin /lib /src目录。

二、依赖管理

 1、插件依赖

        一个插件可能依赖于其他插件的类,这些插件可能是捆绑的、第三方的或同一作者的。设置其他插件或模块的类的依赖,需要三个必需步骤:1、找到插件 ID;2、项目设置;3、plugin.xml中的声明

找到插件id

  1. JetBrains 市场上提供的可扩展插件

        对于在JetBrains Marketplace上发布的插件:可以打开插件的详细信息页面,然后向下滚动到底部的附加信息部分,最后复制插件 ID;

     2、Jetllij IDE捆绑的可扩展插件

        常见的插件如下,也可以在build.gradle文件中配置一个listBundledPlugins功能task列出插件列表。

Plugin Name

Plugin ID

Related Documentation

Copyright

com.intellij.copyright

CSS

com.intellij.css

WebStorm Plugin Development

Database Tools and SQL

com.intellij.database

DataGrip Plugin Development

IntelliLang

org.intellij.intelliLang

Language Injection

Java

com.intellij.java

Java

JavaScript and TypeScript

JavaScript

WebStorm Plugin Development

Kotlin

org.jetbrains.kotlin

Configuring Kotlin Support

Markdown

org.intellij.plugins.markdown

Maven

org.jetbrains.idea.maven

Spring

com.intellij.spring

Spring API

Spring Boot

com.intellij.spring.boot

Spring Boot

项目设置

        需要分别在build和plugin两个文件中设置要扩展的插件,先在build.gradle中引入插件依赖,然后在plugin.xml中配置具体的扩展。

intellij { plugins.set(listOf("com.example.another-plugin:1.0")) }

idea插件开发(3)-插件开发基础_ide_04

plugin.xml中的依赖声明

<depends>com.example.another-plugin</depends>

idea插件开发(3)-插件开发基础_java_05

依赖插件设置

        如下代表表示,指定要依赖的kotlin插件optional="true"为可选的插件依赖项。在这种情况下,即使依赖的插件没有安装或启用,插件也会加载,但插件的部分功能将不可用。

        plugin.xml:主plugin.xml定义了对 Java 插件(插件 ID com.intellij.java)的必需依赖并注册了相应的com.intellij.annotator扩展。此外,它还指定了对 Kotlin 插件的可选依赖项(插件 ID org.jetbrains.kotlin):

<idea-plugin>
   <depends>com.intellij.java</depends>

   <depends
       optional="true"
       config-file="myPluginId-withKotlin.xml">org.jetbrains.kotlin</depends>

   <extensions defaultExtensionNs="com.intellij">
      <annotator
          language="JAVA"
          implementationClass="com.example.MyJavaAnnotator"/>
   </extensions>
</idea-plugin>

idea插件开发(3)-插件开发基础_intellij_06

        myPluginId-withKotlin.xml:配置文件myPluginId-withKotlin.xml与主plugin.xml文件位于同一目录中。它注册了相应的com.intellij.annotator扩展:

<idea-plugin>
   <extensions defaultExtensionNs="com.intellij">
      <annotator
          language="kotlin"
          implementationClass="com.example.MyKotlinAnnotator"/>
   </extensions>
</idea-plugin>

idea插件开发(3)-插件开发基础_intellij_07

2、依赖冲突解决

        因为idea本身集成了一些捆绑的插件,如果自己开发的idea插件扩展了特定插件的特定版本,有可能出现版本冲突等问题。所以此时依赖管理就派上用场了,可以全局配置也可以用类加载器细致管理。

build.gradle全局配置

configurations.all { 
   resolutionStrategy.sortArtifacts(ResolutionStrategy.SortOrder.DEPENDENCY_FIRST)
}

idea插件开发(3)-插件开发基础_ide_08

自定义类加载器

Thread currentThread = Thread.currentThread();
ClassLoader originalClassLoader = currentThread.getContextClassLoader();
ClassLoader pluginClassLoader = this.getClass().getClassLoader();
try {
  currentThread.setContextClassLoader(pluginClassLoader);
  // code working with ServiceLoader here
} finally {
  currentThread.setContextClassLoader(originalClassLoader);
}

idea插件开发(3)-插件开发基础_intellij_09

三、插件扩展

监听器和扩展点

1、声明扩展

        本质上来说就是基于一个已有的插件进行开发,需要注意在使用时需要在plugin和build文件中添加依赖才会生效,插件一般有三种扩展类型:

  • com.intellij.toolWindow:可向IDE两加的工具栏添加按钮
  • com.intellij.applicationConfigurable和com.intellij.projectConfigurable:可在idea的settings页面添加一个自定义的插件设置页面;
  • 自定义语言插件使用许多扩展点来扩展 IDE 中的各种语言支持功能。

官方提供的扩展点:Extension Point and Listener List | IntelliJ Platform Plugin SDK

开源提供的扩展点:IntelliJ Platform Explorer | JetBrains Marketplace

plugin.xml声明扩展配置如下:

<extensions defaultExtensionNs="com.intellij">
    <!--appStarter 还有id, order, os属性,有必要设置id的唯一值避免冲突-->
    <appStarter 
            implementation="com.zd.MyAppStarter"/>
    <projectTemplatesFactory
            implementation="com.zd.MyProjectTemplatesFactory"/>
</extensions>

idea插件开发(3)-插件开发基础_ide_10

        defaultExtensionNs值,如果扩展了idea核心的需要输入com.intellij。如果是其它的非捆绑的扩展输入plugin的具体值,自定义的扩展实现类如下:

public class MyAppStarter implements ApplicationStarter {
    @Override
    public String getCommandName() {
        return null;
    }
}

public class MyProjectTemplatesFactory extends ProjectTemplatesFactory {
    @Override
    public String @NotNull [] getGroups() {
        return new String[0];
    }

    @Override
    public ProjectTemplate @NotNull [] createTemplates(@Nullable String group, @NotNull WizardContext context) {
        return new ProjectTemplate[0];
    }
}

idea插件开发(3)-插件开发基础_java_11

        以上是用接口声明的,如果扩展点是使用beanClass属性声明的,则在指定的 bean 类中设置@Attribute注解来设置相应的属性。

2、定义扩展点

        通过在您的插件中定义扩展点,可以允许其他插件扩展您的插件的功能。在plugin.xml文件中可把扩展点<extensionPoints>定义在<extension>中,也可以单独定义。扩展点支持动态属性,但由于限制比较多,暂时不建议使用。扩展点的两种定义方式:

Interface扩展点

        允许其他插件使用代码扩展您的插件,其他插件将提供实现该接口的类。然后就可以能够调用这些接口上的方法。name属性为此扩展点分配一个唯一的名称,插件内全局唯一,所以建议用类的全限定名。

<extensionPoints>
    <extensionPoint
            name="myExtensionPoint1"
            beanClass="com.zd.MyBeanClass">
        <with attribute="implementationClass" implements="com.intellij.serviceContainer.LazyExtensionInstance"/>
    </extensionPoint>
</extensionPoints>

idea插件开发(3)-插件开发基础_jar_12

public class MyBeanClass extends LazyExtensionInstance {

    @Attribute("key")
    public String key;

    @Attribute("implementationClass")
    public String implementationClass;

    public String getKey() {
        return key;
    }

    @Override
    protected @Nullable String getImplementationClassName() {
        return null;
    }
}

idea插件开发(3)-插件开发基础_intellij_13

Bean扩展点

        允许其他插件使用数据扩展您的插件。需要指定扩展类的完全限定名称,其他插件将提供将转换为该类实例的数据。

<extensionPoints>
    <extensionPoint
            name="myExtensionPoint2"
            interface="com.zd.MyInterface"/>
</extensionPoints>

idea插件开发(3)-插件开发基础_intellij_14

public class MyInterface {
}

idea插件开发(3)-插件开发基础_ide_15

使用扩展点

        在另一个插件中使用自定义的扩展点。这里有一个注意点其实是有双层意义,扩展别人的插件或是为了别人扩展插件。但扩展点可以定义但不一定会公布出去,可以查看打包一节。

        下图代码中depends值为上面扩展点定义中的插件的id值,也时也可以了解下defaultExtensionNs的使用方法。

<idea-plugin>
  <id>another.plugin</id>

  <!-- Declare dependency on plugin defining extension point: -->
  <depends>my.plugin</depends>

  <!-- Use "my.plugin" namespace: -->
  <extensions defaultExtensionNs="my.plugin">
    <myExtensionPoint1
            key="someKey"
            implementationClass="another.some.implementation.class"/>

    <myExtensionPoint2
            implementation="another.MyInterfaceImpl"/>
  </extension>

</idea-plugin>

idea插件开发(3)-插件开发基础_xml_16

        在代码中拿到扩展点的实例进行编程:

public class MyExtensionUsingService {

  private static final ExtensionPointName<MyBeanClass> EP_NAME =
          ExtensionPointName.create("my.plugin.myExtensionPoint1");

  public void useExtensions() {
    for (MyBeanClass extension : EP_NAME.getExtensionList()) {
      String key = extension.getKey();
      String clazz = extension.getImplementationClassName();
      // ...
    }
  }

}

idea插件开发(3)-插件开发基础_jar_17

四、定义服务

        可以简单理解为spring中的service,用于处理复杂的逻辑,类似于bean的概念。目的是为了当您的插件调用相应实例的方法时按需加载的插件组件。IntelliJ 平台确保仅加载服务的一个实例,即使它被多次调用也是如此。

        服务必须具有用于服务实例化的实现类。服务也可能有一个接口类,用于获取服务实例并提供服务的 API。需要关闭挂钩/清理例程的服务可以实现Disposable并执行必要的工作dispose()。

        IntelliJ 平台提供三种类型的服务:应用程序级服务(全局单例)、项目级服务和模块级服务。对于后两者,会为其对应作用域的每个实例创建一个单独的服务实例。

避免或谨慎使用模块级服务,因为它会增加包含许多模块的项目的内存使用量

        定义服务有配置和注解两种实现方式:

1、配置服务

配置方式

应用型

public interface MyAppService {
  void doSomething(String param);
}
public class MyAppServiceImpl implements MyAppService {
  @Override
  public void doSomething(String param) {
    // ...
  }
}

idea插件开发(3)-插件开发基础_intellij_18

项目型

public interface MyProjectService {
  void doSomething(String param);
}
public class MyProjectServiceImpl {
  private final Project myProject;

  public MyProjectServiceImpl(Project project) {
    myProject = project;
  }

  public void doSomething(String param) {
    String projectName = myProject.getName();
    // ...
  }
}

idea插件开发(3)-插件开发基础_intellij_19

在plugin.xml中配置上述服务

<extensions defaultExtensionNs="com.intellij">
  <!-- Declare the application-level service -->
  <applicationService
      serviceInterface="com.example.MyAppService"
      serviceImplementation="com.example.MyAppServiceImpl"/>

  <!-- Declare the project-level service -->
  <projectService
      serviceInterface="com.example.MyProjectService"
      serviceImplementation="com.example.MyProjectServiceImpl"/>
</extensions>

idea插件开发(3)-插件开发基础_intellij_20

注解方式

        这种方式不需要在plugin.xml中注册,但服务的实现类必须是final类型的。

应用型

@Service
public final class MyAppService {
  public void doSomething(String param) {
    // ...
  }
}

idea插件开发(3)-插件开发基础_java_21

项目型

@Service(Service.Level.PROJECT) 
public final class MyProjectService {
  private final Project myProject;

  public MyProjectService(Project project) {
    myProject = project;
  }

  public void doSomething(String param) {
    String projectName = myProject.getName();
    // ...
  }
}

idea插件开发(3)-插件开发基础_jar_22

2、获取服务

MyAppService applicationService =
    ApplicationManager.getApplication().getService(MyAppService.class);

MyProjectService projectService =
    project.getService(MyProjectService.class);

idea插件开发(3)-插件开发基础_ide_23

MyAppService applicationService = MyAppService.getInstance();

MyProjectService projectService = MyProjectService.getInstance(project);

idea插件开发(3)-插件开发基础_intellij_24

五、侦听器

消息传递基础结构)。侦听器实现必须是无状态的,并且不能实现生命周期(例如,Disposable),可以定义应用程序级和项目级侦听器。

        类似于mq的概念,订阅topic然后再实现一个handle处理类。在Idea中侦听器的设计大概如下所示:

idea插件开发(3)-插件开发基础_jar_25

idea插件开发(3)-插件开发基础_xml_26编辑

1、定义应用程序级侦听器

<applicationListeners>
    <listener
            class="com.zd.MyVfsListener"
            topic="com.intellij.openapi.vfs.newvfs.BulkFileListener"/>
</applicationListeners>

idea插件开发(3)-插件开发基础_jar_27

public class MyVfsListener implements BulkFileListener {

    @Override
    public void after(@NotNull List<? extends VFileEvent> events) {
        // handle the events
//        events.connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
//            @Override
//            public void after(@NotNull List<? extends VFileEvent> events) {
//                // handle the events
//            }
//        });
    }
}

idea插件开发(3)-插件开发基础_xml_28

  • topic属性:指定与要接收的事件类型对应的侦听器接口的完全限定名
  • class属性:指定插件中实现侦听器接口并接收事件的实现逻辑类

2、定义项目级监听器

<projectListeners>
    <listener
            class="com.zd.MyToolWindowListener"
            topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
</projectListeners>

idea插件开发(3)-插件开发基础_java_29

public class MyToolWindowListener implements ToolWindowManagerListener {
    private final Project project;

    public MyToolWindowListener(Project project) {
        this.project = project;
    }

    @Override
    public void stateChanged(@NotNull ToolWindowManager toolWindowManager) {
        // handle the state change
    }
}

idea插件开发(3)-插件开发基础_intellij_30

3、侦听器设置

        activeInTestMode和activeInHeadlessMode属性用于设置是否禁用此侦听器,但前提是需要知道Application.isHeadlessEnvironment()和Application.isUnitTestMode()的返回值为ture时相应的属性才会生效。

4、自定义侦听器

定义Topic

public class Notifier {
 
  @Topic.AppLevel
  public static final Topic<FileDocumentManagerListener> FILE_DOCUMENT_SYNC 
= new Topic<>(FileDocumentManagerListener.class, 
              Topic.BroadcastDirection.TO_DIRECT_CHILDREN, 
              true);
   void beforeAction(Context context);
   void afterAction(Context context);
}

idea插件开发(3)-插件开发基础_ide_31

实现Subscriber

public class NotifierListener {

    public void init(MessageBus bus) {
        bus.connect().subscribe(Notifier.CHANGE_ACTION_TOPIC,
                new ChangeActionNotifier() {
                    @Override
                    public void beforeAction(Context context) {
                        // Process 'before action' event.
                    }
                    @Override
                    public void afterAction(Context context) {
                        // Process 'after action' event.
                    }
                });
    }
}

idea插件开发(3)-插件开发基础_xml_32

实现publisher

public class NotifierPublisher {
    public void doChange(Context context) {
        Notifier publisher =
                ComponentManager.getMessageBus().syncPublisher(Notifier.CHANGE_ACTION_TOPIC);
        publisher.beforeAction(context);
        try {
            // do action
        } finally {
            publisher.afterAction(context);
        }
    }
}

idea插件开发(3)-插件开发基础_java_33

六、设置插件图标

        要求:1、尺寸:40 x 40 或80 x 80像素;2、形状:插件Logo周边至少要留2px的透明边距;3、所有插件徽标图像必须为 SVG 格式;3、放在resources/META-INT文件夹下:

  • pluginIcon.svg:是默认的插件标志,仅用于浅色IDE 主题,
  • pluginIcon_dark.svg:可选的替代插件徽标,仅用于深色 IDE 主题

七、开发插件常见问题

https://plugins.jetbrains.com/docs/intellij/faq.html



举报

相关推荐

0 条评论