diff --git a/.idea/.name b/.idea/.name index 2aa6b92..7864501 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -ActionModelsGenerator \ No newline at end of file +DynamicFormTools \ No newline at end of file diff --git a/.intellijPlatform/coroutines-javaagent-legacy.jar b/.intellijPlatform/coroutines-javaagent-legacy.jar index 8a73742..06fd3bd 100644 Binary files a/.intellijPlatform/coroutines-javaagent-legacy.jar and b/.intellijPlatform/coroutines-javaagent-legacy.jar differ diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledModule-intellij.json.split-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledModule-intellij.json.split-IU-251.23774.435.xml new file mode 100644 index 0000000..46449ff --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledModule-intellij.json.split-IU-251.23774.435.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-JavaScript-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-JavaScript-IU-251.23774.435.xml new file mode 100644 index 0000000..7744a29 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-JavaScript-IU-251.23774.435.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.css-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.css-IU-251.23774.435.xml new file mode 100644 index 0000000..00f1892 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.css-IU-251.23774.435.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.java-i18n-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.java-i18n-IU-251.23774.435.xml new file mode 100644 index 0000000..c030a03 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.java-i18n-IU-251.23774.435.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee-IU-251.23774.435.xml new file mode 100644 index 0000000..3d0455e --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee-IU-251.23774.435.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee.el-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee.el-IU-251.23774.435.xml new file mode 100644 index 0000000..55e4ba8 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee.el-IU-251.23774.435.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee.web-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee.web-IU-251.23774.435.xml new file mode 100644 index 0000000..f154b58 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.javaee.web-IU-251.23774.435.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.jsp-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.jsp-IU-251.23774.435.xml new file mode 100644 index 0000000..45e1127 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.jsp-IU-251.23774.435.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.modules.json-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.modules.json-IU-251.23774.435.xml new file mode 100644 index 0000000..2f11df2 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.modules.json-IU-251.23774.435.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.platform.images-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.platform.images-IU-251.23774.435.xml new file mode 100644 index 0000000..55512cd --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.platform.images-IU-251.23774.435.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.properties-IU-251.23774.435.xml b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.properties-IU-251.23774.435.xml new file mode 100644 index 0000000..8a4cfd2 --- /dev/null +++ b/.intellijPlatform/localPlatformArtifacts/IU-251.23774.435/bundledPlugin-com.intellij.properties-IU-251.23774.435.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.intellijPlatform/self-update.lock b/.intellijPlatform/self-update.lock index 3659e5d..42345ef 100644 --- a/.intellijPlatform/self-update.lock +++ b/.intellijPlatform/self-update.lock @@ -1 +1 @@ -2026-04-03 \ No newline at end of file +2026-04-07 \ No newline at end of file diff --git a/DevResources/Message.java b/DevResources/Message.java new file mode 100755 index 0000000..128a5f4 --- /dev/null +++ b/DevResources/Message.java @@ -0,0 +1,274 @@ +package sdk.i18n; + +import com.apps.Constants; +import com.apps.SystemFactory; +import jakarta.servlet.ServletContext; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.input.SAXBuilder; +import sdk.config.ApiConfig; +import sdk.json.JSONObject; +import sdk.context.AppContext; +import sdk.utils.JUtils; +import sdk.utils.SDKLogger; + +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +public class Message { + private LinkedHashMap> msgMaps; + private SystemFactory system; + private LinkedHashMap rescMessage; + private final SDKLogger logger; + + public Message() { + rescMessage = new LinkedHashMap<>(); + logger = new SDKLogger("i18N-Message"); + } + + public Message(SystemFactory factory) { + this.system = factory; + AppContext app = factory.getAppInstance(); + logger = new SDKLogger("i18N-Message"); + + msgMaps = (LinkedHashMap>) app.getAttribute(Constants._APP_MESSAGE); + + if (msgMaps != null) { + for (String key : msgMaps.keySet()) { + if (msgMaps.get(key) == null) { + msgMaps = null; + break; + } + } + } + + if (msgMaps == null || msgMaps.isEmpty() || msgMaps.containsValue(null)) { + msgMaps = new LinkedHashMap<>(); + ApiConfig msgAPI = factory.getApi("i18n"); + for (ApiConfig.ApiParameter param : msgAPI.getParameters().values()) { + String rescFile = param.getValue(); + if (!"".equals(rescFile)) { + LinkedHashMap rescMsg = readResource(rescFile); + String lang = param.getName().toUpperCase(); + msgMaps.put(lang, rescMsg); + } + } + app.setAttribute(Constants._APP_MESSAGE, msgMaps); + } + + String lang = factory.getLanguage("TH"); + rescMessage = msgMaps.get(lang.toUpperCase()); + + if (rescMessage == null) { + rescMessage = msgMaps.get("TH"); + } + } + + public LinkedHashMap getLang(String lang) { + return msgMaps.get(lang.toUpperCase()); + } + + public String parseExp(String inStr) { + String result = inStr; + +// String[] ssnToken = JUtils.getRegxToken(inStr, "@\\{[\\s\\S]+?\\}"); +// int idxFactor = 2; +// if (ssnToken.length > 0) { +// DTO_SYSTEM_CONFIG sysData= system.getSysData(); +// for (String token : ssnToken) { +// String ssnKey = token.substring(0, token.length() - 1).substring(idxFactor).trim(); +// String ssnValue = sysData.getValue(ssnKey, ""); +// if (!ssnValue.isBlank()) { +// result = result.replace(token, ssnValue); +// } +// } +// } + + String[] ssnToken = JUtils.getRegxToken(result, "@[M]\\{[\\s\\S]+?\\}"); + int idxFactor = 3; + + for (String token : ssnToken) { + String msgKey = token.substring(0, token.length() - 1).substring(idxFactor).trim(); + result = result.replace(token, this.get(msgKey)); + } + return result.replaceAll("\\|\\|",""); + } + + private LinkedHashMap readResource(String fileName) { + LinkedHashMap result = new LinkedHashMap<>(); + SAXBuilder formBuilder = new SAXBuilder(); + formBuilder.setFeature("http://xml.org/sax/features/validation", false); + formBuilder.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); + formBuilder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + ServletContext context = system.getAppContext(); + InputStream rescFile = context.getResourceAsStream(fileName); + + try { + Document rescDoc = formBuilder.build(rescFile); + Element rescRoot = rescDoc.getRootElement(); + List msgList = rescRoot.getChildren("entry"); + for (Element elMsg : msgList) { + String key = elMsg.getAttributeValue("key", ""); + String value = elMsg.getTextTrim(); + if (!"".equals(key)) { + value = parseExp(value); + result.put(key.toLowerCase(), value); + } + } + } catch (Exception ex) { + result = null; + logger.error(ex); + } + return result; + } + + public String get(String key, String locale) { + if (key.startsWith("#")) { + return key.substring(1); + } + + LinkedHashMap message = null; + if (locale != null) { + message = getLang(locale.toUpperCase()); + } + if (message == null) { + message = rescMessage; + } + if (!message.isEmpty()) { + boolean useDot = false; + String msgKey = key.replaceAll("-", "_").toLowerCase(); + String[] keys = msgKey.split(" "); + if (keys.length == 1) { + keys = msgKey.split("\\+"); + useDot = keys.length > 1; + } + String result; + if (keys.length > 1) { + result = msgKey; + int seq = 0; + for (String id : keys) { + if (id.isBlank()) continue; + String value = message.get(id); + if (value != null) { + result = result.replace((seq > 0 && useDot) ? "+" + id : id, value); + } else { + value = this.get(id,locale); + result = result.replace(id, value); + } + seq++; + } + } else { + result = message.get(msgKey.toLowerCase()); + if (result == null) { + result = JUtils.beautify(msgKey.replace("_", " ")); + } + } + return result; + } else { + return key; + } + } + + public String get(String key) { + if (key.startsWith("#")||key.startsWith("=")) { + return key.substring(1); + } + + if (!rescMessage.isEmpty()) { + boolean useDot = false; + String msgKey = key.toLowerCase(); + String[] keys = key.split(" "); + if (keys.length == 1) { + keys = msgKey.split("\\+"); + useDot = keys.length > 1; + } + + if (keys.length == 1) { + keys = msgKey.split("\\|\\|"); + } + + String result; + if (keys.length > 1) { + result = msgKey; + int seq = 0; + for (String id : keys) { + String value; + String idKey = id; + + if (id.isBlank()) continue; + + id = id.replaceAll("-", "_").toLowerCase(); + + if (idKey.startsWith("#")) { + result = result.replace(id, idKey.substring(1)); + } else { + if (!id.equals("-")) { + value = rescMessage.get(id); + } else { + value = "-"; + } + + if (value != null) { + result = result.replace((seq > 0 && useDot) ? "+" + id : id, value); + } else { + value = this.get(id); + result = result.replace((seq > 0 && useDot) ? "+" + id : id, value); + result = result.replace(id, value); + } + } + seq ++; + } + } else { + msgKey = key.replaceAll("-","_").toLowerCase(); + result = rescMessage.get(msgKey.toLowerCase()); + if (result == null) { + if (!msgKey.contains(".")) { + result = rescMessage.get("sys."+msgKey.toLowerCase()); + } else { + result = msgKey.substring(msgKey.indexOf(".")+1); + if (result.isBlank()) { + result = msgKey; + } + result = JUtils.beautify(result.replace("_", " ")); + } + result = (result == null) ? JUtils.beautify(msgKey.replace("_", " ")) : result; + } + } + return result.replaceAll("\\|\\|",""); + } else { + return key; + } + } + + public String buildJson(String lang) { + LinkedHashMap langMsg = msgMaps.get(lang.toUpperCase()); + if (langMsg == null) { + msgMaps.get("TH"); + } + JSONObject jsResult = new JSONObject(); + SortedSet keys = new TreeSet<>(langMsg.keySet()); + for (String key : keys) { + String value = langMsg.get(key); + jsResult.put(key,value); + } + return jsResult.toString(); + } + + public String buildObject(String lang) { + LinkedHashMap langMsg = msgMaps.get(lang.toUpperCase()); + if (langMsg == null) { + langMsg = msgMaps.get("TH"); + } + if (langMsg == null) { + langMsg = msgMaps.firstEntry().getValue(); + } + + JSONObject jsResult = new JSONObject(); + langMsg.forEach(jsResult::put); + return jsResult.toString(4); + } +} diff --git a/GEMINI.md b/GEMINI.md index 82bd944..19af356 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,8 +1,8 @@ -# ActionModelsGenerator +# DynamicFormTools ## Project Overview -The `ActionModelsGenerator` is an IntelliJ Platform Plugin designed to automate the generation of Java `ActionBean` and Data Transfer Object (DTO) classes from a database schema. This plugin integrates directly into the IntelliJ IDE, allowing developers to select a database table and generate corresponding Java classes based on predefined FreeMarker templates. +The `DynamicFormTools` is an IntelliJ Platform Plugin designed to automate the generation of Java `ActionBean` and Data Transfer Object (DTO) classes from a database schema. This plugin integrates directly into the IntelliJ IDE, allowing developers to select a database table and generate corresponding Java classes based on predefined FreeMarker templates. **Key Features:** * **Database Schema Introspection:** Utilizes IntelliJ's database tools to read table and column metadata. diff --git a/README.md b/README.md index 82bd944..19af356 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# ActionModelsGenerator +# DynamicFormTools ## Project Overview -The `ActionModelsGenerator` is an IntelliJ Platform Plugin designed to automate the generation of Java `ActionBean` and Data Transfer Object (DTO) classes from a database schema. This plugin integrates directly into the IntelliJ IDE, allowing developers to select a database table and generate corresponding Java classes based on predefined FreeMarker templates. +The `DynamicFormTools` is an IntelliJ Platform Plugin designed to automate the generation of Java `ActionBean` and Data Transfer Object (DTO) classes from a database schema. This plugin integrates directly into the IntelliJ IDE, allowing developers to select a database table and generate corresponding Java classes based on predefined FreeMarker templates. **Key Features:** * **Database Schema Introspection:** Utilizes IntelliJ's database tools to read table and column metadata. diff --git a/build.gradle.kts b/build.gradle.kts index 399d521..3a6e6af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("org.jetbrains.intellij.platform") version "2.7.0" } -group = "com.sdk.generators.actionmodels.v3" +group = "com.sdk.dynform.tools" version = "2.1.2" repositories { @@ -24,6 +24,8 @@ dependencies { // Add necessary plugin dependencies for compilation here, example: bundledPlugin("com.intellij.java") bundledPlugin("com.intellij.database") + bundledPlugin("com.intellij.jsp") + bundledPlugin("JavaScript") } implementation("org.freemarker:freemarker:2.3.32") diff --git a/package-lock.json b/package-lock.json index c0b0e27..fb7f9bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "ActionModelsGenerator", + "name": "DynamicFormTools", "lockfileVersion": 3, "requires": true, "packages": {} diff --git a/settings.gradle.kts b/settings.gradle.kts index e7833a6..fe396d8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "ActionModelsGenerator" \ No newline at end of file +rootProject.name = "DynamicFormTools" \ No newline at end of file diff --git a/src/main/java/com/sdk/generators/GUtils.java b/src/main/java/com/sdk/dynform/helper/GUtils.java similarity index 96% rename from src/main/java/com/sdk/generators/GUtils.java rename to src/main/java/com/sdk/dynform/helper/GUtils.java index ee2ffe2..5a8494c 100644 --- a/src/main/java/com/sdk/generators/GUtils.java +++ b/src/main/java/com/sdk/dynform/helper/GUtils.java @@ -1,4 +1,4 @@ -package com.sdk.generators; +package com.sdk.dynform.helper; import com.intellij.database.model.DasColumn; import com.intellij.database.model.DasObject; @@ -139,15 +139,15 @@ public class GUtils { ApplicationManager.getApplication().invokeLater(() -> Messages.showErrorDialog(project, message, "Generation Failed")); NotificationGroupManager.getInstance() // This ID should be registered in your plugin.xml - .getNotificationGroup("Action-Models-Generator-Notification") - .createNotification("ActionModels Generator", message, NotificationType.ERROR) + .getNotificationGroup("Dynamic-Form-Tools-Notification") + .createNotification("ActionModels generator", message, NotificationType.ERROR) .notify(project); } public static void showInfo(Project project, String title, String content) { NotificationGroupManager.getInstance() // This ID should be registered in your plugin.xml - .getNotificationGroup("Action-Models-Generator-Notification") + .getNotificationGroup("Dynamic-Form-Tools-Notification") .createNotification(title, content, NotificationType.INFORMATION) .notify(project); } diff --git a/src/main/java/com/sdk/generators/actionmodels/GenerateAction.java b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateAction.java similarity index 98% rename from src/main/java/com/sdk/generators/actionmodels/GenerateAction.java rename to src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateAction.java index aa65808..dff9ffa 100644 --- a/src/main/java/com/sdk/generators/actionmodels/GenerateAction.java +++ b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateAction.java @@ -1,4 +1,4 @@ -package com.sdk.generators.actionmodels; +package com.sdk.dynform.tools.generators.actionmodels; import com.intellij.database.model.DasColumn; import com.intellij.database.model.ObjectKind; @@ -18,7 +18,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileWrapper; import com.intellij.psi.PsiElement; import com.intellij.util.containers.JBIterable; -import com.sdk.generators.GUtils; +import com.sdk.dynform.helper.GUtils; import org.jetbrains.annotations.NotNull; import java.io.IOException; diff --git a/src/main/java/com/sdk/generators/actionmodels/GenerateBeanAction.java b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateBeanAction.java similarity index 96% rename from src/main/java/com/sdk/generators/actionmodels/GenerateBeanAction.java rename to src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateBeanAction.java index ea31905..0ee9ef6 100644 --- a/src/main/java/com/sdk/generators/actionmodels/GenerateBeanAction.java +++ b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateBeanAction.java @@ -1,4 +1,4 @@ -package com.sdk.generators.actionmodels; +package com.sdk.dynform.tools.generators.actionmodels; import com.intellij.database.psi.DbTable; import com.intellij.openapi.actionSystem.AnAction; @@ -15,7 +15,7 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; -import com.sdk.generators.GUtils; +import com.sdk.dynform.helper.GUtils; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/src/main/java/com/sdk/generators/actionmodels/GenerateBeanActionV3.java b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateBeanActionV3.java similarity index 96% rename from src/main/java/com/sdk/generators/actionmodels/GenerateBeanActionV3.java rename to src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateBeanActionV3.java index d5d9966..8e3a0be 100644 --- a/src/main/java/com/sdk/generators/actionmodels/GenerateBeanActionV3.java +++ b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateBeanActionV3.java @@ -1,4 +1,4 @@ -package com.sdk.generators.actionmodels; +package com.sdk.dynform.tools.generators.actionmodels; import com.intellij.database.psi.DbTable; import com.intellij.openapi.actionSystem.AnAction; @@ -15,7 +15,7 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; -import com.sdk.generators.GUtils; +import com.sdk.dynform.helper.GUtils; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/src/main/java/com/sdk/generators/actionmodels/GenerateDatasetAction.java b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateDatasetAction.java similarity index 96% rename from src/main/java/com/sdk/generators/actionmodels/GenerateDatasetAction.java rename to src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateDatasetAction.java index 74a1565..ffc9929 100644 --- a/src/main/java/com/sdk/generators/actionmodels/GenerateDatasetAction.java +++ b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GenerateDatasetAction.java @@ -1,4 +1,4 @@ -package com.sdk.generators.actionmodels; +package com.sdk.dynform.tools.generators.actionmodels; import com.intellij.database.psi.DbTable; import com.intellij.openapi.actionSystem.AnAction; @@ -15,7 +15,7 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; -import com.sdk.generators.GUtils; +import com.sdk.dynform.helper.GUtils; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; diff --git a/src/main/java/com/sdk/generators/actionmodels/GeneratorServices.java b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GeneratorServices.java similarity index 99% rename from src/main/java/com/sdk/generators/actionmodels/GeneratorServices.java rename to src/main/java/com/sdk/dynform/tools/generators/actionmodels/GeneratorServices.java index 60a7fe2..7f19e24 100644 --- a/src/main/java/com/sdk/generators/actionmodels/GeneratorServices.java +++ b/src/main/java/com/sdk/dynform/tools/generators/actionmodels/GeneratorServices.java @@ -1,4 +1,4 @@ -package com.sdk.generators.actionmodels; +package com.sdk.dynform.tools.generators.actionmodels; import com.intellij.database.model.DasColumn; import com.intellij.database.model.DasTableKey; @@ -9,7 +9,7 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.JBIterable; -import com.sdk.generators.GUtils; +import com.sdk.dynform.helper.GUtils; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; diff --git a/src/main/java/com/sdk/dynform/tools/helper/DynFormCompletionContributor.java b/src/main/java/com/sdk/dynform/tools/helper/DynFormCompletionContributor.java new file mode 100644 index 0000000..bf32f8e --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/helper/DynFormCompletionContributor.java @@ -0,0 +1,76 @@ +package com.sdk.dynform.tools.helper; + +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class DynFormCompletionContributor extends CompletionContributor { + public DynFormCompletionContributor() { + extend(CompletionType.BASIC, PlatformPatterns.psiElement().withParent(PsiLiteralExpression.class), + new CompletionProvider() { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + @NotNull ProcessingContext context, + @NotNull CompletionResultSet resultSet) { + PsiElement position = parameters.getPosition(); + PsiLiteralExpression literal = PsiTreeUtil.getParentOfType(position, PsiLiteralExpression.class); + if (literal == null) return; + + PsiNewExpression newExp = getNewDynFormExpression(literal); + if (newExp != null) { + PsiExpressionList argList = newExp.getArgumentList(); + if (argList != null) { + PsiExpression[] args = argList.getExpressions(); + // Suggest modules at index 3 + if (args.length >= 4 && args[3] == literal) { + List modules = DynFormPathUtils.getAllModules(position.getProject()); + for (String module : modules) { + resultSet.addElement(LookupElementBuilder.create(module) + .withIcon(com.intellij.icons.AllIcons.Nodes.Module)); + } + } + // Suggest frml files at index 4 + if (args.length >= 5 && args[4] == literal) { + String moduleName = getArgumentValue(args, 3); + if (moduleName != null) { + List frmls = DynFormPathUtils.getFrmlFiles(position.getProject(), moduleName); + for (String frml : frmls) { + resultSet.addElement(LookupElementBuilder.create(frml) + .withIcon(com.intellij.icons.AllIcons.FileTypes.Xml)); + } + } + } + } + } + } + }); + } + + private PsiNewExpression getNewDynFormExpression(PsiLiteralExpression literal) { + PsiElement parent = literal.getParent(); + if (parent instanceof PsiExpressionList) { + PsiElement grandParent = parent.getParent(); + if (grandParent instanceof PsiNewExpression newExp) { + PsiJavaCodeReferenceElement classRef = newExp.getClassReference(); + if (classRef != null && "DynForm".equals(classRef.getReferenceName())) { + return newExp; + } + } + } + return null; + } + + private String getArgumentValue(PsiExpression[] args, int index) { + if (args.length > index && args[index] instanceof PsiLiteralExpression literal) { + Object val = literal.getValue(); + return val instanceof String ? (String) val : null; + } + return null; + } +} diff --git a/src/main/java/com/sdk/dynform/tools/helper/DynFormPathUtils.java b/src/main/java/com/sdk/dynform/tools/helper/DynFormPathUtils.java new file mode 100644 index 0000000..b60107f --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/helper/DynFormPathUtils.java @@ -0,0 +1,83 @@ +package com.sdk.dynform.tools.helper; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.search.FilenameIndex; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class DynFormPathUtils { + + public static final String MODULE_BASE_PATH = "src/main/webapp/WEB-INF/app/module"; + + @Nullable + public static VirtualFile getModuleBaseDir(@NotNull Project project) { + // ค้นหาจาก project root โดยตรง + VirtualFile baseDir = project.getBaseDir(); + if (baseDir == null) return null; + return baseDir.findFileByRelativePath(MODULE_BASE_PATH); + } + + @NotNull + public static List getAllModules(@NotNull Project project) { + List modules = new ArrayList<>(); + VirtualFile baseDir = getModuleBaseDir(project); + if (baseDir != null && baseDir.isDirectory()) { + for (VirtualFile child : baseDir.getChildren()) { + if (child.isDirectory()) { + modules.add(child.getName()); + } + } + } + return modules; + } + + @NotNull + public static List getFrmlFiles(@NotNull Project project, @NotNull String moduleName) { + List files = new ArrayList<>(); + VirtualFile baseDir = getModuleBaseDir(project); + if (baseDir != null) { + VirtualFile frmDir = baseDir.findFileByRelativePath(moduleName + "/view/frm"); + if (frmDir != null && frmDir.isDirectory()) { + for (VirtualFile child : frmDir.getChildren()) { + if (!child.isDirectory() && child.getName().endsWith(".frml")) { + files.add(child.getNameWithoutExtension()); + } + } + } + } + return files; + } + + @Nullable + public static PsiDirectory findModuleDirectory(@NotNull Project project, @NotNull String moduleName) { + VirtualFile baseDir = getModuleBaseDir(project); + if (baseDir != null) { + VirtualFile moduleDir = baseDir.findFileByRelativePath(moduleName); + if (moduleDir != null && moduleDir.isDirectory()) { + return PsiManager.getInstance(project).findDirectory(moduleDir); + } + } + return null; + } + + @Nullable + public static PsiFile findFrmlFile(@NotNull Project project, @NotNull String moduleName, @NotNull String frmlName) { + VirtualFile baseDir = getModuleBaseDir(project); + if (baseDir != null) { + VirtualFile frmFile = baseDir.findFileByRelativePath(moduleName + "/view/frm/" + frmlName + ".frml"); + if (frmFile != null && !frmFile.isDirectory()) { + return PsiManager.getInstance(project).findFile(frmFile); + } + } + return null; + } +} diff --git a/src/main/java/com/sdk/dynform/tools/helper/DynFormReferenceContributor.java b/src/main/java/com/sdk/dynform/tools/helper/DynFormReferenceContributor.java new file mode 100644 index 0000000..e99eb88 --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/helper/DynFormReferenceContributor.java @@ -0,0 +1,100 @@ +package com.sdk.dynform.tools.helper; + +import com.intellij.openapi.util.TextRange; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.*; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class DynFormReferenceContributor extends PsiReferenceContributor { + @Override + public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression.class), + new PsiReferenceProvider() { + @NotNull + @Override + public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { + PsiLiteralExpression literal = (PsiLiteralExpression) element; + String value = literal.getValue() instanceof String ? (String) literal.getValue() : null; + if (value == null) return PsiReference.EMPTY_ARRAY; + + PsiNewExpression newExp = getNewDynFormExpression(literal); + if (newExp != null) { + PsiExpressionList argList = newExp.getArgumentList(); + if (argList != null) { + PsiExpression[] args = argList.getExpressions(); + // Argument index 3 is moduleName + if (args.length >= 4 && args[3] == literal) { + return new PsiReference[]{new DynFormModuleReference(element, new TextRange(1, value.length() + 1), value)}; + } + // Argument index 4 is frmlName + if (args.length >= 5 && args[4] == literal) { + String moduleName = getArgumentValue(args, 3); + if (moduleName != null) { + return new PsiReference[]{new DynFormFrmlReference(element, new TextRange(1, value.length() + 1), moduleName, value)}; + } + } + } + } + return PsiReference.EMPTY_ARRAY; + } + }); + } + + @Nullable + private PsiNewExpression getNewDynFormExpression(PsiLiteralExpression literal) { + PsiElement parent = literal.getParent(); + if (parent instanceof PsiExpressionList) { + PsiElement grandParent = parent.getParent(); + if (grandParent instanceof PsiNewExpression newExp) { + PsiJavaCodeReferenceElement classRef = newExp.getClassReference(); + if (classRef != null && "DynForm".equals(classRef.getReferenceName())) { + return newExp; + } + } + } + return null; + } + + @Nullable + private String getArgumentValue(PsiExpression[] args, int index) { + if (args.length > index && args[index] instanceof PsiLiteralExpression literal) { + Object val = literal.getValue(); + return val instanceof String ? (String) val : null; + } + return null; + } + + private static class DynFormModuleReference extends PsiReferenceBase { + private final String moduleName; + + public DynFormModuleReference(@NotNull PsiElement element, TextRange textRange, String moduleName) { + super(element, textRange); + this.moduleName = moduleName; + } + + @Nullable + @Override + public PsiElement resolve() { + return DynFormPathUtils.findModuleDirectory(myElement.getProject(), moduleName); + } + } + + private static class DynFormFrmlReference extends PsiReferenceBase { + private final String moduleName; + private final String frmlName; + + public DynFormFrmlReference(@NotNull PsiElement element, TextRange textRange, String moduleName, String frmlName) { + super(element, textRange); + this.moduleName = moduleName; + this.frmlName = frmlName; + } + + @Nullable + @Override + public PsiElement resolve() { + return DynFormPathUtils.findFrmlFile(myElement.getProject(), moduleName, frmlName); + } + } +} diff --git a/src/main/java/com/sdk/dynform/tools/helper/FRMLFileType.java b/src/main/java/com/sdk/dynform/tools/helper/FRMLFileType.java new file mode 100644 index 0000000..eae1177 --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/helper/FRMLFileType.java @@ -0,0 +1,41 @@ +package com.sdk.dynform.tools.helper; + +import com.intellij.ide.highlighter.XmlLikeFileType; +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.fileTypes.LanguageFileType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class FRMLFileType extends LanguageFileType { + public static final FRMLFileType INSTANCE = new FRMLFileType(); + + private FRMLFileType() { + super(XMLLanguage.INSTANCE); + } + + @NotNull + @Override + public String getName() { + return "FRML"; + } + + @NotNull + @Override + public String getDescription() { + return "DynForm framework blueprint file"; + } + + @NotNull + @Override + public String getDefaultExtension() { + return "frml"; + } + + @Nullable + @Override + public Icon getIcon() { + return com.intellij.icons.AllIcons.FileTypes.Xml; + } +} diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nCompletionContributor.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nCompletionContributor.java new file mode 100644 index 0000000..5ec98ba --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nCompletionContributor.java @@ -0,0 +1,162 @@ +package com.sdk.dynform.tools.i18n; + +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.patterns.XmlPatterns; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLiteralExpression; +import com.intellij.psi.PsiMethodCallExpression; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.psi.xml.XmlToken; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +public class I18nCompletionContributor extends CompletionContributor { + private static final Logger LOG = Logger.getInstance(I18nCompletionContributor.class); + + public I18nCompletionContributor() { + // Completion for Java/JSP/JS $M.get("...") + extend(CompletionType.BASIC, + PlatformPatterns.psiElement(), + new CompletionProvider() { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + @NotNull ProcessingContext context, + @NotNull CompletionResultSet resultSet) { + PsiElement position = parameters.getPosition(); + PsiLiteralExpression literal = PsiTreeUtil.getParentOfType(position, PsiLiteralExpression.class); + + // ในบางภาษาอาจจะไม่ใช่ PsiLiteralExpression โดยตรง + if (literal != null && isMGetArgument(literal)) { + String value = literal.getValue() instanceof String ? (String) literal.getValue() : ""; + int offsetInLiteral = parameters.getOffset() - literal.getTextRange().getStartOffset() - 1; // -1 for starting quote + if (offsetInLiteral >= 0 && offsetInLiteral <= value.length()) { + handleMultiKeyCompletion(parameters, resultSet, value.substring(0, offsetInLiteral)); + } + } else { + // ลองเช็คแบบข้อความดิบ (สำหรับ JS/JSP บางกรณี) + String textBefore = parameters.getEditor().getDocument().getText( + new com.intellij.openapi.util.TextRange(Math.max(0, parameters.getOffset() - 100), parameters.getOffset()) + ); + + java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\$M\\.get\\([\"']([^\"']*)$").matcher(textBefore); + if (m.find()) { + handleMultiKeyCompletion(parameters, resultSet, m.group(1)); + } + } + } + }); + + // Completion for FRML (XML) caption="...", label="..." + extend(CompletionType.BASIC, + XmlPatterns.psiElement().inside(XmlPatterns.xmlAttributeValue()), + new CompletionProvider() { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + @NotNull ProcessingContext context, + @NotNull CompletionResultSet resultSet) { + PsiElement position = parameters.getPosition(); + XmlAttribute attribute = PsiTreeUtil.getParentOfType(position, XmlAttribute.class); + if (attribute != null) { + String name = attribute.getName(); + if (name.equals("CAPTION") || name.equals("LABEL")) { + XmlAttributeValue valueElement = attribute.getValueElement(); + if (valueElement != null) { + int offsetInValue = parameters.getOffset() - valueElement.getTextRange().getStartOffset() - 1; + String value = attribute.getValue(); + if (value != null && offsetInValue >= 0 && offsetInValue <= value.length()) { + handleMultiKeyCompletion(parameters, resultSet, value.substring(0, offsetInValue)); + } else { + addMessageKeys(parameters, resultSet); + } + } + } + } + } + }); + + // Completion for @M{...} pattern in XML text or any other context + extend(CompletionType.BASIC, + PlatformPatterns.psiElement(), + new CompletionProvider() { + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + @NotNull ProcessingContext context, + @NotNull CompletionResultSet resultSet) { + PsiElement position = parameters.getPosition(); + String text = position.getText(); + int offsetInElement = parameters.getOffset() - position.getTextRange().getStartOffset(); + if (offsetInElement < 0 || offsetInElement > text.length()) return; + + String before = text.substring(0, offsetInElement); + + // Find the start of the current @M{ block + int openBracket = before.lastIndexOf("@M{"); + if (openBracket != -1) { + String contentAfterBracket = before.substring(openBracket + 3); + // If we haven't closed the bracket yet + if (!contentAfterBracket.contains("}")) { + handleMultiKeyCompletion(parameters, resultSet, contentAfterBracket); + } + } + } + }); + } + + private void handleMultiKeyCompletion(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet, String content) { + // Find the last separator to determine what the user is currently typing + int lastPlus = content.lastIndexOf('+'); + int lastPipe = content.lastIndexOf("||"); + int lastSpace = content.lastIndexOf(' '); + int lastHash = content.lastIndexOf('#'); + + int lastSeparator = Math.max(lastPlus, Math.max(lastPipe != -1 ? lastPipe + 1 : -1, Math.max(lastSpace, lastHash))); + + String currentPrefix; + if (lastSeparator != -1) { + // Handle || which is 2 chars + if (lastPipe != -1 && lastSeparator == lastPipe + 1) { + currentPrefix = content.substring(lastPipe + 2); + } else { + currentPrefix = content.substring(lastSeparator + 1); + } + } else { + currentPrefix = content; + } + + addMessageKeys(parameters, resultSet.withPrefixMatcher(currentPrefix)); + } + + private void addMessageKeys(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) { + Set keys = I18nUtils.findAllMessageKeys(parameters.getEditor().getProject()); + + for (String key : keys) { + String translation = I18nUtils.findMessageValue(parameters.getEditor().getProject(), key); + resultSet.addElement(LookupElementBuilder.create(key) + .withTypeText(translation != null ? translation : "", true) + .withIcon(com.intellij.openapi.util.IconLoader.getIcon("/META-INF/pluginIcon.svg", I18nCompletionContributor.class))); + } + } + + private boolean isMGetArgument(PsiLiteralExpression literal) { + PsiElement parent = literal.getParent(); + if (parent instanceof com.intellij.psi.PsiExpressionList) { + PsiElement grandParent = parent.getParent(); + if (grandParent instanceof PsiMethodCallExpression) { + PsiMethodCallExpression methodCall = (PsiMethodCallExpression) grandParent; + String methodName = methodCall.getMethodExpression().getReferenceName(); + if ("get".equals(methodName)) { + PsiElement qualifier = methodCall.getMethodExpression().getQualifier(); + return qualifier != null && "$M".equals(qualifier.getText()); + } + } + } + return false; + } +} diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nConfigurable.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nConfigurable.java new file mode 100644 index 0000000..1c6c7e6 --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nConfigurable.java @@ -0,0 +1,86 @@ +package com.sdk.dynform.tools.i18n; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.util.NlsContexts; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +public class I18nConfigurable implements Configurable { + + private JRadioButton foldingBtn; + private JRadioButton inlayBtn; + private JRadioButton disabledBtn; + private JCheckBox showIconCheck; + + @Override + public @NlsContexts.ConfigurableName String getDisplayName() { + return "DynForm I18n Tools"; + } + + @Override + public @Nullable JComponent createComponent() { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + panel.add(new JLabel("Message Display Mode:")); + + foldingBtn = new JRadioButton("Folding (Hide key, show translation)"); + inlayBtn = new JRadioButton("Inlay Hints (Show translation next to key)"); + disabledBtn = new JRadioButton("Disabled"); + + ButtonGroup group = new ButtonGroup(); + group.add(foldingBtn); + group.add(inlayBtn); + group.add(disabledBtn); + + panel.add(foldingBtn); + panel.add(inlayBtn); + panel.add(disabledBtn); + + panel.add(Box.createVerticalStrut(10)); + showIconCheck = new JCheckBox("Show icon in code completion"); + panel.add(showIconCheck); + + panel.add(Box.createVerticalGlue()); + + return panel; + } + + @Override + public boolean isModified() { + I18nSettings settings = I18nSettings.getInstance(); + I18nSettings.DisplayMode currentMode = getCurrentModeFromUI(); + return settings.displayMode != currentMode || settings.showIcon != showIconCheck.isSelected(); + } + + @Override + public void apply() { + I18nSettings settings = I18nSettings.getInstance(); + settings.displayMode = getCurrentModeFromUI(); + settings.showIcon = showIconCheck.isSelected(); + + // Refresh all editors to apply changes + com.intellij.codeInsight.daemon.DaemonCodeAnalyzer.getInstance( + com.intellij.openapi.project.ProjectManager.getInstance().getOpenProjects()[0] + ).restart(); + } + + private I18nSettings.DisplayMode getCurrentModeFromUI() { + if (foldingBtn.isSelected()) return I18nSettings.DisplayMode.FOLDING; + if (inlayBtn.isSelected()) return I18nSettings.DisplayMode.INLAY_HINTS; + return I18nSettings.DisplayMode.DISABLED; + } + + @Override + public void reset() { + I18nSettings settings = I18nSettings.getInstance(); + switch (settings.displayMode) { + case FOLDING: foldingBtn.setSelected(true); break; + case INLAY_HINTS: inlayBtn.setSelected(true); break; + case DISABLED: disabledBtn.setSelected(true); break; + } + showIconCheck.setSelected(settings.showIcon); + } +} diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nFoldingBuilder.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nFoldingBuilder.java new file mode 100644 index 0000000..2d2bb54 --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nFoldingBuilder.java @@ -0,0 +1,109 @@ +package com.sdk.dynform.tools.i18n; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.folding.FoldingBuilderEx; +import com.intellij.lang.folding.FoldingDescriptor; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlAttributeValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class I18nFoldingBuilder extends FoldingBuilderEx { + + @NotNull + @Override + public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { + if (I18nSettings.getInstance().displayMode != I18nSettings.DisplayMode.FOLDING) { + return new FoldingDescriptor[0]; + } + + List descriptors = new ArrayList<>(); + Project project = root.getProject(); + + // Check for Java/JSP $M.get("key") + Collection literalExpressions = PsiTreeUtil.findChildrenOfType(root, PsiLiteralExpression.class); + for (PsiLiteralExpression literal : literalExpressions) { + String value = literal.getValue() instanceof String ? (String) literal.getValue() : null; + if (value != null && isMGetArgument(literal)) { + String translation = I18nUtils.findMessageValue(project, value); + if (translation != null) { + descriptors.add(new FoldingDescriptor(literal.getNode(), literal.getTextRange(), null, translation)); + } + } + } + + // Check for XML/FRML @M{key} in any XML Token + Collection xmlTokens = PsiTreeUtil.findChildrenOfType(root, com.intellij.psi.xml.XmlToken.class); + for (com.intellij.psi.xml.XmlToken token : xmlTokens) { + String text = token.getText(); + if (text != null && text.contains("@M{")) { + java.util.regex.Matcher matcher = I18nUtils.getMPattern().matcher(text); + while (matcher.find()) { + String key = matcher.group(1); + if (key != null) { + String translation = I18nUtils.findMessageValue(project, key); + if (translation != null) { + TextRange range = new TextRange(token.getTextRange().getStartOffset() + matcher.start(), + token.getTextRange().getStartOffset() + matcher.end()); + descriptors.add(new FoldingDescriptor(token.getNode(), range, null, translation)); + } + } + } + } + } + + Collection xmlAttributes = PsiTreeUtil.findChildrenOfType(root, XmlAttribute.class); + for (XmlAttribute attribute : xmlAttributes) { + String name = attribute.getName(); + if (name.equals("CAPTION") || name.equals("LABEL")) { + XmlAttributeValue valueElement = attribute.getValueElement(); + if (valueElement != null) { + String value = attribute.getValue(); + if (value != null && !value.contains("@M{")) { + String translation = I18nUtils.findMessageValue(project, value); + if (translation != null) { + descriptors.add(new FoldingDescriptor(valueElement.getNode(), valueElement.getTextRange(), null, translation)); + } + } + } + } + } + + return descriptors.toArray(new FoldingDescriptor[0]); + } + + private boolean isMGetArgument(PsiLiteralExpression literal) { + PsiElement parent = literal.getParent(); + if (parent instanceof PsiExpressionList) { + PsiElement grandParent = parent.getParent(); + if (grandParent instanceof PsiMethodCallExpression methodCall) { + String methodName = methodCall.getMethodExpression().getReferenceName(); + if ("get".equals(methodName)) { + PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); + return qualifier != null && "$M".equals(qualifier.getText()); + } + } + } + return false; + } + + @Nullable + @Override + public String getPlaceholderText(@NotNull ASTNode node) { + return "..."; + } + + @Override + public boolean isCollapsedByDefault(@NotNull ASTNode node) { + return true; + } +} diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nInlayHintsProvider.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nInlayHintsProvider.java new file mode 100644 index 0000000..3138916 --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nInlayHintsProvider.java @@ -0,0 +1,155 @@ +package com.sdk.dynform.tools.i18n; + +import com.intellij.codeInsight.hints.*; +import com.intellij.lang.Language; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlToken; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +@SuppressWarnings("UnstableApiUsage") +public class I18nInlayHintsProvider implements InlayHintsProvider { + + @Nullable + @Override + public InlayHintsCollector getCollectorFor(@NotNull PsiFile file, @NotNull Editor editor, @NotNull NoSettings settings, @NotNull InlayHintsSink sink) { + if (I18nSettings.getInstance().displayMode != I18nSettings.DisplayMode.INLAY_HINTS) { + return null; + } + + // Prevent duplicate hints: only provide hints if the language being queried + // matches the primary language of the file. + if (!file.getLanguage().equals(file.getViewProvider().getBaseLanguage())) { + return null; + } + + return new I18nCollector(editor); + } + + private static class I18nCollector extends FactoryInlayHintsCollector { + public I18nCollector(@NotNull Editor editor) { + super(editor); + } + + @Override + public boolean collect(@NotNull PsiElement element, @NotNull Editor editor, @NotNull InlayHintsSink sink) { + String lang = element.getLanguage().getDisplayName().toLowerCase(); + Language baseLang = element.getLanguage().getBaseLanguage(); + String checkLangs = "java#jsp#javascript#ecmascript#js#ecmascript 6"; + + // 1. Java/JSP/JS $M.get("key") + boolean isJsOrJava = checkLangs.contains(lang) || (baseLang != null && checkLangs.contains(baseLang.getDisplayName().toLowerCase())); + + if (isJsOrJava) { + String elementText = element.getText(); + if (elementText != null && elementText.startsWith("$M.get")) { + java.util.regex.Matcher matcher = I18nUtils.getMGetPattern().matcher(elementText); + while (matcher.find()) { + String key = matcher.group(1); + if (key != null) { + String translation = I18nUtils.findMessageValue(element.getProject(), key); + if (translation != null) { + sink.addInlineElement(element.getTextRange().getStartOffset() + matcher.end(), true, + getFactory().text(" \u00AB " + translation + " \u00BB"), false); + } + } + } + } + } + + // 2. FRML XML @M{} + if ("xml#frml".contains(lang) && element instanceof XmlToken token) { + String text = token.getText(); + if (text != null) { + // Handle @M{key} + if (text.contains("@M{")) { + java.util.regex.Matcher matcher = I18nUtils.getMPattern().matcher(text); + while (matcher.find()) { + String key = matcher.group(1); + if (key != null) { + String translation = I18nUtils.findMessageValue(element.getProject(), key); + if (translation != null) { + sink.addInlineElement(token.getTextRange().getStartOffset() + matcher.end(), true, + getFactory().text(" \u00AB " + translation + " \u00BB"), false); + } + } + } + } + + // Handle $M.get in XML (e.g. inside script tags or attributes) + if (text.contains("$M.get")) { + java.util.regex.Matcher matcher = I18nUtils.getMGetPattern().matcher(text); + while (matcher.find()) { + String key = matcher.group(1); + if (key != null) { + String translation = I18nUtils.findMessageValue(element.getProject(), key); + if (translation != null) { + sink.addInlineElement(token.getTextRange().getStartOffset() + matcher.end(), true, + getFactory().text(" \u00AB " + translation + " \u00BB"), false); + } + } + } + } + } + } + + // 3. FRML XML CAPTION/LABEL + if ("xml#frml".contains(lang) && element instanceof XmlAttribute attribute) { + String name = attribute.getName(); + if (name.equals("CAPTION") || name.equals("LABEL")) { + String value = attribute.getValue(); + if (value != null && !value.contains("@M{")) { + String translation = I18nUtils.findMessageValue(element.getProject(), value); + if (translation != null) { + sink.addInlineElement(attribute.getTextRange().getEndOffset(), true, + getFactory().text(" \u00AB " + translation + " \u00BB"), false); + } + } + } + } + return true; + } + } + + @NotNull + @Override + public NoSettings createSettings() { + return new NoSettings(); + } + + @NotNull + @Override + public String getName() { + return "DynForm I18n Tools"; + } + + @NotNull + @Override + public SettingsKey getKey() { + return new SettingsKey<>("dynform.i18n.tools"); + } + + @NotNull + @Override + public ImmediateConfigurable createConfigurable(@NotNull NoSettings settings) { + return listener -> new JPanel(); + } + + @Override + public @Nullable String getPreviewText() { + return null; + } + + @Override + public boolean isLanguageSupported(@NotNull com.intellij.lang.Language language) { + String id = language.getID(); + return id.equals("JAVA") || id.equals("XML") || id.equals("JSP") || + id.contains("JavaScript") || id.contains("JS") || + id.contains("ECMAScript") || id.contains("TypeScript"); + } +} diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nReferenceContributor.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nReferenceContributor.java new file mode 100644 index 0000000..84e744d --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nReferenceContributor.java @@ -0,0 +1,200 @@ +package com.sdk.dynform.tools.i18n; + +import com.intellij.openapi.util.TextRange; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.patterns.XmlPatterns; +import com.intellij.psi.*; +import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class I18nReferenceContributor extends PsiReferenceContributor { + @Override + public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { + // 1. Java Reference Provider (Specific for Java PSI) + registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression.class), + new PsiReferenceProvider() { + @NotNull + @Override + public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { + PsiLiteralExpression literalExpression = (PsiLiteralExpression) element; + String value = literalExpression.getValue() instanceof String ? (String) literalExpression.getValue() : null; + if (value != null && isMGetArgument(literalExpression)) { + return new PsiReference[]{new I18nReference(element, new TextRange(1, value.length() + 1), value)}; + } + return PsiReference.EMPTY_ARRAY; + } + }); + + // 2. XML Attribute Reference Provider (for .frml CAPTION/LABEL) + registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().withParent(XmlPatterns.xmlAttribute().withName("CAPTION", "LABEL")), + new PsiReferenceProvider() { + @NotNull + @Override + public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { + XmlAttributeValue attributeValue = (XmlAttributeValue) element; + String value = attributeValue.getValue(); + if (value == null || value.isEmpty()) return PsiReference.EMPTY_ARRAY; + + List refs = new ArrayList<>(); + Pattern sepPattern = Pattern.compile("(\\|\\||[\\s+#])"); + Matcher matcher = sepPattern.matcher(value); + + int lastEnd = 0; + while (matcher.find()) { + addRef(refs, element, value, lastEnd, matcher.start(), 1); + lastEnd = matcher.end(); + } + addRef(refs, element, value, lastEnd, value.length(), 1); + + return refs.toArray(new PsiReference[0]); + } + }); + + // 3. Generic Reference Provider for @M{} and $M.get() + // Works for JavaScript (including ES6), JSP, and XML/FRML text/tokens + registrar.registerReferenceProvider(PlatformPatterns.psiElement(), + new PsiReferenceProvider() { + @NotNull + @Override + public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { + // Avoid duplicates if already handled + if (element instanceof PsiLiteralExpression || element instanceof XmlAttributeValue) { + return PsiReference.EMPTY_ARRAY; + } + + String text = element.getText(); + if (text == null || text.isEmpty()) return PsiReference.EMPTY_ARRAY; + + List refs = new ArrayList<>(); + + // CASE A: Element is a string literal (quoted) + if ((text.startsWith("\"") || text.startsWith("'")) && text.length() > 2) { + if (isInsideMGet(element)) { + String key = text.substring(1, text.length() - 1); + if (!key.isEmpty()) { + refs.add(new I18nReference(element, new TextRange(1, text.length() - 1), key)); + } + } + } + + // CASE B: Element contains @M{key} or $M.get("key") + // This handles cases where the element is a larger token containing the pattern + if (text.contains("@M{")) { + addMultiKeyReferences(refs, element, text, I18nUtils.getMPattern()); + } + + if (text.contains("$M.get")) { + addMultiKeyReferences(refs, element, text, I18nUtils.getMGetPattern()); + } + + return refs.isEmpty() ? PsiReference.EMPTY_ARRAY : refs.toArray(new PsiReference[0]); + } + }); + } + + /** + * Robust check if the element is part of a $M.get(...) call. + * Searches up the tree to handle different PSI structures in JS/JSP/XML. + */ + private boolean isInsideMGet(PsiElement element) { + PsiElement current = element.getParent(); + int depth = 0; + // Search up to 3 levels: Argument -> ArgumentList -> CallExpression + while (current != null && depth < 3) { + String txt = current.getText(); + if (txt != null && txt.contains("$M.get")) return true; + current = current.getParent(); + depth++; + } + return false; + } + + private void addMultiKeyReferences(List refs, PsiElement element, String text, Pattern pattern) { + Matcher mMatcher = pattern.matcher(text); + int elementStart = element.getTextRange().getStartOffset(); + while (mMatcher.find()) { + if (mMatcher.groupCount() >= 1) { + String content = mMatcher.group(1); + int contentStartInElement = mMatcher.start(1); + + Pattern sepPattern = Pattern.compile("(\\|\\||[\\s+#])"); + Matcher sMatcher = sepPattern.matcher(content); + + int lastEnd = 0; + while (sMatcher.find()) { + addRef(refs, element, content, lastEnd, sMatcher.start(), contentStartInElement); + lastEnd = sMatcher.end(); + } + addRef(refs, element, content, lastEnd, content.length(), contentStartInElement); + } + } + } + + private void addRef(List refs, PsiElement element, String content, int start, int end, int baseOffset) { + if (start < end) { + String key = content.substring(start, end); + if (!key.isEmpty() && !key.equals("-")) { + refs.add(new I18nReference(element, new TextRange(baseOffset + start, baseOffset + end), key)); + } + } + } + + private boolean isMGetArgument(PsiLiteralExpression literal) { + PsiElement parent = literal.getParent(); + if (parent instanceof PsiExpressionList) { + PsiElement grandParent = parent.getParent(); + if (grandParent instanceof PsiMethodCallExpression methodCall) { + String methodName = methodCall.getMethodExpression().getReferenceName(); + if ("get".equals(methodName)) { + PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); + return qualifier != null && "$M".equals(qualifier.getText()); + } + } + } + return false; + } + + public static class I18nReference extends PsiReferenceBase implements PsiPolyVariantReference { + private final String key; + + public I18nReference(@NotNull PsiElement element, TextRange textRange, String key) { + super(element, textRange); + this.key = key; + } + + @Override + public boolean isSoft() { + return true; + } + + @NotNull + @Override + public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) { + PsiElement entry = I18nUtils.findMessageEntry(myElement.getProject(), key); + if (entry != null) { + return new ResolveResult[]{new PsiElementResolveResult(entry)}; + } + return new ResolveResult[0]; + } + + @Nullable + @Override + public PsiElement resolve() { + ResolveResult[] resolveResults = multiResolve(false); + return resolveResults.length == 1 ? resolveResults[0].getElement() : null; + } + + @NotNull + @Override + public Object @NotNull [] getVariants() { + return new Object[0]; + } + } +} diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nSettings.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nSettings.java new file mode 100644 index 0000000..ad77a46 --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nSettings.java @@ -0,0 +1,40 @@ +package com.sdk.dynform.tools.i18n; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@State( + name = "com.sdk.dynform.tools.i18n.I18nSettings", + storages = @Storage("DynamicFormTools.xml") +) +public class I18nSettings implements PersistentStateComponent { + + public enum DisplayMode { + FOLDING, + INLAY_HINTS, + DISABLED + } + + public DisplayMode displayMode = DisplayMode.FOLDING; + public boolean showIcon = true; + + public static I18nSettings getInstance() { + return ApplicationManager.getApplication().getService(I18nSettings.class); + } + + @Nullable + @Override + public I18nSettings getState() { + return this; + } + + @Override + public void loadState(@NotNull I18nSettings state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java new file mode 100644 index 0000000..e2b6742 --- /dev/null +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java @@ -0,0 +1,214 @@ +package com.sdk.dynform.tools.i18n; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.search.FilenameIndex; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class I18nUtils { + + private static final List MESSAGE_FILENAMES = Arrays.asList("message.xml", "message_th.xml", "message_en.xml"); + + @NotNull + public static List findMessageFiles(@NotNull Project project) { + List result = new ArrayList<>(); + for (String filename : MESSAGE_FILENAMES) { + Collection files = FilenameIndex.getVirtualFilesByName(filename, GlobalSearchScope.everythingScope(project)); + for (VirtualFile file : files) { + PsiFile psiFile = PsiManager.getInstance(project).findFile(file); + if (psiFile instanceof XmlFile) { + result.add((XmlFile) psiFile); + } + } + } + return result; + } + + private static final Map> cache = new LinkedHashMap<>(); + private static long lastCacheUpdate = 0; + private static final long CACHE_TIMEOUT = 5000; // 5 seconds + + @Nullable + public static String findMessageValue(@NotNull Project project, @NotNull String key) { + if (key.startsWith("#") || key.startsWith("=")) { + return key.substring(1); + } + + updateCache(project); + + // Try to find in any of the cached message files + for (Map messageMap : cache.values()) { + String value = resolveKey(messageMap, key); + if (value != null) { + return value; + } + } + return null; + } + + @Nullable + private static String resolveKey(Map messageMap, String key) { + if (messageMap.isEmpty()) return null; + + String msgKey = key.toLowerCase(); + String[] keys = key.split(" "); + boolean usePlus = false; + if (keys.length == 1) { + keys = msgKey.split("\\+"); + usePlus = keys.length > 1; + } + if (keys.length == 1) { + keys = msgKey.split("\\|\\|"); + } + + if (keys.length > 1) { + StringBuilder result = new StringBuilder(msgKey); + int seq = 0; + for (String id : keys) { + if (id.isBlank()) continue; + String cleanId = id.replace("-", "_").toLowerCase(); + String value; + if (id.startsWith("#")) { + value = id.substring(1); + } else if (id.equals("-")) { + value = "-"; + } else { + value = messageMap.get(cleanId); + if (value == null) { + // Recursive-like resolve for sub-keys if not found directly + value = resolveKey(messageMap, cleanId); + } + } + + if (value != null) { + String target = (seq > 0 && usePlus) ? "+" + id : id; + int start = result.indexOf(target); + if (start != -1) { + result.replace(start, start + target.length(), value); + } + } + seq++; + } + return result.toString().replaceAll("\\|\\|", ""); + } else { + msgKey = key.replaceAll("-", "_").toLowerCase(); + String result = messageMap.get(msgKey); + if (result == null) { + if (!msgKey.contains(".")) { + result = messageMap.get("sys." + msgKey); + } else { + result = msgKey.substring(msgKey.indexOf(".") + 1); + if (result.isBlank()) { + result = msgKey; + } + result = beautify(result.replace("_", " ")); + } + if (result == null) { + result = beautify(msgKey.replace("_", " ")); + } + } + return result != null ? result.replaceAll("\\|\\|", "") : null; + } + } + + private static String beautify(String str) { + if (str == null || str.isEmpty()) return str; + StringBuilder result = new StringBuilder(str.length()); + boolean capitalizeNext = true; + for (char c : str.toCharArray()) { + if (Character.isWhitespace(c) || c == '_') { + capitalizeNext = true; + result.append(' '); + } else if (capitalizeNext) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + result.append(Character.toLowerCase(c)); + } + } + return result.toString().trim(); + } + + private static void updateCache(@NotNull Project project) { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastCacheUpdate < CACHE_TIMEOUT && !cache.isEmpty()) { + return; + } + + cache.clear(); + List messageFiles = findMessageFiles(project); + for (XmlFile messageFile : messageFiles) { + String filePath = messageFile.getVirtualFile().getPath(); + Map messageMap = new HashMap<>(); + XmlTag rootTag = messageFile.getRootTag(); + if (rootTag != null) { + for (XmlTag entryTag : rootTag.findSubTags("entry")) { + String entryKey = entryTag.getAttributeValue("key"); + if (entryKey != null) { + messageMap.put(entryKey, entryTag.getValue().getText()); + } + } + } + cache.put(filePath, messageMap); + } + lastCacheUpdate = currentTime; + } + + @Nullable + public static PsiElement findMessageEntry(@NotNull Project project, @NotNull String key) { + List messageFiles = findMessageFiles(project); + for (XmlFile messageFile : messageFiles) { + XmlTag rootTag = messageFile.getRootTag(); + if (rootTag != null) { + for (XmlTag entryTag : rootTag.findSubTags("entry")) { + if (key.equals(entryTag.getAttributeValue("key"))) { + return entryTag.getAttribute("key"); + } + } + } + } + return null; + } + + @NotNull + public static Set findAllMessageKeys(@NotNull Project project) { + updateCache(project); + Set allKeys = new TreeSet<>(); + for (Map messageMap : cache.values()) { + allKeys.addAll(messageMap.keySet()); + } + return allKeys; + } + + private static final java.util.regex.Pattern M_PATTERN = java.util.regex.Pattern.compile("@M\\{(.*?)}"); + private static final java.util.regex.Pattern MGET_PATTERN = java.util.regex.Pattern.compile("\\$M\\.get\\(\\s*[\"'](.*?)[\"']\\s*\\)"); + + + @NotNull + public static List extractMessageKeys(@NotNull String text) { + List keys = new ArrayList<>(); + java.util.regex.Matcher matcher = M_PATTERN.matcher(text); + while (matcher.find()) { + keys.add(matcher.group(1)); + } + return keys; + } + + @NotNull + public static java.util.regex.Pattern getMPattern() { + return M_PATTERN; + } + @NotNull + public static java.util.regex.Pattern getMGetPattern() { + return MGET_PATTERN; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 25e9a3b..4bf98f2 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,12 +1,12 @@ - com.sdk.generators.actionmodels - Database Action Models Generator + com.sdk.dynform.tools + Dynamic Form Helper Sakda Sakprapakorn Automate Backend Boilerplate with Database Action Models Generator +

Automate Backend Boilerplate with Dynamic form tools

This plugin streamlines development in Java-based web applications (such as vrms-system and teddy-taxi-web) by automating the generation of ActionBean, DTO, and Dataset XML files directly from your database schema.

Key Features:

@@ -65,7 +65,7 @@

Refactoring and Improvements

    -
  • Project Structure Refactoring: The project structure has been reorganized. Generator-related classes were moved to a new package (com.sdk.generators.actionmodels), and template directory names were standardized to src/main/resources/templates.
  • +
  • Project Structure Refactoring: The project structure has been reorganized. Generator-related classes were moved to a new package (com.sdk.dynform.tools.generators.actionmodels), and template directory names were standardized to src/main/resources/templates.
  • Build System Updates: Updated build.gradle.kts, plugin.xml, and Gradle wrapper files to reflect the structural and functional enhancements.
]]> @@ -73,30 +73,68 @@ com.intellij.modules.platform com.intellij.modules.java com.intellij.database + com.intellij.jsp + com.intellij.modules.xml + com.intellij.modules.javascript + JavaScript - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/main/resources/messages/DynamicFormToolsBundle.properties b/src/main/resources/messages/DynamicFormToolsBundle.properties new file mode 100644 index 0000000..8814aa0 --- /dev/null +++ b/src/main/resources/messages/DynamicFormToolsBundle.properties @@ -0,0 +1 @@ +# Bundle for DynForm I18n Tools