当前位置: 首页 > news >正文

IntelliJ IDE 插件开发 | (十二)自定义项目脚手架(上)

系列文章

本系列文章已收录到专栏,交流群号:689220994,也可点击链接加入。

前言

在开发创建一个新项目的时候,我们一般都会使用平台自带的脚手架,如下图所示:

image-20240914163053383

或者是使用网页版:

image-20240914163144526

尽管平台已经提供了灵活的配置项,甚至是可以修改原有的模板内容,例如创建插件的plugin.xml的模板:

image-20240914163318214

但是我们并没有办法增删脚手架所创建的内容,也没有办法在模板中使用未提供的参数。因此,本文将介绍如何创建自己的脚手架,不过本文所介绍的方法只适用于 IDEA,对于其它平台(WebStorm、PyCharm)将在后续文章进行介绍,本文所涉及到的完整代码也已上传到GitHub。

最终效果

动画

实现思路

  1. 继承ModuleBuilder实现自己的模块创建方式。
  2. 自定义模块类型、设置面板和模板文件。
  3. 读取自定义内容创建项目目录和文件。

注册并实现自己的模块构建类

首先创建TemplateBuilder类继承ModuleBuilder类(这里先展示全部内容,后续会针对其中的方法按点进行介绍):

class TemplateBuilder: ModuleBuilder() {// 1. 持久化配置private val state = TemplateState.getInstance()// 2. 模块类型override fun getModuleType() = TemplateModuleType()// 3. 项目创建配置override fun setupRootModel(rootModel: ModifiableRootModel) {// 3.1 设置项目路径val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(state.location)rootModel.addContentEntry(virtualFile!!)// 3.2 读取模板并设置变量val template = FileTemplateManager.getInstance(rootModel.project).getJ2eeTemplate(TemplateFileFactory.PLUGIN_XML)val properties = Properties()properties.setProperty("name", state.name)properties.setProperty("group", state.group)properties.setProperty("artifact", state.artifact)properties.setProperty("location", state.location)properties.setProperty("description", state.description)properties.setProperty("author", state.author)properties.setProperty("email", state.email)properties.setProperty("blogUrl", state.blogUrl)val renderedText = template.getText(properties)// 3.3 创建 resources 文件夹并写入 XML 文件val resFile = File("${state.location}${File.separator}src${File.separator}resources")resFile.mkdirs()val file = File(resFile.absolutePath + File.separator + TemplateFileFactory.PLUGIN_XML)FileUtil.writeToFile(file, renderedText)// 3.4 设置 source 文件夹rootModel.contentEntries.forEach {val src = LocalFileSystem.getInstance().refreshAndFindFileByPath("${state.location}${File.separator}src")!!it.addSourceFolder(src, false)}}// 4. 设置项目名称override fun modifyProjectTypeStep(settingsStep: SettingsStep): ModuleWizardStep? {settingsStep.context.projectName = state.namereturn super.modifyProjectTypeStep(settingsStep)}// 5. 创建项目override fun createProject(name: String?, path: String?) = super.createProject(state.name, state.location)// 6. 设置第二个步骤override fun createWizardSteps(wizardContext: WizardContext, modulesProvider: ModulesProvider) =arrayOf(TemplateSecondStep())// 7. 设置第一个步骤override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?) = TemplateFirstStep()// 8. 忽略自带的步骤override fun getIgnoredSteps(): MutableList<Class<out ModuleWizardStep>> = mutableListOf(ProjectSettingsStep::class.java)}

然后在plugin.xml中进行配置:

<extensions defaultExtensionNs="com.intellij"><moduleBuilder builderClass="cn.butterfly.template.module.TemplateBuilder"/>
</extensions>

自定义模块类型

TemplateBuilder中的第二点的getModuleType()返回了TemplateModuleType(),其中TemplateModuleType就是我们自定义的模块类型,可以用于设置模块的图标,名称以及描述:

class TemplateModuleType(id: @NonNls String = "TEMPLATE_MODULE") : ModuleType<TemplateBuilder>(id) {override fun createModuleBuilder() = TemplateBuilder()// 设置模块名称override fun getName() = "Template"// 设置描述override fun getDescription() = "TEMPLATE_MODULE"// 设置模块图标override fun getNodeIcon(isOpened: Boolean) = PluginIcons.TEMPLATE_ICON}

对应效果如下:

image-20240914180629017

自定义设置面板

TemplateBuilder中的第六和七点对应我们自己的设置面板,其中第七点对应下图中的效果:

image-20240914180917679

对应的代码如下(这里只展示第七点即第一个设置面板内容,第二个界面的代码类似,在GitHub上进行查看即可),需要继承并实现ModuleWizardStep作为脚手架中的一个设置步骤:

class TemplateFirstStep: ModuleWizardStep() {private val model = Model()// 持久化配置private val state: TemplateState = TemplateState.getInstance()// UI 界面private var panel = panel {indent {row("Name: ") {textField().bindText(model::name)}.topGap(TopGap.MEDIUM)row("Location: ") {textFieldWithBrowseButton("",ProjectManager.getInstance().defaultProject,FileChooserDescriptorFactory.createSingleFolderDescriptor()).bindText(model::location)}.topGap(TopGap.MEDIUM)row("Group: ") {textField().bindText(model::group)}.topGap(TopGap.MEDIUM)row("Artifact: ") {textField().bindText(model::artifact)}.topGap(TopGap.MEDIUM)row("Description: ") {textField().bindText(model::description)}.topGap(TopGap.MEDIUM) }}override fun getComponent() = panel// 更新数据override fun updateDataModel() {panel.apply()state.name = model.namestate.group = model.groupstate.artifact = model.artifactstate.location = model.locationstate.description = model.description}// 数据存储类data class Model(var name: String = "",var group: String = "",var artifact: String = "",var location: String = "",var description: String = "")}

这里使用了Kotlin UI DSL来绘制 UI,代码比较简单,不再介绍。其中持久化配置(TemplateBuilder中的第一点也使用了该持久化配置)对应的代码如下:

@Service
@State(name = "TemplateState", storages = [Storage("template-state.xml")])
class TemplateState: PersistentStateComponent<TemplateState> {var name = ""var group = ""var artifact = ""var location = ""var description = ""var email = "1945912314@qq.com"var author = "butterfly"var blogUrl = "https://juejin.cn/user/3350967174567352"override fun getState() = thisoverride fun loadState(state: TemplateState) {XmlSerializerUtil.copyBean(state, this)}companion object {fun getInstance(): TemplateState = ApplicationManager.getApplication().getService(TemplateState::class.java)}}

需要在plugin.xml中进行配置:

<extensions defaultExtensionNs="com.intellij"><applicationService serviceImplementation="cn.butterfly.template.state.TemplateState"/>
</extensions>

自定义模板文件

为了方便读取和后期的可定制性,本文也参考其它脚手架项目的思路将模板文件添加到了 IDEA 自带的模板管理中(这里只介绍如何添加到模板的Other模块,详细内容可参考官网),首先在 resources 文件夹下创建fileTemplates/j2ee文件层级,然后在下面创建一个template-plugin.xml.ft(在原有文件名基础上以.ft结尾)文件用于保存模板内容,之后再创建一个TemplateFileFactory类实现FileTemplateGroupDescriptorFactory接口,用于将模板文件注册到 IDEA 中:

class TemplateFileFactory: FileTemplateGroupDescriptorFactory {companion object {// 上述创建模板文件去掉.ft 后缀const val PLUGIN_XML = "template-plugin.xml"}override fun getFileTemplatesDescriptor(): FileTemplateGroupDescriptor {// 设置模板分组val templateGroup = FileTemplateGroupDescriptor("Template", PluginIcons.TEMPLATE_ICON)// 添加模板文件templateGroup.addTemplate(FileTemplateDescriptor(PLUGIN_XML, PluginIcons.TEMPLATE_ICON))return templateGroup}}

然后在plugin.xml中进行配置:

<extensions defaultExtensionNs="com.intellij"><fileTemplateGroup implementation="cn.butterfly.template.template.TemplateFileFactory"/>
</extensions>

然后就可以在 IDEA 中进行管理了:

image-20240914184421883

创建项目结构和文件

TemplateBuilder中的第三点即用于读取上述中的模板和配置然后用于生成项目文件,其中 3.1 用于读取第一步骤中的 location 值然后作为项目路径,3.2 即是读取上文中模板然后再根据读取到的配置完善文件内容,3.3 用于创建目录层级并将生成的文件写入到 resources 文件夹中,3.4 步骤则是手动将文件夹标记为源代码文件夹

其它细节

TemplateBuilder中的第四和五点用于手动设置项目的名称和项目路径。

TemplateBuilder中的第八点忽略步骤用于忽略掉平台自带的创建项目步骤,如果不进行忽略,就会在原有的步骤基础上多一个步骤:

image-20240914182851963

总结

本文简单介绍了如何在 IDEA 中创建属于自己的脚手架,关于自动识别模块(可在创建完项目后手动导入解决)和其它平台的脚手架创建方式将在后续文章中进行介绍,大家也可以加入开头提到的交流群一起交流讨论。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • RabbitMQ 基础入门
  • 3DGS:3D Gaussian Splatting for Real-Time Radiance Field Rendering 论文解读
  • Leetcode 移动零
  • OA项目值用户登入首页展示
  • docker镜像源更换
  • 华为云分布式缓存服务DCS 8月新特性发布
  • uniapp业务实现
  • 快速完成论文初稿写作的ChatGPT提示词分享
  • 前端三件套
  • Qt-QPushButton按钮类控件(22)
  • 联合仿真(FMI,FMU)资料收集
  • 【三】TDengine 3.3.2 生产级别集群搭建
  • 非线性规划及其MATLAB实现
  • 2024世界技能大赛某省选拔赛“网络安全项目”B模块--数字取证解析②(超详细~)
  • Linux:命令行参数
  • 4个实用的微服务测试策略
  • Angular 2 DI - IoC DI - 1
  • javascript 哈希表
  • PHP 小技巧
  • yii2中session跨域名的问题
  • 安卓应用性能调试和优化经验分享
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 京东美团研发面经
  • 你不可错过的前端面试题(一)
  • 设计模式 开闭原则
  • 使用SAX解析XML
  • 数组的操作
  • 微信开源mars源码分析1—上层samples分析
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (3)llvm ir转换过程
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (pojstep1.3.1)1017(构造法模拟)
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (原創) 物件導向與老子思想 (OO)
  • .NET C# 操作Neo4j图数据库
  • .net framework 4.0中如何 输出 form 的name属性。
  • .net 使用ajax控件后如何调用前端脚本
  • .NET多线程执行函数
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .Net中间语言BeforeFieldInit
  • :“Failed to access IIS metabase”解决方法
  • @Autowired和@Resource的区别
  • @NotNull、@NotEmpty 和 @NotBlank 区别
  • [].slice.call()将类数组转化为真正的数组
  • [023-2].第2节:SpringBoot中接收参数相关注解
  • [100天算法】-每个元音包含偶数次的最长子字符串(day 53)
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——