JetBrains IDE插件开发教程(二)——学习初始代码

前言

前面初步看了关键的build和setting这两个kt文件。

启动了项目,接下来继续看看具体插件的代码

正文

展开src目录

可以发现如下这些文件,一个一个看看

MyMessageBundle.kt

Kotlin 复制代码
package org.plugin

import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.PropertyKey
import java.util.function.Supplier

private const val BUNDLE = "messages.MyMessageBundle"

internal object MyMessageBundle {
    private val instance = DynamicBundle(MyMessageBundle::class.java, BUNDLE)

    @JvmStatic
    fun message(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg params: Any?): @Nls String {
        return instance.getMessage(key, *params)
    }

    @JvmStatic
    fun lazyMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any?): Supplier<@Nls String> {
        return instance.getLazyMessage(key, *params)
    }
}

前面几行代码都是导入,不必多说

Kotlin 复制代码
private const val BUNDLE = "messages.MyMessageBundle"

private是修饰符,英文的意思是"私密",限定范围为这个文件内。

const说明是一个固定不变的值。

val是kotlin的语法,意思是只读,声明一个只能赋值一次的变量,后面不能再改。

变量声明不再踩坑,深入理解Kotlin中的var与val类型机制-CSDN博客https://blog.csdn.net/IterLoom/article/details/153919658const val 值必须是编译器在编译时就能算出来的,直接写进程序里。仅适用于基本类型和字符串

有点意思。

变量名字叫BUNDLE,这是一个字符串,值是messages.MyMessageBundle

这是 classpath 资源路径,用点号(.)代替文件夹层级。

结合src展开图,意思就是指的resources/messages/MyMessageBundle.properties这个配置文件

继续看

Kotlin 复制代码
internal object MyMessageBundle 

internal这个可见性修饰符,英文意思是内部的

可见性修饰符 · Kotlin 官方文档 中文版https://book.kotlincn.net/text/visibility-modifiers.htmlinternal的可见范围是同一个模块内都能用 ,当前这个fitst整个项目就是一个模块

那么在org.plugin这个模块中的包中,在这个包下的文件都能使用MyMessageBundle ,比如下面的MyToolWindowFactory这个类

就可以使用。

object ,英文意思是对象,笔者的理解,对象往往是一个class经过new方法之后出现的东西

而现在直接就是一个对象,还能这么操作,

object是 Kotlin 提供的专门用来造"单例"的关键字。

object的名字叫MyMessageBundle 。


算了,明天再说,kotlin也太麻烦了。0.0


继续

Kotlin 复制代码
    private val instance = DynamicBundle(MyMessageBundle::class.java, BUNDLE)

private和val不必细说,关注DynamicBundle,在IDEA中可以按住CTRL键,左键点击,就会跳转到Java 字节码文件(.class)

当然IDEA反编译变成了人可以看得懂的java代码。

java 复制代码
    public DynamicBundle(@NotNull Class<?> bundleClass, @NotNull String pathToBundle) {
        super(bundleClass, pathToBundle);
    }

可以发现DynamicBundle里面直接是调用了super方法,那么代码的意思大致可以理解,初始化一个对象。

需要传入两个非空参数,第一个参数需要传递一个类,第二个参数是字符串。

把BUNDLE传进去了,创建了一个 DynamicBundle 对象,读取 resource/messages/MyMessageBundle.properties 这个资源文件

Kotlin 复制代码
    @JvmStatic

kotlin 中 @JvmStatic 注解@JvmStatic 注解的作用是:-- 兼容 java 中的静态成员 主要 - 掘金https://juejin.cn/post/7574685632094994459

这又是一个注解,搜索了一下。

原来是把 Kotlin 对象里的方法"伪装"成 Java 的静态方法,方便 Java 代码直接调用。

Kotlin 复制代码
 fun message(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg params: Any?): @Nls String {
        return instance.getMessage(key, *params)
    }

fun,说明这是一个函数。

这个message函数,kotlin默认函数是public的,里面调用DynamicBundle 对象的getMessage方法

需要传递两个参数,第一个参数是字符串,第二个参数是Any,任意类型的,可以为空。

返回字符串。

Kotlin 复制代码
@PropertyKey(resourceBundle = BUNDLE)

这个是IDE 智能提示注解

"这个 key 参数不是普通字符串,它必须是 BUNDLE 指向的那个 .properties 文件里真实存在的一个 key"

Kotlin 复制代码
    @JvmStatic
    fun lazyMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any?): Supplier<@Nls String> {
        return instance.getLazyMessage(key, *params)
    }

这个方法和message方法相似,但是返回的类型是**Supplier,**泛型是字符串

笔者虽然没有学过kotlin,java也几乎没学过,但是看到泛型还是有点亲切的

这是什么东西?

