前言

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

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

正文

展开src目录

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

MyMessageBundle.kt

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

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

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这个配置文件

继续看

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


继续

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

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

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

    public DynamicBundle(@NotNull Class<?> bundleClass, @NotNull String pathToBundle) {
        super(bundleClass, pathToBundle);
    }

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

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

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

    @JvmStatic

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

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

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

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

fun,说明这是一个函数。

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

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

返回字符串。

@PropertyKey(resourceBundle = BUNDLE)

这个是IDE 智能提示注解

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

    @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相关的字节码

@FunctionalInterface
public interface Supplier<T> {

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

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

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

原来如此

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

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

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

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

MyMessageBundle.properties

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

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

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

MyToolWindowFactory

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

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字节码

// 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类型的,不知道是干什么的

那么看看怎么重写的。

        val myToolWindow = MyToolWindow()
        val content = ContentFactory.getInstance().createContent(myToolWindow.getContent(), null, false)
        toolWindow.contentManager.addContent(content)

首先,初始化一个MyToolWindow对象,在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是什么东西?

先看一下字节码。

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,看看字节码

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方法

    @NotNull Content createContent(@Nullable JComponent var1, @TabTitle @Nullable String var2, boolean var3);

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

public abstract class JComponent extends Container implements Serializable,

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

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

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

返回Content对象。

最后一行代码

        toolWindow.contentManager.addContent(content)

toolWindow的类型是ToolWindow,什么的ToolWindow

结合运行之后的结果

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

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

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

最前面还有一段代码

override fun shouldBeAvailable(project: Project) = true

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

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

因此,总结一下,

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

就干了这个事情。

总结

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

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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