diff --git a/build.gradle.kts b/build.gradle.kts
index f218fe3..95deec7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,12 +3,12 @@ plugins {
id("org.jetbrains.kotlin.jvm") version "2.1.0"
id("org.jetbrains.intellij.platform") version "2.7.0"
}
-
group = "com.sdk.dynform.tools"
-version = "3.2.2"
+version = "3.2.3"
repositories {
mavenCentral()
+
intellijPlatform {
defaultRepositories()
}
@@ -39,6 +39,12 @@ intellijPlatform {
}
changeNotes = """
+
[3.2.3]
+
+ - Advanced Data Referencing: Implemented comprehensive reference and completion support for
<FOREIGN-DATASETS> and <MASTER-DATA> structures.
+ - Contextual Field Resolution: Enhanced field resolution logic to resolve fields from datasets specified by
DATASET-ID and DATAID attributes within their respective tags.
+ - Recursive Resource Scanning: Improved dataset and grid scanning to search across recursively included
.frml files, ensuring consistent navigation and completion throughout the project.
+
[3.2.2]
- UI/UX Improvement: Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.
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 df9b7c9..38e10ed 100644
--- a/src/main/java/com/sdk/dynform/tools/dynform/DynFormCompletionContributor.java
+++ b/src/main/java/com/sdk/dynform/tools/dynform/DynFormCompletionContributor.java
@@ -102,19 +102,60 @@ public class DynFormCompletionContributor extends CompletionContributor {
}
});
- // XML completion for Dataset ID
+ // XML completion for Dataset ID (DATAID, DATASET, DATASET-ID, VIEW-DATASET)
extend(CompletionType.BASIC, XmlPatterns.psiElement()
.inside(XmlPatterns.xmlAttributeValue()
- .withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID"))),
+ .withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID", "VIEW-DATASET"))),
new CompletionProvider() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet resultSet) {
- addDatasetsInFile(parameters.getOriginalFile(), resultSet);
- PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(parameters.getOriginalFile());
- if (ajaxFile != null) {
- addDatasetsInFile(ajaxFile, resultSet);
+ addDatasetsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
+ }
+ });
+
+ // XML completion for MASTER-FIELDS and DETAIL-FIELDS in MASTER-DATA or FOREIGN-DATASETS > DATASET
+ extend(CompletionType.BASIC, XmlPatterns.psiElement()
+ .inside(XmlPatterns.xmlAttributeValue()
+ .withParent(XmlPatterns.xmlAttribute().withName("MASTER-FIELDS", "DETAIL-FIELDS")
+ .withParent(XmlPatterns.xmlTag().withName("MASTER-DATA", "DATASET")))),
+ new CompletionProvider() {
+ @Override
+ protected void addCompletions(@NotNull CompletionParameters parameters,
+ @NotNull ProcessingContext context,
+ @NotNull CompletionResultSet resultSet) {
+ PsiElement position = parameters.getPosition();
+ XmlAttribute attr = PsiTreeUtil.getParentOfType(position, XmlAttribute.class);
+ if (attr == null) return;
+ XmlTag tag = (XmlTag) attr.getParent();
+ String tagName = tag.getName();
+ String attrName = attr.getName();
+
+ if ("MASTER-DATA".equals(tagName)) {
+ if ("MASTER-FIELDS".equals(attrName)) {
+ addFieldsFromDatasetId(tag, resultSet);
+ } else if ("DETAIL-FIELDS".equals(attrName)) {
+ XmlTag gridTag = tag.getParentTag();
+ if (gridTag != null && "DATA-GRID".equals(gridTag.getName())) {
+ addFieldsFromGrid(gridTag, resultSet);
+ }
+ }
+ } else if ("DATASET".equals(tagName) && hasAncestorWithName(tag, "FOREIGN-DATASETS")) {
+ if ("DETAIL-FIELDS".equals(attrName)) {
+ addFieldsFromDatasetId(tag, resultSet);
+ } else if ("MASTER-FIELDS".equals(attrName)) {
+ XmlTag foreignDatasetsTag = tag.getParentTag();
+ if (foreignDatasetsTag != null) {
+ XmlTag parentDataset = foreignDatasetsTag.getParentTag();
+ if (parentDataset != null && "DATASET".equals(parentDataset.getName())) {
+ XmlTag fieldsTag = parentDataset.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ addFieldsInTagRecursive(fieldsTag, resultSet);
+ }
+ }
+ }
+ }
}
}
});
@@ -195,6 +236,36 @@ public class DynFormCompletionContributor extends CompletionContributor {
}
}
+ private void addDatasetsInFileRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set visited) {
+ if (file == null || !visited.add(file)) return;
+
+ // 1. Add datasets from current file
+ addDatasetsInFile(file, resultSet);
+
+ // 2. Add datasets from ajax.xml (only if not already added)
+ PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
+ if (ajaxFile != null && ajaxFile != file) {
+ addDatasetsInFile(ajaxFile, resultSet);
+ }
+
+ // 3. Add datasets from included files
+ if (file instanceof XmlFile xmlFile) {
+ XmlTag rootTag = xmlFile.getRootTag();
+ if (rootTag != null) {
+ XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
+ if (includesTag != null) {
+ for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
+ String path = includeTag.getAttributeValue("FILE");
+ if (path != null) {
+ PsiFile includedFile = DynFormPathUtils.findIncludedFile(file, path);
+ addDatasetsInFileRecursive(includedFile, resultSet, visited);
+ }
+ }
+ }
+ }
+ }
+ }
+
private void addDatasetsInFile(PsiFile file, @NotNull CompletionResultSet resultSet) {
if (!(file instanceof XmlFile xmlFile)) return;
XmlTag rootTag = xmlFile.getRootTag();
@@ -242,6 +313,110 @@ public class DynFormCompletionContributor extends CompletionContributor {
}
}
+ private void addFieldsFromDatasetId(XmlTag tag, @NotNull CompletionResultSet resultSet) {
+ String datasetId = tag.getAttributeValue("DATASET-ID");
+ if (datasetId == null || datasetId.isEmpty()) return;
+
+ PsiFile file = tag.getContainingFile();
+ PsiElement datasetElement = findDatasetElement(file, datasetId);
+ if (datasetElement instanceof XmlTag datasetTag) {
+ XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ addFieldsInTagRecursive(fieldsTag, resultSet);
+ }
+ }
+ }
+
+ private void addFieldsFromGrid(XmlTag gridTag, @NotNull CompletionResultSet resultSet) {
+ // 1. Try to get DATAID from GRID-LIST or GRID-EDITOR
+ 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");
+ }
+ }
+
+ // 2. Add fields from that DATASET
+ if (dataId != null && !dataId.isEmpty()) {
+ PsiElement datasetElement = findDatasetElement(gridTag.getContainingFile(), dataId);
+ if (datasetElement instanceof XmlTag datasetTag) {
+ XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ addFieldsInTagRecursive(fieldsTag, resultSet);
+ }
+ }
+ }
+
+ // Fallback: Check for inline FIELDS in GRID-LIST/GRID-EDITOR (if any)
+ if (listTag != null) {
+ XmlTag fieldsTag = listTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ addFieldsInTagRecursive(fieldsTag, resultSet);
+ }
+ }
+ XmlTag editorTag = gridTag.findFirstSubTag("GRID-EDITOR");
+ if (editorTag != null) {
+ XmlTag fieldsTag = editorTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ addFieldsInTagRecursive(fieldsTag, resultSet);
+ }
+ }
+ }
+
+ private PsiElement findDatasetElement(PsiFile file, String id) {
+ return findDatasetInFileRecursiveForCompletion(file, id, new HashSet<>());
+ }
+
+ private PsiElement findDatasetInFileRecursiveForCompletion(PsiFile file, String id, Set visited) {
+ if (file == null || !visited.add(file)) return null;
+ 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;
+ }
+ }
+
+ PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
+ if (ajaxFile != null && ajaxFile != file) {
+ PsiElement found = findDatasetInFileRecursiveForCompletion(ajaxFile, id, visited);
+ if (found != null) return found;
+ }
+
+ XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
+ if (includesTag != null) {
+ for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
+ String path = includeTag.getAttributeValue("FILE");
+ if (path != null) {
+ PsiFile includedFile = DynFormPathUtils.findIncludedFile(file, path);
+ PsiElement found = findDatasetInFileRecursiveForCompletion(includedFile, id, visited);
+ if (found != null) return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static boolean hasAncestorWithName(XmlTag tag, String name) {
+ XmlTag current = tag.getParentTag();
+ while (current != null) {
+ if (name.equals(current.getName())) return true;
+ current = current.getParentTag();
+ }
+ return false;
+ }
+
private PsiNewExpression getNewDynFormExpression(PsiLiteralExpression literal) {
PsiElement parent = literal.getParent();
if (parent instanceof PsiExpressionList) {
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 2fe0ef6..350be4a 100644
--- a/src/main/java/com/sdk/dynform/tools/dynform/DynFormReferenceContributor.java
+++ b/src/main/java/com/sdk/dynform/tools/dynform/DynFormReferenceContributor.java
@@ -83,9 +83,9 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
}
});
- // XML Reference for Dataset ID (DATAID, DATASET, DATASET-ID)
+ // XML Reference for Dataset ID (DATAID, DATASET, DATASET-ID, VIEW-DATASET)
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
- .withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID")),
+ .withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID", "VIEW-DATASET")),
new PsiReferenceProvider() {
@NotNull
@Override
@@ -97,6 +97,41 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
}
});
+ // XML Reference for MASTER-FIELDS and DETAIL-FIELDS in MASTER-DATA or FOREIGN-DATASETS > DATASET
+ registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
+ .withParent(XmlPatterns.xmlAttribute().withName("MASTER-FIELDS", "DETAIL-FIELDS")
+ .withParent(XmlPatterns.xmlTag().withName("MASTER-DATA", "DATASET"))),
+ 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();
+ XmlTag tag = (XmlTag) attr.getParent();
+ String tagName = tag.getName();
+
+ // Only process DATASET tag if it's inside FOREIGN-DATASETS
+ if ("DATASET".equals(tagName) && !hasAncestorWithName(tag, "FOREIGN-DATASETS")) {
+ return PsiReference.EMPTY_ARRAY;
+ }
+
+ // Support comma-separated fields
+ String[] fields = value.split(",");
+ PsiReference[] refs = new PsiReference[fields.length];
+ int currentOffset = 1;
+ for (int i = 0; i < fields.length; i++) {
+ String field = fields[i].trim();
+ int start = value.indexOf(field, currentOffset - 1);
+ refs[i] = new DynFormMasterDataFieldReference(attrValue, new TextRange(start + 1, start + 1 + field.length()), field);
+ currentOffset = start + field.length();
+ }
+ return refs;
+ }
+ });
+
// XML Reference for Grid ID
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
.withParent(XmlPatterns.xmlAttribute().withName("GRID-ID")),
@@ -324,12 +359,39 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
this.datasetId = datasetId;
}
@Nullable @Override public PsiElement resolve() {
- PsiElement found = findDatasetInFile(myElement.getContainingFile(), datasetId);
+ return findDatasetInFileRecursive(myElement.getContainingFile(), datasetId, new HashSet<>());
+ }
+
+ @Nullable
+ private PsiElement findDatasetInFileRecursive(PsiFile file, String id, Set visited) {
+ if (file == null || !visited.add(file)) return null;
+ if (!(file instanceof XmlFile xmlFile)) return null;
+
+ // 1. Search in current file
+ PsiElement found = findDatasetInFile(xmlFile, id);
if (found != null) return found;
- PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(myElement.getContainingFile());
- if (ajaxFile != null) {
- return findDatasetInFile(ajaxFile, datasetId);
+ // 2. Search in ajax.xml (only from main file)
+ PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
+ if (ajaxFile != null && ajaxFile != file) {
+ found = findDatasetInFile(ajaxFile, id);
+ if (found != null) return found;
+ }
+
+ // 3. Search in included files
+ XmlTag rootTag = xmlFile.getRootTag();
+ if (rootTag != null) {
+ XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
+ if (includesTag != null) {
+ for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
+ String path = includeTag.getAttributeValue("FILE");
+ if (path != null) {
+ PsiFile includedFile = DynFormPathUtils.findIncludedFile(file, path);
+ found = findDatasetInFileRecursive(includedFile, id, visited);
+ if (found != null) return found;
+ }
+ }
+ }
}
return null;
}
@@ -353,6 +415,163 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
}
}
+ private static class DynFormMasterDataFieldReference extends PsiReferenceBase {
+ private final String fieldName;
+
+ public DynFormMasterDataFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
+ super(element, range, true);
+ this.fieldName = fieldName;
+ }
+
+ @Nullable
+ @Override
+ public PsiElement resolve() {
+ XmlAttribute attr = (XmlAttribute) myElement.getParent();
+ XmlTag tag = (XmlTag) attr.getParent();
+ String tagName = tag.getName();
+ String attrName = attr.getName();
+
+ if ("MASTER-DATA".equals(tagName)) {
+ if ("MASTER-FIELDS".equals(attrName)) {
+ // MASTER-FIELDS refers to the DATASET specified in DATASET-ID
+ return resolveInDataset(tag.getAttributeValue("DATASET-ID"));
+ } else if ("DETAIL-FIELDS".equals(attrName)) {
+ // DETAIL-FIELDS refers to fields in current DATA-GRID
+ XmlTag gridTag = tag.getParentTag();
+ if (gridTag != null && "DATA-GRID".equals(gridTag.getName())) {
+ return findFieldInGrid(gridTag);
+ }
+ }
+ } else if ("DATASET".equals(tagName)) {
+ if ("DETAIL-FIELDS".equals(attrName)) {
+ // DETAIL-FIELDS refers to the DATASET specified in DATASET-ID
+ return resolveInDataset(tag.getAttributeValue("DATASET-ID"));
+ } else if ("MASTER-FIELDS".equals(attrName)) {
+ // MASTER-FIELDS refers to parent DATASET
+ XmlTag foreignDatasetsTag = tag.getParentTag();
+ if (foreignDatasetsTag != null) {
+ XmlTag parentDataset = foreignDatasetsTag.getParentTag();
+ if (parentDataset != null && "DATASET".equals(parentDataset.getName())) {
+ XmlTag fieldsTag = parentDataset.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ return findFieldInTag(fieldsTag, fieldName);
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private PsiElement resolveInDataset(String datasetId) {
+ if (datasetId == null || datasetId.isEmpty()) return null;
+
+ // Re-use DynFormDatasetReference's finding logic if possible, or just look up
+ PsiElement datasetElement = findDatasetElement(datasetId);
+ if (datasetElement instanceof XmlTag datasetTag) {
+ XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ return findFieldInTag(fieldsTag, fieldName);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private PsiElement findDatasetElement(String id) {
+ // This is a simplified lookup, similar to DynFormDatasetReference's resolve
+ PsiFile file = myElement.getContainingFile();
+ PsiElement found = findDatasetInFileRecursive(file, id, new HashSet<>());
+ if (found instanceof XmlAttributeValue attrValue) {
+ PsiElement parent = attrValue.getParent();
+ if (parent instanceof XmlAttribute attr) {
+ return attr.getParent();
+ }
+ }
+ if (found instanceof XmlTag) return found;
+ return null;
+ }
+
+ @Nullable
+ private PsiElement findDatasetInFileRecursive(PsiFile file, String id, Set visited) {
+ if (file == null || !visited.add(file)) return null;
+ 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"))) {
+ XmlAttribute idAttr = datasetTag.getAttribute("ID");
+ return idAttr != null ? idAttr.getValueElement() : datasetTag;
+ }
+ }
+
+ // Search in ajax.xml
+ PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
+ if (ajaxFile != null && ajaxFile != file) {
+ PsiElement found = findDatasetInFileRecursive(ajaxFile, id, visited);
+ if (found != null) return found;
+ }
+
+ // Search in includes
+ XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
+ if (includesTag != null) {
+ for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
+ String path = includeTag.getAttributeValue("FILE");
+ if (path != null) {
+ PsiFile includedFile = DynFormPathUtils.findIncludedFile(file, path);
+ PsiElement found = findDatasetInFileRecursive(includedFile, id, visited);
+ if (found != null) return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private PsiElement findFieldInGrid(XmlTag gridTag) {
+ // 1. Try to get DATAID from GRID-LIST or GRID-EDITOR
+ 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");
+ }
+ }
+
+ // 2. Resolve fields from that DATASET
+ if (dataId != null && !dataId.isEmpty()) {
+ return resolveInDataset(dataId);
+ }
+
+ // Fallback: Check for inline FIELDS in GRID-LIST/GRID-EDITOR (if any)
+ if (listTag != null) {
+ XmlTag fieldsTag = listTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ PsiElement found = findFieldInTag(fieldsTag, fieldName);
+ if (found != null) return found;
+ }
+ }
+ XmlTag editorTag = gridTag.findFirstSubTag("GRID-EDITOR");
+ if (editorTag != null) {
+ XmlTag fieldsTag = editorTag.findFirstSubTag("FIELDS");
+ if (fieldsTag != null) {
+ return findFieldInTag(fieldsTag, fieldName);
+ }
+ }
+ return null;
+ }
+ }
+
private static class DynFormGridReference extends PsiReferenceBase {
private final String gridId;
public DynFormGridReference(@NotNull XmlAttributeValue element, TextRange range, String gridId) {
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 7e06c8c..86096ae 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -34,6 +34,12 @@
]]>
[3.2.3]
+
+ - Advanced Data Referencing: Implemented comprehensive reference and completion support for
<FOREIGN-DATASETS> and <MASTER-DATA> structures.
+ - Contextual Field Resolution: Enhanced field resolution logic to resolve fields from datasets specified by
DATASET-ID and DATAID attributes within their respective tags.
+ - Recursive Resource Scanning: Improved dataset and grid scanning to search across recursively included
.frml files, ensuring consistent navigation and completion throughout the project.
+
[3.2.2]
- UI/UX Improvement: Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.