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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/423958.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

先楫HPM6750 Windows下VSCode开发环境配置

用的是EVKmini&#xff0c;ft2232作为调试器jtag接口调试 启动start_gui.exe 以hello_world为例&#xff0c;更改一下build path&#xff0c;可以generate并使用gcc compile 最后会得到这些 点击start_gui里面的命令行&#xff0c;用命令行启动vscode 新建.vscode文件夹&…

如何让Windows控制台窗口不接受鼠标点击(禁用鼠标输入)

一、简述 在我们编写控制台应用程序时&#xff0c;默认情况下程序的打印输出会在控制台窗口中进行显示&#xff0c;我们在写服务功能时在窗口中会不断打印消息输出&#xff0c;这个时候如果使用鼠标点击了控制台窗口&#xff0c;会阻塞程序的继续运行&#xff0c;导致我们的程…

vue使用TreeSelect设置带所有父级节点的回显

Element Plus的el-tree-select组件 思路&#xff1a; 选中节点时&#xff0c;给选中的节点赋值 pathLabel&#xff0c;pathLabel 为函数生成的节点名字拼接&#xff0c;数据源中不包含。 在el-tree-select组件中设置 props“{ label: ‘pathLabel’ }” 控制选中时input框中回…

树莓派智能语音助手实现音乐播放

树莓派语音助手从诞生的第一天开始&#xff0c;我就想着让它能像小爱音箱一样&#xff0c;可以语音控制播放音乐。经过这些日子的倒腾&#xff0c;今天终于实现了。 接下里&#xff0c;和大家分享下我的实现方法&#xff1a;首先音乐播放模块用的是我在上一篇博文写的《用sound…

基于spring的博客系统(二)

4. 业务代码 4.1 持久层 根据需求, 先⼤致计算有哪些DB相关操作, 完成持久层初步代码, 后续再根据业务需求进⾏完善 1. ⽤⼾登录⻚ a. 根据⽤⼾名查询⽤⼾信息 2. 博客列表⻚ a. 根据id查询user信息 b. 获取所有博客列表 3. 博客详情⻚ a. 根据博客ID查询博客信息 b. 根据博客I…

现代 Web 开发工具箱:Element-UI 表单组件全攻略(二)

现代 Web 开发工具箱&#xff1a;Element-UI 表单组件全攻略&#xff08;二&#xff09; 一 . Switch 开关控件1.1 Switch 组件的创建① 注册路由② 创建 Switch 组件 1.2 Switch 组件的属性① 开关的宽度② 开关 打开/关闭 的文字提示③ 开关打开或者关闭时候的值④ 开关打开或…

Qt控制开发板的LED

Qt控制开发板的LED 使用开发板的IO接口进行控制是嵌入式中非常重要的一点&#xff0c;就像冯诺依曼原理说的一样&#xff0c;一个计算机最起码要有输入输出吧&#xff0c;我们有了信息的接收和处理&#xff0c;那我们就要有输出。 我们在开发板上一般都是使用开发板的GPIO接口…

测试通用面试题大全

24年软件测试的发展如何&#xff1f; 1、IT行业还会继续升温&#xff0c;高质量人才需求相对还是短缺。 2、要求变高之后&#xff0c;很难再下降了&#xff0c;学历和经验。 3、功能测试之外的东西&#xff0c;接口、性能和自动化要掌握一点。 4、长远来看&#xff0c;软件…

数据集 wider_face 人脸数据集 人脸检测 >> DataBall

