feat: comprehensive cross-file support and performance optimization (v3.3.0)
- Implemented cross-file completion, references, and validation for .frml files. - Optimized resource discovery using IntelliJ indexing (ReferencesSearch) to fix IDE freeze. - Refactored shared search logic into DynFormPathUtils. - Excluded <ROW> tags from field definition requirements. - Updated plugin version to 3.3.0.
This commit is contained in:
@@ -9,71 +9,47 @@ import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.intellij.psi.PsiFile;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class DynFormAnnotator implements Annotator {
|
||||
@Override
|
||||
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
|
||||
if (!(element instanceof XmlAttributeValue attrValue)) return;
|
||||
|
||||
|
||||
PsiElement parent = attrValue.getParent();
|
||||
if (!(parent instanceof XmlAttribute attr)) return;
|
||||
|
||||
if (!"NAME".equals(attr.getName())) return;
|
||||
|
||||
PsiElement tagElem = attr.getParent();
|
||||
if (!(tagElem instanceof XmlTag tag) || !"FIELD".equals(tag.getName())) return;
|
||||
|
||||
// Check if this FIELD is inside a LAYOUT tag
|
||||
String attrName = attr.getName();
|
||||
PsiElement tagElem = attr.getParent();
|
||||
if (!(tagElem instanceof XmlTag tag)) return;
|
||||
|
||||
String tagName = tag.getName();
|
||||
boolean isField = "FIELD".equals(tagName) && "NAME".equals(attrName);
|
||||
boolean isContainer = ("SECTION".equals(tagName) || "SECTIONS".equals(tagName)) && "ID".equals(attrName);
|
||||
|
||||
if (!isField && !isContainer) return;
|
||||
|
||||
// Check if this tag is inside a LAYOUT tag
|
||||
XmlTag layoutTag = tag.getParentTag();
|
||||
while (layoutTag != null && !"LAYOUT".equals(layoutTag.getName())) {
|
||||
layoutTag = layoutTag.getParentTag();
|
||||
}
|
||||
|
||||
|
||||
if (layoutTag == null) return;
|
||||
|
||||
// The "Owner" of the LAYOUT (e.g., FORM_ENTRY, FILTERS, GRID-EDITOR)
|
||||
// The "Owner" of the LAYOUT (e.g., FORM_ENTRY, FILTERS, GRID-EDITOR, FORM_BROWSE, GRID-LIST)
|
||||
XmlTag ownerTag = layoutTag.getParentTag();
|
||||
if (ownerTag == null) return;
|
||||
|
||||
|
||||
String fieldName = attrValue.getValue();
|
||||
if (fieldName == null || fieldName.isEmpty()) return;
|
||||
|
||||
// Look for the FIELDS tag that is a sibling of the current LAYOUT
|
||||
XmlTag fieldsTag = ownerTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag == null) {
|
||||
holder.newAnnotation(HighlightSeverity.ERROR, "No <FIELDS> definition found in <" + ownerTag.getName() + ">")
|
||||
.range(attrValue.getTextRange())
|
||||
.create();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFieldDefined(fieldsTag, fieldName)) {
|
||||
holder.newAnnotation(HighlightSeverity.ERROR, "Field '" + fieldName + "' is not defined in <FIELDS> of <" + ownerTag.getName() + ">")
|
||||
PsiFile currentFile = tag.getContainingFile();
|
||||
if (DynFormPathUtils.findFieldInFormContext(currentFile, ownerTag.getName(), fieldName, new HashSet<>()) == null) {
|
||||
holder.newAnnotation(HighlightSeverity.ERROR, (isField ? "Field '" : "Section/Row '") + fieldName + "' is not defined in <FIELDS> of <" + ownerTag.getName() + ">")
|
||||
.range(attrValue.getTextRange())
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFieldDefined(XmlTag fieldsTag, String name) {
|
||||
for (XmlTag field : fieldsTag.findSubTags("FIELD")) {
|
||||
if (name.equals(field.getAttributeValue("NAME"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Also check inside SECTIONS or ROWS if any
|
||||
for (XmlTag sub : fieldsTag.getSubTags()) {
|
||||
if ("SECTION".equals(sub.getName()) || "ROW".equals(sub.getName())) {
|
||||
if (isFieldDefined(sub, name)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,34 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
|
||||
if (formContainer != null) {
|
||||
addFieldsInTagRecursive(formContainer, resultSet);
|
||||
DynFormPathUtils.addFieldsInTagRecursive(formContainer, resultSet);
|
||||
}
|
||||
|
||||
// เพิ่มเติม: เสนอฟิลด์จากไฟล์ที่ include ไฟล์นี้ (Includers)
|
||||
List<PsiFile> includers = DynFormPathUtils.findIncluders(parameters.getOriginalFile());
|
||||
for (PsiFile includer : includers) {
|
||||
if (includer instanceof XmlFile xmlFile) {
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag != null) {
|
||||
for (XmlTag formTag : rootTag.findSubTags("FORM")) {
|
||||
for (XmlTag entryTag : formTag.findSubTags("FORM_ENTRY")) {
|
||||
XmlTag fieldsTag = entryTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
for (XmlTag browseTag : formTag.findSubTags("FORM_BROWSE")) {
|
||||
XmlTag fieldsTag = browseTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// เสนอฟิลด์ภายใต้ LAYOUT container เดียวกัน
|
||||
// เสนอฟิลด์ภายใต้ LAYOUT container เดียวกัน (ข้ามไฟล์ได้)
|
||||
XmlTag layoutTag = PsiTreeUtil.getParentOfType(position, XmlTag.class);
|
||||
while (layoutTag != null && !"LAYOUT".equals(layoutTag.getName())) {
|
||||
layoutTag = layoutTag.getParentTag();
|
||||
@@ -96,10 +120,8 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
if (layoutTag == null) return;
|
||||
XmlTag containerTag = layoutTag.getParentTag();
|
||||
if (containerTag == null) return;
|
||||
XmlTag fieldsTag = containerTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
|
||||
DynFormPathUtils.addFieldsInFormContext(parameters.getOriginalFile(), containerTag.getName(), resultSet, new HashSet<>());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -120,10 +142,10 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
if (isAjaxOption) {
|
||||
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(parameters.getOriginalFile());
|
||||
if (ajaxFile != null) {
|
||||
addDatasetsInFile(ajaxFile, resultSet);
|
||||
DynFormPathUtils.addDatasetsInFile(ajaxFile, resultSet);
|
||||
}
|
||||
} else {
|
||||
addDatasetsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>(), false);
|
||||
DynFormPathUtils.addDatasetsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>(), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -164,7 +186,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
if (parentDataset != null && "DATASET".equals(parentDataset.getName())) {
|
||||
XmlTag fieldsTag = parentDataset.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,7 +227,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
if (datasetId.equals(datasetTag.getAttributeValue("ID"))) {
|
||||
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,7 +245,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||
@NotNull ProcessingContext context,
|
||||
@NotNull CompletionResultSet resultSet) {
|
||||
addGridsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
|
||||
DynFormPathUtils.addGridsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -239,7 +261,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||
@NotNull ProcessingContext context,
|
||||
@NotNull CompletionResultSet resultSet) {
|
||||
addFormFieldsForNameRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
|
||||
DynFormPathUtils.addFormFieldsForNameRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -284,171 +306,16 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
});
|
||||
}
|
||||
|
||||
private void addFieldsInTagRecursive(XmlTag container, @NotNull CompletionResultSet resultSet) {
|
||||
for (XmlTag subTag : container.getSubTags()) {
|
||||
if ("FIELD".equals(subTag.getName())) {
|
||||
String name = subTag.getAttributeValue("NAME");
|
||||
if (name != null && !name.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(name)
|
||||
.withIcon(com.intellij.icons.AllIcons.Nodes.Field)
|
||||
.withTypeText(subTag.getAttributeValue("TYPE")));
|
||||
}
|
||||
} else {
|
||||
if (("SECTION".equals(subTag.getName()) || "FIELDS".equals(subTag.getName())) && subTag.getAttributeValue("ID") != null) {
|
||||
String id = subTag.getAttributeValue("ID");
|
||||
resultSet.addElement(LookupElementBuilder.create(id)
|
||||
.withIcon(com.intellij.icons.AllIcons.Nodes.Package)
|
||||
.withItemTextForeground(java.awt.Color.BLUE));
|
||||
}
|
||||
addFieldsInTagRecursive(subTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDatasetsInFileRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited, boolean includeAjax) {
|
||||
if (file == null || !visited.add(file)) return;
|
||||
|
||||
if (includeAjax) {
|
||||
// สำหรับ AJAX-OPTION ให้หาใน ajax.xml เท่านั้น
|
||||
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
|
||||
if (ajaxFile != null) {
|
||||
addDatasetsInFile(ajaxFile, resultSet);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Add datasets from current file
|
||||
addDatasetsInFile(file, resultSet);
|
||||
|
||||
// 2. Add datasets from files that INCLUDE this file (Parent/Main Files)
|
||||
if (visited.size() == 1) {
|
||||
List<PsiFile> includers = DynFormPathUtils.findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
if (visited.add(includer)) {
|
||||
addDatasetsInFile(includer, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add datasets from included files (Downward)
|
||||
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);
|
||||
if (includedFile != null && !visited.contains(includedFile)) {
|
||||
addDatasetsInFileRecursive(includedFile, resultSet, visited, includeAjax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Add datasets from all .frml files in module (fallback)
|
||||
if (visited.size() <= 2) {
|
||||
for (PsiFile frmlFile : DynFormPathUtils.getAllFrmlFiles(file)) {
|
||||
if (visited.add(frmlFile)) {
|
||||
addDatasetsInFile(frmlFile, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDatasetsInFile(PsiFile file, @NotNull CompletionResultSet resultSet) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
|
||||
if (datasetsContainer == null) datasetsContainer = rootTag;
|
||||
|
||||
for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) {
|
||||
String id = datasetTag.getAttributeValue("ID");
|
||||
if (id != null && !id.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(id)
|
||||
.withIcon(com.intellij.icons.AllIcons.Nodes.DataTables)
|
||||
.withTypeText(datasetTag.getAttributeValue("TABLENAME")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addGridsInFileRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return;
|
||||
|
||||
// 1. Add grids from current file
|
||||
addGridsInFile(file, resultSet);
|
||||
|
||||
// 2. Add grids from files that INCLUDE this file (Parent/Main Files)
|
||||
if (visited.size() == 1) {
|
||||
List<PsiFile> includers = DynFormPathUtils.findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
if (visited.add(includer)) {
|
||||
addGridsInFile(includer, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add grids from included files (Downward)
|
||||
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);
|
||||
if (includedFile != null && !visited.contains(includedFile)) {
|
||||
addGridsInFileRecursive(includedFile, resultSet, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Add grids from all .frml files in module (fallback)
|
||||
if (visited.size() <= 2) {
|
||||
for (PsiFile frmlFile : DynFormPathUtils.getAllFrmlFiles(file)) {
|
||||
if (visited.add(frmlFile)) {
|
||||
addGridsInFile(frmlFile, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addGridsInFile(PsiFile file, @NotNull CompletionResultSet resultSet) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag dataGridsTag = rootTag.findFirstSubTag("DATA-GRIDS");
|
||||
if (dataGridsTag != null) {
|
||||
for (XmlTag gridTag : dataGridsTag.findSubTags("DATA-GRID")) {
|
||||
String id = gridTag.getAttributeValue("ID");
|
||||
if (id != null && !id.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(id)
|
||||
.withIcon(com.intellij.icons.AllIcons.Nodes.DataTables));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
PsiElement datasetElement = DynFormPathUtils.findDatasetElement(file, datasetId);
|
||||
if (datasetElement instanceof XmlTag datasetTag) {
|
||||
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,11 +336,11 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
|
||||
// 2. Add fields from that DATASET
|
||||
if (dataId != null && !dataId.isEmpty()) {
|
||||
PsiElement datasetElement = findDatasetElement(gridTag.getContainingFile(), dataId);
|
||||
PsiElement datasetElement = DynFormPathUtils.findDatasetElement(gridTag.getContainingFile(), dataId);
|
||||
if (datasetElement instanceof XmlTag datasetTag) {
|
||||
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,98 +349,18 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
if (listTag != null) {
|
||||
XmlTag fieldsTag = listTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
XmlTag editorTag = gridTag.findFirstSubTag("GRID-EDITOR");
|
||||
if (editorTag != null) {
|
||||
XmlTag fieldsTag = editorTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
DynFormPathUtils.addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PsiElement findDatasetElement(PsiFile file, String id) {
|
||||
Set<PsiFile> visited = new HashSet<>();
|
||||
|
||||
// 1. Local
|
||||
PsiElement found = findDatasetInFile(file, id);
|
||||
if (found != null) return found;
|
||||
visited.add(file);
|
||||
|
||||
// 2. Includers
|
||||
List<PsiFile> includers = DynFormPathUtils.findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
found = findDatasetInFile(includer, id);
|
||||
if (found != null) return found;
|
||||
visited.add(includer);
|
||||
}
|
||||
|
||||
// 3. Ajax
|
||||
PsiFile ajaxXml = DynFormPathUtils.findAjaxXml(file);
|
||||
if (ajaxXml != null && visited.add(ajaxXml)) {
|
||||
found = findDatasetInFile(ajaxXml, id);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
// 4. Recursive search
|
||||
return findDatasetInFileRecursiveForCompletion(file, id, visited);
|
||||
}
|
||||
|
||||
private PsiElement findDatasetInFile(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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Try local (should already be checked by findDatasetElement but for recursion consistency)
|
||||
PsiElement found = findDatasetInFile(xmlFile, id);
|
||||
if (found != null) return found;
|
||||
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return null;
|
||||
|
||||
// Downward search
|
||||
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 = findDatasetInFileRecursiveForCompletion(includedFile, id, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback: Module-wide
|
||||
if (visited.size() <= 2) {
|
||||
for (PsiFile frmlFile : DynFormPathUtils.getAllFrmlFiles(file)) {
|
||||
if (!visited.contains(frmlFile)) {
|
||||
found = findDatasetInFile(frmlFile, id);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean hasAncestorWithName(XmlTag tag, String name) {
|
||||
XmlTag current = tag.getParentTag();
|
||||
while (current != null) {
|
||||
@@ -676,60 +463,4 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFormFieldsForNameRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return;
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
// 1. Search in current file FORM_ENTRY tags
|
||||
for (XmlTag formTag : rootTag.findSubTags("FORM")) {
|
||||
for (XmlTag entryTag : formTag.findSubTags("FORM_ENTRY")) {
|
||||
// Collect hidden fields from FIELDS
|
||||
XmlTag fieldsTag = entryTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
for (XmlTag fieldTag : fieldsTag.findSubTags("FIELD")) {
|
||||
if ("HIDDEN".equals(fieldTag.getAttributeValue("INPUTTYPE"))) {
|
||||
String name = fieldTag.getAttributeValue("NAME");
|
||||
if (name != null && !name.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(name)
|
||||
.withIcon(com.intellij.icons.AllIcons.Nodes.Field)
|
||||
.withTypeText("Hidden Field"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect any field/tag with NAME from LAYOUT
|
||||
XmlTag layoutTag = entryTag.findFirstSubTag("LAYOUT");
|
||||
if (layoutTag != null) {
|
||||
addFieldsInLayoutRecursive(layoutTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in included files
|
||||
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);
|
||||
addFormFieldsForNameRecursive(includedFile, resultSet, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFieldsInLayoutRecursive(XmlTag container, @NotNull CompletionResultSet resultSet) {
|
||||
for (XmlTag subTag : container.getSubTags()) {
|
||||
String name = subTag.getAttributeValue("NAME");
|
||||
if (name != null && !name.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(name)
|
||||
.withIcon(com.intellij.icons.AllIcons.Nodes.Field)
|
||||
.withTypeText("Layout: " + subTag.getName()));
|
||||
}
|
||||
addFieldsInLayoutRecursive(subTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,203 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionResultSet;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import java.awt.Color;
|
||||
|
||||
public class DynFormPathUtils {
|
||||
|
||||
public static final String MODULE_BASE_PATH = "src/main/webapp/WEB-INF/app/module";
|
||||
|
||||
public static void addFieldsInTagRecursive(XmlTag container, @NotNull CompletionResultSet resultSet) {
|
||||
for (XmlTag subTag : container.getSubTags()) {
|
||||
if ("FIELD".equals(subTag.getName())) {
|
||||
String name = subTag.getAttributeValue("NAME");
|
||||
if (name != null && !name.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Field)
|
||||
.withTypeText(subTag.getAttributeValue("TYPE")));
|
||||
}
|
||||
} else {
|
||||
if (("SECTION".equals(subTag.getName()) || "FIELDS".equals(subTag.getName())) && subTag.getAttributeValue("ID") != null) {
|
||||
String id = subTag.getAttributeValue("ID");
|
||||
resultSet.addElement(LookupElementBuilder.create(id)
|
||||
.withIcon(AllIcons.Nodes.Package)
|
||||
.withItemTextForeground(Color.BLUE));
|
||||
}
|
||||
addFieldsInTagRecursive(subTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addFieldsInFormContext(PsiFile file, String containerName, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return;
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
// 1. Search for (containerName) > FIELDS in this file
|
||||
List<XmlTag> containers = new ArrayList<>();
|
||||
collectContainers(rootTag, containerName, containers);
|
||||
for (XmlTag container : containers) {
|
||||
XmlTag fieldsTag = container.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
addFieldsInTagRecursive(fieldsTag, resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in INCLUDES (Downward)
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = findIncludedFile(file, path);
|
||||
addFieldsInFormContext(includedFile, containerName, resultSet, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Search in Includers (Upward)
|
||||
if (visited.size() == 1) {
|
||||
List<PsiFile> includers = findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
addFieldsInFormContext(includer, containerName, resultSet, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findFieldInTag(XmlTag container, String name) {
|
||||
for (XmlTag subTag : container.getSubTags()) {
|
||||
if ("FIELD".equals(subTag.getName()) && name.equals(subTag.getAttributeValue("NAME"))) {
|
||||
XmlAttribute nameAttr = subTag.getAttribute("NAME");
|
||||
return nameAttr != null ? nameAttr.getValueElement() : subTag;
|
||||
}
|
||||
if (("SECTION".equals(subTag.getName()) || "SECTIONS".equals(subTag.getName())) && name.equals(subTag.getAttributeValue("ID"))) {
|
||||
XmlAttribute idAttr = subTag.getAttribute("ID");
|
||||
return idAttr != null ? idAttr.getValueElement() : subTag;
|
||||
}
|
||||
PsiElement found = findFieldInTag(subTag, name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void collectContainers(XmlTag parent, String containerName, List<XmlTag> results) {
|
||||
if (containerName.equals(parent.getName())) {
|
||||
results.add(parent);
|
||||
} else {
|
||||
for (XmlTag subTag : parent.getSubTags()) {
|
||||
collectContainers(subTag, containerName, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findFieldInFormContext(PsiFile file, String containerName, String fieldName, 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;
|
||||
|
||||
// 1. Search in all containers matching containerName anywhere in the file
|
||||
List<XmlTag> containers = new ArrayList<>();
|
||||
collectContainers(rootTag, containerName, containers);
|
||||
for (XmlTag container : containers) {
|
||||
XmlTag fieldsTag = container.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
PsiElement found = findFieldInTag(fieldsTag, fieldName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in INCLUDES (Downward)
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = findIncludedFile(file, path);
|
||||
PsiElement found = findFieldInFormContext(includedFile, containerName, fieldName, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Search in Includers (Upward)
|
||||
if (visited.size() == 1) {
|
||||
List<PsiFile> includers = findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
PsiElement found = findFieldInFormContext(includer, containerName, fieldName, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findUsageInFormContext(PsiFile file, String containerName, String fieldName, 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;
|
||||
|
||||
// 1. Search in all containers matching containerName
|
||||
List<XmlTag> containers = new ArrayList<>();
|
||||
collectContainers(rootTag, containerName, containers);
|
||||
for (XmlTag container : containers) {
|
||||
XmlTag layoutTag = container.findFirstSubTag("LAYOUT");
|
||||
if (layoutTag != null) {
|
||||
PsiElement found = findFieldInTag(layoutTag, fieldName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
XmlTag titlesTag = container.findFirstSubTag("TITLES");
|
||||
if (titlesTag != null) {
|
||||
PsiElement found = findFieldInTag(titlesTag, fieldName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in INCLUDES (Downward)
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = findIncludedFile(file, path);
|
||||
PsiElement found = findUsageInFormContext(includedFile, containerName, fieldName, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Search in Includers (Upward)
|
||||
if (visited.size() == 1) {
|
||||
List<PsiFile> includers = findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
PsiElement found = findUsageInFormContext(includer, containerName, fieldName, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VirtualFile getModuleBaseDir(@NotNull Project project) {
|
||||
VirtualFile baseDir = project.getBaseDir();
|
||||
@@ -155,19 +340,6 @@ public class DynFormPathUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<PsiFile> getAllFrmlFiles(@NotNull PsiFile contextFile) {
|
||||
List<PsiFile> files = new ArrayList<>();
|
||||
VirtualFile moduleDir = findModuleDir(contextFile);
|
||||
if (moduleDir != null) {
|
||||
VirtualFile frmDir = moduleDir.findFileByRelativePath("view/frm");
|
||||
if (frmDir != null) {
|
||||
collectFrmlFilesRecursive(frmDir, contextFile.getProject(), files);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<PsiFile> findIncluders(@NotNull PsiFile includedFile) {
|
||||
List<PsiFile> includers = new ArrayList<>();
|
||||
@@ -175,41 +347,380 @@ public class DynFormPathUtils {
|
||||
VirtualFile includedVFile = includedFile.getVirtualFile();
|
||||
if (includedVFile == null) return includers;
|
||||
|
||||
VirtualFile moduleDir = findModuleDir(includedFile);
|
||||
if (moduleDir == null) return includers;
|
||||
// Optimization: Use ReferencesSearch to find files that point to this file via <INCLUDE FILE="...">
|
||||
// This leverages IntelliJ's indices and is MUCH faster than scanning all files manually.
|
||||
com.intellij.psi.search.searches.ReferencesSearch.search(includedFile).forEach(ref -> {
|
||||
PsiElement element = ref.getElement();
|
||||
if (element != null) {
|
||||
PsiFile file = element.getContainingFile();
|
||||
if (file != null && !includers.contains(file)) {
|
||||
includers.add(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
List<PsiFile> allFiles = getAllFrmlFiles(includedFile);
|
||||
for (PsiFile file : allFiles) {
|
||||
if (file.equals(includedFile)) continue;
|
||||
if (!(file instanceof XmlFile xmlFile)) continue;
|
||||
// Fallback: If for some reason index-based search yields nothing,
|
||||
// we might do a limited scan, but let's stick to indices for performance to avoid freezing.
|
||||
return includers;
|
||||
}
|
||||
|
||||
public static void addDatasetsInFileRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited, boolean includeAjax) {
|
||||
if (file == null || !visited.add(file)) return;
|
||||
|
||||
if (includeAjax) {
|
||||
// สำหรับ AJAX-OPTION ให้หาใน ajax.xml เท่านั้น
|
||||
PsiFile ajaxFile = findAjaxXml(file);
|
||||
if (ajaxFile != null) {
|
||||
addDatasetsInFile(ajaxFile, resultSet);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Add datasets from current file
|
||||
addDatasetsInFile(file, resultSet);
|
||||
|
||||
// 2. Add datasets from files that INCLUDE this file (Parent/Main Files)
|
||||
if (visited.size() == 1) {
|
||||
List<PsiFile> includers = findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
if (visited.add(includer)) {
|
||||
addDatasetsInFile(includer, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add datasets from included files (Downward)
|
||||
if (file instanceof XmlFile xmlFile) {
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return includers;
|
||||
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile resolved = findIncludedFile(file, path);
|
||||
if (resolved != null && resolved.getVirtualFile().equals(includedVFile)) {
|
||||
includers.add(file);
|
||||
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 = findIncludedFile(file, path);
|
||||
if (includedFile != null && !visited.contains(includedFile)) {
|
||||
addDatasetsInFileRecursive(includedFile, resultSet, visited, includeAjax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return includers;
|
||||
}
|
||||
|
||||
private static void collectFrmlFilesRecursive(VirtualFile dir, Project project, List<PsiFile> files) {
|
||||
for (VirtualFile child : dir.getChildren()) {
|
||||
if (child.isDirectory()) {
|
||||
collectFrmlFilesRecursive(child, project, files);
|
||||
} else if (child.getName().endsWith(".frml")) {
|
||||
PsiFile psiFile = PsiManager.getInstance(project).findFile(child);
|
||||
if (psiFile != null) files.add(psiFile);
|
||||
public static void addDatasetsInFile(PsiFile file, @NotNull CompletionResultSet resultSet) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
|
||||
if (datasetsContainer == null) datasetsContainer = rootTag;
|
||||
|
||||
for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) {
|
||||
String id = datasetTag.getAttributeValue("ID");
|
||||
if (id != null && !id.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(id)
|
||||
.withIcon(AllIcons.Nodes.DataTables)
|
||||
.withTypeText(datasetTag.getAttributeValue("TABLENAME")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addGridsInFileRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return;
|
||||
|
||||
// 1. Add grids from current file
|
||||
addGridsInFile(file, resultSet);
|
||||
|
||||
// 2. Add grids from files that INCLUDE this file (Parent/Main Files)
|
||||
if (visited.size() == 1) {
|
||||
List<PsiFile> includers = findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
if (visited.add(includer)) {
|
||||
addGridsInFile(includer, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add grids from included files (Downward)
|
||||
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 = findIncludedFile(file, path);
|
||||
if (includedFile != null && !visited.contains(includedFile)) {
|
||||
addGridsInFileRecursive(includedFile, resultSet, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addGridsInFile(PsiFile file, @NotNull CompletionResultSet resultSet) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag dataGridsTag = rootTag.findFirstSubTag("DATA-GRIDS");
|
||||
if (dataGridsTag != null) {
|
||||
for (XmlTag gridTag : dataGridsTag.findSubTags("DATA-GRID")) {
|
||||
String id = gridTag.getAttributeValue("ID");
|
||||
if (id != null && !id.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(id)
|
||||
.withIcon(AllIcons.Nodes.DataTables));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findDatasetElement(PsiFile file, String id) {
|
||||
Set<PsiFile> visited = new HashSet<>();
|
||||
|
||||
// 1. Local
|
||||
PsiElement found = findDatasetInFile(file, id);
|
||||
if (found != null) return found;
|
||||
visited.add(file);
|
||||
|
||||
// 2. Includers
|
||||
List<PsiFile> includers = findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
found = findDatasetInFile(includer, id);
|
||||
if (found != null) return found;
|
||||
visited.add(includer);
|
||||
}
|
||||
|
||||
// 3. Ajax
|
||||
PsiFile ajaxXml = findAjaxXml(file);
|
||||
if (ajaxXml != null && visited.add(ajaxXml)) {
|
||||
found = findDatasetInFile(ajaxXml, id);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
// 4. Recursive search
|
||||
return findDatasetInFileRecursiveForCompletion(file, id, visited);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findDatasetInFile(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;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findDatasetInFileRecursiveForCompletion(PsiFile file, String id, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return null;
|
||||
if (!(file instanceof XmlFile xmlFile)) return null;
|
||||
|
||||
// Try local
|
||||
PsiElement found = findDatasetInFile(xmlFile, id);
|
||||
if (found != null) return found;
|
||||
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return null;
|
||||
|
||||
// Downward search
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = findIncludedFile(file, path);
|
||||
found = findDatasetInFileRecursiveForCompletion(includedFile, id, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void addFormFieldsForNameRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return;
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
// 1. Search in current file FORM_ENTRY tags
|
||||
for (XmlTag formTag : rootTag.findSubTags("FORM")) {
|
||||
for (XmlTag entryTag : formTag.findSubTags("FORM_ENTRY")) {
|
||||
// Collect hidden fields from FIELDS
|
||||
XmlTag fieldsTag = entryTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
for (XmlTag fieldTag : fieldsTag.findSubTags("FIELD")) {
|
||||
if ("HIDDEN".equals(fieldTag.getAttributeValue("INPUTTYPE"))) {
|
||||
String name = fieldTag.getAttributeValue("NAME");
|
||||
if (name != null && !name.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Field)
|
||||
.withTypeText("Hidden Field"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect any field/tag with NAME from LAYOUT
|
||||
XmlTag layoutTag = entryTag.findFirstSubTag("LAYOUT");
|
||||
if (layoutTag != null) {
|
||||
addFieldsInLayoutRecursive(layoutTag, resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in included files
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = findIncludedFile(file, path);
|
||||
addFormFieldsForNameRecursive(includedFile, resultSet, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addFieldsInLayoutRecursive(XmlTag container, @NotNull CompletionResultSet resultSet) {
|
||||
for (XmlTag subTag : container.getSubTags()) {
|
||||
String name = subTag.getAttributeValue("NAME");
|
||||
if (name != null && !name.isEmpty()) {
|
||||
resultSet.addElement(LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Field)
|
||||
.withTypeText("Layout: " + subTag.getName()));
|
||||
}
|
||||
addFieldsInLayoutRecursive(subTag, resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findFormFieldByNameRecursive(PsiFile file, String name, 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;
|
||||
|
||||
// 1. Search in current file FORM_ENTRY tags
|
||||
for (XmlTag formTag : rootTag.findSubTags("FORM")) {
|
||||
for (XmlTag entryTag : formTag.findSubTags("FORM_ENTRY")) {
|
||||
// Check hidden fields in FIELDS
|
||||
XmlTag fieldsTag = entryTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
for (XmlTag fieldTag : fieldsTag.findSubTags("FIELD")) {
|
||||
if ("HIDDEN".equals(fieldTag.getAttributeValue("INPUTTYPE")) && name.equals(fieldTag.getAttributeValue("NAME"))) {
|
||||
XmlAttribute nameAttr = fieldTag.getAttribute("NAME");
|
||||
return nameAttr != null ? nameAttr.getValueElement() : fieldTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check fields in LAYOUT
|
||||
XmlTag layoutTag = entryTag.findFirstSubTag("LAYOUT");
|
||||
if (layoutTag != null) {
|
||||
PsiElement found = findFieldInTag(layoutTag, name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in included files
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
for (XmlTag includeTag : includesTag.findSubTags("INCLUDE")) {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = findIncludedFile(file, path);
|
||||
PsiElement found = findFormFieldByNameRecursive(includedFile, name, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findGridInFile(PsiFile file, String id) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return null;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return null;
|
||||
|
||||
XmlTag dataGridsTag = rootTag.findFirstSubTag("DATA-GRIDS");
|
||||
if (dataGridsTag != null) {
|
||||
for (XmlTag gridTag : dataGridsTag.findSubTags("DATA-GRID")) {
|
||||
if (id.equals(gridTag.getAttributeValue("ID"))) {
|
||||
return gridTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement findGridElement(PsiFile file, String id) {
|
||||
Set<PsiFile> visited = new HashSet<>();
|
||||
|
||||
// 1. Local
|
||||
PsiElement found = findGridInFile(file, id);
|
||||
if (found != null) return found;
|
||||
visited.add(file);
|
||||
|
||||
// 2. Includers
|
||||
List<PsiFile> includers = findIncluders(file);
|
||||
for (PsiFile includer : includers) {
|
||||
found = findGridInFile(includer, id);
|
||||
if (found != null) return found;
|
||||
visited.add(includer);
|
||||
}
|
||||
|
||||
// 3. Recursive
|
||||
return findGridInIncludesRecursive(file, id, visited);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiElement findGridInIncludesRecursive(PsiFile file, String id, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file) || !(file instanceof XmlFile xmlFile)) return null;
|
||||
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return 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 = findIncludedFile(file, path);
|
||||
PsiElement found = findGridInFile(includedFile, id);
|
||||
if (found != null) return found;
|
||||
found = findGridInIncludesRecursive(includedFile, id, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
// XML Reference Provider for Field/Section NAME/ID
|
||||
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue()
|
||||
.withParent(XmlPatterns.xmlAttribute().withName("NAME", "ID", "TARGET")
|
||||
.withParent(XmlPatterns.xmlTag().withName("FIELD", "SECTION"))),
|
||||
.withParent(XmlPatterns.xmlTag().withName("FIELD", "SECTION", "SECTIONS"))),
|
||||
new PsiReferenceProvider() {
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -74,12 +74,12 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
}
|
||||
|
||||
// เคส 1: อยู่ใน LAYOUT หรือ TITLES -> ลิงก์ไปหา FIELDS (นิยาม)
|
||||
if (hasAncestorWithName(tag, "LAYOUT") || hasAncestorWithName(tag, "TITLES")) {
|
||||
if (DynFormPathUtils.hasAncestorWithName(tag, "LAYOUT") || DynFormPathUtils.hasAncestorWithName(tag, "TITLES")) {
|
||||
return new PsiReference[]{new DynFormFieldDefinitionReference(attrValue, new TextRange(1, value.length() + 1), value)};
|
||||
}
|
||||
|
||||
// เคส 2: อยู่ใน FIELDS (นิยาม) -> ลิงก์กลับไปหา LAYOUT หรือ TITLES (การใช้งาน)
|
||||
if (hasAncestorWithName(tag, "FIELDS")) {
|
||||
if (DynFormPathUtils.hasAncestorWithName(tag, "FIELDS")) {
|
||||
return new PsiReference[]{new DynFormFieldUsageReference(attrValue, new TextRange(1, value.length() + 1), value)};
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
String tagName = tag.getName();
|
||||
|
||||
// Only process DATASET tag if it's inside FOREIGN-DATASETS
|
||||
if ("DATASET".equals(tagName) && !hasAncestorWithName(tag, "FOREIGN-DATASETS")) {
|
||||
if ("DATASET".equals(tagName) && !DynFormPathUtils.hasAncestorWithName(tag, "FOREIGN-DATASETS")) {
|
||||
return PsiReference.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
@@ -263,15 +263,6 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiNewExpression getNewDynFormExpression(PsiLiteralExpression literal) {
|
||||
PsiElement parent = literal.getParent();
|
||||
@@ -328,15 +319,23 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
}
|
||||
@Nullable @Override public PsiElement resolve() {
|
||||
XmlTag container = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
|
||||
while (container != null && !"FORM_ENTRY".equals(container.getName()) && !"FORM_BROWSE".equals(container.getName()) && !"GRID-LIST".equals(container.getName())) {
|
||||
while (container != null &&
|
||||
!"FORM_ENTRY".equals(container.getName()) &&
|
||||
!"FORM_BROWSE".equals(container.getName()) &&
|
||||
!"GRID-LIST".equals(container.getName()) &&
|
||||
!"GRID-EDITOR".equals(container.getName()) &&
|
||||
!"FILTERS".equals(container.getName())) {
|
||||
container = container.getParentTag();
|
||||
}
|
||||
if (container == null) {
|
||||
XmlFile file = (XmlFile) myElement.getContainingFile();
|
||||
container = file.getRootTag();
|
||||
// If not in a specific container, search in all known containers across context
|
||||
PsiElement found = DynFormPathUtils.findFieldInFormContext(myElement.getContainingFile(), "FORM_ENTRY", fieldName, new HashSet<>());
|
||||
if (found != null) return found;
|
||||
return DynFormPathUtils.findFieldInFormContext(myElement.getContainingFile(), "FORM_BROWSE", fieldName, new HashSet<>());
|
||||
}
|
||||
if (container == null) return null;
|
||||
return findFieldInTag(container, fieldName);
|
||||
|
||||
// Search in current container's FIELDS across context
|
||||
return DynFormPathUtils.findFieldInFormContext(myElement.getContainingFile(), container.getName(), fieldName, new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,28 +352,19 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
XmlTag tag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
|
||||
if (tag == null) return null;
|
||||
|
||||
// หา container (FORM_ENTRY, FORM_BROWSE, GRID-LIST หรือ FILTERS)
|
||||
// หา container (FORM_ENTRY, FORM_BROWSE, GRID-LIST, GRID-EDITOR หรือ FILTERS)
|
||||
XmlTag container = tag;
|
||||
while (container != null &&
|
||||
!"LAYOUT".equals(container.getName()) &&
|
||||
!"TITLES".equals(container.getName()) &&
|
||||
!"FORM_ENTRY".equals(container.getName()) &&
|
||||
!"FORM_BROWSE".equals(container.getName()) &&
|
||||
!"GRID-LIST".equals(container.getName()) &&
|
||||
!"GRID-EDITOR".equals(container.getName()) &&
|
||||
!"FILTERS".equals(container.getName())) {
|
||||
container = container.getParentTag();
|
||||
}
|
||||
|
||||
if (container != null && ("LAYOUT".equals(container.getName()) || "TITLES".equals(container.getName()))) {
|
||||
container = container.getParentTag();
|
||||
}
|
||||
|
||||
if (container == null) return null;
|
||||
|
||||
XmlTag fieldsTag = container.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag == null) return null;
|
||||
|
||||
return findFieldInTag(fieldsTag, fieldName);
|
||||
return DynFormPathUtils.findFieldInFormContext(myElement.getContainingFile(), container.getName(), fieldName, new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,41 +391,10 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
XmlTag containerTag = fieldsTag.getParentTag();
|
||||
if (containerTag == null) return null;
|
||||
|
||||
// หาใน LAYOUT ก่อน
|
||||
XmlTag layoutTag = containerTag.findFirstSubTag("LAYOUT");
|
||||
if (layoutTag != null) {
|
||||
PsiElement found = findFieldInTag(layoutTag, fieldName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
// ถ้าไม่เจอใน LAYOUT ให้หาใน TITLES
|
||||
XmlTag titlesTag = containerTag.findFirstSubTag("TITLES");
|
||||
if (titlesTag != null) {
|
||||
PsiElement found = findFieldInTag(titlesTag, fieldName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
return null;
|
||||
return DynFormPathUtils.findUsageInFormContext(myElement.getContainingFile(), containerTag.getName(), fieldName, new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiElement findFieldInTag(XmlTag container, String name) {
|
||||
for (XmlTag subTag : container.getSubTags()) {
|
||||
if ("FIELD".equals(subTag.getName()) && name.equals(subTag.getAttributeValue("NAME"))) {
|
||||
XmlAttribute nameAttr = subTag.getAttribute("NAME");
|
||||
return nameAttr != null ? nameAttr.getValueElement() : subTag;
|
||||
}
|
||||
if (("SECTION".equals(subTag.getName()) || "ROW".equals(subTag.getName())) && name.equals(subTag.getAttributeValue("ID"))) {
|
||||
XmlAttribute idAttr = subTag.getAttribute("ID");
|
||||
return idAttr != null ? idAttr.getValueElement() : subTag;
|
||||
}
|
||||
PsiElement found = findFieldInTag(subTag, name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class DynFormDatasetReference extends PsiReferenceBase<XmlAttributeValue> implements PsiPolyVariantReference {
|
||||
private final String datasetId;
|
||||
private final boolean isAjaxOption;
|
||||
@@ -473,31 +432,15 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
|
||||
// 3. ค้นหาในไฟล์ที่ถูก INCLUDE (Downward)
|
||||
findDatasetInIncludesRecursive(currentFile, datasetId, results, new HashSet<>());
|
||||
if (!results.isEmpty()) return filterOpenFiles(results, myElement.getProject());
|
||||
|
||||
// 4. ค้นหาแบบ Module-wide (fallback สุดท้าย)
|
||||
List<PsiFile> allFiles = DynFormPathUtils.getAllFrmlFiles(currentFile);
|
||||
for (PsiFile file : allFiles) {
|
||||
if (file.equals(currentFile) || includers.contains(file)) continue;
|
||||
findDatasetInFile(file, datasetId, results);
|
||||
}
|
||||
|
||||
return filterOpenFiles(results, myElement.getProject());
|
||||
}
|
||||
|
||||
private void findDatasetInFile(PsiFile file, String id, List<ResolveResult> results) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
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");
|
||||
results.add(new PsiElementResolveResult(idAttr != null ? idAttr.getValueElement() : datasetTag));
|
||||
}
|
||||
PsiElement found = DynFormPathUtils.findDatasetInFile(file, id);
|
||||
if (found instanceof XmlTag datasetTag) {
|
||||
XmlAttribute idAttr = datasetTag.getAttribute("ID");
|
||||
results.add(new PsiElementResolveResult(idAttr != null ? idAttr.getValueElement() : datasetTag));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +516,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
if (parentDataset != null && "DATASET".equals(parentDataset.getName())) {
|
||||
XmlTag fieldsTag = parentDataset.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
PsiElement found = findFieldInTag(fieldsTag, fieldName);
|
||||
PsiElement found = DynFormPathUtils.findFieldInTag(fieldsTag, fieldName);
|
||||
if (found != null) results.add(new PsiElementResolveResult(found));
|
||||
}
|
||||
}
|
||||
@@ -605,7 +548,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
if (datasetTag != null) {
|
||||
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
PsiElement found = findFieldInTag(fieldsTag, fieldName);
|
||||
PsiElement found = DynFormPathUtils.findFieldInTag(fieldsTag, fieldName);
|
||||
if (found != null) results.add(new PsiElementResolveResult(found));
|
||||
}
|
||||
}
|
||||
@@ -642,18 +585,10 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
}
|
||||
|
||||
private void findDatasetInFile(PsiFile file, String id, List<ResolveResult> results) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
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");
|
||||
results.add(new PsiElementResolveResult(idAttr != null ? idAttr.getValueElement() : datasetTag));
|
||||
}
|
||||
PsiElement found = DynFormPathUtils.findDatasetInFile(file, id);
|
||||
if (found instanceof XmlTag datasetTag) {
|
||||
XmlAttribute idAttr = datasetTag.getAttribute("ID");
|
||||
results.add(new PsiElementResolveResult(idAttr != null ? idAttr.getValueElement() : datasetTag));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,14 +642,6 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
|
||||
// 3. ค้นหาในไฟล์ที่ถูก INCLUDE (Downward)
|
||||
findGridInIncludesRecursive(currentFile, gridId, results, new HashSet<>());
|
||||
if (!results.isEmpty()) return filterOpenFiles(results, myElement.getProject());
|
||||
|
||||
// 4. ค้นหาแบบ Module-wide (fallback)
|
||||
List<PsiFile> allFiles = DynFormPathUtils.getAllFrmlFiles(currentFile);
|
||||
for (PsiFile file : allFiles) {
|
||||
if (file.equals(currentFile) || includers.contains(file)) continue;
|
||||
findGridInFile(file, gridId, results);
|
||||
}
|
||||
|
||||
return filterOpenFiles(results, myElement.getProject());
|
||||
}
|
||||
@@ -800,7 +727,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
if (datasetId.equals(datasetTag.getAttributeValue("ID"))) {
|
||||
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
return findFieldInTag(fieldsTag, fieldName);
|
||||
return DynFormPathUtils.findFieldInTag(fieldsTag, fieldName);
|
||||
}
|
||||
return datasetTag;
|
||||
}
|
||||
@@ -831,51 +758,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
return findFieldInFormEntriesRecursive(myElement.getContainingFile(), fieldName, new HashSet<>());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement findFieldInFormEntriesRecursive(PsiFile file, String name, 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;
|
||||
|
||||
// 1. Search in current file FORM_ENTRY tags
|
||||
for (XmlTag formTag : rootTag.findSubTags("FORM")) {
|
||||
for (XmlTag entryTag : formTag.findSubTags("FORM_ENTRY")) {
|
||||
// Check hidden fields in FIELDS
|
||||
XmlTag fieldsTag = entryTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
for (XmlTag fieldTag : fieldsTag.findSubTags("FIELD")) {
|
||||
if ("HIDDEN".equals(fieldTag.getAttributeValue("INPUTTYPE")) && name.equals(fieldTag.getAttributeValue("NAME"))) {
|
||||
XmlAttribute nameAttr = fieldTag.getAttribute("NAME");
|
||||
return nameAttr != null ? nameAttr.getValueElement() : fieldTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check fields in LAYOUT
|
||||
XmlTag layoutTag = entryTag.findFirstSubTag("LAYOUT");
|
||||
if (layoutTag != null) {
|
||||
PsiElement found = findFieldInTag(layoutTag, name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in included files
|
||||
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 = findFieldInFormEntriesRecursive(includedFile, name, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return DynFormPathUtils.findFormFieldByNameRecursive(myElement.getContainingFile(), fieldName, new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -951,33 +834,17 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
|
||||
if (dataId != null && !dataId.isEmpty()) {
|
||||
PsiFile file = gridTag.getContainingFile();
|
||||
PsiElement datasetElement = findDatasetElement(file, dataId);
|
||||
PsiElement datasetElement = DynFormPathUtils.findDatasetElement(file, dataId);
|
||||
if (datasetElement instanceof XmlTag datasetTag) {
|
||||
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
return findFieldInTag(fieldsTag, fieldName);
|
||||
return DynFormPathUtils.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];
|
||||
|
||||
Reference in New Issue
Block a user