= {
27 | mutableMapOf(
28 | Pair("PACKAGE_NAME", "com.example"),
29 | Pair("FEATURE_NAME", "Feature1")
30 | )
31 | }
32 |
33 | val APP = FileTreeDsl {
34 | addFileTemplates(aucFileTemplates())
35 | putPlaceholders(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 | putPlaceholders(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 | putPlaceholders(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 | putPlaceholders(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/main/kotlin/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.FileTypeManager
8 | import javax.swing.Icon
9 |
10 |
11 | /**
12 | *
13 | * author : dengzi
14 | * e-mail : dengzixx@gmail.com
15 | * github : https://github.com/dengzii
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.java", getFileIconByExt("java")))
27 | descriptor.addTemplate(getDescriptor("Manifest.xml", getFileIconByExt("xml")))
28 | descriptor.addTemplate(getDescriptor("Application.java", getFileIconByExt("java")))
29 | descriptor.addTemplate(getDescriptor("build.gradle", getFileIconByExt("gradle")))
30 | return descriptor
31 | }
32 |
33 | private fun getFileIconByExt(ext: String): Icon {
34 | return FileTypeManager.getInstance().getFileTypeByExtension(ext).icon ?: AllIcons.FileTypes.Unknown
35 | }
36 |
37 | private fun getDescriptor(templateName: String, icon: Icon?): FileTemplateDescriptor {
38 | return FileTemplateDescriptor(templateName, icon)
39 | }
40 | }
--------------------------------------------------------------------------------
/src/main/kotlin/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 : dengzixx@gmail.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 |
37 | }
38 |
39 | val ANDROID_MVP = FileTreeDsl {
40 | placeholder("MVP_NAME", "Example")
41 |
42 | file("\${MVP_NAME}Contract.java")
43 | file("\${MVP_NAME}View.java")
44 | file("\${MVP_NAME}Presenter.java")
45 | file("\${MVP_NAME}Model.java")
46 | }
47 |
48 | val ANDROID_APP = FileTreeDsl {
49 | placeholder("MODULE_NAME", "app")
50 | placeholder("PACKAGE_NAME", "com.example")
51 |
52 | fileTemplate("MainActivity.java", "Template MainActivity.java")
53 | fileTemplate("AndroidManifest.xml", "Template Manifest.xml")
54 | fileTemplate("build.gradle", "Template build.gradle")
55 |
56 | dir("\${MODULE_NAME}") {
57 | dir("src") {
58 | include(ANDROID_TEST)
59 | dir("main") {
60 | dir("java") {
61 | dir("\${PACKAGE_NAME}") {
62 | dir("\${MODULE_NAME}") {
63 | file("MainActivity.java")
64 | }
65 | }
66 | }
67 | include(ANDROID_RES)
68 | file("AndroidManifest.xml")
69 | }
70 | include(JUNIT_TEST)
71 | }
72 | file(".gitignore")
73 | file("build.gradle")
74 | file("ProGuard.pro")
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/NotificationUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools
2 |
3 | import com.intellij.notification.Notification
4 | import com.intellij.notification.NotificationType
5 | import com.intellij.notification.Notifications
6 | import com.intellij.openapi.Disposable
7 | import com.intellij.openapi.application.ApplicationManager
8 | import com.intellij.openapi.project.Project
9 | import com.intellij.openapi.util.Disposer
10 | import com.intellij.util.Alarm
11 |
12 | /**
13 | *
14 | * author : dengzi
15 | * e-mail : dengzii@foxmail.com
16 | * github : https://github.com/dengzii
17 | * time : 2020/11/18
18 | * desc : Utils about Notification.
19 |
*
20 | */
21 | object NotificationUtils {
22 |
23 | var defaultTitle = "Notification"
24 | var defaultGroupId = "Untitled_Group"
25 |
26 | fun getInfo(msg: String, title: String = defaultTitle, groupId: String = defaultGroupId): Notification {
27 | return Notification(groupId, null, NotificationType.INFORMATION).also {
28 | it.setContent(msg)
29 | it.setTitle(title)
30 | Notifications.Bus.notify(it, null)
31 | }
32 | }
33 |
34 | fun getError(msg: String, title: String = defaultTitle, groupId: String = defaultGroupId): Notification {
35 | return Notification(groupId, null, NotificationType.ERROR).also {
36 | it.setContent(msg)
37 | it.setTitle(title)
38 | it.isImportant = true
39 | Notifications.Bus.notify(it, null)
40 | }
41 | }
42 |
43 | fun getWarning(msg: String, title: String = defaultTitle, groupId: String = defaultGroupId): Notification {
44 | return Notification(groupId, null, NotificationType.WARNING).also {
45 | it.setContent(msg)
46 | it.setTitle(title)
47 | Notifications.Bus.notify(it, null)
48 | }
49 | }
50 |
51 | fun showInfo(msg: String, title: String = defaultTitle): Notification {
52 | return getInfo(msg, title).show()
53 | }
54 |
55 | fun showError(msg: String, title: String = defaultTitle): Notification {
56 | return getError(msg, title).show()
57 | }
58 |
59 | fun showWarning(msg: String, title: String = defaultTitle): Notification {
60 | return getWarning(msg, title).show()
61 | }
62 |
63 | fun Notification.show(expireMillis: Long? = null, project: Project? = null): Notification {
64 | Notifications.Bus.notify(this, project)
65 | if (expireMillis != null) {
66 | val alarm = Alarm(((project ?: ApplicationManager.getApplication()) as Disposable))
67 | alarm.addRequest({
68 | expire()
69 | Disposer.dispose(alarm)
70 | }, expireMillis)
71 | }
72 | return this
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/PersistentConfig.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools
2 |
3 | import com.dengzii.plugin.template.tools.PersistentConfig.ObjectSerializer
4 | import com.google.gson.Gson
5 | import com.google.gson.JsonParseException
6 | import com.intellij.ide.util.PropertiesComponent
7 | import com.intellij.openapi.project.Project
8 | import kotlin.properties.ReadWriteProperty
9 | import kotlin.reflect.KClass
10 | import kotlin.reflect.KProperty
11 |
12 | /**
13 | * Make data persistence more easy.
14 | *
15 | * Delegate field's getter/setter to [PropertiesComponent], the complex type will serialize/deserialize
16 | * use gson by default, you can custom [ObjectSerializer] instead it.
17 | *
18 | * @param propertiesComp The [PropertiesComponent]
19 | * @param project The project use for obtain [PropertiesComponent], ignored when propertiesComp is non-null.
20 | * @param keySuffix The key suffix use for persist.
21 | * @param objectSerializer The serialize/deserialize factory for complex type.
22 | *
23 | * @author https://github.com/dengzii
24 | */
25 | open class PersistentConfig(
26 | propertiesComp: PropertiesComponent? = null,
27 | project: Project? = null,
28 | private val keySuffix: String = "KEY",
29 | private val objectSerializer: ObjectSerializer = JsonObjectSerializer()
30 | ) {
31 |
32 | private val propertiesComponent: PropertiesComponent = propertiesComp ?: if (project == null) {
33 | PropertiesComponent.getInstance()
34 | } else {
35 | PropertiesComponent.getInstance(project)
36 | }
37 |
38 | /**
39 | * Return the delegate for field need to persist.
40 | *
41 | * @param defaultValue The default when load value failed.
42 | * @param keyName The key name use for persist.
43 | *
44 | * @return [PropertyDelegate]
45 | */
46 | inline fun persistentProperty(defaultValue: T, keyName: String? = null): PropertyDelegate {
47 | return when (defaultValue) {
48 | is Int?,
49 | is Boolean?,
50 | is Float?,
51 | is String?,
52 | is Array<*>? -> {
53 | PropertyDelegate(defaultValue, T::class, keyName)
54 | }
55 | else -> PropertyDelegate(defaultValue, T::class, keyName)
56 | }
57 | }
58 |
59 | /**
60 | * The interface defines how to serializer/deserializer the object not primitive type.
61 | */
62 | interface ObjectSerializer {
63 | fun serialize(obj: Any, clazz: KClass<*>): String
64 | fun deserialize(str: String, clazz: KClass<*>): Any
65 | }
66 |
67 | /**
68 | * The json serializer/deserializer use gson.
69 | */
70 | class JsonObjectSerializer : ObjectSerializer {
71 |
72 | private val gson: Gson = Gson().newBuilder()
73 | .setLenient()
74 | .serializeNulls()
75 | .create()
76 |
77 | @Throws(JsonParseException::class)
78 | override fun serialize(obj: Any, clazz: KClass<*>): String {
79 | return gson.toJson(obj)
80 | }
81 |
82 | @Throws(JsonParseException::class)
83 | override fun deserialize(str: String, clazz: KClass<*>): Any {
84 | return gson.fromJson(str, clazz.java)
85 | }
86 | }
87 |
88 | /**
89 | * This class is a delegate class for a field need to persist.
90 | *
91 | * @param default The default when read property failed, the following situation will return [default]:
92 | * 1, The key does not exist.
93 | * 2, Read value successful but serialize/deserialize failed.
94 | * 3, An exception was caught.
95 | * @param clazz The KClass of field.
96 | * @param keyName The key name, the field name is used when null.
97 | */
98 | inner class PropertyDelegate
99 | constructor(
100 | private val default: T,
101 | private val clazz: KClass<*>,
102 | private val keyName: String? = null
103 | ) : ReadWriteProperty {
104 |
105 | @Suppress("UNCHECKED_CAST")
106 | override fun getValue(thisRef: PersistentConfig, property: KProperty<*>): T? {
107 | val keyName = getKeyName(property)
108 | return try {
109 | with(thisRef.propertiesComponent) {
110 | when (clazz) {
111 | Int::class -> getInt(keyName, default as Int)
112 | Boolean::class -> getBoolean(keyName, default as Boolean)
113 | String::class -> getValue(keyName, default as String)
114 | Float::class -> getFloat(keyName, default as Float)
115 | Array::class -> getValues(keyName)
116 | // deserialize to object
117 | else -> {
118 | val v = getValue(keyName)
119 | if (v != null) {
120 | objectSerializer.deserialize(v, clazz)
121 | } else {
122 | default
123 | }
124 | }
125 | }
126 | } as T
127 | } catch (ignore: TypeCastException) {
128 | default
129 | } catch (e: Throwable) {
130 | e.printStackTrace()
131 | default
132 | }
133 | }
134 |
135 | override fun setValue(thisRef: PersistentConfig, property: KProperty<*>, value: T?) {
136 | val keyName = getKeyName(property)
137 | with(thisRef.propertiesComponent) {
138 | when (value) {
139 | is Int -> setValue(keyName, value, default as Int)
140 | is Boolean -> setValue(keyName, value, default as Boolean)
141 | is String -> setValue(keyName, value, default as String)
142 | is Float -> setValue(keyName, value, default as Float)
143 | is Array<*> -> {
144 | val arr = value.filterIsInstance().toTypedArray()
145 | setValues(keyName, arr)
146 | }
147 | null -> unsetValue(keyName)
148 | else -> {
149 | try {
150 | val serialized = objectSerializer.serialize(value, clazz)
151 | setValue(keyName, serialized)
152 | } catch (e: Throwable) {
153 | throw RuntimeException("Type unsupported.", e)
154 | }
155 | }
156 | }
157 | }
158 | }
159 |
160 | private fun getKeyName(property: KProperty<*>): String {
161 | return "${keySuffix}_${keyName ?: property.name}"
162 | }
163 | }
164 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/ActionToolBarUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import com.intellij.openapi.actionSystem.AnAction
4 | import com.intellij.openapi.actionSystem.AnActionEvent
5 | import com.intellij.openapi.actionSystem.impl.ActionButton
6 | import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl
7 | import com.intellij.openapi.actionSystem.impl.PresentationFactory
8 | import com.intellij.util.ui.JBUI
9 | import java.awt.Dimension
10 | import java.awt.FlowLayout
11 | import javax.swing.Icon
12 | import javax.swing.JComponent
13 | import javax.swing.JPanel
14 |
15 | object ActionToolBarUtils {
16 |
17 | fun create(place: String, action: List): JComponent {
18 |
19 | val panel = JPanel()
20 | panel.layout = FlowLayout().also {
21 | it.alignment = FlowLayout.LEFT
22 | it.hgap = 0
23 | it.vgap = 0
24 | }
25 | val factory = PresentationFactory()
26 | action.forEach {
27 | val presentation = factory.getPresentation(it).also { p ->
28 | p.icon = it.icon
29 | p.isEnabled = it.isEnabled
30 | p.description = it.desc
31 | }
32 | val bt = ActionButton(it, presentation, place, Dimension(22, 24))
33 | bt.setIconInsets(JBUI.insets(0))
34 | panel.add(bt)
35 | }
36 | return panel
37 | }
38 |
39 | class Action(var icon: Icon,
40 | var isEnabled: Boolean = true,
41 | var desc: String = "",
42 | var action: () -> Unit) : AnAction() {
43 |
44 | override fun actionPerformed(p0: AnActionEvent) {
45 | action.invoke()
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/ColumnInfo.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import com.intellij.ui.components.JBLabel
4 | import com.intellij.ui.components.JBTextField
5 | import java.awt.Component
6 |
7 |
8 | /**
9 | * The model of the column of [JTable].
10 | *
11 | * @author https://github.com/dengzii
12 | */
13 | open class ColumnInfo- (val colName: String) {
14 |
15 | private var editable: Boolean = false
16 |
17 | open val columnClass: Class<*> get() = String::class.java
18 |
19 | open var columnWidth: Int? = null
20 |
21 | constructor(colName: String, editable: Boolean) : this(colName) {
22 | this.editable = editable
23 | }
24 |
25 | companion object {
26 | fun of(vararg columns: String): List> {
27 | return columns.map { ColumnInfo(it) }
28 | }
29 | }
30 |
31 | /**
32 | * Return the value of column when edit finish, if this column is editable.
33 | *
34 | * @param component The edit component.
35 | * @param oldValue The old value before edit.
36 | * @return A new value.
37 | */
38 | open fun getEditorValue(component: Component, oldValue: Item?, row: Int, col: Int): Item? {
39 | return null
40 | }
41 |
42 | /**
43 | * Whether this column editable.
44 | * @param item The item value.
45 | * @return True is editable, otherwise not.
46 | */
47 | open fun isCellEditable(item: Item?): Boolean {
48 | return editable
49 | }
50 |
51 | /**
52 | * Return a [Component] use for display item value.
53 | * @param item The item value.
54 | * @return The component.
55 | */
56 | open fun getRendererComponent(item: Item?, row: Int, col: Int): Component {
57 | return JBLabel(item?.toString().orEmpty())
58 | }
59 |
60 | /**
61 | * Return a [Component] use for edit this column.
62 | *
63 | * Working just when [isCellEditable] returns true.
64 | *
65 | * @param item The item value.
66 | * @return The component.
67 | */
68 | open fun getEditComponent(item: Item?, row: Int, col: Int): Component {
69 | return JBTextField(item?.toString().orEmpty())
70 | }
71 |
72 | override fun equals(other: Any?): Boolean {
73 | return if (this === other) {
74 | true
75 | } else if (other != null && this.javaClass == other.javaClass) {
76 | val that = other as ColumnInfo<*>
77 | colName == that.colName
78 | } else {
79 | false
80 | }
81 | }
82 |
83 | override fun hashCode(): Int {
84 | return colName.hashCode()
85 | }
86 |
87 | override fun toString(): String {
88 | return colName
89 | }
90 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/ComponentExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import java.awt.Component
4 | import java.awt.event.MouseAdapter
5 | import java.awt.event.MouseEvent
6 |
7 |
8 | /**
9 | * Add right mouse button clicked listener to component.
10 | *
11 | * @param action The click event callback.
12 | */
13 | inline fun Component.onRightMouseButtonClicked(crossinline action: (MouseEvent) -> Unit) {
14 | onMouseButtonClicked(MouseEvent.BUTTON3, action)
15 | }
16 |
17 |
18 | /**
19 | * Add mouse pressed listener.
20 | *
21 | * @param action The click event callback.
22 | */
23 | inline fun Component.onClick(crossinline action: (MouseEvent?) -> Unit){
24 | addMouseListener(object : MouseAdapter() {
25 | override fun mousePressed(e: MouseEvent?) {
26 | super.mousePressed(e)
27 | action(e)
28 | }
29 | })
30 | }
31 |
32 | /**
33 | * Add a listener to component when specified [button] clicked.
34 | *
35 | * @param button The mouse button code.
36 | * @param action The click event callback.
37 | */
38 | inline fun Component.onMouseButtonClicked(button: Int, crossinline action: (MouseEvent) -> Unit) {
39 | addMouseListener(object : MouseAdapter() {
40 | override fun mouseClicked(e: MouseEvent?) {
41 | super.mouseClicked(e)
42 | if (e?.button == button) {
43 | action(e)
44 | }
45 | }
46 | })
47 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/PopMenuUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import com.intellij.openapi.ui.JBMenuItem
4 | import com.intellij.openapi.ui.JBPopupMenu
5 | import java.awt.Component
6 | import java.awt.event.MouseEvent
7 |
8 | /**
9 | *
10 | * author : dengzi
11 | * e-mail : dengzii@foxmail.com
12 | * github : https://github.com/dengzii
13 | * time : 2020/1/16
14 | * desc :
15 | *
16 | */
17 | object PopMenuUtils {
18 |
19 | /**
20 | * Return a [JBPopupMenu] instance with [menu].
21 | *
22 | * @param menu The menu item and click event callback pair.
23 | */
24 | fun create(menu: Map Unit?>): JBPopupMenu {
25 |
26 | val popMenu = JBPopupMenu()
27 | menu.forEach { (k, v) ->
28 | if (k.isBlank()) {
29 | popMenu.addSeparator()
30 | return@forEach
31 | }
32 | val item = JBMenuItem(k)
33 | item.addActionListener {
34 | v.invoke()
35 | }
36 | popMenu.add(item)
37 | }
38 | return popMenu
39 | }
40 |
41 | /**
42 | * Show a [JBPopupMenu] that location depends on [event]'s x, y.
43 | *
44 | * @param event The mouse event.
45 | * @param menu The menu item and click event callback pair.
46 | */
47 | fun show(event: MouseEvent, menu: Map Unit?>) {
48 | create(menu).show(event.source as Component, event.x, event.y)
49 | }
50 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/TableAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import com.intellij.ui.components.JBTextField
4 | import com.intellij.util.ui.AbstractTableCellEditor
5 | import java.awt.Component
6 | import javax.swing.JTable
7 | import javax.swing.table.AbstractTableModel
8 | import javax.swing.table.TableCellRenderer
9 | import javax.swing.table.TableColumn
10 | import javax.swing.table.TableColumnModel
11 |
12 | /**
13 | * ## The JTable Adapter
14 | *
15 | * This is an adapter use for auto manager JTable data model.
16 | *
17 | * @param tableData The table data of each column and each row, you should ensure each row has the same size.
18 | * @param columnInfo The column model express how to display and edit.
19 | *
20 | * @author https://github.com/dengzii
21 | */
22 | open class TableAdapter(private val tableData: MutableList>,
23 | private val columnInfo: MutableList>) : AbstractTableModel() {
24 |
25 | private lateinit var jTable: JTable
26 |
27 | // the map of table column header value and ColumnInfo
28 | // using for find ColumnInfo when don't know column index.
29 | private var columnInfoMap = mutableMapOf>()
30 |
31 | // the table cell adapter
32 | var cellAdapter: CellAdapter = DefaultCellAdapter()
33 |
34 | fun setup(table: JTable) {
35 | jTable = table
36 | table.model = this
37 | table.columnModel = DelegateTableColumnModel(table.columnModel)
38 | }
39 |
40 | fun eachColumn(action: (TableColumn, Int) -> Unit) {
41 | for (i in 0 until jTable.columnModel.columnCount) {
42 | action(jTable.columnModel.getColumn(i), i)
43 | }
44 | }
45 |
46 | /**
47 | * Wrapper of JTable's default column model.
48 | *
49 | * Using for custom column renderer and editor.
50 | */
51 | inner class DelegateTableColumnModel(columnModel: TableColumnModel) : TableColumnModelDecorator(columnModel) {
52 |
53 | override fun getColumn(columnIndex: Int): TableColumn {
54 | val column = super.getColumn(columnIndex)
55 | column.cellRenderer = cellAdapter
56 | column.cellEditor = cellAdapter
57 | columnInfoMap[column.headerValue]?.columnWidth?.takeIf {
58 | it > 0
59 | }?.let {
60 | column.preferredWidth = it
61 | }
62 | return column
63 | }
64 | }
65 |
66 | abstract class CellAdapter : AbstractTableCellEditor(), TableCellRenderer
67 |
68 | /**
69 | * Definition how cell render and edit.
70 | */
71 | inner class DefaultCellAdapter : CellAdapter() {
72 |
73 | private lateinit var editorComponent: Component
74 | private lateinit var rendererComponent: Component
75 | private var value: Any? = null
76 | private var editLocation = Pair(-1, -1)
77 |
78 | override fun getCellEditorValue(): Any? {
79 | val v = columnInfo[editLocation.second].getEditorValue(
80 | editorComponent, value, editLocation.first, editLocation.second)
81 | return v ?: (editorComponent as? JBTextField)?.text ?: value
82 | }
83 |
84 | override fun getTableCellEditorComponent(table: JTable?, value: Any?, isSelected: Boolean,
85 | row: Int, column: Int): Component? {
86 | // temp the old value before edit cell
87 | this.value = value
88 | editLocation = Pair(row, column)
89 | val header = jTable.columnModel.getColumn(column).headerValue
90 | // get the edit component from ColumnInfo
91 | editorComponent = columnInfoMap[header]?.getEditComponent(value, row, column) ?: return null
92 | return editorComponent
93 | }
94 |
95 | override fun getTableCellRendererComponent(table: JTable?, value: Any?, isSelected: Boolean,
96 | hasFocus: Boolean, row: Int, column: Int): Component? {
97 | val header = jTable.columnModel.getColumn(column).headerValue
98 | rendererComponent = columnInfoMap[header]?.getRendererComponent(value, row, column) ?: return null
99 | return rendererComponent
100 | }
101 | }
102 |
103 | override fun fireTableStructureChanged() {
104 | // when table structure changed, the columns may changed.
105 | columnInfoMap.clear()
106 | columnInfo.forEach {
107 | columnInfoMap[it.colName] = it
108 | }
109 | super.fireTableStructureChanged()
110 | }
111 |
112 | override fun getColumnClass(columnIndex: Int): Class<*> {
113 | return columnInfo[columnIndex].columnClass
114 | }
115 |
116 | override fun getColumnName(column: Int): String {
117 | return columnInfo[column].colName
118 | }
119 |
120 | override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean {
121 | return columnInfo[columnIndex].isCellEditable(tableData[rowIndex].getOrNull(columnIndex))
122 | }
123 |
124 | override fun setValueAt(aValue: Any?, rowIndex: Int, columnIndex: Int) {
125 | tableData[rowIndex][columnIndex] = aValue
126 | }
127 |
128 | override fun getRowCount(): Int {
129 | return tableData.size
130 | }
131 |
132 | override fun getColumnCount(): Int {
133 | return columnInfo.size
134 | }
135 |
136 | override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? {
137 | return tableData[rowIndex].getOrNull(columnIndex)
138 | }
139 |
140 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/TableColumnModelDecorator.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import java.util.*
4 | import javax.swing.ListSelectionModel
5 | import javax.swing.event.TableColumnModelListener
6 | import javax.swing.table.TableColumn
7 | import javax.swing.table.TableColumnModel
8 |
9 | open class TableColumnModelDecorator(private val wrapper: TableColumnModel) : TableColumnModel {
10 |
11 | override fun addColumn(aColumn: TableColumn?) {
12 | wrapper.addColumn(aColumn)
13 | }
14 |
15 | override fun removeColumn(column: TableColumn?) {
16 | wrapper.removeColumn(column)
17 | }
18 |
19 | override fun moveColumn(columnIndex: Int, newIndex: Int) {
20 | wrapper.moveColumn(columnIndex, newIndex)
21 | }
22 |
23 | override fun setColumnMargin(newMargin: Int) {
24 | wrapper.columnMargin = newMargin
25 | }
26 |
27 | override fun getColumnCount(): Int {
28 | return wrapper.columnCount
29 | }
30 |
31 | override fun getColumns(): Enumeration {
32 | return wrapper.columns
33 | }
34 |
35 | override fun getColumnIndex(columnIdentifier: Any?): Int {
36 | return wrapper.getColumnIndex(columnIdentifier)
37 | }
38 |
39 | override fun getColumn(columnIndex: Int): TableColumn {
40 | return wrapper.getColumn(columnIndex)
41 | }
42 |
43 | override fun getColumnMargin(): Int {
44 | return wrapper.columnMargin
45 | }
46 |
47 | override fun getColumnIndexAtX(xPosition: Int): Int {
48 | return wrapper.getColumnIndexAtX(xPosition)
49 | }
50 |
51 | override fun getTotalColumnWidth(): Int {
52 | return wrapper.totalColumnWidth
53 | }
54 |
55 | override fun setColumnSelectionAllowed(flag: Boolean) {
56 | wrapper.columnSelectionAllowed = flag
57 | }
58 |
59 | override fun getColumnSelectionAllowed(): Boolean {
60 | return wrapper.columnSelectionAllowed
61 | }
62 |
63 | override fun getSelectedColumns(): IntArray {
64 | return wrapper.selectedColumns
65 | }
66 |
67 | override fun getSelectedColumnCount(): Int {
68 | return wrapper.selectedColumnCount
69 | }
70 |
71 | override fun setSelectionModel(newModel: ListSelectionModel?) {
72 | wrapper.selectionModel = newModel
73 | }
74 |
75 | override fun getSelectionModel(): ListSelectionModel {
76 | return wrapper.selectionModel
77 | }
78 |
79 | override fun addColumnModelListener(x: TableColumnModelListener?) {
80 | wrapper.addColumnModelListener(x)
81 | }
82 |
83 | override fun removeColumnModelListener(x: TableColumnModelListener?) {
84 | wrapper.removeColumnModelListener(x)
85 | }
86 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/XDialog.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.intellij.openapi.util.WindowStateService
5 | import java.awt.Point
6 | import java.awt.Rectangle
7 | import java.awt.Toolkit
8 | import java.awt.event.KeyEvent
9 | import java.awt.event.WindowAdapter
10 | import java.awt.event.WindowEvent
11 | import javax.swing.JComponent
12 | import javax.swing.JDialog
13 | import javax.swing.JPanel
14 | import javax.swing.KeyStroke
15 |
16 | /**
17 | * The class implements some frequently-used feature of [JDialog].
18 | *
19 | * - Add dialog lifecycle callback, like opened, closed etc.
20 | * - Default be modal.
21 | * - Default dispose on dialog close.
22 | * - Persist the size, location, and restore it the next time opened.
23 | *
24 | * @author https://github.com/dengzii
25 | */
26 | abstract class XDialog() : JDialog() {
27 |
28 | var persistDialogState = true
29 | var project: Project? = null
30 |
31 | private val keyWindowStatePersist = this::class.java.name
32 |
33 | constructor(title: String) : this() {
34 | this.title = title
35 | }
36 |
37 | init {
38 | isModal = true
39 | defaultCloseOperation = DISPOSE_ON_CLOSE
40 | addWindowListener(object : WindowAdapter() {
41 |
42 | override fun windowOpened(e: WindowEvent?) {
43 | onOpened()
44 | (contentPane as? JPanel)?.registerKeyboardAction({
45 | onClosing()
46 | hideAndDispose()
47 | }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
48 | super.windowOpened(e)
49 | }
50 |
51 | override fun windowClosing(e: WindowEvent) {
52 | onClosing()
53 | }
54 |
55 | override fun windowClosed(e: WindowEvent?) {
56 | super.windowClosed(e)
57 | onClosed()
58 | }
59 | })
60 | }
61 |
62 | fun getLocationCenterOfScreen(): Point {
63 | val screen = toolkit.screenSize
64 | val x = screen.width / 2 - width / 2
65 | val y = screen.height / 2 - height /2
66 | return Point(x, y)
67 | }
68 |
69 | fun packAndShow() {
70 | pack()
71 | isVisible = true
72 | }
73 |
74 | fun hideAndDispose() {
75 | isVisible = false
76 | dispose()
77 | }
78 |
79 | open fun onOpened() {
80 | if (persistDialogState) {
81 | restoreState()
82 | }
83 | }
84 |
85 |
86 | open fun onClosing() {
87 | if (persistDialogState) {
88 | persistState()
89 | }
90 | }
91 |
92 |
93 | open fun onClosed() {
94 |
95 | }
96 |
97 | /**
98 | * Doing some work about persistent.
99 | */
100 | open fun persistState() {
101 | WindowStateService.getInstance().putBounds(keyWindowStatePersist, Rectangle(x, y, width, height))
102 | }
103 |
104 | /**
105 | * Restore the state of dialog.
106 | */
107 | open fun restoreState() {
108 | val bounds = WindowStateService.getInstance().getBounds(keyWindowStatePersist)
109 | ?: Rectangle().apply {
110 | val screenSize = Toolkit.getDefaultToolkit().screenSize
111 | height = 300
112 | width = 500
113 | y = screenSize.height / 2 - height
114 | x = screenSize.width / 2 - width / 2
115 | }
116 | setBounds(bounds)
117 | }
118 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/XMenu.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import java.awt.event.MouseAdapter
4 | import java.awt.event.MouseEvent
5 | import javax.swing.JMenu
6 | import javax.swing.JMenuItem
7 |
8 | /**
9 | *
10 | * author : dengzi
11 | * e-mail : dengzii@foxmail.com
12 | * github : https://github.com/dengzii
13 | * time : 2019/11/23
14 | * desc :
15 | *
16 | */
17 | class XMenu(name: String?, private val block: XMenu.() -> Unit) : JMenu(name) {
18 |
19 | constructor(name: String?) : this(name, {})
20 |
21 | operator fun invoke(): XMenu {
22 | block(this)
23 | return this
24 | }
25 |
26 | fun addItem(title: String?, onItemClick: OnItemClick) {
27 | val menuItem = JMenuItem(title)
28 | menuItem.onClick {
29 | onItemClick.onItemClick()
30 | }
31 | add(menuItem)
32 | }
33 |
34 | fun item(title: String, onClick: () -> Unit) {
35 | addItem(title, object : OnItemClick {
36 | override fun onItemClick() {
37 | onClick()
38 | }
39 | })
40 | }
41 |
42 | interface OnItemClick {
43 | fun onItemClick()
44 | }
45 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/tools/ui/XMenuBar.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.tools.ui
2 |
3 | import javax.swing.JMenuBar
4 |
5 | class XMenuBar(private val block: XMenuBar.() -> Unit) : JMenuBar() {
6 |
7 | init {
8 | block.invoke(this)
9 | }
10 |
11 | operator fun invoke(): XMenuBar {
12 | block.invoke(this)
13 | return this
14 | }
15 |
16 | fun menu(title: String, block: XMenu.() -> Unit) {
17 | val menu = XMenu(title)
18 | block(menu)
19 | add(menu)
20 | }
21 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/ui/EditToolbar.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui
2 |
3 | import com.intellij.icons.AllIcons
4 | import com.intellij.openapi.actionSystem.ActionGroup
5 | import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl
6 | import java.awt.Dimension
7 | import java.awt.FlowLayout
8 | import java.awt.event.ActionEvent
9 | import java.awt.event.MouseAdapter
10 | import java.awt.event.MouseEvent
11 | import javax.swing.Icon
12 | import javax.swing.JButton
13 | import javax.swing.JPanel
14 |
15 | /**
16 | *
17 | * author : dengzi
18 | * e-mail : dengzixx@gmail.com
19 | * github : https://github.com/dengzii
20 | * time : 2020/1/14
21 | * desc :
22 | *
23 | */
24 | class EditToolbar : JPanel() {
25 |
26 | private val btAdd = JButton("Add")
27 | private val btRemove = JButton("Remove")
28 | private val btCopy = JButton("Copy")
29 | private val btShare = JButton("Export")
30 |
31 | init {
32 | initLayout()
33 | initButton()
34 | }
35 |
36 | fun onAdd(listener: MouseAdapter) {
37 | btAdd.addMouseListener(listener)
38 | }
39 |
40 | fun onAdd(listener: (MouseEvent?) -> Unit) {
41 | btAdd.addMouseListener(object :MouseAdapter(){
42 | override fun mouseClicked(e: MouseEvent?) {
43 | super.mouseClicked(e)
44 | listener(e)
45 | }
46 | })
47 | }
48 |
49 | fun onRemove(listener: () -> Unit) {
50 | btRemove.addActionListener {
51 | listener()
52 | }
53 | }
54 |
55 | fun onCopy(listener: () -> Unit) {
56 | btCopy.addActionListener {
57 | listener()
58 | }
59 | }
60 |
61 | fun onExport(listener: () -> Unit) {
62 | btShare.addActionListener {
63 | listener()
64 | }
65 | }
66 |
67 | private fun initLayout() {
68 |
69 | val flowLayout = FlowLayout()
70 | flowLayout.vgap = 0
71 | flowLayout.hgap = 5
72 | flowLayout.alignment = FlowLayout.LEFT
73 | layout = flowLayout
74 | }
75 |
76 | private fun initButton() {
77 | setIconButton(btAdd, AllIcons.General.Add)
78 | setIconButton(btRemove, AllIcons.General.Remove)
79 | setIconButton(btCopy, AllIcons.General.CopyHovered)
80 | setIconButton(btShare, AllIcons.Actions.Download)
81 | }
82 |
83 | private fun setIconButton(button: JButton, icon: Icon) {
84 | add(button)
85 | button.toolTipText = button.text
86 | button.icon = icon
87 | button.text = ""
88 | button.preferredSize = Dimension(25, 25)
89 | }
90 |
91 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/ui/EditableTable.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui
2 |
3 | import com.dengzii.plugin.template.tools.ui.ActionToolBarUtils
4 | import com.intellij.icons.AllIcons
5 | import com.intellij.ui.components.JBScrollPane
6 | import com.intellij.ui.table.JBTable
7 | import java.awt.BorderLayout
8 | import javax.swing.JComponent
9 | import javax.swing.JPanel
10 | import javax.swing.event.TableModelEvent
11 | import javax.swing.table.DefaultTableModel
12 |
13 | /**
14 | *
15 | * author : dengzi
16 | * e-mail : dengzixx@gmail.com
17 | * github : https://github.com/dengzii
18 | * time : 2020/1/14
19 | * desc :
20 | *
21 | */
22 | class EditableTable(header: Array, colEditable: Array = emptyArray()) : JPanel() {
23 |
24 | private val scrollPanel = JBScrollPane()
25 | private val editToolbar: JComponent
26 | private val table = JBTable()
27 | private var tableModel = TableModel(header, colEditable)
28 |
29 | init {
30 | layout = BorderLayout()
31 | editToolbar = ActionToolBarUtils.create("Edit1", listOf(
32 | ActionToolBarUtils.Action(AllIcons.General.Add) {
33 | tableModel.add()
34 | table.updateUI()
35 | },
36 | ActionToolBarUtils.Action(AllIcons.General.Remove) {
37 | if (table.selectedRow == -1) {
38 | return@Action
39 | }
40 | tableModel.remove(table.selectedRow)
41 | table.updateUI()
42 | },
43 | ActionToolBarUtils.Action(AllIcons.General.CopyHovered) {
44 | if (table.selectedRow == -1) {
45 | return@Action
46 | }
47 | tableModel.copy(table.selectedRow)
48 | table.updateUI()
49 | }
50 | ))
51 | add(editToolbar, BorderLayout.NORTH)
52 | scrollPanel.setViewportView(table)
53 | add(scrollPanel, BorderLayout.CENTER)
54 | table.fillsViewportHeight = true
55 | table.showHorizontalLines = true
56 | table.showVerticalLines = true
57 | table.model = tableModel
58 | table.putClientProperty("terminateEditOnFocusLost", true)
59 | }
60 |
61 | fun addChangeListener(listener: (TableModelEvent) -> Unit) {
62 | tableModel.addTableModelListener {
63 | listener.invoke(it)
64 | }
65 | }
66 |
67 | fun setToolBarVisible(visible: Boolean) {
68 | editToolbar.isVisible = visible
69 | }
70 |
71 | fun setData(data: MutableList>) {
72 | tableModel.setData(data)
73 | tableModel.fireTableDataChanged()
74 | table.updateUI()
75 | }
76 |
77 | fun setPairData(data: Map?) {
78 | val dataList = mutableListOf>()
79 | data?.forEach { (t, u) ->
80 | dataList.add(mutableListOf(t, u))
81 | }
82 | tableModel.setData(dataList)
83 | tableModel.fireTableDataChanged()
84 | table.updateUI()
85 | }
86 |
87 | fun stopEdit() {
88 | if (table.isEditing) {
89 | table.cellEditor.stopCellEditing()
90 | }
91 | }
92 |
93 | fun getPairResult(): MutableMap {
94 | val result = mutableMapOf()
95 | for (i in 0 until table.rowCount) {
96 | val key = table.getValueAt(i, 0).toString()
97 | val value = table.getValueAt(i, 1).toString()
98 | if (key.isBlank()) {
99 | continue
100 | }
101 | result[key] = value
102 | }
103 | return result
104 | }
105 |
106 | internal class TableModel(private val header: Array, private val colEditable: Array) :
107 | DefaultTableModel() {
108 |
109 | fun setData(fileTemp: MutableList>?) {
110 | while (rowCount > 0) {
111 | removeRow(0)
112 | }
113 | if (fileTemp == null) {
114 | return
115 | }
116 | fileTemp.forEach {
117 | addRow(it.toTypedArray())
118 | }
119 | }
120 |
121 | fun add() {
122 | addRow(Array(header.size) { "" })
123 | fireTableDataChanged()
124 | }
125 |
126 | fun remove(row: Int) {
127 | removeRow(row)
128 | fireTableDataChanged()
129 | }
130 |
131 | fun copy(row: Int) {
132 | val r = mutableListOf()
133 | for (c in 0 until columnCount) {
134 | r.add(getValueAt(row, c).toString())
135 | }
136 | addRow(r.toTypedArray())
137 | fireTableDataChanged()
138 | }
139 |
140 | override fun isCellEditable(row: Int, column: Int): Boolean {
141 | return if (colEditable.isEmpty()) super.isCellEditable(row, column) else colEditable[column]
142 | }
143 |
144 | override fun getColumnClass(columnIndex: Int): Class<*> {
145 | return String::class.java
146 | }
147 |
148 | override fun getColumnCount(): Int {
149 | return header.size
150 | }
151 |
152 | override fun getColumnName(column: Int): String {
153 | return if (header.isEmpty()) super.getColumnName(column) else header[column]
154 | }
155 | }
156 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/ui/PreviewPanel.kt:
--------------------------------------------------------------------------------
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.tools.ui.PopMenuUtils
6 | import com.dengzii.plugin.template.tools.ui.onRightMouseButtonClicked
7 | import com.dengzii.plugin.template.utils.Logger
8 | import com.dengzii.plugin.ui.FileDialog
9 | import com.intellij.icons.AllIcons
10 | import com.intellij.openapi.fileTypes.FileTypeManager
11 | import com.intellij.packageDependencies.ui.TreeModel
12 | import com.intellij.ui.ColoredTreeCellRenderer
13 | import com.intellij.ui.components.JBCheckBox
14 | import com.intellij.ui.components.JBScrollPane
15 | import com.intellij.ui.treeStructure.Tree
16 | import org.apache.velocity.VelocityContext
17 | import java.awt.BorderLayout
18 | import java.awt.event.KeyAdapter
19 | import java.awt.event.KeyEvent
20 | import java.awt.event.MouseEvent
21 | import java.util.function.Consumer
22 | import javax.swing.Icon
23 | import javax.swing.JPanel
24 | import javax.swing.JTree
25 | import javax.swing.tree.DefaultMutableTreeNode
26 | import javax.swing.tree.TreeNode
27 | import javax.swing.tree.TreePath
28 |
29 | class PreviewPanel(preview: Boolean) : JPanel() {
30 |
31 | private val fileTree: Tree = Tree()
32 | private val showPlaceholder: JBCheckBox = JBCheckBox("Show placeholders")
33 |
34 | private lateinit var module: Module
35 | private var replacePlaceholder = true
36 | private var onTreeUpdateListener: (() -> Unit)? = null
37 | private var onPlaceholderUpdateListener: (() -> Unit)? = null
38 |
39 | private var context: VelocityContext = VelocityContext().apply {
40 | put("StringUtils", org.apache.velocity.util.StringUtils::class.java)
41 | }
42 |
43 | // render tree node icon, title
44 | private val treeCellRenderer = object : ColoredTreeCellRenderer() {
45 | override fun customizeCellRenderer(
46 | jTree: JTree, value: Any, b: Boolean, b1: Boolean, b2: Boolean, i: Int, b3: Boolean
47 | ) {
48 | if (value is DefaultMutableTreeNode) {
49 | val node = value.userObject
50 | if (node is FileTreeNode) {
51 | val name = if (replacePlaceholder) {
52 | node.getRealName(context)
53 | } else {
54 | node.name
55 | }
56 | this.append(name)
57 | icon = if (node.isDir) {
58 | AllIcons.Nodes.Package
59 | } else {
60 | getIconByFileName(name)
61 | }
62 | }
63 | }
64 | }
65 | }
66 |
67 | init {
68 | layout = BorderLayout()
69 | add(JBScrollPane().apply {
70 | setViewportView(fileTree)
71 | }, BorderLayout.CENTER)
72 | showPlaceholder.isSelected = !replacePlaceholder
73 | if (preview) {
74 | add(showPlaceholder, BorderLayout.NORTH)
75 | showPlaceholder.addChangeListener {
76 | replacePlaceholder = !showPlaceholder.isSelected
77 | setModuleConfig(module)
78 | }
79 | }
80 | initPanel()
81 | }
82 |
83 | fun setPreviewMode(preview: Boolean) {
84 | if (preview != replacePlaceholder) {
85 | replacePlaceholder = preview
86 | showPlaceholder.isSelected = !replacePlaceholder
87 | fileTree.updateUI()
88 | }
89 | }
90 |
91 | fun onPlaceholderUpdate(l: (() -> Unit)?) {
92 | onPlaceholderUpdateListener = l
93 | }
94 |
95 | fun updateTree() {
96 | fileTree.updateUI()
97 | }
98 |
99 | fun setModuleConfig(module: Module) {
100 | Logger.i("PreviewPanel", "setModuleConfig")
101 | this.module = module
102 | fileTree.model = getTreeModel(this.module.template)
103 | context.apply {
104 | put("StringUtils", org.apache.velocity.util.StringUtils::class.java)
105 | module.template.getPlaceholderInherit()?.forEach {
106 | put(it.key, it.value)
107 | }
108 | }
109 | fileTree.doLayout()
110 | fileTree.updateUI()
111 | expandAll(fileTree, TreePath(fileTree.model.root), true)
112 | }
113 |
114 | /**
115 | * Set the callback of tree edit, delete, add event.
116 | */
117 | fun setOnTreeUpdateListener(listener: (() -> Unit)?) {
118 | onTreeUpdateListener = listener
119 | }
120 |
121 | private fun getIconByFileName(fileName: String): Icon {
122 | return FileTypeManager.getInstance().getFileTypeByExtension(fileName.split(".").last()).icon
123 | ?: AllIcons.FileTypes.Text
124 | }
125 |
126 | /**
127 | * init Tree icon, node title, mouse listener
128 | */
129 | private fun initPanel() {
130 | fileTree.onRightMouseButtonClicked { e ->
131 | val row = fileTree.getRowForLocation(e.x, e.y)
132 | if (row != -1) {
133 | val treeNode = fileTree.lastSelectedPathComponent as DefaultMutableTreeNode
134 | val nodes = treeNode.userObjectPath
135 | // has selected node and the node is FileTreeNode
136 | if (nodes.isEmpty() || nodes[0] !is FileTreeNode) {
137 | return@onRightMouseButtonClicked
138 | }
139 | val selectedNode = nodes[nodes.size - 1] as FileTreeNode
140 | showEditMenu(e, selectedNode)
141 | Logger.d("PreviewPanel", selectedNode.toString())
142 | }
143 | }
144 | fileTree.addKeyListener(object : KeyAdapter() {
145 | override fun keyPressed(e: KeyEvent?) {
146 | if (e?.keyCode == KeyEvent.VK_DELETE) {
147 | fileTree.getSelectedNodes(DefaultMutableTreeNode::class.java, null).forEach {
148 | (it.userObject as? FileTreeNode)?.removeFromParent()
149 | it.removeFromParent()
150 | }
151 | updateFileTreeUI()
152 | }
153 | }
154 | })
155 | fileTree.isEditable = true
156 | fileTree.cellRenderer = treeCellRenderer
157 | }
158 |
159 | private fun showEditMenu(anchor: MouseEvent, current: FileTreeNode) {
160 | val item = if (current.isDir) {
161 | val i = mutableMapOf("New Directory" to {
162 | FileDialog.showForCreate(current, true) { fileTreeNode: FileTreeNode ->
163 | addTreeNode(current, fileTreeNode)
164 | }
165 | }, "New File" to {
166 | FileDialog.showForCreate(current, false) { fileTreeNode: FileTreeNode ->
167 | addTreeNode(current, fileTreeNode)
168 | }
169 | })
170 | val parent = current.parent
171 | if (!current.isRoot() && current.children.isNotEmpty() && parent != null) {
172 | i["Remove This Node Only"] = {
173 | current.removeFromParent()
174 | for (child in current.children) {
175 | addTreeNode(parent, child)
176 | }
177 | // TODO optimize update
178 | setModuleConfig(module)
179 | }
180 | }
181 | i
182 | } else {
183 | mutableMapOf()
184 | }
185 | if (!current.isRoot()) {
186 | item["Rename"] = {
187 | val oldP = current.placeholders.orEmpty()
188 | val olds = current.placeholders?.keys?.joinToString()
189 | FileDialog.showForRefactor(current) { fileTreeNode: FileTreeNode ->
190 | (fileTree.lastSelectedPathComponent as DefaultMutableTreeNode).userObject = fileTreeNode
191 | updateFileTreeUI()
192 | val newPlaceholder = fileTreeNode.getPlaceholderInNodeName()
193 | if (olds != newPlaceholder.sorted().joinToString()) {
194 | val appendPlaceholder = newPlaceholder.filter { !oldP.containsKey(it) }.associateWith { "" }
195 | module.template.putPlaceholders(appendPlaceholder)
196 | onPlaceholderUpdateListener?.invoke()
197 | }
198 | }
199 | }
200 | item["Delete"] = {
201 | if (current.removeFromParent()) {
202 | (fileTree.lastSelectedPathComponent as DefaultMutableTreeNode).removeFromParent()
203 | ((fileTree.lastSelectedPathComponent as DefaultMutableTreeNode).userObject as? FileTreeNode)?.removeFromParent()
204 | updateFileTreeUI()
205 | }
206 | }
207 | }
208 | PopMenuUtils.show(anchor, item)
209 | }
210 |
211 | private fun updateFileTreeUI() {
212 | fileTree.updateUI()
213 | onTreeUpdateListener?.invoke()
214 | }
215 |
216 | private fun addTreeNode(parent: FileTreeNode, node: FileTreeNode) {
217 | parent.addChild(node, true)
218 | module.template.putPlaceholders(node.getPlaceholderInNodeName().toTypedArray())
219 | module.template.putPlaceholders(node.placeholders.orEmpty())
220 | module.template.addFileTemplates(node.fileTemplates.orEmpty())
221 | node.fileTemplates?.clear()
222 | node.placeholders?.clear()
223 | (fileTree.lastSelectedPathComponent as DefaultMutableTreeNode).add(DefaultMutableTreeNode(node))
224 | updateFileTreeUI()
225 | }
226 |
227 | private fun getTreeModel(fileTreeNode: FileTreeNode): TreeModel {
228 | return TreeModel(getTree(fileTreeNode))
229 | }
230 |
231 | // convert FileTreeNode to JTree TreeNode
232 | private fun getTree(treeNode: FileTreeNode): DefaultMutableTreeNode {
233 | val result = DefaultMutableTreeNode(treeNode, true)
234 | if (treeNode.isDir) {
235 | treeNode.children.forEach(Consumer { i: FileTreeNode -> result.add(getTree(i)) })
236 | }
237 | return result
238 | }
239 |
240 | // expand all nodes
241 | private fun expandAll(tree: JTree, parent: TreePath, expand: Boolean) {
242 | val node = parent.lastPathComponent as TreeNode
243 | if (node.childCount >= 0) {
244 | val e = node.children()
245 | while (e.hasMoreElements()) {
246 | val n = e.nextElement() as TreeNode
247 | val path = parent.pathByAddingChild(n)
248 | expandAll(tree, path, expand)
249 | }
250 | }
251 | if (expand) {
252 | tree.expandPath(parent)
253 | } else {
254 | tree.collapsePath(parent)
255 | }
256 | }
257 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/dengzii/plugin/template/ui/RealConfigurePanel.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui
2 |
3 | import com.dengzii.plugin.template.Config
4 | import com.dengzii.plugin.template.Config.GSON
5 | import com.dengzii.plugin.template.CreateModuleAction.Companion.project
6 | import com.dengzii.plugin.template.model.FileTreeNode
7 | import com.dengzii.plugin.template.model.Module
8 | import com.dengzii.plugin.template.model.Module.Companion.getAndroidApplication
9 | import com.dengzii.plugin.template.model.Module.Companion.getAndroidMvp
10 | import com.dengzii.plugin.template.model.Module.Companion.getAucApp
11 | import com.dengzii.plugin.template.model.Module.Companion.getAucExport
12 | import com.dengzii.plugin.template.model.Module.Companion.getAucModule
13 | import com.dengzii.plugin.template.model.Module.Companion.getAucPkg
14 | import com.dengzii.plugin.template.model.Module.Companion.getEmpty
15 | import com.dengzii.plugin.template.tools.ui.ActionToolBarUtils
16 | import com.dengzii.plugin.template.tools.ui.PopMenuUtils
17 | import com.dengzii.plugin.ui.ConfigurePanel
18 | import com.intellij.icons.AllIcons
19 | import com.intellij.openapi.fileChooser.FileChooser
20 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
21 | import com.intellij.ui.DocumentAdapter
22 | import java.awt.BorderLayout
23 | import java.io.*
24 | import java.util.function.Consumer
25 | import javax.swing.DefaultListModel
26 | import javax.swing.ListSelectionModel
27 | import javax.swing.event.DocumentEvent
28 |
29 | class RealConfigurePanel : ConfigurePanel() {
30 |
31 | private var configs: MutableList? = null
32 | private var templateListModel: DefaultListModel? = null
33 |
34 | private var currentConfig: Module? = null
35 |
36 | private lateinit var panelPreview: PreviewPanel
37 |
38 | private lateinit var tablePlaceholder: EditableTable
39 | private lateinit var tableFileTemp: EditableTable
40 | private var modified = false
41 |
42 | init {
43 | panelActionBar.add(ActionToolBarUtils.create("ActionBar1", listOf(
44 | ActionToolBarUtils.Action(AllIcons.General.Add, desc = "Create template") {
45 | onAddConfig()
46 | },
47 | ActionToolBarUtils.Action(AllIcons.General.Remove, desc = "Remove selected template") {
48 | onRemoveConfig()
49 | },
50 | ActionToolBarUtils.Action(AllIcons.General.CopyHovered, desc = "Copy selected template") {
51 | onCopyConfig()
52 | },
53 | ActionToolBarUtils.Action(AllIcons.ToolbarDecorator.Import, desc = "Import template from file") {
54 | onImportTemplate()
55 | },
56 | ActionToolBarUtils.Action(AllIcons.Actions.MenuSaveall, desc = "Export selected template") {
57 | onExportTemplate()
58 | }
59 | )))
60 | initComponent()
61 | loadConfig()
62 | initData()
63 | }
64 |
65 | fun isModified() = modified
66 |
67 | fun cacheConfig() {
68 | currentConfig ?: return
69 | currentConfig!!.template.run {
70 | removeAllPlaceHolderInTree()
71 | removeAllTemplateInTree()
72 | fileTemplates = tableFileTemp.getPairResult()
73 | placeholders = tablePlaceholder.getPairResult()
74 | }
75 | }
76 |
77 | fun saveConfig() {
78 | Config.saveModuleTemplates(configs!!)
79 | modified = false
80 | }
81 |
82 | fun createTempFromDir(node: FileTreeNode) {
83 | val module = Module.create(node, node.children.firstOrNull()?.name ?: "-")
84 | addModuleTemplate(module)
85 | }
86 |
87 | private fun initComponent() {
88 | layout = BorderLayout()
89 | add(contentPane)
90 | panelPreview = PreviewPanel(false)
91 | tablePlaceholder = EditableTable(arrayOf("Placeholder", "Default Value"), arrayOf(true, true))
92 | tableFileTemp = EditableTable(arrayOf("FileName", "Template"), arrayOf(true, true))
93 | panelPreview.onPlaceholderUpdate {
94 | tablePlaceholder.setPairData(currentConfig!!.template.getAllPlaceholdersMap().toMutableMap())
95 | }
96 | panelStructure.add(panelPreview)
97 | panelPlaceholder.add(tablePlaceholder, BorderLayout.CENTER)
98 | panelFileTemp.add(tableFileTemp, BorderLayout.CENTER)
99 | }
100 |
101 | private fun initData() {
102 | tableFileTemp.addChangeListener {
103 | modified = true
104 | currentConfig?.template?.removeAllTemplateInTree()
105 | currentConfig?.template?.fileTemplates = tableFileTemp.getPairResult()
106 | }
107 | tablePlaceholder.addChangeListener {
108 | modified = true
109 | currentConfig
110 | currentConfig?.template?.removeAllPlaceHolderInTree()
111 | currentConfig?.template?.placeholders = tablePlaceholder.getPairResult()
112 | }
113 | panelPreview.setPreviewMode(cbPlaceholder.isSelected)
114 | panelPreview.setOnTreeUpdateListener {
115 | modified = true
116 | }
117 | cbPlaceholder.addChangeListener {
118 | panelPreview.setPreviewMode(cbPlaceholder.isSelected)
119 | }
120 | cbLowercaseDir.addChangeListener {
121 | currentConfig?.lowercaseDir = cbLowercaseDir.isSelected
122 | panelPreview.updateTree()
123 | }
124 | cbCapitalizeFile.addChangeListener {
125 | currentConfig?.capitalizeFile = cbCapitalizeFile.isSelected
126 | panelPreview.updateTree()
127 | }
128 | cbEnableVelocity.addChangeListener {
129 | currentConfig?.enableApacheVelocity = cbEnableVelocity.isSelected
130 | panelPreview.updateTree()
131 | }
132 | cbExpandPkgName.addChangeListener {
133 | currentConfig?.packageNameToDir = cbExpandPkgName.isSelected
134 | }
135 | tabbedPane.addChangeListener {
136 | currentConfig ?: return@addChangeListener
137 | when (tabbedPane.selectedIndex) {
138 | 1 -> tableFileTemp.setPairData(currentConfig!!.template.getAllTemplateMap())
139 | 2 -> tablePlaceholder.setPairData(currentConfig!!.template.getAllPlaceholdersMap())
140 | }
141 | panelPreview.setModuleConfig(currentConfig!!)
142 | }
143 | listTemplate.selectionMode = ListSelectionModel.SINGLE_SELECTION
144 | listTemplate.addListSelectionListener {
145 | if (isNoConfigSelected()) return@addListSelectionListener
146 | onConfigListSelected()
147 | }
148 | listTemplate.setModel(templateListModel)
149 | tfName.document.addDocumentListener(object : DocumentAdapter() {
150 | override fun textChanged(documentEvent: DocumentEvent) {
151 | currentConfig?.templateName = tfName.text
152 | }
153 | })
154 | if (templateListModel!!.size() > 1) {
155 | listTemplate.selectedIndex = 0
156 | }
157 | }
158 |
159 | private fun addModuleTemplate(module: Module) {
160 | configs!!.forEach {
161 | if (it.templateName == module.templateName) {
162 | module.templateName += "_New"
163 | }
164 | }
165 | configs!!.add(module)
166 | templateListModel!!.addElement(module.templateName)
167 | listTemplate.doLayout()
168 | listTemplate.selectedIndex = configs!!.indexOf(module)
169 | onConfigListSelected()
170 | }
171 |
172 | private fun onRemoveConfig() {
173 | if (isNoConfigSelected()) {
174 | return
175 | }
176 | val selectedIndex = getSelectedConfigIndex()
177 | configs!!.removeAt(selectedIndex)
178 | templateListModel!!.remove(selectedIndex)
179 | if (!listTemplate.isEmpty) {
180 | panelPreview.isEnabled = true
181 | listTemplate.selectedIndex = 0
182 | onConfigListSelected()
183 | } else {
184 | currentConfig = null
185 | tablePlaceholder.setPairData(mapOf())
186 | tableFileTemp.setPairData(mapOf())
187 | panelPreview.setModuleConfig(getEmpty())
188 | panelPreview.isEnabled = false
189 | }
190 | listTemplate.doLayout()
191 | }
192 |
193 | private fun onCopyConfig() {
194 | if (isNoConfigSelected()) {
195 | return
196 | }
197 | val newConfig = currentConfig!!.clone()
198 | newConfig.templateName += "_Copy"
199 | configs!!.add(newConfig)
200 | templateListModel!!.addElement(newConfig.templateName)
201 | listTemplate.doLayout()
202 | listTemplate.selectedIndex = configs!!.indexOf(newConfig)
203 | }
204 |
205 | private fun loadConfig() {
206 | configs = Config.loadModuleTemplates()
207 | templateListModel = DefaultListModel()
208 | configs!!.forEach(Consumer { module: Module -> templateListModel!!.addElement(module.templateName) })
209 | if (templateListModel!!.size() > 0) {
210 | listTemplate.selectedIndex = 0
211 | }
212 | }
213 |
214 | private fun onConfigListSelected() {
215 | val index = getSelectedConfigIndex()
216 | if (currentConfig == configs!![index]) {
217 | return
218 | }
219 | cacheConfig()
220 | currentConfig = configs!![index]
221 | currentConfig?.apply {
222 | tfName.text = templateName
223 | cbCapitalizeFile.isSelected = capitalizeFile
224 | cbLowercaseDir.isSelected = lowercaseDir
225 | cbExpandPkgName.isSelected = packageNameToDir
226 | cbEnableVelocity.isSelected = enableApacheVelocity
227 | // update tree, file template and placeholder table
228 | panelPreview.setModuleConfig(this)
229 | tableFileTemp.setPairData(template.fileTemplates)
230 | tablePlaceholder.setPairData(template.placeholders)
231 | }
232 | }
233 |
234 | private fun getSelectedConfigIndex() = listTemplate.selectedIndex
235 |
236 | private fun isNoConfigSelected() = getSelectedConfigIndex() == -1
237 |
238 | private fun onAddConfig() {
239 | PopMenuUtils.create(linkedMapOf(
240 | "Empty Template" to { addModuleTemplate(getEmpty()) },
241 | "Android Application" to { addModuleTemplate(getAndroidApplication()) },
242 | "Android Mvp" to { addModuleTemplate(getAndroidMvp()) },
243 | // "Auc Module" to { addModuleTemplate(getAucModule()) },
244 | // "Auc App" to { addModuleTemplate(getAucApp()) },
245 | // "Auc Pkg" to { addModuleTemplate(getAucPkg()) },
246 | // "Auc Export" to { addModuleTemplate(getAucExport()) }
247 | )).show(panelActionBar, panelActionBar.x, panelActionBar.y)
248 | }
249 |
250 | private fun onExportTemplate() {
251 | val descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor()
252 | descriptor.title = "Save template ${currentConfig!!.templateName} to File"
253 | val vf = FileChooser.chooseFile(descriptor, project, null)
254 | if (vf != null && vf.isWritable) {
255 | val config = GSON.toJson(currentConfig)
256 | val file = File(vf.path, currentConfig!!.templateName + ".json")
257 | var outputStream: OutputStreamWriter? = null
258 | try {
259 | outputStream = OutputStreamWriter(FileOutputStream(file))
260 | outputStream.write(config)
261 | } catch (e: IOException) {
262 | e.printStackTrace()
263 | } finally {
264 | try {
265 | outputStream?.flush()
266 | outputStream?.close()
267 | } catch (e: IOException) {
268 | e.printStackTrace()
269 | }
270 |
271 | }
272 | }
273 | }
274 |
275 | private fun onImportTemplate() {
276 | val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor("json")
277 | descriptor.title = "Import Template From File"
278 | val vf = FileChooser.chooseFile(descriptor, project, null)
279 | if (vf != null && vf.exists()) {
280 | val file = File(vf.path)
281 | var inputStream: BufferedInputStream? = null
282 | try {
283 | inputStream = BufferedInputStream(FileInputStream(file))
284 | val bytes = ByteArray(1024)
285 | var len = 0
286 | val stringBuilder = StringBuilder()
287 | while (inputStream.read(bytes).also { len = it } > 0) {
288 | stringBuilder.append(String(bytes, 0, len))
289 | }
290 | val template = GSON.fromJson(stringBuilder.toString(), Module::class.java)
291 | template.initTemplate(template.template)
292 | addModuleTemplate(template)
293 | } catch (e: IOException) {
294 | e.printStackTrace()
295 | } finally {
296 | if (inputStream != null) {
297 | try {
298 | inputStream.close()
299 | } catch (e: IOException) {
300 | e.printStackTrace()
301 | }
302 | }
303 | }
304 | }
305 | }
306 | }
--------------------------------------------------------------------------------
/src/main/kotlin/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 : dengzixx@gmail.com
10 | * github : https://github.com/dengzii
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/main/kotlin/com/dengzii/plugin/template/utils/PluginKit.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.utils
2 |
3 | import com.dengzii.plugin.template.tools.NotificationUtils
4 | import com.intellij.ide.fileTemplates.FileTemplate
5 | import com.intellij.ide.fileTemplates.FileTemplateManager
6 | import com.intellij.ide.fileTemplates.FileTemplateUtil
7 | import com.intellij.ide.fileTemplates.impl.FileTemplateManagerImpl
8 | import com.intellij.openapi.actionSystem.AnActionEvent
9 | import com.intellij.openapi.actionSystem.PlatformDataKeys
10 | import com.intellij.openapi.project.Project
11 | import com.intellij.openapi.project.ProjectManager
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 : dengzixx@gmail.com
24 | * github : https://github.com/dengzii
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 | private inline fun requireProject(action: () -> Unit) {
41 | if (!isProjectValid()) {
42 | NotificationUtils.showError("Plugin GenerateModuleFromTemplate require a project.")
43 | action.invoke()
44 | }
45 | }
46 |
47 | fun isProjectValid(): Boolean {
48 | return project.isOpen && project.isInitialized
49 | }
50 |
51 | fun getCurrentPsiFile(): PsiFile? {
52 | return event.getData(PlatformDataKeys.PSI_FILE)
53 | }
54 |
55 | fun getCurrentPsiDirectory(): PsiDirectory? {
56 | val e = event.getData(PlatformDataKeys.PSI_ELEMENT);
57 | if (e is PsiDirectory) {
58 | return e
59 | }
60 | return null
61 | }
62 |
63 | fun getPsiDirectoryByPath(path: String): PsiDirectory? {
64 | val vf = VirtualFileManager.getInstance().findFileByUrl("") ?: return null
65 | return PsiManager.getInstance(project).findDirectory(vf)
66 | }
67 |
68 | fun getPsiDirectoryByVirtualFile(vf: VirtualFile): PsiDirectory? {
69 | if (!vf.isDirectory) {
70 | Logger.e(TAG, "${vf.path} is not a directory.")
71 | return null
72 | }
73 | return PsiManager.getInstance(project).findDirectory(vf)
74 | }
75 |
76 | fun getVirtualFile(): VirtualFile? {
77 | return event.getData(PlatformDataKeys.VIRTUAL_FILE)
78 | }
79 |
80 | fun createDir(name: String, vf: VirtualFile? = getVirtualFile()): VirtualFile? {
81 | if (vf == null || !vf.isDirectory) {
82 | Logger.e(TAG, "target is null or is not a directory.")
83 | return null
84 | }
85 | if (vf.findChild(name)?.exists() == true) {
86 | return vf.findChild(name)
87 | }
88 | return vf.createChildDirectory(null, name)
89 | }
90 |
91 | fun createFile(name: String, vf: VirtualFile? = getVirtualFile()): Boolean {
92 | if (!checkCreateFile(name, vf)) {
93 | return false
94 | }
95 | return try {
96 | vf!!.createChildData(null, name)
97 | true
98 | } catch (e: Exception) {
99 | e.printStackTrace()
100 | false
101 | }
102 | }
103 |
104 | fun calculateTemplate(templateContent: String): Array? {
105 | requireProject {
106 | return null
107 | }
108 | return FileTemplateUtil.calculateAttributes(templateContent, Properties(), true, project)
109 | }
110 |
111 | @Throws(Throwable::class)
112 | fun createFileFromTemplate(
113 | fileName: String,
114 | templateName: String,
115 | propertiesMap: Map,
116 | directory: VirtualFile
117 | ): PsiElement {
118 |
119 | val fileTemplateManager = FileTemplateManager.getInstance(project)
120 | val properties = Properties(fileTemplateManager.defaultProperties)
121 | propertiesMap.forEach { (t, u) ->
122 | properties.setProperty(t.trim(), u)
123 | }
124 | val template =
125 | fileTemplateManager.getTemplate(templateName) ?: throw IllegalArgumentException("template not found.")
126 | val psiDirectory =
127 | getPsiDirectoryByVirtualFile(directory) ?: throw IllegalArgumentException("directory not found.")
128 | return try {
129 | FileTemplateUtil.createFromTemplate(template, fileName, properties, psiDirectory)
130 | } catch (e: Throwable) {
131 | throw e
132 | }
133 | }
134 |
135 | fun addTemplate(name: String, template: String) {
136 | FileTemplateManager.getInstance(project).addTemplate(name, template)
137 | }
138 |
139 | private fun String.strip(): String {
140 | var result = this
141 | if (startsWith("\${") && endsWith("}")) {
142 | result = result.substring(2, length - 1)
143 | }
144 | return result
145 | }
146 |
147 | private fun checkCreateFile(name: String, vf: VirtualFile?): Boolean {
148 | if (vf == null || !vf.isDirectory) {
149 | Logger.e(TAG, "target is null or is not a directory.")
150 | return false
151 | }
152 | if (vf.findChild(name)?.exists() == true) {
153 | Logger.e(TAG, "directory already exists.")
154 | return false
155 | }
156 | return true
157 | }
158 |
159 | companion object {
160 |
161 | private val TAG = PluginKit::class.java.simpleName
162 |
163 | private lateinit var INSTANCE: PluginKit
164 |
165 | fun init(e: AnActionEvent): PluginKit {
166 | INSTANCE = PluginKit(e)
167 | return INSTANCE
168 | }
169 |
170 | fun getAllFileTemplate(): Array {
171 | val ts = FileTemplateManagerImpl.getDefaultInstance().allTemplates
172 | return ts
173 | }
174 |
175 | fun getFileTemplate(name: String): FileTemplate? =
176 | FileTemplateManagerImpl.getDefaultInstance().getTemplate(name)
177 | }
178 | }
--------------------------------------------------------------------------------
/src/main/kotlin/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 : dengzixx@gmail.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/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.dengzii.plugin.template
3 | Generate Module From Template
4 | 1.6.8
5 | dengzi
6 |
7 | Create a directory structure from a highly customizable template
9 |
10 | Video Tutorial - YouTube
11 |
12 | Feature
13 | 1. Custom directory/file tree structure.
14 | 2. Support placeholders / ApacheVelocity template lang in file name.
15 | 3. Specify file templates from IDE custom/build-in templates.
16 | 4. Passing placeholders to file template as variables.
17 | 5. Export / Import template, share template by file.
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 or ApacheVelocity template lang, 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 | for same name file, you can use path to distinguish them, for example: c/x.js is match a/b/c/x.js
25 | 5. The 'Placeholder' tab's table defines placeholders for replacing filenames and file templates
26 | More information: README
27 |
28 | NOTE
29 | - The existing files will be skipped.
30 | - The Java class file name may depend on ClassName, you better keep the class name and file name consistent, else the file name in the template will not effective.
31 | - The placeholders are best not the same as the built-in property of `Apache Velocity`.
32 | - The dot(.) in the directory name will split and expand as several directories.
33 |
34 | Contribute
35 | GitHub
36 | Any question please create issue
37 |
38 | Screenshot
39 |
40 |
41 |
42 |
43 | ]]>
44 |
45 | Version 1.6.8 (2024/02/21)
47 | 1. Fix: file tree node creation problem.
48 | Version 1.6.5 (2023/12/11)
49 | 1. Update: File template support match by file path.
50 | 2. Fix: ApacheVelocity template lang not work.
51 | Version 1.6.4 (2023/07/06)
52 | 1. Feature: Support ApacheVelocity in file name.
53 |
54 | ]]>
55 |
56 |
57 |
58 |
59 | com.intellij.modules.platform
60 | com.intellij.modules.lang
61 |
62 |
63 |
64 |
67 |
68 |
69 |
70 |
73 |
74 |
75 |
76 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/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 | }
--------------------------------------------------------------------------------
/src/main/resources/fileTemplates/Template Manifest.xml.ft:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/fileTemplates/Template build.gradle.ft:
--------------------------------------------------------------------------------
1 | dependencies {
2 |
3 | }
--------------------------------------------------------------------------------
/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 | // app.expandPath()
17 | // app.expandPkgName(true)
18 | println(app)
19 | println(app.getTreeGraph())
20 | }
21 |
22 | @Test
23 | fun aucModuleTemplateTest() {
24 | val module = AucTemplate.MODULE
25 | module {
26 | placeholder("FEATURE_NAME", "plugin")
27 | placeholder("PACKAGE_NAME", "com.dengzi")
28 | }
29 | module.expandPath()
30 | println(module.getTreeGraph())
31 | }
32 | }
--------------------------------------------------------------------------------
/test/FileTreeDslTest.kt:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import com.dengzii.plugin.template.model.FileTreeDsl
4 | import org.apache.velocity.VelocityContext
5 | import org.apache.velocity.util.StringUtils
6 | import org.junit.Test
7 |
8 | class FileTreeDslTest {
9 |
10 | @Test
11 | fun createSimpleFileTreeTest() {
12 | val tree = FileTreeDsl {
13 | file("file")
14 | dir("dir1") {
15 | file("file2")
16 | dir("dir2") {
17 | file("_${this.name}_file")
18 | dir("dir3") {
19 | dir("dir4")
20 | }
21 | dir("dir5") {
22 | dir("dir6")
23 | dir("dir9")
24 | }
25 | }
26 | file("file3")
27 | }
28 | dir("dir7") {
29 | dir("dir8")
30 | }
31 | }
32 | println(tree.getTreeGraph())
33 | }
34 |
35 | @Test
36 | fun createPackageDirTest() {
37 |
38 | val tree = FileTreeDsl {
39 | dir("src") {
40 | dir("com/example/app") {
41 | file("Main.java")
42 | }
43 | dir("com/example/app1")
44 | }
45 | file("README.md")
46 | }
47 | println(tree.getTreeGraph())
48 | }
49 |
50 |
51 | @Test
52 | fun fileNamePlaceholderTest() {
53 | val tree = FileTreeDsl {
54 | placeholder("FILE_1", "first_file")
55 | placeholder("FILE_2", "second_file")
56 | file("\${FILE_1}.java")
57 | dir("com/example") {
58 | println(name)
59 | file("\${FILE_2}.java")
60 | }
61 | }
62 | println(tree.getTreeGraph())
63 | }
64 |
65 | @Test
66 | fun expandDirectoriesTest() {
67 | val tree = FileTreeDsl {
68 | dir("src") {
69 | dir("com.dengzii.plugin") {
70 | dir("model")
71 | dir("template")
72 | }
73 | dir("test")
74 | }
75 | }
76 | tree.expandPath()
77 | println(tree.getTreeGraph())
78 |
79 | val tree2 = FileTreeDsl {
80 | dir("src") {
81 | dir("com.dengzii.plugin") {
82 | dir("dir1/dir2/") {
83 | dir("model")
84 | dir("template")
85 | }
86 | dir("model")
87 | dir("template")
88 | }
89 | dir("test")
90 | }
91 | }
92 | tree2.expandPath()
93 | println(tree2.getTreeGraph())
94 | }
95 |
96 | @Test
97 | fun expandDirectoriesInPlaceholderTest() {
98 |
99 | val tree = FileTreeDsl {
100 | placeholder("PACKAGE_NAME", "com.dengzii.plugin")
101 | dir("src") {
102 | dir("\${PACKAGE_NAME}") {
103 | dir("model")
104 | dir("template")
105 | }
106 | dir("test")
107 | }
108 | }
109 | tree.expandPath()
110 | println(tree.getTreeGraph())
111 | }
112 |
113 | @Test
114 | fun getAllPlaceholderInTreeNodeNameTest() {
115 | val tree = FileTreeDsl {
116 | placeholder("PACKAGE_NAME", "com.dengzii.plugin")
117 | dir("src") {
118 | dir("\${PACKAGE_NAME}") {
119 | dir("model")
120 | dir("template"){
121 | file("\${TEST}")
122 | file("\${TEST2}")
123 | }
124 | }
125 | dir("test")
126 | }
127 | }
128 | println(tree.getTreeGraph())
129 | }
130 |
131 |
132 | @Test
133 | fun aucPkgTemplateTest() {
134 | val s = VelocityContext().apply {
135 | put("StringUtils", StringUtils::class.java)
136 | put("A","a_b_c")
137 | }
138 | val d = FileTreeDsl {
139 | file("\${StringUtils.removeAndHump(\${A})}")
140 | dir("\${StringUtils.sub(\${A}, \"_\", '')}")
141 | }
142 |
143 | println(d.getTreeGraph(s))
144 | }
145 | }
--------------------------------------------------------------------------------
/test/com/dengzii/plugin/template/model/FileTreeNodeTest.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.model
2 |
3 | import com.dengzii.plugin.template.Config
4 | import junit.framework.TestCase
5 | import org.apache.velocity.VelocityContext
6 | import org.apache.velocity.util.StringUtils
7 | import org.junit.Test
8 |
9 | /**
10 | *
11 | * @author https://github.com/dengzii
12 | */
13 | class FileTreeNodeTest : TestCase() {
14 |
15 | @Test
16 | fun testGetPlaceholders() {
17 | val root = FileTreeNode(null, "\${AAA}_\${BBB}_\${CCC_\${DDD}}", true)
18 | println(root.name)
19 | println(root.getPlaceholderInNodeName().joinToString(","))
20 | root.putPlaceholders(
21 | mapOf(
22 | Pair("AAA", "1"),
23 | Pair("BBB", "2"),
24 | Pair("CCC_4", "3"),
25 | Pair("DDD", "4"),
26 | )
27 | )
28 | println(root.getRealName())
29 | }
30 |
31 | @Test
32 | fun testPlaceholderCycle() {
33 |
34 | }
35 |
36 | @Test
37 | fun testFileTemplate() {
38 | FileTreeDsl {
39 | placeholder("PKG", "com.example")
40 | placeholder("F3", "f3")
41 |
42 | fileTemplate("f1.java", "file1.java")
43 | fileTemplate("f2.java", "ff2.java")
44 | fileTemplate("\${PKG}/f2.java", "file2.java")
45 | fileTemplate("f3.java", "file3.java")
46 |
47 | dir("root") {
48 | dir("a/b") {
49 | dir("\${PKG}") {
50 | file("f1.java")
51 | file("f2.java")
52 | file("\${F3}.java")
53 |
54 | }
55 | dir("c") {
56 | file("f2.java")
57 | }
58 | }
59 | }
60 |
61 | // parse file template file name use placeholder, and support mat
62 |
63 | resolveFileTemplate()
64 | println(getAllTemplateMap())
65 | println(getTreeGraph(templateFile = true))
66 | }
67 | }
68 |
69 | @Test
70 | fun testExpandPath() {
71 | val dsl = FileTreeDsl {
72 | dir("root") {
73 | dir("a/b") {
74 | dir("com.example") {
75 |
76 | }
77 | }
78 | }
79 | }
80 | val m = Module.create(dsl, "test")
81 | m.packageNameToDir = true
82 | println(m.template.getTreeGraph())
83 | m.template.expandPath()
84 | println(m.template.getTreeGraph())
85 | m.template.expandPkgName(true)
86 | println(m.template.getTreeGraph())
87 | }
88 |
89 |
90 | fun testAppcacheVelocity() {
91 | val r = """
92 | !{StringUtils.removeAndHump(FEATURE_NAME.replaceAll("[^\w]", "_"))}Module.kt
93 | """.replace("!", "\$")
94 |
95 | val dsl = FileTreeDsl {
96 | placeholder("FT", "com-example")
97 | file("\${StringUtils.removeAndHump(\${FT.replaceAll(\"[_-]\", \"_\")})}")
98 | file(r)
99 | file("\${FT.replaceAll(\"[-]\", \"_\")}")
100 | }
101 | val module = Config.GSON.fromJson("", Module::class.java)
102 |
103 | val m = Module.create(dsl, "test")
104 | m.enableApacheVelocity = true
105 | m.template.context = VelocityContext().apply {
106 | put("StringUtils", StringUtils::class.java)
107 | put("FT", "com-example")
108 | }
109 | m.template.resolve()
110 | println(m.template.getTreeGraph())
111 | }
112 |
113 | fun testExpandPkg() {
114 | val dsl = FileTreeDsl {
115 | placeholder("FT", "com_demo")
116 | fileTemplate("\${FT.replaceAll(\"[_]\", \".\")}.java", "Example")
117 | fileTemplate("\${StringUtils.removeAndHump(\${FT.replaceAll(\"[_]\", \"-\")})}Module.kt", "ExampleModule")
118 | dir("root") {
119 | dir("\${FT.replaceAll(\"[_]\", \".\")}") {
120 | file("a.txt")
121 | }
122 | file("\${FT.replaceAll(\"[_]\", \".\")}.java")
123 | file("\${StringUtils.removeAndHump(\${FT.replaceAll(\"[_]\", \"-\")})}Module.kt")
124 | }
125 | }
126 |
127 | val m = Module.create(dsl, "test")
128 | m.packageNameToDir = true
129 | m.packageNameToDir = true
130 | m.enableApacheVelocity = true
131 |
132 | m.template.context = VelocityContext().apply {
133 | put("StringUtils", StringUtils::class.java)
134 | m.template.getPlaceholderInherit()?.forEach { (k, v) ->
135 | put(k, v)
136 | }
137 | }
138 | println("\ntemplates")
139 | m.template.placeholders?.forEach { (k, v) ->
140 | println("$k, $v")
141 | }
142 | println("\nplaceholders")
143 | m.template.fileTemplates?.forEach { (k, v) ->
144 | println("$k, $v")
145 | }
146 | println(m.template.getTreeGraph(templateFile = true))
147 |
148 | m.template.resolveTreeFileName()
149 | m.template.resolveFileTemplate()
150 |
151 | println("\ntemplates")
152 | m.template.placeholders?.forEach { (k, v) ->
153 | println("$k, $v")
154 | }
155 | println("\nplaceholders")
156 | m.template.fileTemplates?.forEach { (k, v) ->
157 | println("$k, $v")
158 | }
159 |
160 | m.template.expandPkgName(true)
161 | println(m.template.getTreeGraph(templateFile = true))
162 | }
163 | }
--------------------------------------------------------------------------------
/test/com/dengzii/plugin/template/model/ModuleTest.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.model
2 |
3 | import com.dengzii.plugin.template.Config
4 | import junit.framework.TestCase
5 |
6 | /**
7 | *
8 | * @author https://github.com/dengzii
9 | */
10 | class ModuleTest : TestCase() {
11 |
12 | fun testPersist() {
13 | val t = FileTreeDsl {
14 | dir("root") {
15 |
16 | }
17 | }
18 | val create = Module.create(t, "test")
19 | create.lowercaseDir = true
20 | val j = Config.GSON.toJson(create)
21 | println(j)
22 | }
23 | }
--------------------------------------------------------------------------------