前言
前面初步看了关键的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 对象里的方法"伪装"成 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里面。
就干了这个事情。
总结
就这样把,原来这么麻烦的。
还有一个关键的文件没有看,算了,以后再说。