Idea Plugin 翻译篇(1)

初始化环境

插件项目结构

Plugin Content

有两种方式让插件可以生效
都是需要放在IDEA的plugin目录中,但是一种是直接放一个.jar文件,另一种是放一个文件夹,内部有插件的.jar文件,目录结构如下

  • .IntelliJIDEAx0/

    • plugins/
      • sample.jar/
        • com/foo/…
        • META-INF/
          • plugin.xml
  • .IntelliJIDEAx0/

    • plugins/
      • sample.jar/
        • com/foo/…
        • META-INF/
          • plugin.xml

Plugin Class Loaders

当加载插件的时候,IDEA使用了一个单独的Class Loader去加载插件需要的classes,所以这就让每一个插件可以使用不同版本的库,甚至是其他或者IDEA本身都使用了同一个库,可以做到互不干扰

Plugin Components

组件是基本的概念当插件集成的时候.一种有三种不同的组件:

  • Application level components

    这个组件是在你的IDEA启动的时候创建和初始化的.
    你可以通过getComponent(Class)方法获取到实例

  • Project level components
    IDEA 为每一个 Project instance 都创建了一个实例(注意:可能也会为没有打开的项目创建).
    你可以通过getComponent(Class)方法获取到实例
  • Module level components
    IDEA 为每一个 Module instance 创建实例在IDEA加载每一个项目的时候.
    你可以通过getComponent(Class)方法获取到实例

每个组件都应该在配置文件中配置接口和实现类.接口会在其他地方被检索,实现类也会被检索并且用于初始化组件 注意:两个等级(Application, Project or Module)一样的组件不能有相同的接口类.必须是不同的 每一个组件有一个唯一的名字,这个名字需要在getComponentName()方法中返回

Components naming notation

组件的命名推荐使用.

Application level components

这是可选的,一个Application level的组件必须实现ApplicationComponent接口 Application level的组件的类必须有一个无参的构造函数用于初始化.如果你的组件依赖其他的Application level的组件,那么在构造函数中直接写上依赖的组件作为参数. IntelliJ Platform 会感知这个组件,并且初始化好作为参数来满足你的构造函数 Application level的组件必须在 plugin.xml 的 中注册 Project level components 这是可选的,一个Project level的组件实现的类可选的实现ProjectComponent接口 Project level的组件的构造函数.参数可以是 Project 类型,也可以是其他的application-level or project-level的组件作为参数. 一个Project level的组件必须在 plugin.xml 的 中注册

Module level components

这是可选的,一个Module level的组件实现的类可选的实现ModuleComponent接口 Module level的组件的构造函数.参数可以是 Module 类型,也可以是其他的application-level or project-level or module level的组件作为参数.

持久化组件的状态

每一个组件的状态会被自动的保存和加载如果组件类实现了JDOMExternalizable (deprecated) or PersistentStateComponent 接口 当组件实现了 PersistentStateComponent 接口,组件的状态被保存在一个xml中当你使用注解@State and @Storage 当组件类实现了 JDOMExternalizable 接口,组件的状态被保存到下面几个地方:
  • Project level 的组件的状态保存到Project的 (.ipr) 文件中.然而,如果在 plugin.xml 中 workspace 的选项被设置为 true,组件的状态会被保存到工作空间的 (.iws) 文件中
  • Module level 的组件保存他们的状态到Module 的 (.iml) 文件中.

更多的信息请参考
Persisting State of Components

Defaults

组件一些默认的设置应该被放在 .xml 文件中. 把这个文件放在插件的 classpath 目录的对应的包名中. readExternal() 方法会被调用在 IDEA 平台加载 .xml 文件读取到 节点内容的时候

如果一个组件有默认的配置, readExternal() 方法会被调用两次:
- 第一次是为了默认的设置
- 第二次是为了保存配置信息

Plugin components lifecycle

各个组件加载的顺序如下:

  • 创建(Creation):构造函数被调用
  • 初始化(Initialization):初始化方法被调用(如果组件实现了 ApplicationComponent 接口)
  • 配置(Configuration):readExternal 方法被调用(如果组件实现了 JDOMExternalizable 接口),或者 loadState 方法被调用(如果组件实现了 PersistentStateComponent 并且有不是默认的 persisted 状态)
  • 对于 Module level 的组件, ModuleComponent 接口 的 moduleAdded 方法会被调用当一个 Module 被加载到 Project的时候
  • 对于Project level 的组件, ProjectComponent 接口 的 projectOpened 方法会被调用当一个 Project 被加载

各个组件的卸载的顺序如下:

  • 保存配置(Saving configuration):writeExternal 方法会被调用(如果组件实现了 JDOMExternalizable 接口),或者 getState 方法会被调用(如果组件实现了 PersistentStateComponent 接口)
  • 最后的处理(Disposal):disposeComponent 方法会被调用