Kotlin学习:委托的理解_kotlin supplier-CSDN博客https://blog.csdn.net/qq_41989109/article/details/106175717相关的字节码

Kotlin 复制代码
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

可以发现这个所谓的Supplier原来是一个接口,里面只有一个get方法。

调用get方法才会返回字符串。

原来如此

message() 是立即返回字符串;

lazyMessage() 是返回一个"容器",等你调用 .get() 时才真正去读取和格式化字符串。用在需要延迟执行或按需获取的场景。

这一个kt文件的作用,说白了,就是读取相关的配置文件,在后续插件开发中使用

搞的这么麻烦,也是牛皮。

MyMessageBundle.properties

看看配置文件,里面写了什么东西

java 复制代码
toolwindow.stripe.MyToolWindow=My Tool Window
toolwindow.MyToolWindow.number.label=The random number is: {0}
toolwindow.MyToolWindow.shuffle.button=Shuffle

就是几个字符串,没什么东西。

MyToolWindowFactory

终于可以看看最关键的东西,到底怎么写的插件。

Kotlin 复制代码
package org.plugin

import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBPanel
import com.intellij.ui.content.ContentFactory
import javax.swing.JButton
import kotlin.random.Random

class MyToolWindowFactory : ToolWindowFactory {
    override fun shouldBeAvailable(project: Project) = true

    override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
        val myToolWindow = MyToolWindow()
        val content = ContentFactory.getInstance().createContent(myToolWindow.getContent(), null, false)
        toolWindow.contentManager.addContent(content)
    }

    class MyToolWindow {
        private val content = JBPanel<JBPanel<*>>().apply {
            val label = JBLabel(MyMessageBundle.message("toolwindow.MyToolWindow.number.label", "?"))

            add(label)
            add(JButton(MyMessageBundle.message("toolwindow.MyToolWindow.shuffle.button")).apply {
                addActionListener {
                    label.text = MyMessageBundle.message(
                        "toolwindow.MyToolWindow.number.label", Random(System.currentTimeMillis()).nextInt(1000)
                    )
                }
            })
        }

        fun getContent(): JBPanel<JBPanel<*>> = content
    }
}

首先,这是一个类------MyToolWindowFactory,后面有个冒号,应该是继承或者实现接口。看看ToolWindowFactory字节码

Kotlin 复制代码
// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package com.intellij.openapi.wm

public interface ToolWindowFactory : com.intellij.openapi.project.PossiblyDumbAware {
    public open val isDoNotActivateOnStart: kotlin.Boolean /* compiled code */
        @kotlin.Deprecated public open get() { /* compiled code */ }

    public open val anchor: com.intellij.openapi.wm.ToolWindowAnchor? /* compiled code */
        @org.jetbrains.annotations.ApiStatus.Internal public open get() { /* compiled code */ }

    public open val icon: javax.swing.Icon? /* compiled code */
        @org.jetbrains.annotations.ApiStatus.Internal public open get() { /* compiled code */ }

    public open suspend fun isApplicableAsync(project: com.intellij.openapi.project.Project): kotlin.Boolean { /* compiled code */ }

    @kotlin.Deprecated public open fun isApplicable(project: com.intellij.openapi.project.Project): kotlin.Boolean { /* compiled code */ }

    public abstract fun createToolWindowContent(project: com.intellij.openapi.project.Project, toolWindow: com.intellij.openapi.wm.ToolWindow): kotlin.Unit

    public open fun init(toolWindow: com.intellij.openapi.wm.ToolWindow): kotlin.Unit { /* compiled code */ }

    @org.jetbrains.annotations.ApiStatus.Experimental @org.jetbrains.annotations.ApiStatus.Internal public open suspend fun manage(toolWindow: com.intellij.openapi.wm.ToolWindow, toolWindowManager: com.intellij.openapi.wm.ToolWindowManager): kotlin.Unit { /* compiled code */ }

    public open fun shouldBeAvailable(project: com.intellij.openapi.project.Project): kotlin.Boolean { /* compiled code */ }
}

可以发现这是一个接口,看来MyToolWindowFactory是使用ToolWindowFactory这个接口。

而且可以发现createToolWindowContent这个方法是abstract ,必须重写。

MyToolWindowFactory里面也重写了,没问题。

有两个参数,一个类型是Project,另一个是ToolWindow类型的,不知道是干什么的

那么看看怎么重写的。

Kotlin 复制代码
        val myToolWindow = MyToolWindow()
        val content = ContentFactory.getInstance().createContent(myToolWindow.getContent(), null, false)
        toolWindow.contentManager.addContent(content)

首先,初始化一个MyToolWindow对象,在kotlin初始化对象,直接在一个类的后面加括号就可以了,也是挺方便的。

看看类的定义