数据集 wider 人脸检测数据集 WIDER FACE: A Face Detection Benchmark inproceedings{yang2016wider, Author {Yang, Shuo and Luo, Ping and Loy, Chen Change and Tang, Xiaoou}, Booktitle {IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, Title…

Radware Alteon 负载均衡-基于URL Filetype的七层负载均衡

作者&#xff1a;Xiaolei Ren Radware Alteon作为一款高性能的负载均衡器&#xff0c;其基于URL Filetype的七层负载均衡功能为众多企业提供了灵活、高效的解决方案。 该案例实现如下需求&#xff1a;当客户端访问服务器时&#xff0c;默认访问10.200.1.100&#xff0c;在ht…

【Ubuntu】Ubuntu双网卡配置 实现内外网互不影响同时可用

【Ubuntu】Ubuntu双网卡配置 实现内外网互不影响同时可用 建议前提配置用到的命令参考文献&#xff1a; 建议 本文仅作个人记录&#xff0c;请勿完全照搬&#xff0c;建议直接看此视频&#xff0c;按作者的步骤进行配置 linux配置内外网&#xff08;ubuntu举例&#xff09;&am…

决策树算法上篇

决策树概述 决策树是属于有监督机器学习的一种&#xff0c;起源非常早&#xff0c;符合直觉并且非常直观&#xff0c;模仿人类做决策的过程&#xff0c;早期人工智能模型中有很多应用&#xff0c;现在更多的是使用基于决策树的一些集成学习的算法。 示例一&#xff1a; 上表根据…

Sparse4D v1

Sparse4D: Multi-view 3D Object Detection with Sparse Spatial-Temporal Fusion Abstract 基于鸟瞰图 (BEV) 的方法最近在多视图 3D 检测任务方面取得了重大进展。与基于 BEV 的方法相比&#xff0c;基于稀疏的方法在性能上落后&#xff0c;但仍然有很多不可忽略的优点。为了…

四数之和--力扣18

四数之和 题目思路代码 题目 思路 类似于三数之和&#xff0c;先排序&#xff0c;利用双指针解题。 如果排序后的第一个元素大于目标值&#xff0c;直接返回&#xff0c;为什么nums[i]需要大于等于0&#xff0c;因为目标值可能为负数。比如&#xff1a;数组是[-4, -3, -2, -1…

大数据安全需求分析与安全保护工程

大数据安全威胁与需求分析 特征&#xff1a;海量是数据规模、快速的数据流转、多样的数据类型和价值密度低 种类和来源&#xff1a;结构化、半结构化和非结构化数据 数据种类&#xff1a; 结构化数据&#xff1a;关系模型数据&#xff0c;以关系数据库表形式管理的数据 非…

Docker:对已有的容器,对当前容器映射的端口实时 (增删改查)

首先我的docker已经起了一个容器&#xff0c;我突然想把他的80->80映射的端口改成80->8080 但是我不想去新启动容器&#xff0c;想在现有容器基础上去修改&#xff0c;或者我想删除某个端口映射&#xff08;只是大概思路&#xff09; 如何寻找容器配置文件位置 首先我这…

Linux系统使用Docker安装DockerUI并实现远程管理本地容器无需公网IP

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

立足本土,面向全球 | 全视通闪耀亮相Medical Fair Asia新加坡医疗展

Medical Fair Asia是亚洲地区最大的医疗设备、医疗器械和医疗技术展览会之一&#xff0c;自1997年创办以来&#xff0c;每两年在新加坡举办一次。该展会不仅是新加坡医疗行业交流的龙头平台&#xff0c;也是亚洲乃至全球医疗企业和专业人士共聚一堂、展示最新产品和技术的重要舞…

红黑树的删除

文章目录 前言一.删除的节点左子树右子树都有二.删除的节点只有左/右子树删除调整操作 三.删除的节点没有孩子1.删除的节点为红色2.删除的节点为黑色1).兄弟节点为黑色(1).兄弟节点至少有一个红色的孩子节点LL型RR型RL型LR型 (2).兄弟节点没有孩子或所有孩子为黑色 2).兄弟节点…

vue3使用leaflet+trackplayer实现非地图动画轨迹(市场平面图动态轨迹)

vue3使用leaflettrackplayer实现非地图动画轨迹(市场平面图动态轨迹) 先下载 leaflet 和 leaflet-trackplayer两个主要库 leaflet官方文档 npm install leaflet npm install leaflet-trackplayer然后在页面中引用 html <template><button click"playMap&quo…