编写一个IDEA插件之:事件监听
事件监听,我们最熟悉不过的就是开发APP
时,监听按钮点击事件、手指触摸及移动事件、网络状态事件等等。事件监听大多通过观察者模式实现,首先API
调用者不需要知道后台是如何检测出网络状态不可用的,而只需要向系统注册一个监听器,当网络状态发生改变时,由系统回调给监听器。
本篇内容:
- 项目或模块事件监听:在模块或者整个项目发生改变时,通过事件监听做出反应,如项目新增了一个模块或是删除了某个模块;
- 文件编辑事件监听:在
Java
代码文件编辑时,通过事件监听能够知道哪个类的代码改变了,此时后台就可以刷新一些数据的缓存;
如何监听项目或模块改变事件
首先是项目级别的事件监听。添加一个项目管理事件监听器,我们需要实现ProjectManagerListener
接口,该接口有四个方法,其源码如下。
public interface ProjectManagerListener extends EventListener {
default void projectOpened(@NotNull Project project) {
}
default void projectClosed(@NotNull Project project) {
}
default void projectClosing(@NotNull Project project) {
}
default void projectClosingBeforeSave(@NotNull Project project) {
}
}
- projectOpened:该方法在项目打开时被回调;
- projectClosingBeforeSave:在关闭项目时,开始保存项目之前被回调,或者说是在调用
FileDocumentManager#saveAllDocuments
方法保存所有文件之前被调用; - projectClosing:在
projectClosingBeforeSave
方法之后被回调; - projectClosed:与projectClosing的区别在于,
projectClosed
在项目已经关闭时被回调,在ProjectManagerImpl#closeProject
方法执行到最后一行代码时被调用。
有了项目管理事件监听器之后,我们如何注册该监听器呢?
有两种方法,一种是代码方式注册,一种是在plugin.xml插件配置文件中注册。
代码方式注册可调用ProjectManager.getInstance().addProjectManagerListener();
方法注册,但这种方式注册有一个弊端,就是无法监听到项目打开事件,projectOpened
方法不会被调用,应该在我们能够调用该方法注册监听器时,项目实际已经打开了。
所以注册项目管理监听器我们只能通过修改plugin.xml
配置文件方式注册,配置代码如下:
<applicationListeners>
<listener class="com.msyc.ycpay.plugin.listener.MyProjectManagerListener"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
</applicationListeners>
- topic:填写事件主题,类似于消息中间件中的Topic,只不过这里填写的是事件监听器的接口类名;
- class:添加接口的实现类名;
当我们给IDEA
注册自定义的项目管理事件监听器后,我们就可以通过项目管理事件监听器注册其它的事件监听器,例如注册模块监听事件,这是因为模块的事件触发在项目打开事件触发之后才会触发。因此,在projectOpened
方法中可注册任何其它的事件监听器。
注册模块事件监听器代码如下:
project.getMessageBus().connect()
.subscribe(ProjectTopics.MODULES, new ModuleListener(){});
subscribe
方法需要两个参数:
-
topic
:主题,可选值参见ProjectTopics
类的源码,有PROJECT_ROOTS
和MODULES
; -
handler
:事件处理器、监听器,当topic
为MODULES
时,要求传递一个ModuleListener
;
ModuleListener
接口的定义如下:
public interface ModuleListener extends EventListener {
default void moduleAdded(@NotNull Project project, @NotNull Module module) {
}
default void beforeModuleRemoved(@NotNull Project project, @NotNull Module module) {
}
default void moduleRemoved(@NotNull Project project, @NotNull Module module) {
}
default void modulesRenamed(@NotNull Project project, @NotNull List<Module> modules, @NotNull Function<Module, String> oldNameProvider) {
}
}
- moduleAdded:添加模块完成时被调用;
- beforeModuleRemoved:模块被移除之前被调用;
- moduleRemoved:模块被移除时被调用;
- modulesRenamed:模块修改名字时被调用;
如何监听文件编辑事件
通过前面两篇的学习,我们已经了解什么是PSI
,知道一个文件对应一个PsiFile
,一个PsiFile
本身也是一个PsiElement
,由许多的PsiElement
构成,每个PsiElement
也都可以有子PsiElement
。
因此,监听文件改变事件其实就是监听PSI
树的结构改变事件,我们需要通过PsiManager
注册PsiTreeChangeListener
,代码如下。
PsiManager.getInstance(project).addPsiTreeChangeListener(
new PsiTreeChangeListener() {
// .....
}, FILES::clear);
至于注册时机,视情况而定,可以在Service
初始化时注册,可以在AnAction
触发时注册,也可以在projectOpened
事件方法中注册。
PsiTreeChangeListener
接口定义的方法较多,可以分为两类事件,一类是before
事件、一类是after
事件,接口源码如下。
public interface PsiTreeChangeListener extends EventListener {
void beforeChildAddition(@NotNull PsiTreeChangeEvent event);
void beforeChildRemoval(@NotNull PsiTreeChangeEvent event);
void beforeChildReplacement(@NotNull PsiTreeChangeEvent event);
void beforeChildMovement(@NotNull PsiTreeChangeEvent event);
void beforeChildrenChange(@NotNull PsiTreeChangeEvent event);
void beforePropertyChange(@NotNull PsiTreeChangeEvent event);
void childAdded(@NotNull PsiTreeChangeEvent event);
void childRemoved(@NotNull PsiTreeChangeEvent event);
void childReplaced(@NotNull PsiTreeChangeEvent event);
void childrenChanged(@NotNull PsiTreeChangeEvent event);
void childMoved(@NotNull PsiTreeChangeEvent event);
void propertyChanged(@NotNull PsiTreeChangeEvent event);
}
- childrenChanged:子元素内容改变时被调用;
- childReplaced:子元素被替换时被调用,触发
childReplaced
事件也会伴随着childrenChanged
事件; - childAdded:子元素添加时被调用,触发
childAdded
事件时也会伴随着childReplaced
、childrenChanged
或事件; - childRemoved:子元素移除时被调用,触发
childRemoved
事件也会伴随着childReplaced
、childrenChanged
事件; - propertyChanged:属性改变时被调用,例如修改文件名;
最后
“编写一个IDEA
插件”系列暂时就写这些,因为对这方面感兴趣的读者可能比对汇编语言感兴趣的读者还少。其实这几篇分析的也是笔者写插件过程中用到的一些笔者认为非常重要的知识点,当然还有很多没分享,如果要继续写,估计还可以写几篇,但看到上篇的阅读量就没动力继续写下去了。
参考:
-
intellij-platform-plugin-template
的项目管理监听器注册:https://sourcegraph.com/github.com/JetBrains/intellij-platform-plugin-template@main/-/blob/src/main/resources/META-INF/plugin.xml#L17:55 - 接收有关项目结构变更的通知:https://jetbrains.org/intellij/sdk/docs/reference_guide/project_model/project.html?search=projectClosingBeforeSave
[Java艺术] 微信号:javaskill
一个只推送原创文章的技术公众号,分享Java后端相关技术。
- 2018年12大顶级云安全威胁
- 缤果盒子为域名意识打call 六位数秒下bingobox.com
- 用Qt写软件系列二:QCookieViewer(浏览器Cookie查看器)
- 用Qt写软件系列一:QCacheViewer(浏览器缓存查看器)
- 用Qt写软件系列三:一个简单的系统工具(上)
- ChartDirector应用笔记(三)
- 汇编语言 手记9
- 程序员一年写百万行代码是什么体验?这肯定是个Bug
- ChartDirector应用笔记(二)
- ChartDirector应用笔记(一)
- DB Cache
- oracle多用户并发及事务处理
- 数据中心整合会导致更高的云价格吗?
- Linux下oracle开机自启动服务
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- MATLAB批量读取一个文件夹下的图片
- python next()迭代器完成会引发StopIteration异常
- 没想到,Git居然有3种“后悔药”!
- JetCache埋点的骚操作,不服不行啊
- StringBuider 在什么条件下、如何使用效率更高?
- SpringCache与redis集成,优雅的缓存解决方案
- 理解一下5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO
- WinXP的MS08-067漏洞利用复现和解决方案
- 实用FRIDA进阶:内存漫游、hook anywhere、抓包
- 聊聊claudb的server command
- BFE.dev前端刷题88 - 在JavaScript中实现负索引
- Springboot 原理
- Qt音视频开发17-海康sdk解码
- pytest文档47-allure报告添加用例失败截图
- [060]监听应用的前后台切换