feat(dynform): enhance dataset and field referencing for master-detail structures
- Implemented comprehensive reference and completion support for <FOREIGN-DATASETS> and <MASTER-DATA> tags. - Enhanced dataset resolution to support recursive scanning across included .frml files. - Improved field resolution logic for MASTER-FIELDS and DETAIL-FIELDS to resolve from datasets specified by DATASET-ID or DATAID. - Bumped plugin version to 3.2.3 and updated change notes.
This commit is contained in:
@@ -3,12 +3,12 @@ plugins {
|
|||||||
id("org.jetbrains.kotlin.jvm") version "2.1.0"
|
id("org.jetbrains.kotlin.jvm") version "2.1.0"
|
||||||
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.2"
|
version = "3.2.3"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
||||||
intellijPlatform {
|
intellijPlatform {
|
||||||
defaultRepositories()
|
defaultRepositories()
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,12 @@ intellijPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
changeNotes = """
|
changeNotes = """
|
||||||
|
<h2>[3.2.3]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Advanced Data Referencing:</strong> Implemented comprehensive reference and completion support for <code><FOREIGN-DATASETS></code> and <code><MASTER-DATA></code> structures.</li>
|
||||||
|
<li><strong>Contextual Field Resolution:</strong> Enhanced field resolution logic to resolve fields from datasets specified by <code>DATASET-ID</code> and <code>DATAID</code> attributes within their respective tags.</li>
|
||||||
|
<li><strong>Recursive Resource Scanning:</strong> Improved dataset and grid scanning to search across recursively included <code>.frml</code> files, ensuring consistent navigation and completion throughout the project.</li>
|
||||||
|
</ul>
|
||||||
<h2>[3.2.2]</h2>
|
<h2>[3.2.2]</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li>
|
<li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li>
|
||||||
|
|||||||
@@ -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()
|
extend(CompletionType.BASIC, XmlPatterns.psiElement()
|
||||||
.inside(XmlPatterns.xmlAttributeValue()
|
.inside(XmlPatterns.xmlAttributeValue()
|
||||||
.withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID"))),
|
.withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID", "VIEW-DATASET"))),
|
||||||
new CompletionProvider<CompletionParameters>() {
|
new CompletionProvider<CompletionParameters>() {
|
||||||
@Override
|
@Override
|
||||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||||
@NotNull ProcessingContext context,
|
@NotNull ProcessingContext context,
|
||||||
@NotNull CompletionResultSet resultSet) {
|
@NotNull CompletionResultSet resultSet) {
|
||||||
addDatasetsInFile(parameters.getOriginalFile(), resultSet);
|
addDatasetsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
|
||||||
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(parameters.getOriginalFile());
|
}
|
||||||
if (ajaxFile != null) {
|
});
|
||||||
addDatasetsInFile(ajaxFile, resultSet);
|
|
||||||
|
// 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<CompletionParameters>() {
|
||||||
|
@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<PsiFile> 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) {
|
private void addDatasetsInFile(PsiFile file, @NotNull CompletionResultSet resultSet) {
|
||||||
if (!(file instanceof XmlFile xmlFile)) return;
|
if (!(file instanceof XmlFile xmlFile)) return;
|
||||||
XmlTag rootTag = xmlFile.getRootTag();
|
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<PsiFile> 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) {
|
private PsiNewExpression getNewDynFormExpression(PsiLiteralExpression literal) {
|
||||||
PsiElement parent = literal.getParent();
|
PsiElement parent = literal.getParent();
|
||||||
if (parent instanceof PsiExpressionList) {
|
if (parent instanceof PsiExpressionList) {
|
||||||
|
|||||||
@@ -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()
|
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
|
||||||
.withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID")),
|
.withParent(XmlPatterns.xmlAttribute().withName("DATAID", "DATASET", "DATASET-ID", "VIEW-DATASET")),
|
||||||
new PsiReferenceProvider() {
|
new PsiReferenceProvider() {
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@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
|
// XML Reference for Grid ID
|
||||||
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
|
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
|
||||||
.withParent(XmlPatterns.xmlAttribute().withName("GRID-ID")),
|
.withParent(XmlPatterns.xmlAttribute().withName("GRID-ID")),
|
||||||
@@ -324,12 +359,39 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
|||||||
this.datasetId = datasetId;
|
this.datasetId = datasetId;
|
||||||
}
|
}
|
||||||
@Nullable @Override public PsiElement resolve() {
|
@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<PsiFile> 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;
|
if (found != null) return found;
|
||||||
|
|
||||||
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(myElement.getContainingFile());
|
// 2. Search in ajax.xml (only from main file)
|
||||||
if (ajaxFile != null) {
|
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
|
||||||
return findDatasetInFile(ajaxFile, datasetId);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -353,6 +415,163 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class DynFormMasterDataFieldReference extends PsiReferenceBase<XmlAttributeValue> {
|
||||||
|
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<PsiFile> 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<XmlAttributeValue> {
|
private static class DynFormGridReference extends PsiReferenceBase<XmlAttributeValue> {
|
||||||
private final String gridId;
|
private final String gridId;
|
||||||
public DynFormGridReference(@NotNull XmlAttributeValue element, TextRange range, String gridId) {
|
public DynFormGridReference(@NotNull XmlAttributeValue element, TextRange range, String gridId) {
|
||||||
|
|||||||
@@ -34,6 +34,12 @@
|
|||||||
]]></description>
|
]]></description>
|
||||||
|
|
||||||
<change-notes><![CDATA[
|
<change-notes><![CDATA[
|
||||||
|
<h2>[3.2.3]</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Advanced Data Referencing:</strong> Implemented comprehensive reference and completion support for <code><FOREIGN-DATASETS></code> and <code><MASTER-DATA></code> structures.</li>
|
||||||
|
<li><strong>Contextual Field Resolution:</strong> Enhanced field resolution logic to resolve fields from datasets specified by <code>DATASET-ID</code> and <code>DATAID</code> attributes within their respective tags.</li>
|
||||||
|
<li><strong>Recursive Resource Scanning:</strong> Improved dataset and grid scanning to search across recursively included <code>.frml</code> files, ensuring consistent navigation and completion throughout the project.</li>
|
||||||
|
</ul>
|
||||||
<h2>[3.2.2]</h2>
|
<h2>[3.2.2]</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li>
|
<li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user