注意:不要在构造函数中去获取其他的组件通过 getComponent() 方法,否则你会拿到一个断言(个人理解应该是挂掉吧).如果你需要使用其他的组件,请把他们写成构造函数的参数

Plugin Extensions and Extension Points

IntelliJ Platform 提供了一个 Plugin Extensions and Extension Points 概念让一个插件可以和其他插件或者IDEA交互
Extension Points 笔者暂且理解为插件本身可拓展的点
Extensions 则是扩展其他已有的插件

Extension points

如果你想你的插件是允许其他插件来扩展功能的,在插件中,你必须定义至少一个的
extension points 每一个 extension point 指定一个 class 或者 interface 表示可以 操作到这个 point

Extension

如果你想你的插件扩展其他插件或者 IDEA 平台本身的功能,你必须定义至少一个 Extension

How to declare extensions and extension points

你可以在配置文件 plugin.xml 定义你的 extensions and extension points 分别在 and 节点中

To declare an extension point

在 节点中,插入一个 子节点,在这个子节点中定义名称和 bean class 或者 interface 的名称.
定义的这些可以扩展插件的功能

为了让整个过程更加的清晰, 下面是一个简单的demo,写在 plugin.xml 文件中:

<extensionPoints>
    <extensionPoint name="MyExtensionPoint1" beanClass="MyPlugin.MyBeanClass1">
    <extensionPoint name="MyExtensionPoint2" interface="MyPlugin.MyInterface">
</extensionPoints>
  • interface 属性定义的类是拓展者必须实现的一个类
  • beanClass 属性定义的类内部指定了一个或者多个的用注解 @Attribute 标识的属性

想要拓展插件的开发者会读取到这些属性从 plugin.xml 文件中

下面是一个例子

public class MyBeanClass1 extends AbstractExtensionPointBean {
  @Attribute("key")
  public String key;

  @Attribute("implementationClass")
  public String implementationClass;

  public String getKey() {
    return key;
  }

  public String getClass() {
    return implementationClass;
  }
}

注意:当你去设计拓展 MyExtensionPoint1 的时候,你的 plugin.xml 文件必须包含 节点,并且内部写上了 ‘key’ 和 ‘implementationClass’ 属性去设置适当的值(下面还会有例子)

To declare an extension

  • 对于 元素,设置 xmlns(已废弃) 或者 defaultExtensions 属性,可选的值有下面两个:

    • com.intellij,如果你的插件想要拓展平台的功能
    • {ID of a plugin},如果你的插件想拓展另一个插件的功能
  • 在元素 下面如果你想添加一个子节点,这个子节点必须满足你想要去拓展的目标 extension point

  • 依赖extension point 的类型,做下面的其中一个事情:
    • 如果extention point 使用 interface 声明的,那你在子节点需要设置 implementation 属性,值是实现类的全名称
    • 如果extension point 使用 beanClass 声明的,那你在子节点中不仅需要写 implementation 属性指定全类名,你还需要写上所有在类中用注解 @Attribute 标记的属性

为了说明这个过程,在 plugin.xml 中写上下面的例子,其中定义了两个 extensions 去访问 appStarter 和 applicationConfigurable extensions poins 去扩展 Intellij Platform 和 MyExtensions1 extension point 在一个 test plugin:

<!-- Declare extensions to access extension points in the IntelliJ Platform.
     These extension points have been declared using the "interface" attribute.
 -->
  <extensions defaultExtensionNs="com.intellij">
    <appStarter implementation="MyTestPackage.MyTestExtension1" />
    <applicationConfigurable implementation="MyTestPackage.MyTestExtension2" />
  </extensions>

<!-- Declare extensions to access extension points in a custom plugin
     The MyExtensionPoint1 extension point has been declared using *beanClass* attribute.
-->
  <extensions defaultExtensionNs="MyPluginID">
     <MyExtensionPoint1 key="keyValue" implementationClass="MyTestPackage.MyClassImpl"></MyExtensionPoint1>
  </extensions>

如何获取extension poins 列表(How to get the extension poins list)?

获取系统可用的extension points,请参考 节点在下面的xml配置文件中:

  • LangExtensionPoints.xml
  • PlatformExtensionPoints.xml
  • VcsExtensionPoints.xml

更多的信息和例子

获取更详细创建插件的例子和详情介绍,可以参考 定制IDEA的Setting 对话框和Tools 窗口的创建

Plugin Actions

IntelliJ Platform 提供了一个叫做 ‘actions’ 的概念,一个 action 是一个 class,是 AnAction 的子类, actionPerformed 方法在菜单Item或者标题栏按钮被选中的时候会被调用

