feat: update plugin version to 3.2.9 and enhance i18n/reference support
- Extend I18n support for MESSAGE attribute in UNIQ-CHECK and other tags. - Add code completion and reference navigation for CHECK-FIELDS resolving against related grid datasets. - Fix duplicate i18n Inlay Hints in JSP and JS files by targeting the innermost PSI elements. - Enhance MGET_PATTERN regex to support object-chained calls (e.g., factory.$M.get).
This commit is contained in:
93
DevResources/full-examples/bdgt04/exec/bdgt04-actions.jsp
Executable file
93
DevResources/full-examples/bdgt04/exec/bdgt04-actions.jsp
Executable file
@@ -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.");
|
||||||
|
}
|
||||||
|
%>
|
||||||
@@ -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.dynform.tools"
|
group = "com.sdk.dynform.tools"
|
||||||
version = "3.2.8"
|
version = "3.2.9"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -38,7 +38,13 @@ intellijPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
changeNotes = """
|
changeNotes = """
|
||||||
<h2>[3.2.7]</h2>
|
<h2>[3.2.9]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Extended I18n Support:</strong> Added Inlay Hints, Autocomplete, and Reference support for the <code>MESSAGE</code> attribute in <code><UNIQ-CHECK></code> and other tags.</li>
|
||||||
|
<li><strong>Grid Field Validation:</strong> Introduced code completion and reference navigation for <code>CHECK-FIELDS</code> in <code><UNIQ-CHECK></code>, resolving against the related grid dataset.</li>
|
||||||
|
<li><strong>I18n Hint Stabilization:</strong> 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., <code>factory.${'$'}M.get</code>).</li>
|
||||||
|
</ul>
|
||||||
|
<h2>[3.2.8]</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Persistent Generation Context:</strong> Generator now remembers the last used directory for Action Beans and Dataset XMLs independently per project.</li>
|
<li><strong>Persistent Generation Context:</strong> Generator now remembers the last used directory for Action Beans and Dataset XMLs independently per project.</li>
|
||||||
<li><strong>Configurable Auto-Open:</strong> Added new settings to toggle automatic file opening after generation and customize the table limit.</li>
|
<li><strong>Configurable Auto-Open:</strong> Added new settings to toggle automatic file opening after generation and customize the table limit.</li>
|
||||||
|
|||||||
@@ -256,6 +256,32 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
|||||||
addFileIncludeCompletions(parameters, resultSet);
|
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<CompletionParameters>() {
|
||||||
|
@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) {
|
private void addFieldsInTagRecursive(XmlTag container, @NotNull CompletionResultSet resultSet) {
|
||||||
|
|||||||
@@ -143,6 +143,18 @@ public class DynFormPathUtils {
|
|||||||
return null;
|
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
|
@NotNull
|
||||||
public static List<PsiFile> getAllFrmlFiles(@NotNull PsiFile contextFile) {
|
public static List<PsiFile> getAllFrmlFiles(@NotNull PsiFile contextFile) {
|
||||||
List<PsiFile> files = new ArrayList<>();
|
List<PsiFile> files = new ArrayList<>();
|
||||||
|
|||||||
@@ -203,6 +203,64 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
|||||||
return new PsiReference[]{new DynFormFileIncludeReference(attrValue, new TextRange(1, value.length() + 1), value)};
|
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<PsiReference> 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<PsiReference> 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) {
|
private static boolean hasAncestorWithName(XmlTag tag, String name) {
|
||||||
@@ -821,6 +879,111 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class DynFormExecutorNameReference extends PsiReferenceBase<XmlAttributeValue> {
|
||||||
|
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<XmlAttributeValue> {
|
||||||
|
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<XmlAttributeValue> 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<ResolveResult> 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<ResolveResult> results, Project project) {
|
private static ResolveResult[] filterOpenFiles(List<ResolveResult> results, Project project) {
|
||||||
if (results.size() <= 1) return results.toArray(new ResolveResult[0]);
|
if (results.size() <= 1) return results.toArray(new ResolveResult[0]);
|
||||||
FileEditorManager editorManager = FileEditorManager.getInstance(project);
|
FileEditorManager editorManager = FileEditorManager.getInstance(project);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class I18nCompletionContributor extends CompletionContributor {
|
|||||||
new com.intellij.openapi.util.TextRange(Math.max(0, parameters.getOffset() - 100), parameters.getOffset())
|
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()) {
|
if (m.find()) {
|
||||||
handleMultiKeyCompletion(parameters, resultSet, m.group(1));
|
handleMultiKeyCompletion(parameters, resultSet, m.group(1));
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ public class I18nCompletionContributor extends CompletionContributor {
|
|||||||
XmlAttribute attribute = PsiTreeUtil.getParentOfType(position, XmlAttribute.class);
|
XmlAttribute attribute = PsiTreeUtil.getParentOfType(position, XmlAttribute.class);
|
||||||
if (attribute != null) {
|
if (attribute != null) {
|
||||||
String name = attribute.getName();
|
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();
|
XmlAttributeValue valueElement = attribute.getValueElement();
|
||||||
if (valueElement != null) {
|
if (valueElement != null) {
|
||||||
int offsetInValue = parameters.getOffset() - valueElement.getTextRange().getStartOffset() - 1;
|
int offsetInValue = parameters.getOffset() - valueElement.getTextRange().getStartOffset() - 1;
|
||||||
@@ -152,7 +152,7 @@ public class I18nCompletionContributor extends CompletionContributor {
|
|||||||
String methodName = methodCall.getMethodExpression().getReferenceName();
|
String methodName = methodCall.getMethodExpression().getReferenceName();
|
||||||
if ("get".equals(methodName)) {
|
if ("get".equals(methodName)) {
|
||||||
PsiElement qualifier = methodCall.getMethodExpression().getQualifier();
|
PsiElement qualifier = methodCall.getMethodExpression().getQualifier();
|
||||||
return qualifier != null && "$M".equals(qualifier.getText());
|
return qualifier != null && (qualifier.getText().endsWith("$M") || "$M".equals(qualifier.getText()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ import com.intellij.openapi.project.Project;
|
|||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
import com.intellij.psi.xml.XmlAttribute;
|
import com.intellij.psi.xml.XmlAttribute;
|
||||||
import com.intellij.psi.xml.XmlToken;
|
|
||||||
import com.sdk.dynform.tools.config.DynFormSettings;
|
import com.sdk.dynform.tools.config.DynFormSettings;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
||||||
@@ -25,8 +26,7 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent duplicate hints: only provide hints if the language being queried
|
// Prevent duplicate hints across multiple language trees in the same file
|
||||||
// matches the primary language of the file.
|
|
||||||
if (!file.getLanguage().equals(file.getViewProvider().getBaseLanguage())) {
|
if (!file.getLanguage().equals(file.getViewProvider().getBaseLanguage())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,9 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class I18nCollector extends FactoryInlayHintsCollector {
|
private static class I18nCollector extends FactoryInlayHintsCollector {
|
||||||
|
// ใช้ Set เพื่อจำ offset ที่เคยแสดง Hint ไปแล้ว ป้องกันการแสดงผลซ้ำซ้อนในโหนดแม่/ลูก
|
||||||
|
private final Set<Integer> handledOffsets = new HashSet<>();
|
||||||
|
|
||||||
public I18nCollector(@NotNull Editor editor) {
|
public I18nCollector(@NotNull Editor editor) {
|
||||||
super(editor);
|
super(editor);
|
||||||
}
|
}
|
||||||
@@ -42,58 +45,39 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
|||||||
@Override
|
@Override
|
||||||
public boolean collect(@NotNull PsiElement element, @NotNull Editor editor, @NotNull InlayHintsSink sink) {
|
public boolean collect(@NotNull PsiElement element, @NotNull Editor editor, @NotNull InlayHintsSink sink) {
|
||||||
String lang = element.getLanguage().getDisplayName().toLowerCase();
|
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")
|
// 1. Regex based hints ($M.get และ @M{})
|
||||||
boolean isJsOrJava = checkLangs.contains(lang) || (baseLang != null && checkLangs.contains(baseLang.getDisplayName().toLowerCase()));
|
// ประมวลผลเมื่อเป็นไฟล์เต็มๆ (PsiFile) หรือ element ย่อยที่มีขนาดไม่ใหญ่เกินไปเพื่อป้องกัน O(N^2)
|
||||||
|
if (element instanceof PsiFile || element.getTextLength() < 5000) {
|
||||||
if (isJsOrJava) {
|
String text = element.getText();
|
||||||
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) {
|
if (text != null) {
|
||||||
// Handle @M{key}
|
int baseOffset = element.getTextRange().getStartOffset();
|
||||||
if (text.contains("@M{")) {
|
|
||||||
java.util.regex.Matcher matcher = I18nUtils.getMPattern().matcher(text);
|
// Handle $M.get("key")
|
||||||
while (matcher.find()) {
|
java.util.regex.Matcher matcher1 = I18nUtils.getMGetPattern().matcher(text);
|
||||||
String key = matcher.group(1);
|
while (matcher1.find()) {
|
||||||
if (key != null) {
|
String key = matcher1.group(1);
|
||||||
|
if (key != null) {
|
||||||
|
int offset = baseOffset + matcher1.end();
|
||||||
|
if (handledOffsets.add(offset)) {
|
||||||
String translation = I18nUtils.findMessageValue(element.getProject(), key);
|
String translation = I18nUtils.findMessageValue(element.getProject(), key);
|
||||||
if (translation != null) {
|
if (translation != null) {
|
||||||
sink.addInlineElement(token.getTextRange().getStartOffset() + matcher.end(), true,
|
sink.addInlineElement(offset, true, getFactory().text(" \u00AB " + translation + " \u00BB"), false);
|
||||||
getFactory().text(" \u00AB " + translation + " \u00BB"), false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle $M.get in XML (e.g. inside script tags or attributes)
|
// Handle @M{key}
|
||||||
if (text.contains("$M.get")) {
|
java.util.regex.Matcher matcher2 = I18nUtils.getMPattern().matcher(text);
|
||||||
java.util.regex.Matcher matcher = I18nUtils.getMGetPattern().matcher(text);
|
while (matcher2.find()) {
|
||||||
while (matcher.find()) {
|
String key = matcher2.group(1);
|
||||||
String key = matcher.group(1);
|
if (key != null) {
|
||||||
if (key != null) {
|
int offset = baseOffset + matcher2.end();
|
||||||
|
if (handledOffsets.add(offset)) {
|
||||||
String translation = I18nUtils.findMessageValue(element.getProject(), key);
|
String translation = I18nUtils.findMessageValue(element.getProject(), key);
|
||||||
if (translation != null) {
|
if (translation != null) {
|
||||||
sink.addInlineElement(token.getTextRange().getStartOffset() + matcher.end(), true,
|
sink.addInlineElement(offset, true, getFactory().text(" \u00AB " + translation + " \u00BB"), false);
|
||||||
getFactory().text(" \u00AB " + translation + " \u00BB"), false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,16 +85,18 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. FRML XML CAPTION/LABEL
|
// 2. FRML XML CAPTION/LABEL/MESSAGE
|
||||||
if ("xml#frml".contains(lang) && element instanceof XmlAttribute attribute) {
|
if ("xml#frml".contains(lang) && element instanceof XmlAttribute attribute) {
|
||||||
String name = attribute.getName();
|
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();
|
String value = attribute.getValue();
|
||||||
if (value != null && !value.contains("@M{")) {
|
if (value != null && !value.contains("@M{")) {
|
||||||
String translation = I18nUtils.findMessageValue(element.getProject(), value);
|
int offset = attribute.getTextRange().getEndOffset();
|
||||||
if (translation != null) {
|
if (handledOffsets.add(offset)) {
|
||||||
sink.addInlineElement(attribute.getTextRange().getEndOffset(), true,
|
String translation = I18nUtils.findMessageValue(element.getProject(), value);
|
||||||
getFactory().text(" \u00AB " + translation + " \u00BB"), false);
|
if (translation != null) {
|
||||||
|
sink.addInlineElement(offset, true, getFactory().text(" \u00AB " + translation + " \u00BB"), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,4 +141,4 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
|||||||
id.contains("JavaScript") || id.contains("JS") ||
|
id.contains("JavaScript") || id.contains("JS") ||
|
||||||
id.contains("ECMAScript") || id.contains("TypeScript");
|
id.contains("ECMAScript") || id.contains("TypeScript");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,8 +32,8 @@ public class I18nReferenceContributor extends PsiReferenceContributor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. XML Attribute Reference Provider (for .frml CAPTION/LABEL)
|
// 2. XML Attribute Reference Provider (for .frml CAPTION/LABEL/MESSAGE)
|
||||||
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().withParent(XmlPatterns.xmlAttribute().withName("CAPTION", "LABEL")),
|
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().withParent(XmlPatterns.xmlAttribute().withName("CAPTION", "LABEL", "MESSAGE")),
|
||||||
new PsiReferenceProvider() {
|
new PsiReferenceProvider() {
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
@@ -118,7 +118,6 @@ public class I18nReferenceContributor extends PsiReferenceContributor {
|
|||||||
|
|
||||||
private void addMultiKeyReferences(List<PsiReference> refs, PsiElement element, String text, Pattern pattern) {
|
private void addMultiKeyReferences(List<PsiReference> refs, PsiElement element, String text, Pattern pattern) {
|
||||||
Matcher mMatcher = pattern.matcher(text);
|
Matcher mMatcher = pattern.matcher(text);
|
||||||
int elementStart = element.getTextRange().getStartOffset();
|
|
||||||
while (mMatcher.find()) {
|
while (mMatcher.find()) {
|
||||||
if (mMatcher.groupCount() >= 1) {
|
if (mMatcher.groupCount() >= 1) {
|
||||||
String content = mMatcher.group(1);
|
String content = mMatcher.group(1);
|
||||||
@@ -154,7 +153,7 @@ public class I18nReferenceContributor extends PsiReferenceContributor {
|
|||||||
String methodName = methodCall.getMethodExpression().getReferenceName();
|
String methodName = methodCall.getMethodExpression().getReferenceName();
|
||||||
if ("get".equals(methodName)) {
|
if ("get".equals(methodName)) {
|
||||||
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
|
PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
|
||||||
return qualifier != null && "$M".equals(qualifier.getText());
|
return qualifier != null && (qualifier.getText().endsWith("$M") || "$M".equals(qualifier.getText()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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
|
@NotNull
|
||||||
|
|||||||
@@ -34,6 +34,12 @@
|
|||||||
]]></description>
|
]]></description>
|
||||||
|
|
||||||
<change-notes><![CDATA[
|
<change-notes><![CDATA[
|
||||||
|
<h2>[3.2.9]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Extended I18n Support:</strong> Added Inlay Hints, Autocomplete, and Reference support for the <code>MESSAGE</code> attribute in <code><UNIQ-CHECK></code> and other tags.</li>
|
||||||
|
<li><strong>Grid Field Validation:</strong> Introduced code completion and reference navigation for <code>CHECK-FIELDS</code> in <code><UNIQ-CHECK></code>, resolving against the related grid dataset.</li>
|
||||||
|
<li><strong>I18n Hint Stabilization:</strong> 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., <code>factory.$M.get</code>).</li>
|
||||||
|
</ul>
|
||||||
<h2>[3.2.8]</h2>
|
<h2>[3.2.8]</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Cross-file Reference Resolution:</strong> Enhanced resolution of Datasets and Fields (e.g., DS-MASTER) to search upward across included `.frml` files.</li>
|
<li><strong>Cross-file Reference Resolution:</strong> Enhanced resolution of Datasets and Fields (e.g., DS-MASTER) to search upward across included `.frml` files.</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user