refactor: rebrand to DynamicFormTools and add i18n support
- Rebranded plugin from "ActionModelsGenerator" to "Dynamic Form Helper".
- Refactored package structure from "com.sdk.generators" to "com.sdk.dynform.tools".
- Added comprehensive I18n support for Java, XML, and JavaScript:
- Inlay hints and code folding for internationalization keys.
- Completion and reference contributors for "message.xml" keys.
- Configuration settings and UI for i18n tools.
- Introduced support for the ".frml" (DynForm) file type.
- Added specialized DynForm completion and path resolution helpers.
- Updated "build.gradle.kts" with JSP and JavaScript platform dependencies.
- Updated documentation and project metadata to reflect the new name.
This commit is contained in:
2
.idea/.name
generated
2
.idea/.name
generated
@@ -1 +1 @@
|
|||||||
ActionModelsGenerator
|
DynamicFormTools
|
||||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledModule" module="intellij.json.split" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="intellij.json.split" ext="jar" conf="default" url="plugins/json/lib/modules"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies/>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="JavaScript" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="javascript-plugin" ext="jar" conf="default" url="plugins/javascript-plugin/lib"/>
|
||||||
|
<artifact name="javascript-frontback" ext="jar" conf="default" url="plugins/javascript-plugin/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.modules.json" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.css" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.css" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.css" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="css-impl" ext="jar" conf="default" url="plugins/css-impl/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.platform.images" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.java-i18n" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="java-i18n" ext="jar" conf="default" url="plugins/java-i18n/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.properties" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.javaee" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="jasper-v2-rt" ext="jar" conf="default" url="plugins/JavaEE/lib"/>
|
||||||
|
<artifact name="javaee-rt" ext="jar" conf="default" url="plugins/JavaEE/lib"/>
|
||||||
|
<artifact name="javaee-openapi" ext="jar" conf="default" url="plugins/JavaEE/lib"/>
|
||||||
|
<artifact name="javaee-platform" ext="jar" conf="default" url="plugins/JavaEE/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.java" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.java-i18n" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.properties" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.javaee.el" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="javaee-el-core" ext="jar" conf="default" url="plugins/javaee-el-core/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.javaee" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.javaee.web" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="javaee-web-impl" ext="jar" conf="default" url="plugins/javaee-web-impl/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.java" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.java-i18n" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.properties" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.css" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.javaee" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.jsp" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="javaee-jsp-base-impl" ext="jar" conf="default" url="plugins/javaee-jsp-base-impl/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.javaee.web" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledPlugin" name="com.intellij.javaee.el" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.modules.json" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="json" ext="jar" conf="default" url="plugins/json/lib"/>
|
||||||
|
<artifact name="intellij.json.split" ext="jar" conf="default" url="plugins/json/lib/modules"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="bundledModule" name="intellij.json.split" rev="IU-251.23774.435"/>
|
||||||
|
<dependency org="bundledModule" name="intellij.json.split" rev="IU-251.23774.435"/>
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.platform.images" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="platform-images" ext="jar" conf="default" url="plugins/platform-images/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies/>
|
||||||
|
</ivy-module>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="bundledPlugin" module="com.intellij.properties" revision="IU-251.23774.435"/>
|
||||||
|
<configurations>
|
||||||
|
<conf name="default" visibility="public"/>
|
||||||
|
</configurations>
|
||||||
|
<publications>
|
||||||
|
<artifact name="properties" ext="jar" conf="default" url="plugins/properties/lib"/>
|
||||||
|
<artifact name="properties-frontend" ext="jar" conf="default" url="plugins/properties/lib"/>
|
||||||
|
</publications>
|
||||||
|
<dependencies/>
|
||||||
|
</ivy-module>
|
||||||
@@ -1 +1 @@
|
|||||||
2026-04-03
|
2026-04-07
|
||||||
274
DevResources/Message.java
Executable file
274
DevResources/Message.java
Executable file
@@ -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<String, LinkedHashMap<String, String>> msgMaps;
|
||||||
|
private SystemFactory system;
|
||||||
|
private LinkedHashMap<String, String> 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<String, LinkedHashMap<String, String>>) 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<String, String> 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<String,String> 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<String, String> readResource(String fileName) {
|
||||||
|
LinkedHashMap<String, String> 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<Element> 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<String,String> 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<String, String> langMsg = msgMaps.get(lang.toUpperCase());
|
||||||
|
if (langMsg == null) {
|
||||||
|
msgMaps.get("TH");
|
||||||
|
}
|
||||||
|
JSONObject jsResult = new JSONObject();
|
||||||
|
SortedSet<String> 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<String, String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# ActionModelsGenerator
|
# DynamicFormTools
|
||||||
|
|
||||||
## Project Overview
|
## 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:**
|
**Key Features:**
|
||||||
* **Database Schema Introspection:** Utilizes IntelliJ's database tools to read table and column metadata.
|
* **Database Schema Introspection:** Utilizes IntelliJ's database tools to read table and column metadata.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# ActionModelsGenerator
|
# DynamicFormTools
|
||||||
|
|
||||||
## Project Overview
|
## 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:**
|
**Key Features:**
|
||||||
* **Database Schema Introspection:** Utilizes IntelliJ's database tools to read table and column metadata.
|
* **Database Schema Introspection:** Utilizes IntelliJ's database tools to read table and column metadata.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id("org.jetbrains.intellij.platform") version "2.7.0"
|
id("org.jetbrains.intellij.platform") version "2.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "com.sdk.generators.actionmodels.v3"
|
group = "com.sdk.dynform.tools"
|
||||||
version = "2.1.2"
|
version = "2.1.2"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -24,6 +24,8 @@ dependencies {
|
|||||||
// Add necessary plugin dependencies for compilation here, example:
|
// Add necessary plugin dependencies for compilation here, example:
|
||||||
bundledPlugin("com.intellij.java")
|
bundledPlugin("com.intellij.java")
|
||||||
bundledPlugin("com.intellij.database")
|
bundledPlugin("com.intellij.database")
|
||||||
|
bundledPlugin("com.intellij.jsp")
|
||||||
|
bundledPlugin("JavaScript")
|
||||||
|
|
||||||
}
|
}
|
||||||
implementation("org.freemarker:freemarker:2.3.32")
|
implementation("org.freemarker:freemarker:2.3.32")
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "ActionModelsGenerator",
|
"name": "DynamicFormTools",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {}
|
"packages": {}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
rootProject.name = "ActionModelsGenerator"
|
rootProject.name = "DynamicFormTools"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.sdk.generators;
|
package com.sdk.dynform.helper;
|
||||||
|
|
||||||
import com.intellij.database.model.DasColumn;
|
import com.intellij.database.model.DasColumn;
|
||||||
import com.intellij.database.model.DasObject;
|
import com.intellij.database.model.DasObject;
|
||||||
@@ -139,15 +139,15 @@ public class GUtils {
|
|||||||
ApplicationManager.getApplication().invokeLater(() -> Messages.showErrorDialog(project, message, "Generation Failed"));
|
ApplicationManager.getApplication().invokeLater(() -> Messages.showErrorDialog(project, message, "Generation Failed"));
|
||||||
NotificationGroupManager.getInstance()
|
NotificationGroupManager.getInstance()
|
||||||
// This ID should be registered in your plugin.xml
|
// This ID should be registered in your plugin.xml
|
||||||
.getNotificationGroup("Action-Models-Generator-Notification")
|
.getNotificationGroup("Dynamic-Form-Tools-Notification")
|
||||||
.createNotification("ActionModels Generator", message, NotificationType.ERROR)
|
.createNotification("ActionModels generator", message, NotificationType.ERROR)
|
||||||
.notify(project);
|
.notify(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showInfo(Project project, String title, String content) {
|
public static void showInfo(Project project, String title, String content) {
|
||||||
NotificationGroupManager.getInstance()
|
NotificationGroupManager.getInstance()
|
||||||
// This ID should be registered in your plugin.xml
|
// This ID should be registered in your plugin.xml
|
||||||
.getNotificationGroup("Action-Models-Generator-Notification")
|
.getNotificationGroup("Dynamic-Form-Tools-Notification")
|
||||||
.createNotification(title, content, NotificationType.INFORMATION)
|
.createNotification(title, content, NotificationType.INFORMATION)
|
||||||
.notify(project);
|
.notify(project);
|
||||||
}
|
}
|
||||||
@@ -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.DasColumn;
|
||||||
import com.intellij.database.model.ObjectKind;
|
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.openapi.vfs.VirtualFileWrapper;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.util.containers.JBIterable;
|
import com.intellij.util.containers.JBIterable;
|
||||||
import com.sdk.generators.GUtils;
|
import com.sdk.dynform.helper.GUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -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.database.psi.DbTable;
|
||||||
import com.intellij.openapi.actionSystem.AnAction;
|
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.project.Project;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.sdk.generators.GUtils;
|
import com.sdk.dynform.helper.GUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -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.database.psi.DbTable;
|
||||||
import com.intellij.openapi.actionSystem.AnAction;
|
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.project.Project;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.sdk.generators.GUtils;
|
import com.sdk.dynform.helper.GUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -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.database.psi.DbTable;
|
||||||
import com.intellij.openapi.actionSystem.AnAction;
|
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.project.Project;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.sdk.generators.GUtils;
|
import com.sdk.dynform.helper.GUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -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.DasColumn;
|
||||||
import com.intellij.database.model.DasTableKey;
|
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.project.Project;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.util.containers.JBIterable;
|
import com.intellij.util.containers.JBIterable;
|
||||||
import com.sdk.generators.GUtils;
|
import com.sdk.dynform.helper.GUtils;
|
||||||
import freemarker.template.Configuration;
|
import freemarker.template.Configuration;
|
||||||
import freemarker.template.Template;
|
import freemarker.template.Template;
|
||||||
import freemarker.template.TemplateExceptionHandler;
|
import freemarker.template.TemplateExceptionHandler;
|
||||||
@@ -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<CompletionParameters>() {
|
||||||
|
@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<String> 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> getAllModules(@NotNull Project project) {
|
||||||
|
List<String> 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<String> getFrmlFiles(@NotNull Project project, @NotNull String moduleName) {
|
||||||
|
List<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PsiElement> {
|
||||||
|
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<PsiElement> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/main/java/com/sdk/dynform/tools/helper/FRMLFileType.java
Normal file
41
src/main/java/com/sdk/dynform/tools/helper/FRMLFileType.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<CompletionParameters>() {
|
||||||
|
@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<CompletionParameters>() {
|
||||||
|
@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<CompletionParameters>() {
|
||||||
|
@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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/main/java/com/sdk/dynform/tools/i18n/I18nFoldingBuilder.java
Normal file
109
src/main/java/com/sdk/dynform/tools/i18n/I18nFoldingBuilder.java
Normal file
@@ -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<FoldingDescriptor> descriptors = new ArrayList<>();
|
||||||
|
Project project = root.getProject();
|
||||||
|
|
||||||
|
// Check for Java/JSP $M.get("key")
|
||||||
|
Collection<PsiLiteralExpression> 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<com.intellij.psi.xml.XmlToken> 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<XmlAttribute> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<NoSettings> {
|
||||||
|
|
||||||
|
@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<NoSettings> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PsiReference> 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<PsiReference> 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<PsiReference> 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<PsiReference> 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<PsiElement> 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/main/java/com/sdk/dynform/tools/i18n/I18nSettings.java
Normal file
40
src/main/java/com/sdk/dynform/tools/i18n/I18nSettings.java
Normal file
@@ -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<I18nSettings> {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
214
src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java
Normal file
214
src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java
Normal file
@@ -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<String> MESSAGE_FILENAMES = Arrays.asList("message.xml", "message_th.xml", "message_en.xml");
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static List<XmlFile> findMessageFiles(@NotNull Project project) {
|
||||||
|
List<XmlFile> result = new ArrayList<>();
|
||||||
|
for (String filename : MESSAGE_FILENAMES) {
|
||||||
|
Collection<VirtualFile> 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<String, Map<String, String>> 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<String, String> messageMap : cache.values()) {
|
||||||
|
String value = resolveKey(messageMap, key);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String resolveKey(Map<String, String> 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<XmlFile> messageFiles = findMessageFiles(project);
|
||||||
|
for (XmlFile messageFile : messageFiles) {
|
||||||
|
String filePath = messageFile.getVirtualFile().getPath();
|
||||||
|
Map<String, String> 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<XmlFile> 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<String> findAllMessageKeys(@NotNull Project project) {
|
||||||
|
updateCache(project);
|
||||||
|
Set<String> allKeys = new TreeSet<>();
|
||||||
|
for (Map<String, String> 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<String> extractMessageKeys(@NotNull String text) {
|
||||||
|
List<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
|
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
|
||||||
<idea-plugin>
|
<idea-plugin>
|
||||||
<!-- Unique id for this plugin. Must stay constant for the life of the plugin. -->
|
<!-- Unique id for this plugin. Must stay constant for the life of the plugin. -->
|
||||||
<id>com.sdk.generators.actionmodels</id>
|
<id>com.sdk.dynform.tools</id>
|
||||||
<name>Database Action Models Generator</name>
|
<name>Dynamic Form Helper</name>
|
||||||
<vendor>Sakda Sakprapakorn</vendor>
|
<vendor>Sakda Sakprapakorn</vendor>
|
||||||
|
|
||||||
<description><![CDATA[
|
<description><![CDATA[
|
||||||
<h3>Automate Backend Boilerplate with Database Action Models Generator</h3>
|
<h3>Automate Backend Boilerplate with Dynamic form tools</h3>
|
||||||
<p>This plugin streamlines development in Java-based web applications (such as <code>vrms-system</code> and <code>teddy-taxi-web</code>) by automating the generation of <b>ActionBean</b>, <b>DTO</b>, and <b>Dataset XML</b> files directly from your database schema.</p>
|
<p>This plugin streamlines development in Java-based web applications (such as <code>vrms-system</code> and <code>teddy-taxi-web</code>) by automating the generation of <b>ActionBean</b>, <b>DTO</b>, and <b>Dataset XML</b> files directly from your database schema.</p>
|
||||||
|
|
||||||
<h4>Key Features:</h4>
|
<h4>Key Features:</h4>
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<h3>Refactoring and Improvements</h3>
|
<h3>Refactoring and Improvements</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Project Structure Refactoring:</strong> The project structure has been reorganized. Generator-related classes were moved to a new package (<code>com.sdk.generators.actionmodels</code>), and template directory names were standardized to <code>src/main/resources/templates</code>.</li>
|
<li><strong>Project Structure Refactoring:</strong> The project structure has been reorganized. Generator-related classes were moved to a new package (<code>com.sdk.dynform.tools.generators.actionmodels</code>), and template directory names were standardized to <code>src/main/resources/templates</code>.</li>
|
||||||
<li><strong>Build System Updates:</strong> Updated <code>build.gradle.kts</code>, <code>plugin.xml</code>, and Gradle wrapper files to reflect the structural and functional enhancements.</li>
|
<li><strong>Build System Updates:</strong> Updated <code>build.gradle.kts</code>, <code>plugin.xml</code>, and Gradle wrapper files to reflect the structural and functional enhancements.</li>
|
||||||
</ul>
|
</ul>
|
||||||
]]></change-notes>
|
]]></change-notes>
|
||||||
@@ -73,30 +73,68 @@
|
|||||||
<depends>com.intellij.modules.platform</depends>
|
<depends>com.intellij.modules.platform</depends>
|
||||||
<depends>com.intellij.modules.java</depends>
|
<depends>com.intellij.modules.java</depends>
|
||||||
<depends>com.intellij.database</depends>
|
<depends>com.intellij.database</depends>
|
||||||
|
<depends>com.intellij.jsp</depends>
|
||||||
|
<depends>com.intellij.modules.xml</depends>
|
||||||
|
<depends>com.intellij.modules.javascript</depends>
|
||||||
|
<depends>JavaScript</depends>
|
||||||
|
|
||||||
<actions>
|
<actions>
|
||||||
<group id="com.sdk.dblogergen.actions.AuditGroup" popup="true" text="Generate Action Models">
|
<group id="com.sdk.dynform.tools.generators.actionmodels.GeneratorGroup" popup="true" text="Generate Action Models">
|
||||||
<add-to-group group-id="DatabaseViewPopupMenu" anchor="first"/>
|
<add-to-group group-id="DatabaseViewPopupMenu" anchor="first"/>
|
||||||
<action id="com.sdk.generators.actionmodels.GenerateBeanAction"
|
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction"
|
||||||
class="com.sdk.generators.actionmodels.GenerateBeanAction"
|
class="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction"
|
||||||
text="Generate Action Models V2"
|
text="Generate Action Models V2"
|
||||||
description="Generates ActionBean classes from a database schema.">
|
description="Generates ActionBean classes from a database schema.">
|
||||||
</action>
|
</action>
|
||||||
<action id="com.sdk.generators.actionmodels.GenerateBeanAction.v3"
|
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction.v3"
|
||||||
class="com.sdk.generators.actionmodels.GenerateBeanActionV3"
|
class="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanActionV3"
|
||||||
text="Generate Action Models V3"
|
text="Generate Action Models V3"
|
||||||
description="Generates ActionBean classes from a database schema V3 (sdk.db.xxx).">
|
description="Generates ActionBean classes from a database schema V3 (sdk.db.xxx).">
|
||||||
</action>
|
</action>
|
||||||
<action id="com.sdk.generators.actionmodels.GenerateDatasetAction"
|
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateDatasetAction"
|
||||||
class="com.sdk.generators.actionmodels.GenerateDatasetAction"
|
class="com.sdk.dynform.tools.generators.actionmodels.GenerateDatasetAction"
|
||||||
text="Generate Dataset XML"
|
text="Generate Dataset XML"
|
||||||
description="Generates Dataset XML definition from a database table.">
|
description="Generates Dataset XML definition from a database table.">
|
||||||
</action>
|
</action>
|
||||||
</group>
|
</group>
|
||||||
</actions>
|
</actions>
|
||||||
|
|
||||||
<!-- in plugin.xml, inside the <extensions defaultExtensionNs="com.intellij"> tag -->
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
<notificationGroup id="Action-Models-Generator-Notification" displayType="BALLOON" isLogByDefault="true"/>
|
<applicationService serviceImplementation="com.sdk.dynform.tools.i18n.I18nSettings"/>
|
||||||
|
<applicationConfigurable instance="com.sdk.dynform.tools.i18n.I18nConfigurable"
|
||||||
|
id="com.sdk.dynform.tools.i18n.I18nConfigurable"
|
||||||
|
displayName="DynForm I18n Tools"/>
|
||||||
|
|
||||||
|
<fileType name="FRML" implementationClass="com.sdk.dynform.tools.helper.FRMLFileType" extensions="frml" language="XML"/>
|
||||||
|
<notificationGroup id="Dynamic-Form-Tools-Notification" displayType="BALLOON" isLogByDefault="true"/>
|
||||||
|
|
||||||
|
<!-- Folding Builders -->
|
||||||
|
<lang.foldingBuilder language="JAVA" implementationClass="com.sdk.dynform.tools.i18n.I18nFoldingBuilder"/>
|
||||||
|
<lang.foldingBuilder language="XML" implementationClass="com.sdk.dynform.tools.i18n.I18nFoldingBuilder"/>
|
||||||
|
<lang.foldingBuilder language="JavaScript" implementationClass="com.sdk.dynform.tools.i18n.I18nFoldingBuilder"/>
|
||||||
|
|
||||||
|
<!-- Inlay Hints Providers -->
|
||||||
|
<codeInsight.inlayProvider language="JAVA" implementationClass="com.sdk.dynform.tools.i18n.I18nInlayHintsProvider"/>
|
||||||
|
<codeInsight.inlayProvider language="XML" implementationClass="com.sdk.dynform.tools.i18n.I18nInlayHintsProvider"/>
|
||||||
|
<codeInsight.inlayProvider language="JavaScript" implementationClass="com.sdk.dynform.tools.i18n.I18nInlayHintsProvider"/>
|
||||||
|
|
||||||
|
<!-- Reference Contributors -->
|
||||||
|
<psi.referenceContributor language="JAVA" implementation="com.sdk.dynform.tools.i18n.I18nReferenceContributor"/>
|
||||||
|
<psi.referenceContributor language="XML" implementation="com.sdk.dynform.tools.i18n.I18nReferenceContributor"/>
|
||||||
|
<psi.referenceContributor language="JavaScript" implementation="com.sdk.dynform.tools.i18n.I18nReferenceContributor"/>
|
||||||
|
|
||||||
|
<psi.referenceContributor language="JAVA" implementation="com.sdk.dynform.tools.helper.DynFormReferenceContributor"/>
|
||||||
|
<psi.referenceContributor language="XML" implementation="com.sdk.dynform.tools.helper.DynFormReferenceContributor"/>
|
||||||
|
<psi.referenceContributor language="JavaScript" implementation="com.sdk.dynform.tools.helper.DynFormReferenceContributor"/>
|
||||||
|
|
||||||
|
<!-- Completion Contributors -->
|
||||||
|
<completion.contributor language="JAVA" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/>
|
||||||
|
<completion.contributor language="XML" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/>
|
||||||
|
<completion.contributor language="JavaScript" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/>
|
||||||
|
|
||||||
|
<completion.contributor language="JAVA" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/>
|
||||||
|
<completion.contributor language="XML" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/>
|
||||||
|
<completion.contributor language="JavaScript" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/>
|
||||||
|
|
||||||
</extensions>
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Bundle for DynForm I18n Tools
|
||||||
Reference in New Issue
Block a user