actions 允许插件添加插件自己的 items 到IDEA 的menus 和 toolbars. 所有的 Actions 用groups 组织的,反过来 Action 也可以包含其他的 groups. 一个 group 可以直接是toolbar 上的,也可以是menu上的. 总而言之,两者是互相嵌套的,和你平时捡到的多级菜单一样的.
你可以找到更多的信息关于创建和注册Actions在下面的链接中
IntelliJ Platform Action System

Plugin Services

IntelliJ Platform 提供了一个叫做 ‘services’ 的概念

service 是一个插件组件,当你的插件调用 ServiceManager class 的 getService 方法的时候,Service 会被加载

IntelliJ Platform 会保证 service 的实例只有一个即使 getService 方法会被调用多次.一个 service 必须有一个实现类用于初始化 service. 一个 service 还可以有一个接口用于获取这个 service 实例对象并且提供 service 的API. 接口和实现类都需要定义在 plugin.xml 中

IntelliJ Platform 提供了三种 services:
- application level
- project level
- medule level

How to Declare a Service(如何去声明一个 Service)?

去声明一个 service,你可以使用下面 extension points
- applicationService:去声明一个 application level service
- projectService:去声明一个 project level service
- moduleService:去声明一个 module level service

To declare a service:

在你的项目中,右键打开菜单, new -> Plugin DevKit 菜单中就可以选择创建不同的 service

IDE 会创建一个新的 Java interface 和 class(或者只是一个class) 并且注册这个新的 service 到 plugin.xml

note: Declaring a service via New context menu is available since version 2017.3.
这个操作在IDEA 的 2017.3 之后的版本才有

下面是一个范例代码,在 plugin.xml 中声明下面的代码:

<extensions defaultExtensionNs="com.intellij">
  <!-- Declare the application level service -->
  <applicationService serviceInterface="Mypackage.MyApplicationService" serviceImplementation="Mypackage.MyApplicationServiceImpl" />

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

如果属性 ‘serviceInterface’ 没有声明,那么默认值就是属性 ‘serviceImplementation’ 的值

Retrieving a service(检索一个 service )

为了初始化你的 service,在 Java 代码中,使用下面的语法:

MyApplicationService applicationService = ServiceManager.getService(MyApplicationService.class);

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

MyModuleService moduleService = ModuleServiceManager.getService(module, MyModuleService.class);

Sample Plugin(范例插件)

这部分允许你去下载并且安装一个插件的例子来说明如何去创建并且使用一个 service 插件. 这个插件有一个 project 组件实现了一个 service,他的功能是记录当前打开的项目数目.如果这个数值超过了同时打开的最大项目数,这个插件会返回一个错误信息并且关闭最近打开的项目.

To install and run the sample plugin
  • 下载范例代码here
  • 启动 IDEA,在启动的界面,点击打开项目并且打开刚刚下载好的项目
  • 在主菜单选择run
  • 如果运行有问题,可能需要执行更改run的配置

Plugin Configuration File - plugin.xml

下面是一个范例插件的配置文件,这个范例介绍了plugin.xml所有可使用的元素

