├── .gitignore ├── resources ├── fileTemplates │ ├── Template build.gradle.ft │ ├── Template Application.java.ft │ ├── Template AucApiClass.java.ft │ ├── Template Manifest.xml.ft │ ├── Template AucApiImplClass.java.ft │ └── Template MainActivity.java.ft └── META-INF │ ├── pluginIcon.svg │ └── plugin.xml ├── screenshot ├── main.png ├── preview.png └── settings.png ├── test ├── AucTemplateTest.kt └── FileTreeDslTest.kt ├── src └── com │ └── dengzii │ └── plugin │ └── template │ ├── ui │ ├── PreviewPanel.form │ ├── EditToolbar.kt │ ├── CreateFileDialog.form │ ├── EditableTable.kt │ ├── CreateFileDialog.java │ ├── CreateModuleDialog.java │ ├── ConfigurePanel.form │ ├── PreviewPanel.java │ ├── CreateModuleDialog.form │ └── ConfigurePanel.java │ ├── utils │ ├── PopMenuUtils.kt │ ├── Logger.kt │ └── PluginKit.kt │ ├── CreateModuleAction.kt │ ├── template │ ├── FileTemplateFactory.kt │ ├── Template.kt │ └── AucTemplate.kt │ ├── TemplateConfigurable.kt │ ├── model │ ├── Module.kt │ ├── FileTreeDsl.kt │ └── FileTreeNode.kt │ ├── Config.kt │ └── FileWriteCommand.kt ├── README-ZH.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.out 3 | /*.iml -------------------------------------------------------------------------------- /resources/fileTemplates/Template build.gradle.ft: -------------------------------------------------------------------------------- 1 | dependencies { 2 | 3 | } -------------------------------------------------------------------------------- /screenshot/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/GenerateModuleFromTemplate/master/screenshot/main.png -------------------------------------------------------------------------------- /screenshot/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/GenerateModuleFromTemplate/master/screenshot/preview.png -------------------------------------------------------------------------------- /screenshot/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayshier/GenerateModuleFromTemplate/master/screenshot/settings.png -------------------------------------------------------------------------------- /resources/fileTemplates/Template Application.java.ft: -------------------------------------------------------------------------------- 1 | package ${PACKAGE_NAME}; 2 | 3 | #parse("File Header.java") 4 | public class ${APPLICATION_NAME}App extends BaseApplication{ 5 | 6 | public void onCreate(){ 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /resources/fileTemplates/Template AucApiClass.java.ft: -------------------------------------------------------------------------------- 1 | package ${PACKAGE_NAME}; 2 | 3 | import com.blankj.utilcode.util.ApiUtils; 4 | 5 | #parse("File Header.java") 6 | public abstract class ${FEATURE_NAME}Api extends ApiUtils.BaseApi { 7 | 8 | } -------------------------------------------------------------------------------- /resources/fileTemplates/Template Manifest.xml.ft: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/fileTemplates/Template AucApiImplClass.java.ft: -------------------------------------------------------------------------------- 1 | package ${PACKAGE_NAME}; 2 | 3 | import ${PACKAGE_NAME}.export.${FEATURE_NAME}Api; 4 | import com.blankj.utilcode.util.ApiUtils; 5 | 6 | #parse("File Header.java") 7 | @ApiUtils.Api 8 | public class Feature1ApiImpl extends ${FEATURE_NAME}Api { 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /resources/fileTemplates/Template MainActivity.java.ft: -------------------------------------------------------------------------------- 1 | package ${PACKAGE_NAME}; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.annotation.Nullable; 5 | import android.os.Bundle; 6 | 7 | #parse("File Header.java") 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(@Nullable Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/AucTemplateTest.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import com.dengzii.plugin.template.template.AucTemplate 4 | import org.junit.Test 5 | 6 | class AucTemplateTest { 7 | 8 | @Test 9 | fun aucAppModuleTest() { 10 | val app = AucTemplate.APP 11 | app { 12 | placeholder("PACKAGE_NAME", "com.dengzii.plugin") 13 | } 14 | println(app.placeholders) 15 | println(app.getAllPlaceholderInTree()) 16 | 17 | app.build() 18 | println(app) 19 | println(app.getTreeGraph()) 20 | } 21 | 22 | @Test 23 | fun aucModuleTemplateTest() { 24 | val module = AucTemplate.MODULE { 25 | placeholder("FEATURE_NAME","plugin") 26 | placeholder("PACKAGE_NAME","com.dengzi") 27 | } 28 | module.build() 29 | println(module.getTreeGraph()) 30 | } 31 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/PreviewPanel.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/utils/PopMenuUtils.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.utils 2 | 3 | import com.intellij.openapi.ui.JBMenuItem 4 | import com.intellij.openapi.ui.JBPopupMenu 5 | 6 | /** 7 | *
 8 |  * author : dengzi
 9 |  * e-mail : denua@foxmail.com
10 |  * github : https://github.com/dengzii
11 |  * time   : 2020/1/16
12 |  * desc   :
13 |  * 
14 | */ 15 | object PopMenuUtils { 16 | 17 | fun create(menu: Map): JBPopupMenu { 18 | 19 | val popMenu = JBPopupMenu() 20 | menu.forEach { (k, v) -> 21 | if (k.isBlank()) { 22 | popMenu.addSeparator() 23 | return@forEach 24 | } 25 | val item = JBMenuItem(k) 26 | item.addActionListener { 27 | v?.onClick() 28 | } 29 | popMenu.add(item) 30 | } 31 | return popMenu 32 | } 33 | 34 | interface PopMenuListener { 35 | fun onClick() 36 | } 37 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/CreateModuleAction.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template 2 | 3 | import com.dengzii.plugin.template.ui.CreateModuleDialog 4 | import com.dengzii.plugin.template.utils.Logger 5 | import com.dengzii.plugin.template.utils.PluginKit 6 | import com.intellij.openapi.actionSystem.AnAction 7 | import com.intellij.openapi.actionSystem.AnActionEvent 8 | import com.intellij.openapi.project.Project 9 | 10 | /** 11 | *
12 |  * author : dengzi
13 |  * e-mail : denua@foxmail.com
14 |  * github : https://github.com/dengzii
15 |  * time   : 2019/12/31
16 |  * desc   :
17 | 
* 18 | */ 19 | class CreateModuleAction : AnAction() { 20 | 21 | companion object { 22 | var project: Project? = null 23 | } 24 | 25 | override fun actionPerformed(e: AnActionEvent) { 26 | val kit = PluginKit.get(e) 27 | if (!kit.isProjectValid()) { 28 | Logger.d(CreateModuleAction::class.java.simpleName, "Project is not valid.") 29 | return 30 | } 31 | project = kit.project 32 | CreateModuleDialog.createAndShow(kit.project) { 33 | FileWriteCommand.startAction(kit, it) 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/utils/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.utils 2 | 3 | import com.jetbrains.rd.util.printlnError 4 | import java.text.SimpleDateFormat 5 | 6 | /** 7 | *
 8 |  * author : dengzi
 9 |  * e-mail : denua@foxmail.com
10 |  * github : https://github.com/MrDenua
11 |  * time   : 2019/12/31
12 |  * desc   :
13 |  * 
14 | */ 15 | object Logger { 16 | 17 | var enable: Boolean = true 18 | var timeFormatter = SimpleDateFormat("MM-dd hh:mm:ss") 19 | 20 | fun e(tag: String, log: String) { 21 | log("e", tag, log) 22 | } 23 | 24 | fun e(tag: String, e: Throwable) { 25 | log("e", tag, e.toString()) 26 | e.printStackTrace() 27 | } 28 | 29 | fun i(tag: String, log: String) { 30 | log("i", tag, log) 31 | } 32 | 33 | fun d(tag: String, log: String) { 34 | log("d", tag, log) 35 | } 36 | 37 | fun w(tag: String, log: String) { 38 | log("w", tag, log) 39 | } 40 | 41 | private fun log(level: String, tag: String, log: String) { 42 | if (!enable) { 43 | return 44 | } 45 | 46 | val logStr = "${getTime()} ${level.toUpperCase()}/$tag: $log" 47 | if (level == "e") { 48 | printlnError(logStr) 49 | return 50 | } 51 | println(logStr) 52 | } 53 | 54 | private fun getTime(): String { 55 | return timeFormatter.format(System.currentTimeMillis()) 56 | } 57 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/template/FileTemplateFactory.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.template 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.ide.fileTemplates.FileTemplateDescriptor 5 | import com.intellij.ide.fileTemplates.FileTemplateGroupDescriptor 6 | import com.intellij.ide.fileTemplates.FileTemplateGroupDescriptorFactory 7 | import com.intellij.openapi.fileTypes.StdFileTypes 8 | import javax.swing.Icon 9 | 10 | 11 | /** 12 | *
13 |  * author : dengzi
14 |  * e-mail : denua@foxmail.com
15 |  * github : https://github.com/MrDenua
16 |  * time   : 2020/1/2
17 |  * desc   :
18 |  * 
19 | */ 20 | class FileTemplateFactory : FileTemplateGroupDescriptorFactory { 21 | 22 | override fun getFileTemplatesDescriptor(): FileTemplateGroupDescriptor { 23 | 24 | val descriptor = FileTemplateGroupDescriptor("Module Template Plugin Descriptor", AllIcons.Nodes.Plugin) 25 | 26 | descriptor.addTemplate(getDescriptor("MainActivity", StdFileTypes.JAVA.icon)) 27 | descriptor.addTemplate(getDescriptor("Manifest.xml", StdFileTypes.XML.icon)) 28 | descriptor.addTemplate(getDescriptor("Application.java", StdFileTypes.JAVA.icon)) 29 | descriptor.addTemplate(getDescriptor("build.gradle", StdFileTypes.PLAIN_TEXT.icon)) 30 | 31 | return descriptor 32 | } 33 | 34 | private fun getDescriptor(templateName: String, icon: Icon?): FileTemplateDescriptor { 35 | return FileTemplateDescriptor(templateName, icon) 36 | } 37 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/TemplateConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template 2 | 3 | import com.dengzii.plugin.template.ui.ConfigurePanel 4 | import com.intellij.openapi.options.SearchableConfigurable 5 | import org.jetbrains.annotations.Nls 6 | import javax.swing.JComponent 7 | 8 | /** 9 | *
10 |  * author : dengzi
11 |  * e-mail : denua@foxmail.com
12 |  * github : https://github.com/MrDenua
13 |  * time   : 2020/1/16
14 |  * desc   :
15 |  * 
16 | */ 17 | class TemplateConfigurable() : SearchableConfigurable { 18 | 19 | private lateinit var panelConfig: ConfigurePanel 20 | private var onApplyListener: OnApplyListener? = null 21 | 22 | constructor(onApplyListener: OnApplyListener) : this() { 23 | this.onApplyListener = onApplyListener 24 | } 25 | 26 | override fun apply() { 27 | panelConfig.cacheConfig() 28 | panelConfig.saveConfig() 29 | onApplyListener?.onApply() 30 | } 31 | 32 | override fun getId(): String { 33 | return "preferences.ModuleTemplateConfig" 34 | } 35 | 36 | override fun createComponent(): JComponent? { 37 | panelConfig = ConfigurePanel() 38 | return panelConfig 39 | } 40 | 41 | override fun isModified(): Boolean { 42 | return true 43 | } 44 | 45 | @Nls(capitalization = Nls.Capitalization.Title) 46 | override fun getDisplayName(): String? { 47 | return "Module Template Settings" 48 | } 49 | 50 | interface OnApplyListener { 51 | fun onApply() 52 | } 53 | } -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | # Generate Module From Template 2 | 3 | [![JetBrains IntelliJ plugins](https://img.shields.io/jetbrains/plugin/d/13586-generate-module-from-template) ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template) 4 | [![JetBrains IntelliJ plugins](https://img.shields.io/jetbrains/plugin/v/13586-generate-module-from-template) ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template) 5 | 6 | [README - EN](https://github.com/dengzii/GenerateModuleFromTemplate/blob/master/README.md) 7 | 8 | 使用这个用于 IntelliJ IDEs 的目录模板插件, 帮助你从模板生成任何目录结构 9 | 10 | ### 功能 11 | 1. 自定义目录结构 12 | 2. 目录, 文件名, 文件模板支持配置占位符 13 | 3. 支持文件模板配置 14 | 4. 与小伙伴分享你的模板 15 | 16 | ### 使用 17 | 1. 在设置中配置插件的模板: File > Settings > Tools > Module Template Settings 18 | 2. 在 Structure 中配置目录树, 右键编辑树结构. 19 | 3. 目录树可以使用占位符, 占位符是这样的 -> ${YOUR_PLACEHOLDER_HERE}. 20 | 4. File Template 中可以配置指定文件的模板, 文件名中可以使用占位符, 会自动替换成你创建时配置的. 21 | 5. Placeholder 中列出了你目录树中所有的占位符, 你可以给他们设置默认值. 22 | 6. 插件中使用的模板是在 IDE 本身 Editor>File And Code Templates 中的模板. 23 | 7. 如果插件更新升级了, 则之前配置保存的模板可能会存在不兼容问题. 24 | 8. 你的 star, 是我更新的动力. 25 | 26 | ### 更新日志 27 | - 1.4.0: feature: Support export and import template to file, adjust action button position. 28 | - 1.3.1: fix: AucFrame module template bugs. 29 | - 1.3: fix: Placeholder don't work when call FileTreeNode.include. 30 | - 1.2: feature: all IntelliJ platform IDEs support, file template selection support when edit module template. 31 | - 1.1: feature: support create module template, placeholder, file template 32 | - 1.1: feature: support create module template, placeholder, file template 1.0: basically feature, generate module directories from template 33 | - 1.0: basically feature, generate module directories from template 34 | 35 | ### 截图 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/model/Module.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.model 2 | 3 | import com.dengzii.plugin.template.template.AucTemplate 4 | import com.dengzii.plugin.template.template.Template 5 | import com.dengzii.plugin.template.utils.Logger 6 | 7 | class Module( 8 | var template: FileTreeNode, 9 | var language: String, 10 | var templateName: String 11 | ) { 12 | 13 | companion object { 14 | 15 | fun create(template: FileTreeNode, templateName: String): Module { 16 | Logger.i(Module::class.java.simpleName, "create module. templateName=$templateName") 17 | return Module(template, "java", templateName) 18 | } 19 | 20 | fun getAndroidApplication(): Module { 21 | return create(Template.ANDROID_APP.clone(), "AndroidApp") 22 | } 23 | 24 | fun getEmpty(): Module { 25 | return create(Template.EMPTY.clone(), "EmptyModule") 26 | } 27 | 28 | fun getAucModule(): Module { 29 | return create(AucTemplate.MODULE.clone(), "Auc Module") 30 | } 31 | 32 | fun getAucApp(): Module { 33 | return create(AucTemplate.APP.clone(), "Auc App") 34 | } 35 | 36 | fun getAucPkg(): Module { 37 | return create(AucTemplate.PKG.clone(), "Auc Pkg") 38 | } 39 | 40 | fun getAucExport(): Module { 41 | return create(AucTemplate.EXPORT.clone(), "Auc Export") 42 | } 43 | 44 | fun getAndroidMvp(): Module { 45 | return create(Template.ANDROID_MVP, "Android MVP") 46 | } 47 | 48 | fun getLangList() = Language.values().map { it.name.toLowerCase() }.toTypedArray() 49 | } 50 | 51 | enum class Language { 52 | JAVA; 53 | } 54 | 55 | fun initTemplate(node: FileTreeNode = template) { 56 | node.children.forEach { 57 | it.parent = node 58 | if (it.isDir) { 59 | initTemplate(it) 60 | } 61 | } 62 | } 63 | 64 | fun clone(): Module { 65 | return create(template.clone(), templateName) 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/EditToolbar.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.ui 2 | 3 | import com.intellij.icons.AllIcons 4 | import java.awt.Dimension 5 | import java.awt.FlowLayout 6 | import java.awt.event.MouseAdapter 7 | import javax.swing.Icon 8 | import javax.swing.JButton 9 | import javax.swing.JPanel 10 | 11 | /** 12 | *
13 |  * author : dengzi
14 |  * e-mail : denua@foxmail.com
15 |  * github : https://github.com/MrDenua
16 |  * time   : 2020/1/14
17 |  * desc   :
18 |  * 
19 | */ 20 | class EditToolbar : JPanel() { 21 | 22 | private val btAdd = JButton("Add") 23 | private val btRemove = JButton("Remove") 24 | private val btCopy = JButton("Copy") 25 | private val btShare = JButton("Export") 26 | 27 | init { 28 | initLayout() 29 | initButton() 30 | } 31 | 32 | fun onAdd(listener: MouseAdapter) { 33 | btAdd.addMouseListener(listener) 34 | } 35 | 36 | fun onAdd(listener: () -> Unit) { 37 | btAdd.addActionListener { 38 | listener() 39 | } 40 | } 41 | 42 | fun onRemove(listener: () -> Unit) { 43 | btRemove.addActionListener { 44 | listener() 45 | } 46 | } 47 | 48 | fun onCopy(listener: () -> Unit) { 49 | btCopy.addActionListener { 50 | listener() 51 | } 52 | } 53 | 54 | fun onExport(listener: () -> Unit) { 55 | btShare.addActionListener { 56 | listener() 57 | } 58 | } 59 | 60 | private fun initLayout() { 61 | 62 | val flowLayout = FlowLayout() 63 | flowLayout.vgap = 0 64 | flowLayout.hgap = 5 65 | flowLayout.alignment = FlowLayout.LEFT 66 | layout = flowLayout 67 | } 68 | 69 | private fun initButton() { 70 | setIconButton(btAdd, AllIcons.General.Add) 71 | setIconButton(btRemove, AllIcons.General.Remove) 72 | setIconButton(btCopy, AllIcons.General.CopyHovered) 73 | setIconButton(btShare, AllIcons.Actions.Download) 74 | } 75 | 76 | private fun setIconButton(button: JButton, icon: Icon) { 77 | add(button) 78 | button.toolTipText = button.text 79 | button.icon = icon 80 | button.text = "" 81 | button.preferredSize = Dimension(25, 25) 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/template/Template.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.template 2 | 3 | import com.dengzii.plugin.template.model.FileTreeDsl 4 | 5 | /** 6 | *
 7 |  * author : dengzi
 8 |  * e-mail : denua@foxmail.com
 9 |  * github : https://github.com/dengzii
10 |  * time   : 2020/1/14
11 |  * desc   :
12 |  * 
13 | */ 14 | object Template { 15 | 16 | val ANDROID_RES = FileTreeDsl { 17 | dir("res") { 18 | dir("drawable") { } 19 | dir("layout") { } 20 | dir("values") { } 21 | } 22 | } 23 | val ANDROID_TEST = FileTreeDsl { 24 | dir("AndroidTest") { 25 | 26 | } 27 | } 28 | 29 | val JUNIT_TEST = FileTreeDsl { 30 | dir("test") { 31 | 32 | } 33 | } 34 | 35 | val EMPTY = FileTreeDsl { 36 | file("src") 37 | } 38 | 39 | val ANDROID_MVP = FileTreeDsl { 40 | placeholder("MVP_NAME", "Example") 41 | 42 | // fileTemplate("", "") 43 | 44 | file("\${MVP_NAME}Contract.java") 45 | file("\${MVP_NAME}View.java") 46 | file("\${MVP_NAME}Presenter.java") 47 | file("\${MVP_NAME}Model.java") 48 | } 49 | 50 | val ANDROID_APP = FileTreeDsl { 51 | placeholder("MODULE_NAME", "app") 52 | placeholder("PACKAGE_NAME", "com.example") 53 | 54 | fileTemplate("MainActivity.java", "Template MainActivity.java") 55 | fileTemplate("AndroidManifest.xml", "Template Manifest.xml") 56 | fileTemplate("build.gradle", "Template build.gradle") 57 | 58 | dir("\${MODULE_NAME}") { 59 | dir("src") { 60 | include(ANDROID_TEST) 61 | dir("main") { 62 | dir("java") { 63 | dir("\${PACKAGE_NAME}") { 64 | dir("\${MODULE_NAME}") { 65 | file("MainActivity.java") 66 | } 67 | } 68 | } 69 | include(ANDROID_RES) 70 | file("AndroidManifest.xml") 71 | } 72 | include(JUNIT_TEST) 73 | } 74 | file(".gitignore") 75 | file("build.gradle") 76 | file("ProGuard.pro") 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/Config.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template 2 | 3 | import com.dengzii.plugin.template.model.Module 4 | import com.dengzii.plugin.template.utils.Logger 5 | import com.google.gson.GsonBuilder 6 | import com.intellij.ide.util.PropertiesComponent 7 | 8 | /** 9 | *
10 |  * author : dengzi
11 |  * e-mail : denua@foxmail.com
12 |  * github : https://github.com/dengzii
13 |  * time   : 2020/1/3
14 |  * desc   :
15 |  * 
16 | */ 17 | 18 | object Config { 19 | private val TAG = Config::class.java.simpleName 20 | 21 | private const val KEY_TEMPLATES = "KEY_TEMPLATES" 22 | private const val KEY_INIT = "KEY_INIT" 23 | 24 | val GSON by lazy { GsonBuilder().setLenient().create() } 25 | private val STORE by lazy { PropertiesComponent.getInstance() } 26 | 27 | private fun clear() = STORE.unsetValue(KEY_TEMPLATES) 28 | 29 | fun loadModuleTemplates(): MutableList { 30 | val result = mutableListOf() 31 | val arr = STORE.getValues(KEY_TEMPLATES) 32 | 33 | if (STORE.getBoolean(KEY_INIT)) { 34 | Logger.i(TAG, "INIT... load AucFrame template") 35 | result.add(Module.getAucModule()) 36 | result.add(Module.getAucApp()) 37 | result.add(Module.getAucExport()) 38 | result.add(Module.getAucPkg()) 39 | } 40 | if (arr.isNullOrEmpty()) { 41 | return result 42 | } 43 | Logger.i(TAG, "loadModuleTemplates") 44 | arr.filter { !it.isNullOrBlank() }.forEach { 45 | try { 46 | val module = GSON.fromJson(it, Module::class.java) 47 | module.initTemplate() 48 | result.add(module) 49 | } catch (e: Exception) { 50 | clear() 51 | Logger.e(TAG, e) 52 | return result 53 | } 54 | } 55 | 56 | return result 57 | } 58 | 59 | fun saveModuleTemplates(templates: List) { 60 | if (templates.isEmpty()) { 61 | clear() 62 | return 63 | } 64 | val t = mutableListOf() 65 | templates.forEach { 66 | t.add(GSON.toJson(it)) 67 | Logger.d(TAG, "saveModuleTemplates ${t[t.lastIndex]}") 68 | } 69 | STORE.setValue(KEY_INIT, false, false) 70 | STORE.setValues(KEY_TEMPLATES, t.toTypedArray()) 71 | } 72 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generate Module From Template 2 | 3 | [![JetBrains IntelliJ plugins](https://img.shields.io/jetbrains/plugin/d/13586-generate-module-from-template) ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template) 4 | [![JetBrains IntelliJ plugins](https://img.shields.io/jetbrains/plugin/v/13586-generate-module-from-template) ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template) 5 | 6 | [中文 - README](https://github.com/dengzii/GenerateModuleFromTemplate/blob/master/README-ZH.md) 7 | 8 | ### Create a directory structure from a highly customizable template 9 | 10 | Using this plugin, help you create directories and files from customizable template 11 | 12 | ### Feature 13 | 1. custom directory structure. 14 | 2. support placeholders, and replace it when you create module. 15 | 3. create file from template. 16 | 4. support file template variables. 17 | 5. share your template with partner. 18 | 19 | ### Usage 20 | 1. Configure template in plugin settings: File > Settings > Tools > Module Template Settings 21 | 2. Create directories from the 'Structure' tab, click the right mouse button to operate the file tree. 22 | 3. FileTree can use placeholders, the placeholder should like this -> ${YOUR_PLACEHOLDER_HERE}. 23 | 4. The 'File Template' tab lists which template the specified file uses, you can also use placeholders for FileName field. 24 | 5. The 'Placeholder' tab's table defines placeholders for replacing filenames and file templates 25 | 26 | ### Changelog 27 | - 1.4.0: feature: Support export and import template to file, adjust action button position. 28 | - 1.3.1: fix: AucFrame module template bugs. 29 | - 1.3: fix: Placeholder don't work when call FileTreeNode.include. 30 | - 1.2: feature: all IntelliJ platform IDEs support, file template selection support when edit module template. 31 | - 1.1: feature: support create module template, placeholder, file template 32 | - 1.1: feature: support create module template, placeholder, file template 1.0: basically feature, generate module directories from template 33 | - 1.0: basically feature, generate module directories from template 34 | 35 | ### Screenshot 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/model/FileTreeDsl.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.model 2 | 3 | import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty 4 | 5 | class FileTreeDsl() : FileTreeNode() { 6 | 7 | constructor(block: FileTreeDsl.() -> Unit) : this() { 8 | invoke(block) 9 | } 10 | 11 | operator fun invoke(block: FileTreeDsl.() -> Unit): FileTreeDsl { 12 | this.block() 13 | return this 14 | } 15 | 16 | operator fun FileTreeNode.invoke(block: FileTreeNode.() -> Unit) { 17 | block(this) 18 | } 19 | 20 | /** 21 | * create directory nodes from the path 22 | * if contains '/' or '.' in path 23 | * 24 | * @param path The dir path 25 | * @param block The child node domain 26 | */ 27 | fun FileTreeNode.dir(path: String, block: FileTreeNode.() -> Unit = {}) { 28 | if (!isDir) { 29 | this(block) 30 | return 31 | } 32 | val newNode = FileTreeNode(this, path, true) 33 | if (addChild(newNode)) { 34 | newNode.invoke(block) 35 | } 36 | } 37 | 38 | fun FileTreeNode.file(name: String) { 39 | if (!isDir) return 40 | name.getPlaceholder().ifNotEmpty { 41 | allPlaceholder.addAll(this) 42 | } 43 | addChild(FileTreeNode(this, name, false)) 44 | } 45 | 46 | fun FileTreeNode.placeholder(name: String, value: String) { 47 | if (this.placeholders == null) { 48 | this.placeholders = mutableMapOf() 49 | } 50 | placeholders!![name] = value 51 | } 52 | 53 | /** 54 | * merge all children of another node to this. 55 | * all placeholders and file templates of target node 56 | * will be copied to it's each children 57 | */ 58 | fun FileTreeNode.include(other: FileTreeNode, override: Boolean = false) { 59 | if (!isDir) return 60 | other.children.forEach { 61 | val includeChild = it.clone() 62 | if (other.placeholders != null) { 63 | if (includeChild.placeholders == null) { 64 | includeChild.placeholders = mutableMapOf() 65 | } 66 | other.placeholders?.forEach { k, v -> 67 | if (getFileTemplateInherit()?.containsKey(k) != true) { 68 | includeChild.placeholders?.put(k, v) 69 | } 70 | } 71 | } 72 | if (other.fileTemplates != null) { 73 | if (includeChild.fileTemplates == null) { 74 | includeChild.fileTemplates = mutableMapOf() 75 | } 76 | other.fileTemplates?.forEach { k, v -> 77 | if (fileTemplates?.containsKey(k) != true) { 78 | includeChild.fileTemplates?.put(k, v) 79 | } 80 | } 81 | } 82 | addChild(includeChild, override) 83 | } 84 | } 85 | 86 | fun FileTreeNode.fileTemplate(fileName: String, template: String) { 87 | if (this.fileTemplates == null) { 88 | this.fileTemplates = mutableMapOf() 89 | } 90 | fileTemplates!![fileName] = template 91 | } 92 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/CreateFileDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/FileWriteCommand.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template 2 | 3 | import com.dengzii.plugin.template.model.FileTreeNode 4 | import com.dengzii.plugin.template.model.Module 5 | import com.dengzii.plugin.template.utils.Logger 6 | import com.dengzii.plugin.template.utils.PluginKit 7 | import com.intellij.openapi.command.UndoConfirmationPolicy 8 | import com.intellij.openapi.command.WriteCommandAction 9 | import com.intellij.openapi.vfs.VirtualFile 10 | import com.intellij.util.ThrowableRunnable 11 | 12 | /** 13 | *
14 |  * author : dengzi
15 |  * e-mail : denua@foxmail.com
16 |  * github : https://github.com/dengzii
17 |  * time   : 2019/12/31
18 |  * desc   :
19 |  * 
20 | */ 21 | class FileWriteCommand(private var kit: PluginKit, private var module: Module) : ThrowableRunnable { 22 | 23 | companion object { 24 | private val TAG = FileWriteCommand::class.java.simpleName 25 | fun startAction(kit: PluginKit, module: Module) { 26 | WriteCommandAction.writeCommandAction(kit.project) 27 | .withGlobalUndo() 28 | .withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION) 29 | .run(FileWriteCommand(kit, module)) 30 | } 31 | } 32 | 33 | override fun run() { 34 | 35 | val current = kit.getVirtualFile() ?: return 36 | if (!current.isDirectory) { 37 | Logger.i(TAG, "Current target is not directory.") 38 | return 39 | } 40 | val fileTreeNode = module.template 41 | Logger.d(TAG, "Placeholders : " + fileTreeNode.getPlaceholderInherit().toString()) 42 | Logger.d(TAG, "FileTemplates : " + fileTreeNode.getFileTemplateInherit().toString()) 43 | fileTreeNode.build() 44 | fileTreeNode.children.forEach { 45 | createFileTree(it, current) 46 | } 47 | } 48 | 49 | private fun createFileTree(treeNode: FileTreeNode, currentDirectory: VirtualFile) { 50 | Logger.d(TAG, "create node $treeNode") 51 | if (treeNode.isDir) { 52 | val dir = kit.createDir(treeNode.getRealName(), currentDirectory) 53 | if (dir == null) { 54 | Logger.e(TAG, "create directory failure: ${treeNode.getRealName()}") 55 | return 56 | } 57 | treeNode.children.forEach { 58 | createFileTree(it, dir) 59 | Logger.d(TAG, "create dir ${it.getPath()}") 60 | } 61 | } else { 62 | val template = treeNode.getTemplateFile() 63 | if (template != null) { 64 | val result = kit.createFileFromTemplate( 65 | treeNode.getRealName(), 66 | template, 67 | treeNode.getPlaceholderInherit().orEmpty(), 68 | currentDirectory) 69 | if (result == null) { 70 | Logger.e(TAG, "create file from template failed, file: ${treeNode.getRealName()} template:$template") 71 | } else { 72 | Logger.d(TAG, "create file from template ${treeNode.getRealName()}") 73 | } 74 | } else { 75 | kit.createFile(treeNode.getRealName(), currentDirectory) 76 | Logger.d(TAG, "create file ${treeNode.getPath()}") 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.dengzii.plugin.template 3 | Generate Module From Template 4 | 1.4.0 5 | dengzi 6 | 7 | Create a directory structure from a highly customizable template

10 |
11 | Using this plugin, help you create directories and files from customizable template
12 |
13 | Feature
14 | 1. custom directory structure
15 | 2. support placeholders, and replace it when you create module.
16 | 3. create file from template
17 | 4. support file template variables
18 | 19 | Usage
20 | 1. Configure template in plugin settings: File > Settings > Tools > Module Template Settings
21 | 2. Create directories from the 'Structure' tab, click the right mouse button to operate the file tree.
22 | 3. FileTree can use placeholders, the placeholder should like this -> ${YOUR_PLACEHOLDER_HERE}.
23 | 4. The 'File Template' tab lists which template the specified file uses, you can also use placeholders for FileName field.
24 | 5. The 'Placeholder' tab's table defines placeholders for replacing filenames and file templates
25 | 26 |
27 | Contribute
28 | GitHub
29 | Any question please create issue
30 |
31 | 32 | Screenshot
33 | 34 | 35 | 36 | 37 |
38 | ]]> 39 | 40 | 42 | 1.3.1: fix: AucFrame module template bugs
43 | 1.3: fix: Placeholder don't work when call FileTreeNode.include
44 | 1.2: feature: all IntelliJ platform IDEs support, file template selection support when edit module template.
45 | 1.1: feature: support create module template, placeholder, file template
46 | 1.0: basically feature, generate module directories from template
47 | ]]> 48 |
49 | 50 | 51 | 52 | com.intellij.modules.platform 53 | com.intellij.modules.lang 54 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /test/FileTreeDslTest.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import com.dengzii.plugin.template.model.FileTreeDsl 4 | import org.junit.Test 5 | 6 | class FileTreeDslTest { 7 | 8 | @Test 9 | fun createSimpleFileTreeTest() { 10 | val tree = FileTreeDsl { 11 | file("file") 12 | dir("dir1") { 13 | file("file2") 14 | dir("dir2") { 15 | dir("dir3") { 16 | dir("dir4") 17 | } 18 | dir("dir5") { 19 | dir("dir6") 20 | dir("dir9") 21 | } 22 | } 23 | file("file3") 24 | } 25 | dir("dir7") { 26 | dir("dir8") 27 | } 28 | } 29 | println(tree.getTreeGraph()) 30 | } 31 | 32 | @Test 33 | fun createPackageDirTest() { 34 | 35 | val tree = FileTreeDsl { 36 | dir("src") { 37 | dir("com/example/app") { 38 | file("Main.java") 39 | } 40 | dir("com/example/app1") 41 | } 42 | file("README.md") 43 | } 44 | println(tree.getTreeGraph()) 45 | } 46 | 47 | 48 | @Test 49 | fun fileNamePlaceholderTest() { 50 | val tree = FileTreeDsl { 51 | placeholder("FILE_1", "first_file") 52 | placeholder("FILE_2", "second_file") 53 | file("\${FILE_1}.java") 54 | dir("com/example") { 55 | println(name) 56 | file("\${FILE_2}.java") 57 | } 58 | } 59 | println(tree.getTreeGraph()) 60 | } 61 | 62 | @Test 63 | fun expandDirectoriesTest() { 64 | val tree = FileTreeDsl { 65 | dir("src") { 66 | dir("com.dengzii.plugin") { 67 | dir("model") 68 | dir("template") 69 | } 70 | dir("test") 71 | } 72 | } 73 | tree.build() 74 | println(tree.getTreeGraph()) 75 | 76 | val tree2 = FileTreeDsl { 77 | dir("src") { 78 | dir("com.dengzii.plugin") { 79 | dir("dir1/dir2/") { 80 | dir("model") 81 | dir("template") 82 | } 83 | dir("model") 84 | dir("template") 85 | } 86 | dir("test") 87 | } 88 | } 89 | tree2.build() 90 | println(tree2.getTreeGraph()) 91 | } 92 | 93 | @Test 94 | fun expandDirectoriesInPlaceholderTest() { 95 | 96 | val tree = FileTreeDsl { 97 | placeholder("PACKAGE_NAME", "com.dengzii.plugin") 98 | dir("src") { 99 | dir("\${PACKAGE_NAME}") { 100 | dir("model") 101 | dir("template") 102 | } 103 | dir("test") 104 | } 105 | } 106 | tree.build() 107 | println(tree.getTreeGraph()) 108 | } 109 | 110 | @Test 111 | fun getAllPlaceholderInTreeNodeNameTest() { 112 | val tree = FileTreeDsl { 113 | placeholder("PACKAGE_NAME", "com.dengzii.plugin") 114 | dir("src") { 115 | dir("\${PACKAGE_NAME}") { 116 | dir("model") 117 | dir("template"){ 118 | file("\${TEST}") 119 | file("\${TEST2}") 120 | } 121 | } 122 | dir("test") 123 | } 124 | } 125 | println(tree.getAllPlaceholderInTree()) 126 | } 127 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/EditableTable.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.ui 2 | 3 | import com.intellij.ui.components.JBScrollPane 4 | import com.intellij.ui.table.JBTable 5 | import java.awt.BorderLayout 6 | import java.awt.event.FocusEvent 7 | import java.awt.event.FocusListener 8 | import javax.swing.JPanel 9 | import javax.swing.table.DefaultTableModel 10 | 11 | /** 12 | *
 13 |  * author : dengzi
 14 |  * e-mail : denua@foxmail.com
 15 |  * github : https://github.com/MrDenua
 16 |  * time   : 2020/1/14
 17 |  * desc   :
 18 |  * 
19 | */ 20 | class EditableTable(header: Array, colEditable: Array = emptyArray()) : JPanel() { 21 | 22 | private val scrollPanel = JBScrollPane() 23 | private val editToolbar = EditToolbar() 24 | private val table = JBTable() 25 | private var tableModel = TableModel(header, colEditable) 26 | 27 | init { 28 | layout = BorderLayout() 29 | 30 | add(editToolbar, BorderLayout.NORTH) 31 | scrollPanel.setViewportView(table) 32 | add(scrollPanel, BorderLayout.CENTER) 33 | table.fillsViewportHeight = true 34 | table.showHorizontalLines = true 35 | table.showVerticalLines = true 36 | table.model = tableModel 37 | table.putClientProperty("terminateEditOnFocusLost", true) 38 | initListener() 39 | } 40 | 41 | fun setToolBarVisible(visible: Boolean) { 42 | editToolbar.isVisible = visible 43 | } 44 | 45 | fun setData(data: MutableList>) { 46 | tableModel.setData(data) 47 | tableModel.fireTableDataChanged() 48 | table.updateUI() 49 | } 50 | 51 | fun setPairData(data: Map?) { 52 | val dataList = mutableListOf>() 53 | data?.forEach { (t, u) -> 54 | dataList.add(mutableListOf(t, u)) 55 | } 56 | tableModel.setData(dataList) 57 | tableModel.fireTableDataChanged() 58 | table.updateUI() 59 | } 60 | 61 | fun getPairResult(): MutableMap { 62 | val result = mutableMapOf() 63 | for (i in 0 until table.rowCount) { 64 | val key = table.getValueAt(i, 0).toString() 65 | val value = table.getValueAt(i, 1).toString() 66 | if (key.isBlank() || value.isBlank()) { 67 | continue 68 | } 69 | result[key] = value 70 | } 71 | return result 72 | } 73 | 74 | private fun initListener() { 75 | 76 | editToolbar.onAdd { 77 | tableModel.add() 78 | table.updateUI() 79 | } 80 | editToolbar.onCopy { 81 | if (table.selectedRow == -1) { 82 | return@onCopy 83 | } 84 | tableModel.copy(table.selectedRow) 85 | table.updateUI() 86 | } 87 | editToolbar.onRemove { 88 | if (table.selectedRow == -1) { 89 | return@onRemove 90 | } 91 | tableModel.remove(table.selectedRow) 92 | table.updateUI() 93 | } 94 | } 95 | 96 | internal class TableModel(private val header: Array, private val colEditable: Array) : DefaultTableModel() { 97 | 98 | fun setData(fileTemp: MutableList>?) { 99 | while (rowCount > 0) { 100 | removeRow(0) 101 | } 102 | if (fileTemp == null) { 103 | return 104 | } 105 | fileTemp.forEach { 106 | addRow(it.toTypedArray()) 107 | } 108 | } 109 | 110 | fun add() { 111 | addRow(Array(header.size) { "" }) 112 | fireTableDataChanged() 113 | } 114 | 115 | fun remove(row: Int) { 116 | removeRow(row) 117 | fireTableDataChanged() 118 | } 119 | 120 | fun copy(row: Int) { 121 | val r = mutableListOf() 122 | for (c in 0 until columnCount) { 123 | r.add(getValueAt(row, c).toString()) 124 | } 125 | addRow(r.toTypedArray()) 126 | fireTableDataChanged() 127 | } 128 | 129 | override fun isCellEditable(row: Int, column: Int): Boolean { 130 | return if (colEditable.isEmpty()) super.isCellEditable(row, column) else colEditable[column] 131 | } 132 | 133 | override fun getColumnClass(columnIndex: Int): Class<*> { 134 | return String::class.java 135 | } 136 | 137 | override fun getColumnCount(): Int { 138 | return header.size 139 | } 140 | 141 | override fun getColumnName(column: Int): String { 142 | return if (header.isEmpty()) super.getColumnName(column) else header[column] 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/template/AucTemplate.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.template 2 | 3 | import com.dengzii.plugin.template.model.FileTreeDsl 4 | 5 | /** 6 | *
  7 |  * author : dengzi
  8 |  * e-mail : denua@foxmail.com
  9 |  * github : https://github.com/dengzii
 10 |  * time   : 2019/1/1
 11 |  * desc   :
 12 | 
*/ 13 | object AucTemplate { 14 | 15 | private val aucFileTemplates: () -> MutableMap = { 16 | mutableMapOf( 17 | Pair("AndroidManifest.xml", "Template Manifest.xml"), 18 | Pair("build.gradle", "Template build.gradle"), 19 | Pair("MainActivity.java", "Template MainActivity.java"), 20 | Pair("\${FEATURE_NAME}App.java", "Template Application.java"), 21 | Pair("\${FEATURE_NAME}Api.java", "Template AucApiClass.java"), 22 | Pair("\${FEATURE_NAME}ApiImpl.java", "Template AucApiImplClass.java") 23 | ) 24 | } 25 | 26 | private val aucPlaceholders: () -> MutableMap = { 27 | mutableMapOf( 28 | Pair("PACKAGE_NAME", "com.example"), 29 | Pair("FEATURE_NAME", "Feature1") 30 | ) 31 | } 32 | 33 | val APP = FileTreeDsl { 34 | addFileTemplates(aucFileTemplates()) 35 | addPlaceholders(aucPlaceholders()) 36 | placeholder("MODULE_NAME", "app") 37 | 38 | dir("app") { 39 | dir("src") { 40 | dir("main") { 41 | dir("java") { 42 | dir("\${PACKAGE_NAME}") { 43 | dir("\${FEATURE_NAME}") { 44 | dir("app") { 45 | file("MainActivity.java") 46 | file("\${FEATURE_NAME}App.java") 47 | } 48 | } 49 | } 50 | } 51 | include(Template.ANDROID_RES) 52 | file("AndroidManifest.xml") 53 | } 54 | } 55 | file(".gitignore") 56 | file("build.gradle") 57 | file("proguard-rules.pro") 58 | } 59 | } 60 | 61 | val PKG = FileTreeDsl { 62 | addFileTemplates(aucFileTemplates()) 63 | addPlaceholders(aucPlaceholders()) 64 | placeholder("MODULE_NAME", "pkg") 65 | dir("pkg") { 66 | dir("src") { 67 | dir("main") { 68 | dir("java") { 69 | dir("\${PACKAGE_NAME}") { 70 | dir("\${FEATURE_NAME}") { 71 | dir("pkg") { 72 | file("\${FEATURE_NAME}ApiImpl.java") 73 | } 74 | } 75 | } 76 | } 77 | include(Template.ANDROID_RES) 78 | file("AndroidManifest.xml") 79 | } 80 | } 81 | file(".gitignore") 82 | file("build.gradle") 83 | file("proguard-rules.pro") 84 | } 85 | } 86 | 87 | val EXPORT = FileTreeDsl { 88 | addFileTemplates(aucFileTemplates()) 89 | addPlaceholders(aucPlaceholders()) 90 | placeholder("MODULE_NAME", "export") 91 | dir("export") { 92 | dir("src") { 93 | dir("main") { 94 | dir("java") { 95 | dir("\${PACKAGE_NAME}") { 96 | dir("\${FEATURE_NAME}") { 97 | dir("export") { 98 | file("\${FEATURE_NAME}Api.java") 99 | } 100 | } 101 | } 102 | } 103 | include(Template.ANDROID_RES) 104 | file("AndroidManifest.xml") 105 | } 106 | } 107 | file(".gitignore") 108 | file("build.gradle") 109 | file("proguard-rules.pro") 110 | } 111 | } 112 | 113 | val MODULE = FileTreeDsl { 114 | addFileTemplates(aucFileTemplates()) 115 | addPlaceholders(aucPlaceholders()) 116 | dir("\${FEATURE_NAME}") { 117 | 118 | include(APP { 119 | placeholders?.remove("\${FEATURE_NAME}") 120 | placeholders?.remove("\${PACKAGE_NAME}") 121 | }) 122 | include(PKG { 123 | placeholders?.remove("\${FEATURE_NAME}") 124 | placeholders?.remove("\${PACKAGE_NAME}") 125 | }) 126 | include(EXPORT { 127 | placeholders?.remove("\${FEATURE_NAME}") 128 | placeholders?.remove("\${PACKAGE_NAME}") 129 | }) 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/CreateFileDialog.java: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.ui; 2 | 3 | import com.dengzii.plugin.template.model.FileTreeNode; 4 | import com.dengzii.plugin.template.utils.PluginKit; 5 | import com.intellij.ide.fileTemplates.FileTemplate; 6 | 7 | import javax.swing.*; 8 | import java.awt.*; 9 | import java.awt.event.KeyAdapter; 10 | import java.awt.event.KeyEvent; 11 | 12 | public class CreateFileDialog extends JDialog { 13 | private static final String NONE = "None"; 14 | 15 | private JPanel contentPane; 16 | private JComboBox cbTemplate; 17 | private JTextField tfName; 18 | private JLabel lbTemplate; 19 | private JLabel lbName; 20 | private JButton btConfirm; 21 | 22 | private CreateFileCallback createFileCallback; 23 | private boolean isDir; 24 | private FileTreeNode parent; 25 | private FileTreeNode current; 26 | 27 | public static void showForRefactor(FileTreeNode node, CreateFileCallback createFileCallback) { 28 | CreateFileDialog createFileDialog = new CreateFileDialog(); 29 | createFileDialog.createFileCallback = createFileCallback; 30 | createFileDialog.isDir = node.isDir(); 31 | createFileDialog.parent = node.getParent(); 32 | createFileDialog.current = node; 33 | createFileDialog.initDialog(); 34 | } 35 | 36 | public static void showForCreate(FileTreeNode parent, boolean isDir, CreateFileCallback createFileCallback) { 37 | CreateFileDialog createFileDialog = new CreateFileDialog(); 38 | createFileDialog.createFileCallback = createFileCallback; 39 | createFileDialog.isDir = isDir; 40 | createFileDialog.parent = parent; 41 | createFileDialog.initDialog(); 42 | } 43 | 44 | private CreateFileDialog() { 45 | setContentPane(contentPane); 46 | setModal(true); 47 | setDefaultCloseOperation(DISPOSE_ON_CLOSE); 48 | } 49 | 50 | private void initFileTemplateList() { 51 | FileTemplate[] fileTemplates = PluginKit.Companion.getAllFileTemplate(); 52 | String[] items = new String[fileTemplates.length + 1]; 53 | items[0] = NONE; 54 | for (int i = 0; i < fileTemplates.length; i++) { 55 | items[i + 1] = fileTemplates[i].getName(); 56 | } 57 | cbTemplate.setModel(new DefaultComboBoxModel<>(items)); 58 | if (isRefactor() && current.getTemplateFile() != null 59 | && PluginKit.Companion.getFileTemplate(current.getTemplateFile()) != null) { 60 | cbTemplate.setSelectedItem(current.getTemplateFile()); 61 | } 62 | } 63 | 64 | private void onConfirm() { 65 | if (tfName.getText().trim().isEmpty()) { 66 | return; 67 | } 68 | if (isRefactor()) { 69 | current.setName(tfName.getText()); 70 | // setup template 71 | if (!isDir && getSelectedTemplate() != null && !getSelectedTemplate().equals(NONE)) { 72 | current.setTemplate(getSelectedTemplate()); 73 | } 74 | } else { 75 | current = new FileTreeNode(parent, tfName.getText(), isDir); 76 | } 77 | 78 | if (!isRefactor() && parent != null && parent.hasChild(tfName.getText(), isDir)) { 79 | lbName.setText(lbName.getText() + " (already exist.)"); 80 | return; 81 | } 82 | 83 | createFileCallback.callback(current); 84 | dispose(); 85 | } 86 | 87 | private void initDialog() { 88 | 89 | if (isDir) { 90 | lbTemplate.setVisible(false); 91 | cbTemplate.setVisible(false); 92 | } else { 93 | initFileTemplateList(); 94 | } 95 | pack(); 96 | 97 | Dimension screen = getToolkit().getScreenSize(); 98 | int w = getWidth(); 99 | int h = getHeight(); 100 | int x = screen.width / 2 - w / 2; 101 | int y = screen.height / 2 - h / 2 - 100; 102 | setLocation(x, y); 103 | setPreferredSize(new Dimension(w, h)); 104 | 105 | setTitle((isRefactor() ? "Refactor " : "New ") + (isDir ? "Directory" : "File")); 106 | if (isRefactor()) { 107 | tfName.setText(current.getRealName()); 108 | } 109 | tfName.addKeyListener(new KeyAdapter() { 110 | @Override 111 | public void keyReleased(KeyEvent e) { 112 | super.keyReleased(e); 113 | if (e.getKeyCode() == KeyEvent.VK_ENTER) { 114 | onConfirm(); 115 | } 116 | } 117 | }); 118 | btConfirm.addActionListener(e -> onConfirm()); 119 | setVisible(true); 120 | tfName.requestFocus(); 121 | } 122 | 123 | private String getSelectedTemplate() { 124 | if (isDir) { 125 | return null; 126 | } 127 | return cbTemplate.getSelectedItem().toString(); 128 | } 129 | 130 | private boolean isRefactor() { 131 | return current != null; 132 | } 133 | 134 | public interface CreateFileCallback { 135 | void callback(FileTreeNode fileTreeNode); 136 | } 137 | 138 | public static void main(String[] args) { 139 | CreateFileDialog.showForCreate(null, true, fileTreeNode -> { 140 | 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/utils/PluginKit.kt: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.utils 2 | 3 | import com.intellij.ide.fileTemplates.FileTemplate 4 | import com.intellij.ide.fileTemplates.FileTemplateManager 5 | import com.intellij.ide.fileTemplates.FileTemplateUtil 6 | import com.intellij.ide.fileTemplates.impl.FileTemplateManagerImpl 7 | import com.intellij.openapi.actionSystem.AnActionEvent 8 | import com.intellij.openapi.actionSystem.PlatformDataKeys 9 | import com.intellij.openapi.module.Module 10 | import com.intellij.openapi.module.ModuleManager 11 | import com.intellij.openapi.project.Project 12 | import com.intellij.openapi.vfs.VirtualFile 13 | import com.intellij.openapi.vfs.VirtualFileManager 14 | import com.intellij.psi.PsiDirectory 15 | import com.intellij.psi.PsiElement 16 | import com.intellij.psi.PsiFile 17 | import com.intellij.psi.PsiManager 18 | import java.util.* 19 | 20 | /** 21 | *
 22 |  * author : dengzi
 23 |  * e-mail : denua@foxmail.com
 24 |  * github : https://github.com/MrDenua
 25 |  * time   : 2019/12/31
 26 |  * desc   :
 27 |  * 
28 | */ 29 | class PluginKit private constructor(e: AnActionEvent) { 30 | 31 | private val event = e 32 | lateinit var project: Project 33 | 34 | init { 35 | if (e.project != null) { 36 | project = e.project!! 37 | } 38 | } 39 | 40 | fun isProjectValid(): Boolean { 41 | return project.isOpen && project.isInitialized 42 | } 43 | 44 | fun getModules(): Array { 45 | return ModuleManager.getInstance(project).modules 46 | } 47 | 48 | fun getCurrentPsiFile(): PsiFile? { 49 | return event.getData(PlatformDataKeys.PSI_FILE) 50 | } 51 | 52 | fun getCurrentPsiDirectory(): PsiDirectory? { 53 | if (getCurrentPsiFile() is PsiDirectory) { 54 | return getCurrentPsiFile() as PsiDirectory 55 | } 56 | return null 57 | } 58 | 59 | fun getPsiDirectoryByPath(path: String): PsiDirectory? { 60 | val vf = VirtualFileManager.getInstance().findFileByUrl("") ?: return null 61 | return PsiManager.getInstance(project).findDirectory(vf) 62 | } 63 | 64 | fun getPsiDirectoryByVirtualFile(vf: VirtualFile): PsiDirectory? { 65 | if (!vf.isDirectory) { 66 | Logger.e(TAG, "${vf.path} is not a directory.") 67 | return null 68 | } 69 | return PsiManager.getInstance(project).findDirectory(vf) 70 | } 71 | 72 | fun getVirtualFile(): VirtualFile? { 73 | return event.getData(PlatformDataKeys.VIRTUAL_FILE) 74 | } 75 | 76 | fun createDir(name: String, vf: VirtualFile? = getVirtualFile()): VirtualFile? { 77 | if (!checkCreateFile(name, vf)) { 78 | return null 79 | } 80 | return vf!!.createChildDirectory(null, name) 81 | } 82 | 83 | fun createFile(name: String, vf: VirtualFile? = getVirtualFile()) { 84 | if (!checkCreateFile(name, vf)) { 85 | return 86 | } 87 | vf!!.createChildData(null, name) 88 | } 89 | 90 | fun createFileFromTemplate(fileName: String, 91 | templateName: String, 92 | propertiesMap: Map, 93 | directory: VirtualFile): PsiElement? { 94 | 95 | val fileTemplateManager = FileTemplateManager.getInstance(project) 96 | val properties = Properties(fileTemplateManager.defaultProperties) 97 | propertiesMap.forEach { (t, u) -> 98 | properties.setProperty(t.strip(), u) 99 | } 100 | val template = fileTemplateManager.getTemplate(templateName) ?: return null 101 | val psiDirectory = getPsiDirectoryByVirtualFile(directory) ?: return null 102 | return try { 103 | FileTemplateUtil.createFromTemplate(template, fileName, properties, psiDirectory) 104 | } catch (e: Throwable) { 105 | Logger.e(TAG, e) 106 | null 107 | } 108 | } 109 | 110 | fun addTemplate(name: String, template: String) { 111 | FileTemplateManager.getInstance(project).addTemplate(name, template) 112 | } 113 | 114 | private fun String.strip(): String { 115 | var result = this 116 | if (startsWith("\${") && endsWith("}")) { 117 | result = result.substring(2, length - 1) 118 | } 119 | return result 120 | } 121 | 122 | private fun checkCreateFile(name: String, vf: VirtualFile?): Boolean { 123 | if (vf == null || !vf.isDirectory) { 124 | Logger.e(TAG, "target is null or is not a directory.") 125 | return false 126 | } 127 | if (vf.findChild(name)?.exists() == true) { 128 | Logger.e(TAG, "directory already exists.") 129 | return false 130 | } 131 | return true 132 | } 133 | 134 | companion object { 135 | 136 | private val TAG = PluginKit::class.java.simpleName; 137 | 138 | fun get(e: AnActionEvent): PluginKit { 139 | return PluginKit(e) 140 | } 141 | 142 | fun getAllFileTemplate(): Array = FileTemplateManagerImpl.getDefaultInstance().allTemplates 143 | 144 | fun getFileTemplate(name: String): FileTemplate? = FileTemplateManagerImpl.getDefaultInstance().getTemplate(name) 145 | } 146 | } -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/CreateModuleDialog.java: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.ui; 2 | 3 | import com.dengzii.plugin.template.Config; 4 | import com.dengzii.plugin.template.TemplateConfigurable; 5 | import com.dengzii.plugin.template.model.Module; 6 | import com.dengzii.plugin.template.utils.Logger; 7 | import com.intellij.icons.AllIcons; 8 | import com.intellij.openapi.options.ShowSettingsUtil; 9 | import com.intellij.openapi.project.Project; 10 | import org.bouncycastle.math.raw.Mod; 11 | 12 | import javax.swing.*; 13 | import java.awt.*; 14 | import java.awt.event.ActionEvent; 15 | import java.awt.event.KeyEvent; 16 | import java.util.ArrayList; 17 | import java.util.Collections; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | 21 | public class CreateModuleDialog extends JDialog { 22 | 23 | private static final String TAG = CreateModuleDialog.class.getSimpleName(); 24 | 25 | private JPanel rootPanel; 26 | private JLabel labelTitle; 27 | private JComboBox cbModuleTemplate; 28 | private JPanel mainPanel; 29 | private JPanel contentPanel; 30 | 31 | private JButton btConfigure; 32 | private JButton btCancel; 33 | private JButton btPrevious; 34 | private JButton btFinish; 35 | private JPanel panelPlaceholder; 36 | 37 | private EditableTable tablePlaceholder; 38 | private OnFinishListener onFinishListener; 39 | 40 | private java.util.List moduleTemplates = Collections.emptyList(); 41 | private Module selectedModule; 42 | 43 | private HashMap panels = new HashMap<>(); 44 | private List titles = new ArrayList<>(); 45 | 46 | private PreviewPanel previewPanel; 47 | 48 | private int currentPanelIndex; 49 | private Project project; 50 | 51 | private CreateModuleDialog(Project project, OnFinishListener onFinishListener) { 52 | setContentPane(rootPanel); 53 | setModal(true); 54 | getRootPane().setDefaultButton(btFinish); 55 | this.project = project; 56 | this.onFinishListener = onFinishListener; 57 | } 58 | 59 | public static void createAndShow(Project project, OnFinishListener onFinishListener) { 60 | 61 | CreateModuleDialog dialog = new CreateModuleDialog(project, onFinishListener); 62 | dialog.initDialog(); 63 | dialog.initData(); 64 | dialog.pack(); 65 | dialog.setVisible(true); 66 | } 67 | 68 | public static void main(String[] args) { 69 | createAndShow(null, config -> { 70 | 71 | }); 72 | } 73 | 74 | private void onNextClick(ActionEvent e) { 75 | selectedModule.getTemplate().setPlaceholders(tablePlaceholder.getPairResult()); 76 | previewPanel.setModuleConfigPreview(selectedModule); 77 | 78 | if (currentPanelIndex == panels.size() - 1) { 79 | onFinishListener.onFinish(selectedModule); 80 | dispose(); 81 | return; 82 | } 83 | currentPanelIndex++; 84 | setPanel(); 85 | setButton(); 86 | } 87 | 88 | private void onPreviousClick(ActionEvent e) { 89 | if (currentPanelIndex == 0) { 90 | return; 91 | } 92 | currentPanelIndex--; 93 | setPanel(); 94 | setButton(); 95 | } 96 | 97 | private void onConfClick(ActionEvent e) { 98 | ShowSettingsUtil.getInstance().editConfigurable(project, new TemplateConfigurable(this::initData)); 99 | 100 | } 101 | 102 | private void setButton() { 103 | btPrevious.setEnabled(true); 104 | btFinish.setText("Next"); 105 | if (currentPanelIndex == panels.size() - 1) { 106 | btFinish.setText("Finish"); 107 | } else if (currentPanelIndex == 0) { 108 | btPrevious.setEnabled(false); 109 | } 110 | } 111 | 112 | private void setPanel() { 113 | String title = titles.get(currentPanelIndex); 114 | labelTitle.setText(title); 115 | contentPanel.removeAll(); 116 | contentPanel.add(panels.get(title)); 117 | contentPanel.doLayout(); 118 | contentPanel.updateUI(); 119 | } 120 | 121 | private void onModuleConfigChange() { 122 | Logger.INSTANCE.i(TAG, "onModuleConfigChange"); 123 | selectedModule = moduleTemplates.get(cbModuleTemplate.getSelectedIndex()); 124 | previewPanel.setModuleConfigPreview(selectedModule); 125 | tablePlaceholder.setPairData(selectedModule.getTemplate().getPlaceholderInherit()); 126 | } 127 | 128 | private void initDialog() { 129 | 130 | Dimension screen = getToolkit().getScreenSize(); 131 | int w = screen.width / 3 + 160; 132 | int h = 600; 133 | int x = screen.width / 2 - w / 2; 134 | int y = screen.height / 2 - h / 2; 135 | setLocation(x, y); 136 | rootPanel.setPreferredSize(new Dimension(w, h)); 137 | 138 | setTitle("Create Module"); 139 | setDefaultCloseOperation(DISPOSE_ON_CLOSE); 140 | rootPanel.registerKeyboardAction(e -> dispose(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 141 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 142 | 143 | tablePlaceholder = new EditableTable(new String[]{"Key", "Value"}, new Boolean[]{false, true}); 144 | tablePlaceholder.setToolBarVisible(false); 145 | panelPlaceholder.add(tablePlaceholder, BorderLayout.CENTER); 146 | 147 | btConfigure.setIcon(AllIcons.General.GearPlain); 148 | previewPanel = new PreviewPanel(); 149 | titles.add("Create Module From Template"); 150 | titles.add("Preview Module"); 151 | 152 | panels.put(titles.get(0), mainPanel); 153 | panels.put(titles.get(1), previewPanel); 154 | 155 | btFinish.addActionListener(this::onNextClick); 156 | btPrevious.addActionListener(this::onPreviousClick); 157 | btCancel.addActionListener(e -> dispose()); 158 | btConfigure.addActionListener(this::onConfClick); 159 | 160 | cbModuleTemplate.addItemListener(e -> { 161 | onModuleConfigChange(); 162 | }); 163 | } 164 | 165 | private void initData() { 166 | Logger.INSTANCE.i(TAG, "initData"); 167 | moduleTemplates = Config.INSTANCE.loadModuleTemplates(); 168 | List temp = new ArrayList<>(); 169 | moduleTemplates.forEach(i -> temp.add(i.getTemplateName())); 170 | cbModuleTemplate.setModel(new DefaultComboBoxModel<>(temp.toArray())); 171 | if (cbModuleTemplate.getModel().getSize() > 0) { 172 | cbModuleTemplate.setSelectedIndex(0); 173 | onModuleConfigChange(); 174 | } 175 | } 176 | 177 | public interface OnFinishListener { 178 | void onFinish(Module config); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/ConfigurePanel.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/PreviewPanel.java: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.ui; 2 | 3 | import com.dengzii.plugin.template.model.FileTreeNode; 4 | import com.dengzii.plugin.template.model.Module; 5 | import com.dengzii.plugin.template.utils.Logger; 6 | import com.intellij.icons.AllIcons; 7 | import com.intellij.openapi.ui.JBMenuItem; 8 | import com.intellij.openapi.ui.JBPopupMenu; 9 | import com.intellij.packageDependencies.ui.TreeModel; 10 | import com.intellij.ui.ColoredTreeCellRenderer; 11 | import com.intellij.ui.treeStructure.Tree; 12 | import com.intellij.util.IconUtil; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import javax.swing.*; 16 | import javax.swing.tree.DefaultMutableTreeNode; 17 | import javax.swing.tree.TreeCellRenderer; 18 | import javax.swing.tree.TreeNode; 19 | import javax.swing.tree.TreePath; 20 | import java.awt.*; 21 | import java.awt.event.ActionListener; 22 | import java.awt.event.MouseAdapter; 23 | import java.awt.event.MouseEvent; 24 | import java.util.Enumeration; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | *
 30 |  * author : dengzi
 31 |  * e-mail : denua@foxmail.com
 32 |  * github : https://github.com/dengzii
 33 |  * time   : 2020/1/3
 34 |  * desc   :
 35 |  * 
36 | */ 37 | public class PreviewPanel extends JPanel { 38 | 39 | private static final String TAG = PreviewPanel.class.getSimpleName(); 40 | 41 | private JPanel contentPanel; 42 | private Tree fileTree; 43 | 44 | private Map fileIconMap = new HashMap<>(); 45 | private boolean replacePlaceholder = true; 46 | 47 | // render tree node icon, title 48 | private TreeCellRenderer treeCellRenderer = new ColoredTreeCellRenderer() { 49 | @Override 50 | public void customizeCellRenderer(@NotNull JTree jTree, Object value, boolean b, boolean b1, boolean b2, int i, boolean b3) { 51 | if (value instanceof DefaultMutableTreeNode) { 52 | Object object = ((DefaultMutableTreeNode) value).getUserObject(); 53 | if (object instanceof FileTreeNode) { 54 | FileTreeNode node = (FileTreeNode) object; 55 | setIcon(IconUtil.getAddClassIcon()); 56 | this.append(replacePlaceholder ? node.getRealName() : node.getName()); 57 | if (node.isDir()) { 58 | setIcon(AllIcons.Nodes.Package); 59 | } else { 60 | String suffix = ""; 61 | if (node.getName().contains(".")) { 62 | suffix = node.getName().substring(node.getName().lastIndexOf(".")); 63 | } 64 | setIcon(fileIconMap.getOrDefault(suffix, AllIcons.FileTypes.Text)); 65 | } 66 | } else { 67 | setIcon(AllIcons.FileTypes.Unknown); 68 | } 69 | } 70 | } 71 | }; 72 | 73 | PreviewPanel() { 74 | setLayout(new BorderLayout()); 75 | add(contentPanel); 76 | initPanel(); 77 | } 78 | 79 | public void setReplacePlaceholder(boolean replace) { 80 | if (replace != replacePlaceholder) { 81 | replacePlaceholder = replace; 82 | fileTree.updateUI(); 83 | } 84 | } 85 | 86 | public void setModuleConfigPreview(Module module) { 87 | Module clone = module.clone(); 88 | clone.getTemplate().build(); 89 | setModuleConfig(clone); 90 | } 91 | 92 | public void setModuleConfig(Module module) { 93 | Logger.INSTANCE.i("PreviewPanel", "setModuleConfig"); 94 | 95 | FileTreeNode node = module.getTemplate(); 96 | fileTree.setModel(getTreeModel(node)); 97 | fileTree.doLayout(); 98 | fileTree.updateUI(); 99 | expandAll(fileTree, new TreePath(fileTree.getModel().getRoot()), true); 100 | } 101 | 102 | /** 103 | * init Tree icon, node title, mouse listener 104 | */ 105 | private void initPanel() { 106 | fileIconMap.put(".java", AllIcons.Nodes.Class); 107 | fileIconMap.put(".kt", AllIcons.Nodes.Class); 108 | fileIconMap.put(".xml", AllIcons.FileTypes.Xml); 109 | fileIconMap.put(".gradle", AllIcons.FileTypes.Config); 110 | fileIconMap.put(".gitignore", AllIcons.FileTypes.Config); 111 | fileIconMap.put(".pro", AllIcons.FileTypes.Config); 112 | 113 | fileTree.addMouseListener(getTreeMouseListener()); 114 | 115 | fileTree.setEditable(true); 116 | fileTree.setCellRenderer(treeCellRenderer); 117 | } 118 | 119 | /** 120 | * the JTree mouse listener use for edit tree 121 | * 122 | * @return the mouse adapter 123 | */ 124 | private MouseAdapter getTreeMouseListener() { 125 | 126 | return new MouseAdapter() { 127 | @Override 128 | public void mouseClicked(MouseEvent e) { 129 | super.mouseClicked(e); 130 | if (e.getButton() != MouseEvent.BUTTON3) { 131 | return; 132 | } 133 | int row = fileTree.getRowForLocation(e.getX(), e.getY()); 134 | if (row != -1) { 135 | DefaultMutableTreeNode treeNode = ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent()); 136 | Object[] nodes = treeNode.getUserObjectPath(); 137 | // has selected node and the node is FileTreeNode 138 | if (nodes.length == 0 || !(nodes[0] instanceof FileTreeNode)) { 139 | return; 140 | } 141 | FileTreeNode selectedNode = ((FileTreeNode) nodes[nodes.length - 1]); 142 | showEditMenu(e.getComponent(), selectedNode, e.getX(), e.getY()); 143 | Logger.INSTANCE.d(TAG, selectedNode.toString()); 144 | } 145 | } 146 | }; 147 | } 148 | 149 | private void showEditMenu(Component anchor, FileTreeNode current, int x, int y) { 150 | 151 | JBPopupMenu jbPopupMenu = new JBPopupMenu(); 152 | if (current.isDir()) { 153 | jbPopupMenu.add(getMenuItem("New Directory", e -> { 154 | CreateFileDialog.showForCreate(current, true, fileTreeNode -> addTreeNode(current, fileTreeNode)); 155 | })); 156 | jbPopupMenu.add(getMenuItem("New File", e -> { 157 | CreateFileDialog.showForCreate(current, false, fileTreeNode -> addTreeNode(current, fileTreeNode)); 158 | })); 159 | } 160 | if (!current.isRoot()) { 161 | jbPopupMenu.add(getMenuItem("Rename", e -> { 162 | CreateFileDialog.showForRefactor(current, fileTreeNode -> { 163 | ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent()).setUserObject(fileTreeNode); 164 | fileTree.updateUI(); 165 | }); 166 | })); 167 | jbPopupMenu.add(getMenuItem("Delete", e -> { 168 | if (current.removeFromParent()) { 169 | ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent()).removeFromParent(); 170 | fileTree.updateUI(); 171 | } 172 | })); 173 | } 174 | 175 | jbPopupMenu.show(anchor, x, y); 176 | } 177 | 178 | private void addTreeNode(FileTreeNode parent, FileTreeNode node) { 179 | 180 | parent.getChildren().add(node); 181 | ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent()).add(new DefaultMutableTreeNode(node)); 182 | fileTree.updateUI(); 183 | } 184 | 185 | @NotNull 186 | private TreeModel getTreeModel(FileTreeNode fileTreeNode) { 187 | return new TreeModel(getTree(fileTreeNode)); 188 | } 189 | 190 | // convert FileTreeNode to JTree TreeNode 191 | @NotNull 192 | private DefaultMutableTreeNode getTree(FileTreeNode treeNode) { 193 | DefaultMutableTreeNode result = new DefaultMutableTreeNode(treeNode, true); 194 | 195 | if (treeNode.isDir()) { 196 | treeNode.getChildren().forEach(i -> result.add(getTree(i))); 197 | } 198 | return result; 199 | } 200 | 201 | // expand all nodes 202 | private void expandAll(JTree tree, @NotNull TreePath parent, boolean expand) { 203 | TreeNode node = (TreeNode) parent.getLastPathComponent(); 204 | if (node.getChildCount() >= 0) { 205 | for (Enumeration e = node.children(); e.hasMoreElements(); ) { 206 | TreeNode n = (TreeNode) e.nextElement(); 207 | TreePath path = parent.pathByAddingChild(n); 208 | expandAll(tree, path, expand); 209 | } 210 | } 211 | if (expand) { 212 | tree.expandPath(parent); 213 | } else { 214 | tree.collapsePath(parent); 215 | } 216 | } 217 | 218 | private JBMenuItem getMenuItem(String title, ActionListener listener) { 219 | JBMenuItem jbMenuItem = new JBMenuItem(title); 220 | jbMenuItem.addActionListener(listener); 221 | return jbMenuItem; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/CreateModuleDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/ui/ConfigurePanel.java: -------------------------------------------------------------------------------- 1 | package com.dengzii.plugin.template.ui; 2 | 3 | import com.dengzii.plugin.template.Config; 4 | import com.dengzii.plugin.template.CreateModuleAction; 5 | import com.dengzii.plugin.template.model.Module; 6 | import com.dengzii.plugin.template.utils.PopMenuUtils; 7 | import com.intellij.openapi.fileChooser.FileChooser; 8 | import com.intellij.openapi.fileChooser.FileChooserDescriptor; 9 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 10 | import com.intellij.openapi.vfs.VirtualFile; 11 | import com.intellij.ui.DocumentAdapter; 12 | import com.intellij.ui.components.JBList; 13 | import com.intellij.ui.components.JBTabbedPane; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import javax.swing.*; 17 | import javax.swing.event.DocumentEvent; 18 | import java.awt.*; 19 | import java.awt.event.MouseAdapter; 20 | import java.awt.event.MouseEvent; 21 | import java.io.*; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | *
 28 |  * author : dengzi
 29 |  * e-mail : dengzii@foxmail.com
 30 |  * github : https://github.com/dengzii
 31 |  * time   : 2020/1/8
 32 |  * desc   :
 33 |  * 
34 | */ 35 | public class ConfigurePanel extends JPanel { 36 | 37 | private JPanel contentPane; 38 | 39 | private JTextField tfName; 40 | private JBList listTemplate; 41 | private JPanel panelStructure; 42 | private JPanel panelPlaceholder; 43 | private JPanel panelFileTemp; 44 | private EditToolbar actionbar; 45 | private JCheckBox cbPlaceholder; 46 | private JBTabbedPane tabbedPane; 47 | 48 | private List configs; 49 | private DefaultListModel templateListModel; 50 | 51 | private Module currentConfig; 52 | 53 | private ConfigurePanel panelConfig; 54 | private PreviewPanel panelPreview; 55 | 56 | private EditableTable tablePlaceholder; 57 | private EditableTable tableFileTemp; 58 | 59 | public ConfigurePanel() { 60 | 61 | initComponent(); 62 | loadConfig(); 63 | initData(); 64 | } 65 | 66 | public void cacheConfig() { 67 | if (currentConfig == null) return; 68 | currentConfig.getTemplate().setFileTemplates(tableFileTemp.getPairResult()); 69 | currentConfig.getTemplate().setPlaceholders(tablePlaceholder.getPairResult()); 70 | } 71 | 72 | public void saveConfig() { 73 | Config.INSTANCE.saveModuleTemplates(configs); 74 | } 75 | 76 | private void initComponent() { 77 | setLayout(new BorderLayout()); 78 | add(contentPane); 79 | 80 | panelPreview = new PreviewPanel(); 81 | tablePlaceholder = new EditableTable(new String[]{"Placeholder", "Default Value"}, new Boolean[]{true, true}); 82 | tableFileTemp = new EditableTable(new String[]{"FileName", "Template"}, new Boolean[]{true, true}); 83 | panelStructure.add(panelPreview); 84 | panelPlaceholder.add(tablePlaceholder, BorderLayout.CENTER); 85 | panelFileTemp.add(tableFileTemp, BorderLayout.CENTER); 86 | } 87 | 88 | 89 | private void initData() { 90 | 91 | actionbar.onAdd(new MouseAdapter() { 92 | @Override 93 | public void mouseClicked(MouseEvent e) { 94 | onAddConfig(e); 95 | } 96 | }); 97 | actionbar.onRemove(() -> { 98 | onRemoveConfig(); 99 | return null; 100 | }); 101 | actionbar.onCopy(() -> { 102 | onCopyConfig(); 103 | return null; 104 | }); 105 | actionbar.onExport(() -> { 106 | onExportTemplate(); 107 | return null; 108 | }); 109 | 110 | cbPlaceholder.addChangeListener(e -> { 111 | panelPreview.setReplacePlaceholder(cbPlaceholder.isSelected()); 112 | }); 113 | tabbedPane.addChangeListener(e -> onChangeTab()); 114 | listTemplate.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 115 | listTemplate.addListSelectionListener(e -> { 116 | if (noSelectedConfig()) return; 117 | onConfigSelect(getSelectedConfigIndex()); 118 | }); 119 | listTemplate.addMouseListener(new MouseAdapter() { 120 | @Override 121 | public void mouseClicked(MouseEvent e) { 122 | super.mouseClicked(e); 123 | if (e.getButton() != MouseEvent.BUTTON3) { 124 | return; 125 | } 126 | } 127 | }); 128 | //noinspection unchecked 129 | listTemplate.setModel(templateListModel); 130 | tfName.getDocument().addDocumentListener(new DocumentAdapter() { 131 | @Override 132 | protected void textChanged(@NotNull DocumentEvent documentEvent) { 133 | if (currentConfig != null) 134 | currentConfig.setTemplateName(tfName.getText()); 135 | } 136 | }); 137 | if (templateListModel.size() > 1) { 138 | listTemplate.setSelectedIndex(0); 139 | } 140 | } 141 | 142 | private void onChangeTab() { 143 | if (0 == tabbedPane.getSelectedIndex()) { 144 | currentConfig.getTemplate().setPlaceholders(tablePlaceholder.getPairResult()); 145 | } 146 | if (2 == tabbedPane.getSelectedIndex()) { 147 | Map mergedPlaceholder = currentConfig.getTemplate().getAllPlaceholdersMap(); 148 | List allPlaceholders = currentConfig.getTemplate().getAllPlaceholderInTree(); 149 | allPlaceholders.forEach(s -> { 150 | if (!mergedPlaceholder.containsKey(s)) { 151 | mergedPlaceholder.put(s, ""); 152 | } 153 | }); 154 | tablePlaceholder.setPairData(mergedPlaceholder); 155 | } 156 | panelPreview.setModuleConfig(currentConfig); 157 | } 158 | 159 | private void onAddConfig(MouseEvent e) { 160 | 161 | Map items = new HashMap<>(); 162 | items.put("Empty Template", () -> addModuleTemplate(Module.Companion.getEmpty())); 163 | items.put("Android Application", () -> addModuleTemplate(Module.Companion.getAndroidApplication())); 164 | items.put("Auc Module", () -> addModuleTemplate(Module.Companion.getAucModule())); 165 | items.put("Auc app", () -> addModuleTemplate(Module.Companion.getAucApp())); 166 | items.put("Auc Pkg", () -> addModuleTemplate(Module.Companion.getAucPkg())); 167 | items.put("Auc Export", () -> addModuleTemplate(Module.Companion.getAucExport())); 168 | items.put("Android Mvp", () -> addModuleTemplate(Module.Companion.getAndroidMvp())); 169 | items.put("=> Import From File", this::onImportTemplate); 170 | PopMenuUtils.INSTANCE.create(items).show(actionbar, e.getX(), e.getY()); 171 | } 172 | 173 | private void addModuleTemplate(Module module) { 174 | configs.add(module); 175 | currentConfig = module; 176 | templateListModel.addElement(module.getTemplateName()); 177 | listTemplate.doLayout(); 178 | listTemplate.setSelectedIndex(configs.indexOf(module)); 179 | } 180 | 181 | private void onRemoveConfig() { 182 | if (noSelectedConfig()) { 183 | return; 184 | } 185 | int selectedIndex = getSelectedConfigIndex(); 186 | configs.remove(selectedIndex); 187 | templateListModel.remove(selectedIndex); 188 | listTemplate.doLayout(); 189 | } 190 | 191 | private void onCopyConfig() { 192 | if (noSelectedConfig()) { 193 | return; 194 | } 195 | Module newConfig = currentConfig.clone(); 196 | configs.add(newConfig); 197 | templateListModel.addElement(newConfig.getTemplateName()); 198 | listTemplate.doLayout(); 199 | listTemplate.setSelectedIndex(configs.indexOf(newConfig)); 200 | } 201 | 202 | private void loadConfig() { 203 | configs = Config.INSTANCE.loadModuleTemplates(); 204 | templateListModel = new DefaultListModel<>(); 205 | configs.forEach(module -> templateListModel.addElement(module.getTemplateName())); 206 | if (templateListModel.size() > 0) { 207 | listTemplate.setSelectedIndex(0); 208 | } 209 | } 210 | 211 | private void onExportTemplate() { 212 | 213 | FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); 214 | descriptor.setTitle("Save Template to File"); 215 | VirtualFile vf = FileChooser.chooseFile(descriptor, 216 | CreateModuleAction.Companion.getProject(), 217 | null); 218 | if (vf != null && vf.isWritable()) { 219 | String config = Config.INSTANCE.getGSON().toJson(currentConfig); 220 | File file = new File(vf.getPath(), currentConfig.getTemplateName() + ".json"); 221 | OutputStreamWriter outputStream = null; 222 | try { 223 | outputStream = new OutputStreamWriter(new FileOutputStream(file)); 224 | outputStream.write(config); 225 | } catch (IOException e) { 226 | e.printStackTrace(); 227 | } finally { 228 | if (outputStream != null) { 229 | try { 230 | outputStream.flush(); 231 | outputStream.close(); 232 | } catch (IOException e) { 233 | e.printStackTrace(); 234 | } 235 | } 236 | } 237 | } 238 | } 239 | 240 | private void onImportTemplate() { 241 | 242 | FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor("json"); 243 | descriptor.setTitle("Import Template From File"); 244 | VirtualFile vf = FileChooser.chooseFile(descriptor, 245 | CreateModuleAction.Companion.getProject(), 246 | null); 247 | if (vf != null && vf.exists()) { 248 | File file = new File(vf.getPath()); 249 | BufferedInputStream inputStream = null; 250 | try { 251 | inputStream = new BufferedInputStream(new FileInputStream(file)); 252 | byte[] bytes = new byte[1024]; 253 | int len = 0; 254 | StringBuilder stringBuilder = new StringBuilder(); 255 | while ((len = inputStream.read(bytes)) > 0) { 256 | stringBuilder.append(new String(bytes, 0, len)); 257 | } 258 | Module template = Config.INSTANCE.getGSON().fromJson(stringBuilder.toString(), Module.class); 259 | template.initTemplate(template.getTemplate()); 260 | addModuleTemplate(template); 261 | } catch (IOException e) { 262 | e.printStackTrace(); 263 | } finally { 264 | if (inputStream != null) { 265 | try { 266 | inputStream.close(); 267 | } catch (IOException e) { 268 | e.printStackTrace(); 269 | } 270 | } 271 | } 272 | } 273 | } 274 | 275 | private void onConfigSelect(int index) { 276 | if (currentConfig == configs.get(index)) { 277 | return; 278 | } 279 | if (currentConfig != null) { 280 | cacheConfig(); 281 | } 282 | currentConfig = configs.get(index); 283 | tfName.setText(currentConfig.getTemplateName()); 284 | 285 | // update tree, file template and placeholder table 286 | panelPreview.setModuleConfig(currentConfig); 287 | tableFileTemp.setPairData(currentConfig.getTemplate().getFileTemplates()); 288 | tablePlaceholder.setPairData(currentConfig.getTemplate().getPlaceholders()); 289 | } 290 | 291 | private int getSelectedConfigIndex() { 292 | return listTemplate.getSelectedIndex(); 293 | } 294 | 295 | private boolean noSelectedConfig() { 296 | return getSelectedConfigIndex() == -1; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/com/dengzii/plugin/template/model/FileTreeNode.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package com.dengzii.plugin.template.model 4 | 5 | import com.dengzii.plugin.template.utils.Logger 6 | import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty 7 | import java.io.File 8 | import java.util.* 9 | import java.util.regex.Pattern 10 | 11 | /** 12 | *
 13 |  * author : dengzi
 14 |  * e-mail : denua@foxmail.com
 15 |  * github : https://github.com/dengzii
 16 |  * time   : 2019/1/1
 17 |  * desc   :
 18 | 
*/ 19 | open class FileTreeNode() { 20 | 21 | var name: String = "" 22 | 23 | var isDir = true 24 | var children 25 | set(value) { 26 | realChildren = value 27 | realChildren.forEach { labeledChildren[it.getLabel()] = it } 28 | } 29 | get() = realChildren 30 | 31 | var placeholders: MutableMap? = null 32 | // all placeholder in tree node name 33 | val allPlaceholder = mutableListOf() 34 | 35 | // template for node, higher priority than fileTemplates 36 | private var template: String? = null 37 | 38 | // template of filename 39 | var fileTemplates: MutableMap? = null 40 | 41 | private var realChildren = mutableSetOf() 42 | 43 | // the label composed by 'name' and 'isDir'. 44 | @Transient 45 | private val labeledChildren = mutableMapOf() 46 | @Transient 47 | var parent: FileTreeNode? = null 48 | 49 | companion object { 50 | 51 | var packageNameToDirs = true 52 | private val TAG = FileTreeNode::class.java.simpleName 53 | private val sPathSplitPattern = Pattern.compile("[./]") 54 | private val sPlaceholderPattern = Pattern.compile("\\$\\{([A-Za-z0-9_\\-]+)}") 55 | 56 | fun root(path: String): FileTreeNode { 57 | val root = File(path) 58 | if (!root.isDirectory) { 59 | throw RuntimeException("The root must be a directory.") 60 | } 61 | return with(FileTreeNode(null, path, true)) { 62 | this.parent = this 63 | this 64 | } 65 | } 66 | } 67 | 68 | constructor(parent: FileTreeNode?, name: String, isDir: Boolean) : this() { 69 | this.name = name 70 | this.parent = parent 71 | this.isDir = isDir 72 | name.getPlaceholder().ifNotEmpty { 73 | allPlaceholder.addAll(this) 74 | } 75 | } 76 | 77 | fun removeFromParent(): Boolean { 78 | if (parent != null) { 79 | parent!!.labeledChildren.remove(getLabel()) 80 | parent!!.realChildren.remove(this) 81 | parent = null 82 | return true 83 | } 84 | return false 85 | } 86 | 87 | /** 88 | * add child to this node 89 | * 90 | * @param child The child need be add 91 | * @param override Weather override the node with the same name and type 92 | */ 93 | fun addChild(child: FileTreeNode, override: Boolean = false): Boolean { 94 | if (hasChild(child.getLabel())) { 95 | if (override) { 96 | Logger.d(TAG, "node has already exists $child") 97 | return false 98 | } else { 99 | Logger.w(TAG, "THE SAME CHILE ALREADY EXISTS IN $name: ${child.name}") 100 | } 101 | } 102 | child.parent = this 103 | realChildren.add(child) 104 | labeledChildren[child.getLabel()] = child 105 | return true 106 | } 107 | 108 | fun hasChild(name: String, isDir: Boolean): Boolean { 109 | realChildren.forEach { 110 | if (it.name == name && isDir == it.isDir) { 111 | return true 112 | } 113 | } 114 | return false 115 | } 116 | 117 | fun hasChild(label: String): Boolean { 118 | return labeledChildren.containsKey(label) 119 | } 120 | 121 | fun getChild(name: String, isDir: Boolean): FileTreeNode? { 122 | return labeledChildren["${name}_$isDir"] 123 | } 124 | 125 | /** 126 | * get the real name replace with placeholder 127 | */ 128 | fun getRealName(): String { 129 | return getRealName(this.name) 130 | } 131 | 132 | private fun getRealName(fileName: String = this.name): String { 133 | return if (isDir) { 134 | fileName.replacePlaceholder(getPlaceholderInherit(), false).toLowerCase() 135 | } else { 136 | fileName.replacePlaceholder(getPlaceholderInherit(), true) 137 | } 138 | } 139 | 140 | fun getFileTemplateInherit(): MutableMap? { 141 | return fileTemplates ?: parent?.getFileTemplateInherit() 142 | } 143 | 144 | fun getPlaceholderInherit(): MutableMap? { 145 | return placeholders ?: parent?.getPlaceholderInherit() 146 | } 147 | 148 | fun getTemplateFile(): String? { 149 | return template ?: getFileTemplateInherit()?.get(name) 150 | } 151 | 152 | fun setTemplate(name: String) { 153 | template = name 154 | } 155 | 156 | fun addPlaceholders(placeholders: Map) { 157 | if (this.placeholders == null) { 158 | this.placeholders = mutableMapOf() 159 | } 160 | this.placeholders!!.putAll(placeholders) 161 | } 162 | 163 | 164 | fun addFileTemplates(placeholders: Map) { 165 | if (this.fileTemplates == null) { 166 | this.fileTemplates = mutableMapOf() 167 | } 168 | fileTemplates!!.putAll(placeholders) 169 | } 170 | 171 | /** 172 | * set current node as root. 173 | * the file of specified path must be a directory and exist. 174 | * the root's 'name' is the root path of entire file tree. 175 | * 176 | * @param path: The root directory path of whole file tree 177 | */ 178 | fun setRoot(path: String): FileTreeNode { 179 | if (!File(path).isDirectory) { 180 | throw RuntimeException("The root must be a directory.") 181 | } 182 | parent = this 183 | name = path 184 | return this 185 | } 186 | 187 | /** 188 | * traversal and create file tree, must be called from the root node. 189 | * the existing files will be skipped 190 | */ 191 | fun create() { 192 | if (!isRoot()) { 193 | throw RuntimeException("Must create structure from root node.") 194 | } 195 | createChild() 196 | } 197 | 198 | /** 199 | * traverse and call back each node of the entire file tree. 200 | */ 201 | fun traversal(block: (it: FileTreeNode, depth: Int) -> Unit, depth: Int = 0) { 202 | if (!isDir) return 203 | 204 | realChildren.forEach { 205 | block(it, depth) 206 | it.traversal(block, depth + 1) 207 | } 208 | } 209 | 210 | /** 211 | * create directories tree from a list 212 | * the larger the index, the deeper the directory 213 | * 214 | * @param dirs The dirs list to create tree 215 | * @param parent The parent of current node 216 | */ 217 | fun createDirs(dirs: MutableList, parent: FileTreeNode): FileTreeNode { 218 | if (dirs.isEmpty()) { 219 | return parent 220 | } 221 | // the first dir 222 | val first = dirs.first() 223 | dirs.removeAt(0) 224 | val find = parent.getChild(first, true) 225 | if (find != null) { 226 | return createDirs(dirs, find) 227 | } 228 | val newNode = FileTreeNode(parent, first, true) 229 | parent.addChild(newNode) 230 | // create child dir 231 | return createDirs(dirs, newNode) 232 | } 233 | 234 | /** 235 | * get path of current node. 236 | * if the current node is the root node, it will return absolute path, 237 | * otherwise return relative path. 238 | * 239 | * @return The intact path of current node 240 | */ 241 | fun getPath(): String { 242 | if (isRoot() || parent == null || parent!!.getRealName() == "") { 243 | return getRealName() 244 | } 245 | return parent!!.getPath() + "/" + getRealName() 246 | } 247 | 248 | fun isRoot(): Boolean { 249 | return this == parent || parent == null 250 | } 251 | 252 | /** 253 | * build file tree 254 | * replace placeholder in name 255 | * if this is directory and name is a path or package name 256 | * the name will expand to several dirs 257 | */ 258 | fun build() { 259 | 260 | if (!isDir) { 261 | return 262 | } 263 | name = getRealName() 264 | if (!isRoot()) { 265 | val dir = if (packageNameToDirs) { 266 | name.split(sPathSplitPattern) 267 | } else { 268 | name.split("/") 269 | }.filter { 270 | it.isNotBlank() 271 | }.toMutableList() 272 | 273 | expandDirs(dir) 274 | } 275 | realChildren.forEach { 276 | it.build() 277 | } 278 | } 279 | 280 | /** 281 | * expand the dir list 282 | * all the children of this will move the new dir 283 | */ 284 | private fun expandDirs(dirs: MutableList) { 285 | if (dirs.isEmpty() || dirs.size == 1) { 286 | return 287 | } 288 | this.name = dirs.first() 289 | dirs.removeAt(0) 290 | 291 | var preNode: FileTreeNode = this 292 | var newChild: FileTreeNode 293 | 294 | val oldChildren = realChildren.toMutableList() 295 | oldChildren.forEach { 296 | it.removeFromParent() 297 | } 298 | dirs.forEach { 299 | newChild = FileTreeNode(preNode, it, true) 300 | preNode.addChild(newChild) 301 | preNode = newChild 302 | } 303 | oldChildren.forEach { 304 | preNode.addChild(it) 305 | } 306 | } 307 | 308 | fun getAllPlaceholderInTree(): List { 309 | val result = mutableSetOf() 310 | traversal({ fileTreeNode: FileTreeNode, _: Int -> 311 | result.addAll(fileTreeNode.allPlaceholder) 312 | }) 313 | return result.toList() 314 | } 315 | 316 | fun getAllPlaceholdersMap(): Map { 317 | val result = mutableMapOf() 318 | result.putAll(placeholders.orEmpty()) 319 | traversal({ fileTreeNode: FileTreeNode, _: Int -> 320 | if (fileTreeNode.placeholders != null) { 321 | result.putAll(fileTreeNode.placeholders!!) 322 | } 323 | }) 324 | return result 325 | } 326 | 327 | /** 328 | * get the tree graph of node inherit 329 | * 330 | * @return The tree graph of node 331 | */ 332 | fun getTreeGraph(): String { 333 | return getNodeGraph().toString() 334 | } 335 | 336 | /** 337 | * clone current node, the following fields will be copied: 338 | * name, isDir, fileTemplates, placeHolderMap, children 339 | * 340 | * the parent will not be cloned 341 | */ 342 | fun clone(): FileTreeNode { 343 | val clone = FileTreeNode(null, name, isDir) 344 | clone.fileTemplates = fileTemplates?.toMutableMap() 345 | clone.placeholders = placeholders?.toMutableMap() 346 | realChildren.forEach { 347 | clone.addChild(it.clone()) 348 | } 349 | return clone 350 | } 351 | 352 | private fun removeChild(label: String): FileTreeNode? { 353 | if (hasChild(label)) { 354 | realChildren.remove(labeledChildren[label]) 355 | return labeledChildren.remove(label) 356 | } 357 | return null 358 | } 359 | 360 | private fun getNodeGraph(head: Stack = Stack(), str: StringBuilder = StringBuilder()): StringBuilder { 361 | 362 | head.forEach { 363 | str.append(it) 364 | } 365 | str.append(when (this) { 366 | parent?.realChildren?.last() -> "└─" 367 | parent?.realChildren?.first() -> "├─" 368 | else -> { 369 | when { 370 | parent != null -> "├─" 371 | else -> "" 372 | } 373 | } 374 | }) 375 | str.append(getRealName()) 376 | if (isDir) { 377 | // str.append("\tplaceholder: ").append(placeholders) 378 | } 379 | str.append("\n") 380 | 381 | if (!realChildren.isNullOrEmpty()) { 382 | head.push(when { 383 | parent == null -> "" 384 | parent?.realChildren?.last() != this -> "│\t" 385 | else -> "\t" 386 | }) 387 | realChildren.forEach { 388 | str.append(it.getNodeGraph(head)) 389 | } 390 | head.pop() 391 | } 392 | return str 393 | } 394 | 395 | private fun createChild() { 396 | realChildren.forEach { 397 | val file = File(it.getPath()) 398 | if (file.exists()) { 399 | Logger.d(TAG, "${file.absolutePath} already exists.") 400 | } else { 401 | Logger.d(TAG, "create ${file.absolutePath}") 402 | if (it.isDir) { 403 | file.mkdir() 404 | } else { 405 | file.createNewFile() 406 | } 407 | } 408 | if (it.isDir) { 409 | it.createChild() 410 | } 411 | } 412 | } 413 | 414 | private fun getLabel(): String { 415 | return "${name}_$isDir" 416 | } 417 | 418 | override fun toString(): String { 419 | return "FileTreeNode(path='${getPath()}' isDir=$isDir, fileTemplate=${getTemplateFile()}, children=${children.size})" 420 | } 421 | 422 | protected fun String.getPlaceholder(): List { 423 | val result = mutableListOf() 424 | val nameMatcher = sPlaceholderPattern.matcher(this) 425 | while (nameMatcher.find()) { 426 | result.add(nameMatcher.group(1)) 427 | } 428 | return result 429 | } 430 | 431 | private fun String.replacePlaceholder(placeholders: Map?, capitalize: Boolean = false): String { 432 | var after = this 433 | if (placeholders.isNullOrEmpty()) { 434 | return this 435 | } 436 | placeholders.forEach { (k, v) -> 437 | val replaced = if (capitalize) { 438 | v.toLowerCase().capitalize() 439 | } else { 440 | v 441 | } 442 | after = after.replace("\${$k}", replaced) 443 | } 444 | return after 445 | } 446 | } --------------------------------------------------------------------------------