在前两篇文章中讲解了关于插件开发的基础知识,本文将介绍关于消息通知和事件监听方面的内容,关于 IntelliJ IDE 插件开发的基本内容也就到此为止,在下一篇文章中将开发一个简单的插件对这部分内容做一个总结,后续再介绍关于虚拟文件和PSI相关的知识,最后以一个代码生成插件作为结尾。话不多说,下面就开始本文的主题,同时本文涉及到的的完整代码已上传到Github。
在 IntelliJ IDE 中用于展示消息提示的方式有很多种,例如侧边栏的消息通知、对话框、鼠标悬浮代码时出现的弹框等等,下面就一一进行介绍。
这种通知是在 IDE 内的右下角进行显示,同时这类消息会集中收集在消息通知中可进行查看(如下图所示),并且还可以让用户在Settings | Appearance & Behavior | Notifications
中设置。
这种通知可使用Notifications.Bus.notify()
方法或NotificationGroupManager
类来进行实现,例如上面的例子如果使用Notifications.Bus.notify()
方法就是:
val notification = Notification("listener", "Hello, world!", NotificationType.INFORMATION)
Notifications.Bus.notify(notification, e.project)
可以看到需要传递两个参数,一个是通知对象,一个是项目对象(可选)。不过核心还是Notification
这个通知类,其中推荐使用的构造方式有两种:
在上述例子里使用的则是第一个构造参数方式,其中第一个参数是分组 id,可以使用系统的已有的分组,也可以选择自己创建分组,这里还是推荐自行创建,只要在 plugin.xml 中加入如下配置即可:
<extensions defaultExtensionNs="com.intellij">
<notificationGroup id="listener" displayType="BALLOON"/>
</extensions>
其中配置的 id 就可以使用了。
对于第二参数则是我们要显示的内容,如果选择的是第二个构造函数,则可以设置消息的标题,效果如下:
这里的内容也支持使用 HTML 标签:
val content = """
<h4>四级标题<h1>
<p>第一段原色字体</p>
<p style="color: blue;">第二段蓝色字体</p>
""".trimIndent()
val notification = Notification("listener", "Title", content, NotificationType.INFORMATION)
Notifications.Bus.notify(notification, e.project)
对应的效果如下:
最后一个参数则是消息的类型,总共有四种IDE_UPDATE
、INFORMATION
、WARNING
、ERROR
,效果如下图所示:
同样地,如果使用NotificationGroupManager
类,则按照以下方式使用即可,其中 Title 参数也是可选项:
NotificationGroupManager.getInstance()
.getNotificationGroup("listener")
.createNotification("Title", "Hello, world!", NotificationType.INFORMATION)
.notify(e.project)
除了以上设置内容之外,相信你也经常可以看到类似如下包含可点击内容的消息通知:
可以通过 Notification 类的addAction
方法(可添加多个 Action )实现类似的效果:
val notification = Notification("listener", "Title", "Hello, world!", NotificationType.INFORMATION)
notification.addAction(object : NotificationAction("ShowProjectStructureSettings") {
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
// 用于打开设置界面, 这里的 ShowSettings 是 IDEA 定义的常量值
ActionManager.getInstance().getAction("ShowSettings").actionPerformed(e)
}
})
Notifications.Bus.notify(notification, e.project)
最终效果如下:
如果设置了多个 Action 无法在一行显示,则默认会进行折叠,通过点击 More 可以进行查看并操作:
ActionManager.getInstance().getAction(actionName).actionPerformed(e) 可根据 Action 的 id 调用自行创建的或者平台已定义的 Action,例如打开设置界面,这里的 ShowSettings 就是已经定义好的:
也就是说,只要我们知道内部定义的 Action 对应的 id,我们也就可以自由地使用平台已有的功能,同时如果有类似功能需求的时候还可以根据相应的类找到对应源码,然后我们再进行修改即可。哪有没有快速知道一个操作对应 Action 的方法呢?还好,平台内部也给我们准备了内部工具,打开和使用方式参考开启内部工具部分进行查看。
对话框通知的一般效果如下:
官方推荐使用DialogWrapper
抽象类来创建,上面效果对应的代码如下:
class DialogDemo(private val project: Project): DialogWrapper(project) {
init {
// 设置标题并初始化
title = "Title"
init()
}
// 创建布局面板
override fun createCenterPanel(): JComponent {
return panel {
row {
label("Hello, world!")
}
}
}
}
其中 title 用于设置对话框左上角的标题,createCenterPanel()
则用于创建布局内容,这里的布局对象同样支持我们在第二篇文章中的创建方式,也就是说除了基本的文本信息,各种表单输入框也都是支持的。而这里为了方便,就使用了Koltin UI DSL
,也很好理解,可以参考官方文档进行学习,后续会再写一篇文章进行讲解,这里就不再展开说了。
当然除了以上基本的对话框内容,DialogWrapper
还支持自定义按钮事件,并且可以显示帮助按钮以及不再展示的提示:
上述效果对应的代码如下:
class DialogDemo(private val project: Project): DialogWrapper(project) {
private lateinit var checkBox: Cell<JBCheckBox>
init {
// 设置标题并初始化
title = "Title"
init()
}
// 创建 Don't show again 选择框
override fun createDoNotAskCheckbox(): JComponent {
return panel {
row {
checkBox = checkBox("Do not show again")
}
}
}
// 设置帮助按钮 id
override fun getHelpId(): String {
return "ListenerHelp"
}
// 设置帮助按钮的 Tooltip
override fun setHelpTooltip(helpButton: JButton) {
helpButton.toolTipText = "Tip"
}
// 处理帮助按钮事件
override fun doHelpAction() {
showInfo("Help")
}
// 处理 OK 按钮事件
override fun doOKAction() {
super.doOKAction()
showInfo("OK, value: ${checkBox.selected()}")
}
// 处理取消按钮事件
override fun doCancelAction() {
super.doCancelAction()
showInfo("Cancel")
}
// 展示消息
private fun showInfo(msg: String) {
Notifications.Bus.notify(Notification("listener", msg, NotificationType.INFORMATION), project)
}
// 创建布局面板
override fun createCenterPanel(): JComponent {
return panel {
row {
label("Hello, world!")
}
}
}
}
如果还有其它定制化需求,重写DialogWrapper
内的相应方法即可。如果我们只是为了显示简单的文本信息,类似下述效果:
也可以选择平台已经封装好的com.intellij.openapi.ui.Messages
类,使用其中的工具方法即可,上述效果只需要一行代码即可:
Messages.showInfoMessage("Hello, world!", "Title")
在Messages
类中已经封装好了很多基础对话框,这里就不再一一展示。
正如标题,这种信息的展示方式是在编辑器中通过悬浮框展示的,类似下述效果:
使用方式也很简单,只需要一行代码即可:
e.getData(PlatformDataKeys.EDITOR)?.let {
HintManager.getInstance().showInformationHint(it, "Information")
}
同时除了showInformationHint
还支持showErrorHint
展示错误提示信息:
不过还有一个showQuestionHint
使用较为复杂,在 IDEA 中提示我们导包就是通过这种方式:
这种通知方式除了包含提示外还会对指定单词增加下划线,这里就不复现完整的效果了,只展示下基础的使用方式:
e.getData(PlatformDataKeys.EDITOR)?.let {
HintManager.getInstance().showQuestionHint(
it, "Question", it.caretModel.offset, it.caretModel.offset + 6) {
true
}
}
效果如下:
当我们新建一个 Java 项目缺没有配置 JDK,通常会在编辑器内出现类似下图的提示:
这个提示配置 JDK 的提示就是编辑器横幅的效果。
实现这个效果也很简单,只需要继承EditorNotificationProvider
并重写其中的方法即可:
class EditorBanner: EditorNotificationProvider {
override fun collectNotificationData(project: Project, virtualFile: VirtualFile): Function<in FileEditor, out JComponent?> {
return Function {
val banner = EditorNotificationPanel()
banner.text = "EditorBanner"
banner.toolTipText = "ShowSettings"
banner.createActionLabel("ShowSettings") {
ShowSettingsUtil.getInstance().showSettingsDialog(project, "Editor")
}
banner
}
}
}
然后在plugin.xml
中增加如下配置:
<extensions defaultExtensionNs="com.intellij">
<editorNotificationProvider implementation="cn.butterfly.listener.ui.EditorBanner"/>
</extensions>
最终效果如下:
这里展示编辑器横幅一般是先获取本地时候是否缺少指定配置,如果判断已经包含某个配置,则 collectNotificationData 需要返回 null,否则才需要创建 banner 进行展示。
在更新 IDEA 后我们通常能看到类似下图中介绍新功能的提示:
这种效果也很容易实现,只需要使用GotItTooltip
这个类即可,在这里我们结合前文对话框通知中创建的帮助按钮来使用(只展示变更部分):
private lateinit var helpButton: JButton
// 设置帮助按钮的 Tooltip
override fun setHelpTooltip(helpButton: JButton) {
helpButton.toolTipText = "Tip"
this.helpButton = helpButton
}
// 处理帮助按钮事件
override fun doHelpAction() {
showInfo("Help")
GotItTooltip("listener.tip.id", "GotItTooltip")
.show(helpButton, GotItTooltip.BOTTOM_MIDDLE)
}
可以得到如下效果(点击帮助按钮后出现):
GotItTooltip 使用了建造者模式,因此配置项可通过链式调用,可自行选择配置项:
除了以上常见的消息展示方式外,还有一种弹出框选项的方式,当我们使用Ctrl + Shift + Alt + /
快捷键的时候会出现以下弹出框:
这种弹出框则需要使用JBPopupFactory
进行创建,使用方式也很简单:
class PopupAction:AnAction() {
override fun actionPerformed(e: AnActionEvent) {
JBPopupFactory.getInstance()
.createListPopup(MyListPopupStep("Title", arrayOf("Option 1", "Option 2", "Option 3")))
.showInFocusCenter()
}
}
class MyListPopupStep constructor(title: String?, values: Array<String?>) : BaseListPopupStep<String>(title, *values) {
override fun onChosen(selectedValue: String, finalChoice: Boolean): PopupStep<*> {
// 选中事件
return FINAL_CHOICE
}
override fun getIconFor(value: String): Icon? {
// 设置图标
return null
}
override fun isMnemonicsNavigationEnabled(): Boolean = false
}
以上代码可以实现如下的效果:
除了使用createListPopup
,还可以直接选择使用createMessage
直接展示信息,或者使用createConfirmation
展示只有 yes 和 no 的弹出框:
使用createMessage("Hello, world!")
的效果:
使用createConfirmation("Title", {}, 0)
的效果:
在开发插件的过程中,我们有时候需要在项目打开或关闭的时候去完成一些操作,而 IntelliJ 平台也为我们提供了相应的监听器类,当然除了项目监听,平台包含插件还自带了很多其它类型的监听器,这里就不逐一介绍了,以项目监听为例,然后再说明如何自定义一个事件监听器。
对项目打开和关闭的监听方式很简单,只需要继承ProjectManagerListener
类即可,然后既可以在相应方法中处理对应操作:
class MyProjectManagerListener: ProjectManagerListener {
init {
// 项目启动处理
val notification = Notification("listener", "Title", "Open", NotificationType.INFORMATION)
Notifications.Bus.notify(notification, null)
}
override fun projectClosing(project: Project) {
// 项目即将关闭
println("Closing")
}
}
同时在plugin.xml
中增加如下配置:
<projectListeners>
<listener class="cn.butterfly.listener.listener.MyProjectManagerListener"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
</projectListeners>
在新版本中平台建议我们继承ProjectActivity
类并通过postStartupActivity
扩展点来实现,这里也展示一下使用方法:
class MyProjectActivity: ProjectActivity {
override suspend fun execute(project: Project) {
val notification = Notification("listener", "Title", "Open", NotificationType.INFORMATION)
Notifications.Bus.notify(notification, null)
}
}
然后也需要在plugin.xml
中增加配置:
<extensions defaultExtensionNs="com.intellij">
<postStartupActivity implementation="cn.butterfly.listener.listener.MyProjectActivity"/>
</extensions>
在 IntelliJ 平台中的事件可以看作是发布订阅的模式,我们需要先定义一个主题,然后发布者发布相应的事件,订阅者实现对应的监听器事件即可。继续使用前文中的帮助按钮的例子,如果点击了帮助按钮,我们就在界面中显示帮助按钮被点击,下面介绍如何实现这个功能。
首先自定义一个监听器:
interface MyListener {
companion object {
var MY_TOPIC: Topic<MyListener> = Topic.create("listener", MyListener::class.java)
}
fun afterHelpBtnClicked(msg: String)
}
然后定义实现类:
class MyListenerA: MyListener {
override fun afterHelpBtnClicked(msg: String) {
Messages.showInfoMessage(msg, "Title")
}
}
之后在plugin.xml
中注册:
<projectListeners>
<listener class="cn.butterfly.listener.listener.MyListenerA"
topic="cn.butterfly.listener.listener.MyListener"/>
</projectListeners>
最后在点击了帮助按钮后发布事件用于测试:
// 处理帮助按钮事件
override fun doHelpAction() {
showInfo("Help")
// 发布事件
project.messageBus.syncPublisher(MyListener.MY_TOPIC).afterHelpBtnClicked("点击了帮助按钮")
GotItTooltip("listener.tip.id", "GotItTooltip")
.show(helpButton, GotItTooltip.BOTTOM_MIDDLE)
}
效果如下:
除了上文提到的项目监听器,平台还提供了很多其它监听器(还可以使用其它插件内定义的监听器,比如 Git 插件的一些事件),参考上述自定义监听器的实现和使用方式即可,完整列表可参考官方文档。
选择Help | Edit Custom Properties...
,然后在文件内加上idea.is.internal=true
的配置,最后保存文件并重启 IDEA 即可。
重启后我们就可以使用了,例如上文提到的查看某个操作对应 Action 的 id,参考下述动图步骤即可,可以看到在打开设置界面后里面出现了我们前文使用的ShowSettings
这个 id:
内部工具的使用先到此为止,由于其中实用功能挺多的,就在后续专门用一篇文章进行讲解。
本文讲解了关于消息通知和事件监听相关的内容,在下一篇文章将会实战开发一个类似 VSCode 中 Timer Master 的插件(如下图所示)来对这一部分的内容做一个小结。