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"
|
||||
}
|
||||
group = "com.sdk.dynform.tools"
|
||||
version = "3.2.8"
|
||||
version = "3.2.9"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -38,7 +38,13 @@ intellijPlatform {
|
||||
}
|
||||
|
||||
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>
|
||||
<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>
|
||||
|
||||
@@ -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<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) {
|
||||
|
||||
@@ -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<PsiFile> getAllFrmlFiles(@NotNull PsiFile contextFile) {
|
||||
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)};
|
||||
}
|
||||
});
|
||||
|
||||
// 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) {
|
||||
@@ -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) {
|
||||
if (results.size() <= 1) return results.toArray(new ResolveResult[0]);
|
||||
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())
|
||||
);
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<NoSettings> {
|
||||
@@ -25,8 +26,7 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
||||
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<NoSettings> {
|
||||
}
|
||||
|
||||
private static class I18nCollector extends FactoryInlayHintsCollector {
|
||||
// ใช้ Set เพื่อจำ offset ที่เคยแสดง Hint ไปแล้ว ป้องกันการแสดงผลซ้ำซ้อนในโหนดแม่/ลูก
|
||||
private final Set<Integer> handledOffsets = new HashSet<>();
|
||||
|
||||
public I18nCollector(@NotNull Editor editor) {
|
||||
super(editor);
|
||||
}
|
||||
@@ -42,58 +45,39 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
|
||||
@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<NoSettings> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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<NoSettings> {
|
||||
id.contains("JavaScript") || id.contains("JS") ||
|
||||
id.contains("ECMAScript") || id.contains("TypeScript");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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);
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
]]></description>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user