Kotlin 复制代码
class MyToolWindow {
        private val content = JBPanel<JBPanel<*>>().apply {
            val label = JBLabel(MyMessageBundle.message("toolwindow.MyToolWindow.number.label", "?"))

            add(label)
            add(JButton(MyMessageBundle.message("toolwindow.MyToolWindow.shuffle.button")).apply {
                addActionListener {
                    label.text = MyMessageBundle.message(
                        "toolwindow.MyToolWindow.number.label", Random(System.currentTimeMillis()).nextInt(1000)
                    )
                }
            })
        }

        fun getContent(): JBPanel<JBPanel<*>> = content
    }

有一个只读变量和一个public方法。

变量content的类型是JBPanel,泛型是<JBPanel<*>>。

这个JBPanel是什么东西?

先看一下字节码。

Kotlin 复制代码
public class JBPanel<T extends JBPanel> extends JPanel implements JBComponent<T> 

哦,这个JBPanel继承于JPanel ,而JPanel是swing,Swing类似于Python的Tkinter,都是桌面 GUI 开发工具包。

原来是桌面开发的组件,笔者还是优势了解的。

接着,有初始化了一个标签------JBLabel,调用了MyMessageBundle的message方法,得到了标签的文本。

然后调用add方法,把标签放到面板(JBPanel)上,

下一行代码也不难理解,初始化一个按钮,放到面板上,

这个按钮还有对应的点击事件,生成一个 0~999 的随机整数。

getContent方法就是返回content,或者是返回一个面板。这个面板上有一个标签和一个按钮。

继续,初始化MyToolWindow之后,

又使用一个ContentFactory,看看字节码

Kotlin 复制代码
public interface ContentFactory {
    @NotNull Content createContent(@Nullable JComponent var1, @TabTitle @Nullable String var2, boolean var3);

    @NotNull ContentManager createContentManager(@NotNull ContentUI var1, boolean var2, @NotNull Project var3);

    @NotNull ContentManager createContentManager(boolean var1, @NotNull Project var2);

    static ContentFactory getInstance() {
        return (ContentFactory)ApplicationManager.getApplication().getService(ContentFactory.class);
    }

    /** @deprecated */
    @Deprecated(
        forRemoval = true
    )
    public static final class SERVICE {
        private SERVICE() {
        }

        public static ContentFactory getInstance() {
            return ContentFactory.getInstance();
        }
    }
}

调用的是getInstance这个方法,返回ContentFactory这个对象,而且从ApplicationManager这个不知道什么东西里面返回的。

笔者也不知道什么,应该很重要,不管。

继续看ContentFactory的createContent方法

java 复制代码
    @NotNull Content createContent(@Nullable JComponent var1, @TabTitle @Nullable String var2, boolean var3);

而JComponent,显示是swing里面的东西

java 复制代码
public abstract class JComponent extends Container implements Serializable,

看来第一个参数必须是 JComponent 类型或其子类的实例,非空的。

第二个参数是一个字符串,第三个参数是boolen,不知道干什么的。

但是第二个参数有一个TabTitle,看是和标题有关系的。

返回Content对象。

最后一行代码

java 复制代码
        toolWindow.contentManager.addContent(content)

toolWindow的类型是ToolWindow,什么的ToolWindow

结合运行之后的结果

所谓的ToolWindow其实就是指的就是侧边栏。被称为ToolWindow。

当然侧边栏不一定在左边,这个位置可以切换的。

因此,最后一段代码的意思就是把前面的面板放到ToolWindow里面。

最前面还有一段代码

java 复制代码
override fun shouldBeAvailable(project: Project) = true

意思是很显然的,打开项目时调用这个方法,插件是否显示。

中间不知道套壳套了些什么,搞得比较麻烦。

因此,总结一下,

MyToolWindowFactory初始化一个面板,然后在面板里面添加一个标签和按钮,把面板放到window里面。

就干了这个事情。

总结

就这样把,原来这么麻烦的。

还有一个关键的文件没有看,算了,以后再说。

相关推荐
培风图楠10 小时前
vscode初始配置环境后无法编译
ide·vscode·编辑器
雪靡10 小时前
Visual Studio 2026 优雅的给Cmake设置大代理
c++·ide·cmake·visual studio
plainGeekDev11 小时前
Kotlin协程面试题:suspend原理都说不清,协程你真会用?
android·面试·kotlin
周末也要写八哥11 小时前
Visual Studio 2022资源下载附保姆级安装教程
ide·visual studio
『昊纸』℃11 小时前
作为小白,C语言如何从零开始呢
c语言·ide·学习·编程·教材
yanghuashuiyue13 小时前
关于Eclipse和IDEA对比
java·ide·intellij-idea
bestlanzi13 小时前
使用vscode 搭建Java 开发环境
ide·vscode·编辑器
赏金术士1 天前
第六章:UI组件与Material3主题
android·ui·kotlin·compose
赏金术士1 天前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose