diff --git a/DevResources/full-examples/bdgt04/exec/bdgt04-actions.jsp b/DevResources/full-examples/bdgt04/exec/bdgt04-actions.jsp
new file mode 100755
index 0000000..7c944b0
--- /dev/null
+++ b/DevResources/full-examples/bdgt04/exec/bdgt04-actions.jsp
@@ -0,0 +1,93 @@
+<%@ page contentType="text/html; charset=UTF-8" language="java" %>
+<%@ page import="java.lang.reflect.*" %>
+<%@ page import="sdk.json.*" %>
+<%@ page import="sdk.utils.*" %>
+<%@ page import="sdk.api.client.*" %>
+<%@ page import="java.util.*" %>
+<%@ page import="com.apps.SystemFactory" %>
+<%@ page import="sdk.db.connector.*" %>
+<%@ page import="com.bgt.model.bean.*" %>
+<%!
+ SDKLogger logger = new SDKLogger("Operate-actions");
+
+
+ public boolean bgt0102010_add(SystemFactory factory, JSONObject jsData) {
+ if (!jsData.getString("-- some important field --").isBlank()) {
+ DBConnector dbConn = factory.appDatabase.getXConnector();
+ try {
+ {
+ REFER_CODE dsRefcode = new REFER_CODE(dbConn);
+ dsRefcode.append();
+ dsRefcode.setRFG_GRP("STG-ITEMS");
+ dsRefcode.setRFC_CODE(jsData.getString(REFER_CODE.RFC_CODE));
+ dsRefcode.setRFC_DESC(jsData.getString(REFER_CODE.RFC_DESC));
+ dsRefcode.setRFC_FLAG(jsData.getString(REFER_CODE.RFC_FLAG));
+ dsRefcode.execute();
+ }
+ {
+ REFER_GROUP dsRefGroup = new REFER_GROUP(dbConn);
+ dsRefGroup.insert();
+ dsRefGroup.setRFG_CODE(jsData.getString(REFER_CODE.RFC_CODE));
+ dsRefGroup.setRFG_NAME(jsData.getString(REFER_CODE.RFC_DESC));
+ dsRefGroup.setRFG_FLAG(jsData.getString(REFER_CODE.RFC_FLAG));
+ dsRefGroup.setRFG_EDITOR("STG");
+ dsRefGroup.execute();
+ }
+ factory.setRestCode("OK");
+ dbConn.close();
+ } catch (Exception ex) {
+ factory.setRestCode("ERROR");
+ factory.setRestMsg(ex.getMessage());
+ dbConn.close();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean _action_skeleton(SystemFactory factory, JSONObject jsData) {
+ if (!jsData.getString("-- some important field --").isBlank()) {
+ DBConnector dbConn = factory.appDatabase.getXConnector();
+ try {
+
+ factory.setRestCode("OK");
+ dbConn.close();
+ } catch (Exception ex) {
+ factory.setRestCode("ERROR");
+ factory.setRestMsg(ex.getMessage());
+ dbConn.close();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean execute(String action, SystemFactory factory) {
+ try {
+ Class> thisClass = getClass();
+ Method mtAction = thisClass.getMethod(action, SystemFactory.class, JSONObject.class);
+ String data = factory.rqsCtx.getParameter("data", "{}");
+ JSONObject jsData = new JSONObject(data);
+ boolean result = (boolean) mtAction.invoke(this, factory, jsData);
+ return result;
+ } catch (Exception e) {
+ factory.setRestCode("ERROR");
+ factory.setRestMsg(e+"\n"+JUtils.stackToString(e,10));
+ logger.error(e);
+ return false;
+ }
+ }
+%>
+
+<%
+ SystemFactory factory = SystemFactory.getInstance(request);
+ String action = factory.rqsCtx.getParameter("action", "");
+
+ if (factory.isValidJaxVF()) {
+ action = action.replaceAll("-", "_").replaceAll(" ", "_");
+ this.execute(action, factory);
+ } else {
+ factory.setRestCode("ERROR");
+ factory.setRestMsg("TK-Controller mismatch.");
+ }
+%>
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index cccc436..d783f2b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,7 +4,7 @@ plugins {
id("org.jetbrains.intellij.platform") version "2.7.0"
}
group = "com.sdk.dynform.tools"
-version = "3.2.8"
+version = "3.2.9"
repositories {
mavenCentral()
@@ -38,7 +38,13 @@ intellijPlatform {
}
changeNotes = """
-
[3.2.7]
+ [3.2.9]
+
+ - Extended I18n Support: Added Inlay Hints, Autocomplete, and Reference support for the
MESSAGE attribute in <UNIQ-CHECK> and other tags.
+ - Grid Field Validation: Introduced code completion and reference navigation for
CHECK-FIELDS in <UNIQ-CHECK>, resolving against the related grid dataset.
+ - I18n Hint Stabilization: Resolved an issue causing duplicate Inlay Hints in JSP and JS files by targeting the innermost PSI elements and improving regex support for object-chained calls (e.g.,
factory.${'$'}M.get).
+
+ [3.2.8]
- Persistent Generation Context: Generator now remembers the last used directory for Action Beans and Dataset XMLs independently per project.
- Configurable Auto-Open: Added new settings to toggle automatic file opening after generation and customize the table limit.
diff --git a/src/main/java/com/sdk/dynform/tools/dynform/DynFormCompletionContributor.java b/src/main/java/com/sdk/dynform/tools/dynform/DynFormCompletionContributor.java
index 3da8b43..a873479 100644
--- a/src/main/java/com/sdk/dynform/tools/dynform/DynFormCompletionContributor.java
+++ b/src/main/java/com/sdk/dynform/tools/dynform/DynFormCompletionContributor.java
@@ -256,6 +256,32 @@ public class DynFormCompletionContributor extends CompletionContributor {
addFileIncludeCompletions(parameters, resultSet);
}
});
+
+ // XML completion for UNIQ-CHECK:CHECK-FIELDS
+ extend(CompletionType.BASIC, XmlPatterns.psiElement()
+ .inside(XmlPatterns.xmlAttributeValue()
+ .withParent(XmlPatterns.xmlAttribute().withName("CHECK-FIELDS")
+ .withParent(XmlPatterns.xmlTag().withName("UNIQ-CHECK")))),
+ new CompletionProvider() {
+ @Override
+ protected void addCompletions(@NotNull CompletionParameters parameters,
+ @NotNull ProcessingContext context,
+ @NotNull CompletionResultSet resultSet) {
+ PsiElement position = parameters.getPosition();
+ XmlTag uniqCheckTag = PsiTreeUtil.getParentOfType(position, XmlTag.class);
+ if (uniqCheckTag == null) return;
+
+ XmlTag dataGridTag = uniqCheckTag.getParentTag();
+ if (dataGridTag != null && "DATA-GRID".equals(dataGridTag.getName())) {
+ // Split by separators to handle multi-key completion
+ String content = parameters.getPosition().getText().replace(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED, "");
+ int lastSeparator = Math.max(content.lastIndexOf(','), content.lastIndexOf('+'));
+ String prefix = lastSeparator != -1 ? content.substring(lastSeparator + 1).trim() : content;
+
+ addFieldsFromGrid(dataGridTag, resultSet.withPrefixMatcher(prefix));
+ }
+ }
+ });
}
private void addFieldsInTagRecursive(XmlTag container, @NotNull CompletionResultSet resultSet) {
diff --git a/src/main/java/com/sdk/dynform/tools/dynform/DynFormPathUtils.java b/src/main/java/com/sdk/dynform/tools/dynform/DynFormPathUtils.java
index 4d7fa91..ff9da00 100644
--- a/src/main/java/com/sdk/dynform/tools/dynform/DynFormPathUtils.java
+++ b/src/main/java/com/sdk/dynform/tools/dynform/DynFormPathUtils.java
@@ -143,6 +143,18 @@ public class DynFormPathUtils {
return null;
}
+ @Nullable
+ public static PsiFile findExecJsp(@NotNull PsiFile contextFile, @NotNull String name) {
+ VirtualFile moduleDir = findModuleDir(contextFile);
+ if (moduleDir != null) {
+ VirtualFile jspFile = moduleDir.findFileByRelativePath("exec/" + name + ".jsp");
+ if (jspFile != null) {
+ return PsiManager.getInstance(contextFile.getProject()).findFile(jspFile);
+ }
+ }
+ return null;
+ }
+
@NotNull
public static List getAllFrmlFiles(@NotNull PsiFile contextFile) {
List files = new ArrayList<>();
diff --git a/src/main/java/com/sdk/dynform/tools/dynform/DynFormReferenceContributor.java b/src/main/java/com/sdk/dynform/tools/dynform/DynFormReferenceContributor.java
index e76d497..1b1c6f3 100644
--- a/src/main/java/com/sdk/dynform/tools/dynform/DynFormReferenceContributor.java
+++ b/src/main/java/com/sdk/dynform/tools/dynform/DynFormReferenceContributor.java
@@ -203,6 +203,64 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
return new PsiReference[]{new DynFormFileIncludeReference(attrValue, new TextRange(1, value.length() + 1), value)};
}
});
+
+ // XML Reference for EXECUTOR NAME and ACTIONS
+ registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
+ .withParent(XmlPatterns.xmlAttribute().withName("NAME", "ADD-ACTION", "UPDATE-ACTION", "DELETE-ACTION")
+ .withParent(XmlPatterns.xmlTag().withName("EXECUTOR"))),
+ new PsiReferenceProvider() {
+ @NotNull
+ @Override
+ public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
+ XmlAttributeValue attrValue = (XmlAttributeValue) element;
+ String value = attrValue.getValue();
+ if (value == null || value.isEmpty()) return PsiReference.EMPTY_ARRAY;
+
+ XmlAttribute attr = (XmlAttribute) attrValue.getParent();
+ String attrName = attr.getName();
+
+ if ("NAME".equals(attrName)) {
+ return new PsiReference[]{new DynFormExecutorNameReference(attrValue, new TextRange(1, value.length() + 1), value)};
+ } else {
+ return new PsiReference[]{new DynFormExecutorActionReference(attrValue, new TextRange(1, value.length() + 1), value)};
+ }
+ }
+ });
+
+ // XML Reference for UNIQ-CHECK:CHECK-FIELDS
+ registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
+ .withParent(XmlPatterns.xmlAttribute().withName("CHECK-FIELDS")
+ .withParent(XmlPatterns.xmlTag().withName("UNIQ-CHECK"))),
+ new PsiReferenceProvider() {
+ @NotNull
+ @Override
+ public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
+ XmlAttributeValue attrValue = (XmlAttributeValue) element;
+ String value = attrValue.getValue();
+ if (value == null || value.isEmpty()) return PsiReference.EMPTY_ARRAY;
+
+ List refs = new ArrayList<>();
+ java.util.regex.Pattern sepPattern = java.util.regex.Pattern.compile("[,+]");
+ java.util.regex.Matcher matcher = sepPattern.matcher(value);
+
+ int lastEnd = 0;
+ while (matcher.find()) {
+ addUniqCheckRef(refs, attrValue, value, lastEnd, matcher.start());
+ lastEnd = matcher.end();
+ }
+ addUniqCheckRef(refs, attrValue, value, lastEnd, value.length());
+
+ return refs.toArray(new PsiReference[0]);
+ }
+
+ private void addUniqCheckRef(List refs, XmlAttributeValue element, String content, int start, int end) {
+ String field = content.substring(start, end).trim();
+ if (!field.isEmpty()) {
+ int actualStart = content.indexOf(field, start);
+ refs.add(new DynFormUniqCheckFieldReference(element, new TextRange(actualStart + 1, actualStart + 1 + field.length()), field));
+ }
+ }
+ });
}
private static boolean hasAncestorWithName(XmlTag tag, String name) {
@@ -821,6 +879,111 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
}
}
+ private static class DynFormExecutorNameReference extends PsiReferenceBase {
+ private final String executorName;
+ public DynFormExecutorNameReference(@NotNull XmlAttributeValue element, TextRange range, String executorName) {
+ super(element, range, true);
+ this.executorName = executorName;
+ }
+ @Nullable @Override public PsiElement resolve() {
+ return DynFormPathUtils.findExecJsp(myElement.getContainingFile(), executorName);
+ }
+ }
+
+ private static class DynFormExecutorActionReference extends PsiReferenceBase {
+ private final String actionName;
+ public DynFormExecutorActionReference(@NotNull XmlAttributeValue element, TextRange range, String actionName) {
+ super(element, range, true);
+ this.actionName = actionName;
+ }
+ @Nullable @Override public PsiElement resolve() {
+ XmlTag executorTag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
+ if (executorTag == null) return null;
+ String executorName = executorTag.getAttributeValue("NAME");
+ if (executorName == null) return null;
+
+ PsiFile jspFile = DynFormPathUtils.findExecJsp(myElement.getContainingFile(), executorName);
+ if (jspFile != null) {
+ return jspFile;
+ }
+ return null;
+ }
+ }
+
+ private static class DynFormUniqCheckFieldReference extends PsiReferenceBase implements PsiPolyVariantReference {
+ private final String fieldName;
+
+ public DynFormUniqCheckFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
+ super(element, range, true);
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
+ List results = new ArrayList<>();
+ XmlAttribute attr = (XmlAttribute) myElement.getParent();
+ XmlTag uniqCheckTag = (XmlTag) attr.getParent();
+ XmlTag dataGridTag = uniqCheckTag.getParentTag();
+
+ if (dataGridTag != null && "DATA-GRID".equals(dataGridTag.getName())) {
+ PsiElement found = findFieldInGrid(dataGridTag);
+ if (found != null) results.add(new PsiElementResolveResult(found));
+ }
+ return filterOpenFiles(results, myElement.getProject());
+ }
+
+ @Nullable
+ @Override
+ public PsiElement resolve() {
+ ResolveResult[] results = multiResolve(false);
+ return results.length == 1 ? results[0].getElement() : null;
+ }
+
+ @Nullable
+ private PsiElement findFieldInGrid(XmlTag gridTag) {
+ String dataId = null;
+ XmlTag listTag = gridTag.findFirstSubTag("GRID-LIST");
+ if (listTag != null) dataId = listTag.getAttributeValue("DATAID");
+ if (dataId == null) {
+ XmlTag editorTag = gridTag.findFirstSubTag("GRID-EDITOR");
+ if (editorTag != null) dataId = editorTag.getAttributeValue("DATAID");
+ }
+
+ if (dataId != null && !dataId.isEmpty()) {
+ PsiFile file = gridTag.getContainingFile();
+ PsiElement datasetElement = findDatasetElement(file, dataId);
+ if (datasetElement instanceof XmlTag datasetTag) {
+ XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ return findFieldInTag(fieldsTag, fieldName);
+ }
+ }
+ }
+ return null;
+ }
+
+ private PsiElement findDatasetElement(PsiFile file, String id) {
+ if (!(file instanceof XmlFile xmlFile)) return null;
+ XmlTag rootTag = xmlFile.getRootTag();
+ if (rootTag == null) return null;
+
+ XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
+ if (datasetsContainer == null) datasetsContainer = rootTag;
+
+ for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) {
+ if (id.equals(datasetTag.getAttributeValue("ID"))) {
+ return datasetTag;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Object @NotNull [] getVariants() {
+ return new Object[0];
+ }
+ }
+
private static ResolveResult[] filterOpenFiles(List results, Project project) {
if (results.size() <= 1) return results.toArray(new ResolveResult[0]);
FileEditorManager editorManager = FileEditorManager.getInstance(project);
diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nCompletionContributor.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nCompletionContributor.java
index 48ed042..d0e4ba1 100644
--- a/src/main/java/com/sdk/dynform/tools/i18n/I18nCompletionContributor.java
+++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nCompletionContributor.java
@@ -44,7 +44,7 @@ public class I18nCompletionContributor extends CompletionContributor {
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);
+ java.util.regex.Matcher m = java.util.regex.Pattern.compile("(?:\\w+\\.)?\\$M\\.get\\([\"']([^\"']*)$").matcher(textBefore);
if (m.find()) {
handleMultiKeyCompletion(parameters, resultSet, m.group(1));
}
@@ -64,7 +64,7 @@ public class I18nCompletionContributor extends CompletionContributor {
XmlAttribute attribute = PsiTreeUtil.getParentOfType(position, XmlAttribute.class);
if (attribute != null) {
String name = attribute.getName();
- if (name.equals("CAPTION") || name.equals("LABEL")) {
+ if (name.equals("CAPTION") || name.equals("LABEL") || name.equals("MESSAGE")) {
XmlAttributeValue valueElement = attribute.getValueElement();
if (valueElement != null) {
int offsetInValue = parameters.getOffset() - valueElement.getTextRange().getStartOffset() - 1;
@@ -152,7 +152,7 @@ public class I18nCompletionContributor extends CompletionContributor {
String methodName = methodCall.getMethodExpression().getReferenceName();
if ("get".equals(methodName)) {
PsiElement qualifier = methodCall.getMethodExpression().getQualifier();
- return qualifier != null && "$M".equals(qualifier.getText());
+ return qualifier != null && (qualifier.getText().endsWith("$M") || "$M".equals(qualifier.getText()));
}
}
}
diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nInlayHintsProvider.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nInlayHintsProvider.java
index bd024a8..72b068d 100644
--- a/src/main/java/com/sdk/dynform/tools/i18n/I18nInlayHintsProvider.java
+++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nInlayHintsProvider.java
@@ -7,12 +7,13 @@ import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlAttribute;
-import com.intellij.psi.xml.XmlToken;
import com.sdk.dynform.tools.config.DynFormSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
+import java.util.HashSet;
+import java.util.Set;
@SuppressWarnings("UnstableApiUsage")
public class I18nInlayHintsProvider implements InlayHintsProvider {
@@ -25,8 +26,7 @@ public class I18nInlayHintsProvider implements InlayHintsProvider {
return null;
}
- // Prevent duplicate hints: only provide hints if the language being queried
- // matches the primary language of the file.
+ // Prevent duplicate hints across multiple language trees in the same file
if (!file.getLanguage().equals(file.getViewProvider().getBaseLanguage())) {
return null;
}
@@ -35,6 +35,9 @@ public class I18nInlayHintsProvider implements InlayHintsProvider {
}
private static class I18nCollector extends FactoryInlayHintsCollector {
+ // ใช้ Set เพื่อจำ offset ที่เคยแสดง Hint ไปแล้ว ป้องกันการแสดงผลซ้ำซ้อนในโหนดแม่/ลูก
+ private final Set handledOffsets = new HashSet<>();
+
public I18nCollector(@NotNull Editor editor) {
super(editor);
}
@@ -42,58 +45,39 @@ public class I18nInlayHintsProvider implements InlayHintsProvider {
@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();
+ // 1. Regex based hints ($M.get และ @M{})
+ // ประมวลผลเมื่อเป็นไฟล์เต็มๆ (PsiFile) หรือ element ย่อยที่มีขนาดไม่ใหญ่เกินไปเพื่อป้องกัน O(N^2)
+ if (element instanceof PsiFile || element.getTextLength() < 5000) {
+ String text = element.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) {
+ int baseOffset = element.getTextRange().getStartOffset();
+
+ // Handle $M.get("key")
+ java.util.regex.Matcher matcher1 = I18nUtils.getMGetPattern().matcher(text);
+ while (matcher1.find()) {
+ String key = matcher1.group(1);
+ if (key != null) {
+ int offset = baseOffset + matcher1.end();
+ if (handledOffsets.add(offset)) {
String translation = I18nUtils.findMessageValue(element.getProject(), key);
if (translation != null) {
- sink.addInlineElement(token.getTextRange().getStartOffset() + matcher.end(), true,
- getFactory().text(" \u00AB " + translation + " \u00BB"), false);
+ sink.addInlineElement(offset, 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) {
+ // Handle @M{key}
+ java.util.regex.Matcher matcher2 = I18nUtils.getMPattern().matcher(text);
+ while (matcher2.find()) {
+ String key = matcher2.group(1);
+ if (key != null) {
+ int offset = baseOffset + matcher2.end();
+ if (handledOffsets.add(offset)) {
String translation = I18nUtils.findMessageValue(element.getProject(), key);
if (translation != null) {
- sink.addInlineElement(token.getTextRange().getStartOffset() + matcher.end(), true,
- getFactory().text(" \u00AB " + translation + " \u00BB"), false);
+ sink.addInlineElement(offset, true, getFactory().text(" \u00AB " + translation + " \u00BB"), false);
}
}
}
@@ -101,16 +85,18 @@ public class I18nInlayHintsProvider implements InlayHintsProvider {
}
}
- // 3. FRML XML CAPTION/LABEL
+ // 2. FRML XML CAPTION/LABEL/MESSAGE
if ("xml#frml".contains(lang) && element instanceof XmlAttribute attribute) {
String name = attribute.getName();
- if (name.equals("CAPTION") || name.equals("LABEL")) {
+ if (name.equals("CAPTION") || name.equals("LABEL") || name.equals("MESSAGE")) {
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);
+ int offset = attribute.getTextRange().getEndOffset();
+ if (handledOffsets.add(offset)) {
+ String translation = I18nUtils.findMessageValue(element.getProject(), value);
+ if (translation != null) {
+ sink.addInlineElement(offset, true, getFactory().text(" \u00AB " + translation + " \u00BB"), false);
+ }
}
}
}
@@ -155,4 +141,4 @@ public class I18nInlayHintsProvider implements InlayHintsProvider {
id.contains("JavaScript") || id.contains("JS") ||
id.contains("ECMAScript") || id.contains("TypeScript");
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nReferenceContributor.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nReferenceContributor.java
index 84e744d..290c928 100644
--- a/src/main/java/com/sdk/dynform/tools/i18n/I18nReferenceContributor.java
+++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nReferenceContributor.java
@@ -32,8 +32,8 @@ public class I18nReferenceContributor extends PsiReferenceContributor {
}
});
- // 2. XML Attribute Reference Provider (for .frml CAPTION/LABEL)
- registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().withParent(XmlPatterns.xmlAttribute().withName("CAPTION", "LABEL")),
+ // 2. XML Attribute Reference Provider (for .frml CAPTION/LABEL/MESSAGE)
+ registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().withParent(XmlPatterns.xmlAttribute().withName("CAPTION", "LABEL", "MESSAGE")),
new PsiReferenceProvider() {
@NotNull
@Override
@@ -118,7 +118,6 @@ public class I18nReferenceContributor extends PsiReferenceContributor {
private void addMultiKeyReferences(List refs, PsiElement element, String text, Pattern pattern) {
Matcher mMatcher = pattern.matcher(text);
- int elementStart = element.getTextRange().getStartOffset();
while (mMatcher.find()) {
if (mMatcher.groupCount() >= 1) {
String content = mMatcher.group(1);
@@ -154,7 +153,7 @@ public class I18nReferenceContributor extends PsiReferenceContributor {
String methodName = methodCall.getMethodExpression().getReferenceName();
if ("get".equals(methodName)) {
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
- return qualifier != null && "$M".equals(qualifier.getText());
+ return qualifier != null && (qualifier.getText().endsWith("$M") || "$M".equals(qualifier.getText()));
}
}
}
diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java
index f92c02d..fd95311 100644
--- a/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java
+++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java
@@ -217,7 +217,7 @@ public class I18nUtils {
}
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*\\)");
+ private static final java.util.regex.Pattern MGET_PATTERN = java.util.regex.Pattern.compile("(?:[\\w.]+\\.)?\\$M\\.get\\(\\s*[\"'](.*?)[\"']\\s*\\)");
@NotNull
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index e5372e8..dd58f4e 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -34,6 +34,12 @@
]]>
[3.2.9]
+
+ - Extended I18n Support: Added Inlay Hints, Autocomplete, and Reference support for the
MESSAGE attribute in <UNIQ-CHECK> and other tags.
+ - Grid Field Validation: Introduced code completion and reference navigation for
CHECK-FIELDS in <UNIQ-CHECK>, resolving against the related grid dataset.
+ - I18n Hint Stabilization: Resolved an issue causing duplicate Inlay Hints in JSP and JS files by targeting the innermost PSI elements and improving regex support for object-chained calls (e.g.,
factory.$M.get).
+
[3.2.8]
- Cross-file Reference Resolution: Enhanced resolution of Datasets and Fields (e.g., DS-MASTER) to search upward across included `.frml` files.