<!-- url="" specifies the URL of the plugin homepage (displayed in the Welcome Screen and in "Plugins" settings dialog) -->
<idea-plugin url="http://www.jetbrains.com/idea">

  <!-- Plugin name -->
  <name>VssIntegration</name>

  <!-- Unique identifier of the plugin.
       Cannot be changed between the plugin versions.
       If not specified, assumed to be equal to <name>. -->
  <id>VssIntegration</id>

  <!-- Description of the plugin. -->
  <description>Vss integration plugin</description>

  <!-- Description of changes in the latest version of the plugin.
       Displayed in the "Plugins" settings dialog and in the
       plugin repository Web interface. -->
  <change-notes>Initial release of the plugin.</change-notes>

  <!-- Plugin version -->
  <version>1.0</version>

  <!-- The vendor of the plugin.
       The optional "url" attribute specifies the URL of the vendor homepage.
       The optional "email" attribute specifies the e-mail address of the vendor.-->
  <vendor url="http://www.jetbrains.com" email="support@jetbrains.com" />

  <!-- The unique identifiers of the plugins on which this plugin depends. -->
  <depends>MyFirstPlugin</depends>

  <!-- Optional dependency on another plugin.
       If the plugin with the "MySecondPlugin" ID is installed,
       the contents of mysecondplugin.xml (the format of this file
       conforms to the format of plugin.xml) will be loaded. -->
  <depends optional="true" config-file="mysecondplugin.xml">MySecondPlugin</depends>

  <!-- Allows a plugin to integrate its help system (in JavaHelp format)
       with the IDEA help system. The "file" attribute specifies the name
       of the JAR file in the "help" subdirectory of the plugin directory.
       The "path" attribute specifies the name of the helpset file within
       the JAR file.-->
  <helpset file="myhelp.jar" path="/Help.hs" />

  <!-- Minimum and maximum build of IDEA compatible with the plugin -->
  <idea-version since-build="3000" until-build="3999"/>

  <!-- Resource bundle from which the text of plugin descriptions,
       action names and etc. will be loaded -->
  <resource-bundle>messages.MyPluginBundle</resource-bundle>

  <!-- Plugin's application components -->
  <application-components>
    <component>
      <!-- Component's interface class -->
      <interface-class>com.foo.Component1Interface</interface-class>

      <!-- Component's implementation class -->
      <implementation-class>com.foo.impl.Component1Impl</implementation-class>
    </component>
  </application-components>

  <!-- Plugin's project components -->
  <project-components>
    <component>
      <!-- Interface and implementation classes are the same -->
      <interface-class>com.foo.Component2</interface-class>

      <!-- If the "workspace" option is set "true", the component
           saves its state to the .iws file instead of the .ipr file.
           Note that the <option> element is used only if the component
           implements the JDOMExternalizable interface. Otherwise, the
           use of the <option> element takes no effect.  -->
      <option name="workspace" value="true" />

      <!-- If the "loadForDefaultProject" tag is present, the project component is instantiated also for the default project. -->
      <loadForDefaultProject>
    </component>
  </project-components>

  <!-- Plugin's module components -->
  <module-components>
    <component>
      <interface-class>com.foo.Component3</interface-class>
    </component>
  </module-components>

  <!-- Actions -->
  <actions>
    <action id="VssIntegration.GarbageCollection" class="com.foo.impl.CollectGarbage" text="Collect _Garbage" description="Run garbage collector">
      <keyboard-shortcut first-keystroke="control alt G" second-keystroke="C" keymap="$default"/>
    </action>
  </actions>

  <!-- Extension points defined by the plugin.
       Extension points are registered by a plugin so that other
       plugins can provide this plugin with certain data. The
       "beanClass" attribute specifies the class the implementations
       of which can be used for the extension point. -->
  <extensionPoints>
    <extensionPoint name="testExtensionPoint" beanClass="com.foo.impl.MyExtensionBean"/>
  </extensionPoints>

  <!-- Extensions which the plugin adds to extension points
       defined by the IDEA core or by other plugins.
       The "defaultExtensionNs " attribute must be set to the
       ID of the plugin defining the extension point, or to 
       "com.intellij" if the extension point is defined by the
       IDEA core. The name of the tag within the <extensions>
       tag matches the name of the extension point, and the
       "implementation" class specifies the name of the class
       added to the extension point. -->
  <extensions xmlns="VssIntegration">
    <testExtensionPoint implementation="com.foo.impl.MyExtensionImpl"/>
  </extensions>
</idea-plugin>

Plugin Dependencies

在你的插件中,你可能会依赖其他插件的 classed 文件,为了做到这点,你需要执行下面几步:

  • 如果你的插件没有依赖其他的插件,那么你可以在沙盒中直接运行目标 IDEA 去安装插件
  • 否则,你需要添加你依赖的jars 到 IntelliJ Platform SDK 的 classpath. 为了去做到这点,打开项目的结构对话框,选择你正在使用的 SDK,点击 加号按钮,选择 jar 文件
  • 这段我看不太懂,所以不翻译了
    • For bundled plugins, the plugin jar files are located in plugins/ or plugins//lib under the main installation directory. If you’re not sure which jar to add, you can add all of them.
    • For non-bundled plugins, the plugin jar files are located in config/plugins/ or config/plugins//lib under the directory specified as “Sandbox Home” in the IntelliJ Platform Plugin SDK settings.

image

如果你使用的 Gradle 去构建你的插件,那么就可以在你的 build.gradle 文件中添加下面的代码:

intellij {
    plugins 'org.jetbrains.kotlin:1.2.30'
}

添加一个 节点到 plugin.xml,添加插件的 ID

<depends>org.jetbrains.kotlin</depends>

为了找到你要依赖的插件的id,定位到 META-INF/plugin.xml 文件里面有 节点

Optional Plugin Dependencies(可选的插件依赖)

你可以定义一个可选的插件依赖,在这种情况下,你的插件回家再即使依赖的插件没有安装或者不可用,但是你的插件的某些功能可能就不好使了.为了做到这些,添加optional=”true” config-file=”otherconfig.xml” 到 节点.

举个例子,如果你在写一个插件是为 Java 和 kotlin 文件添加额外的高亮,你可以使用下面的设置.你的主要配置文件 plugin.xml 会定义一个解释器作用于 Java并且指定了一个可选的 Kotling 插件依赖:

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

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

然后你在创建一个文件叫做 withKotlin.xml

<idea-plugin>
   <extensions defaultExtensionNs="com.intellij">
      <annotator language="kotlin" implementationClass="com.example.MyKotlinAnnotator"/>
   </extensions>
</idea-plugin>
展开阅读全文

没有更多推荐了,返回首页