Update plugin version to 3.2.8 and enhance DynForm references
- Add cross-file Dataset and Field resolution for included .frml files - Prioritize opened files in editor for reference navigation - Restrict AJAX-OPTION DATASET auto-completion to suggest from ajax.xml - Update change notes for 3.2.8 release
This commit is contained in:
@@ -113,7 +113,18 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||
@NotNull ProcessingContext context,
|
||||
@NotNull CompletionResultSet resultSet) {
|
||||
addDatasetsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
|
||||
PsiElement position = parameters.getPosition();
|
||||
XmlTag parentTag = PsiTreeUtil.getParentOfType(position, XmlTag.class);
|
||||
boolean isAjaxOption = parentTag != null && "AJAX-OPTION".equals(parentTag.getName());
|
||||
|
||||
if (isAjaxOption) {
|
||||
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(parameters.getOriginalFile());
|
||||
if (ajaxFile != null) {
|
||||
addDatasetsInFile(ajaxFile, resultSet);
|
||||
}
|
||||
} else {
|
||||
addDatasetsInFileRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>(), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -268,19 +279,32 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
}
|
||||
|
||||
private void addDatasetsInFileRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
|
||||
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 ajax.xml (only if not already added)
|
||||
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
|
||||
if (ajaxFile != null && ajaxFile != file) {
|
||||
addDatasetsInFile(ajaxFile, 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
|
||||
// 3. Add datasets from included files (Downward)
|
||||
if (file instanceof XmlFile xmlFile) {
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag != null) {
|
||||
@@ -290,12 +314,23 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = DynFormPathUtils.findIncludedFile(file, path);
|
||||
addDatasetsInFileRecursive(includedFile, resultSet, visited);
|
||||
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) {
|
||||
@@ -318,6 +353,50 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
|
||||
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;
|
||||
@@ -332,17 +411,6 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
addGridsInFileRecursive(includedFile, resultSet, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFieldsFromDatasetId(XmlTag tag, @NotNull CompletionResultSet resultSet) {
|
||||
@@ -401,13 +469,34 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
|
||||
private PsiElement findDatasetElement(PsiFile file, String id) {
|
||||
return findDatasetInFileRecursiveForCompletion(file, id, new HashSet<>());
|
||||
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 findDatasetInFileRecursiveForCompletion(PsiFile file, String id, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return null;
|
||||
private PsiElement findDatasetInFile(PsiFile file, String id) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return null;
|
||||
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return null;
|
||||
|
||||
@@ -419,24 +508,43 @@ public class DynFormCompletionContributor extends CompletionContributor {
|
||||
return datasetTag;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(file);
|
||||
if (ajaxFile != null && ajaxFile != file) {
|
||||
PsiElement found = findDatasetInFileRecursiveForCompletion(ajaxFile, id, visited);
|
||||
if (found != null) return found;
|
||||
}
|
||||
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);
|
||||
PsiElement found = findDatasetInFileRecursiveForCompletion(includedFile, id, visited);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ 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.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -140,4 +142,62 @@ 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<>();
|
||||
Project project = includedFile.getProject();
|
||||
VirtualFile includedVFile = includedFile.getVirtualFile();
|
||||
if (includedVFile == null) return includers;
|
||||
|
||||
VirtualFile moduleDir = findModuleDir(includedFile);
|
||||
if (moduleDir == null) return includers;
|
||||
|
||||
List<PsiFile> allFiles = getAllFrmlFiles(includedFile);
|
||||
for (PsiFile file : allFiles) {
|
||||
if (file.equals(includedFile)) continue;
|
||||
if (!(file instanceof XmlFile xmlFile)) continue;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.sdk.dynform.tools.dynform;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.patterns.PlatformPatterns;
|
||||
import com.intellij.patterns.XmlPatterns;
|
||||
@@ -13,7 +15,9 @@ import com.intellij.util.ProcessingContext;
|
||||
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;
|
||||
|
||||
public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
@@ -93,7 +97,12 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
XmlAttributeValue attrValue = (XmlAttributeValue) element;
|
||||
String value = attrValue.getValue();
|
||||
if (value == null || value.isEmpty()) return PsiReference.EMPTY_ARRAY;
|
||||
return new PsiReference[]{new DynFormDatasetReference(attrValue, new TextRange(1, value.length() + 1), value)};
|
||||
|
||||
XmlAttribute attr = (XmlAttribute) attrValue.getParent();
|
||||
XmlTag parentTag = attr != null ? (XmlTag) attr.getParent() : null;
|
||||
boolean isAjaxOption = parentTag != null && "AJAX-OPTION".equals(parentTag.getName());
|
||||
|
||||
return new PsiReference[]{new DynFormDatasetReference(attrValue, new TextRange(1, value.length() + 1), value, isAjaxOption)};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -369,55 +378,59 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class DynFormDatasetReference extends PsiReferenceBase<XmlAttributeValue> {
|
||||
private static class DynFormDatasetReference extends PsiReferenceBase<XmlAttributeValue> implements PsiPolyVariantReference {
|
||||
private final String datasetId;
|
||||
public DynFormDatasetReference(@NotNull XmlAttributeValue element, TextRange range, String datasetId) {
|
||||
private final boolean isAjaxOption;
|
||||
|
||||
public DynFormDatasetReference(@NotNull XmlAttributeValue element, TextRange range, String datasetId, boolean isAjaxOption) {
|
||||
super(element, range, true);
|
||||
this.datasetId = datasetId;
|
||||
}
|
||||
@Nullable @Override public PsiElement resolve() {
|
||||
return findDatasetInFileRecursive(myElement.getContainingFile(), datasetId, new HashSet<>());
|
||||
this.isAjaxOption = isAjaxOption;
|
||||
}
|
||||
|
||||
@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;
|
||||
@Override
|
||||
public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
|
||||
List<ResolveResult> results = new ArrayList<>();
|
||||
PsiFile currentFile = myElement.getContainingFile();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
if (isAjaxOption) {
|
||||
// สำหรับ AJAX-OPTION ให้หาใน ajax.xml เท่านั้น
|
||||
PsiFile ajaxXml = DynFormPathUtils.findAjaxXml(currentFile);
|
||||
if (ajaxXml != null) {
|
||||
findDatasetInFile(ajaxXml, datasetId, results);
|
||||
}
|
||||
return filterOpenFiles(results, myElement.getProject());
|
||||
}
|
||||
return null;
|
||||
|
||||
// 1. ค้นหาในไฟล์ปัจจุบัน
|
||||
findDatasetInFile(currentFile, datasetId, results);
|
||||
if (!results.isEmpty()) return filterOpenFiles(results, myElement.getProject());
|
||||
|
||||
// 2. ค้นหาในไฟล์ที่ INCLUDE ไฟล์นี้ (Parent/Main Files)
|
||||
List<PsiFile> includers = DynFormPathUtils.findIncluders(currentFile);
|
||||
for (PsiFile includer : includers) {
|
||||
findDatasetInFile(includer, datasetId, results);
|
||||
}
|
||||
if (!results.isEmpty()) return filterOpenFiles(results, myElement.getProject());
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement findDatasetInFile(PsiFile file, String id) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return null;
|
||||
private void findDatasetInFile(PsiFile file, String id, List<ResolveResult> results) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return null;
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
|
||||
if (datasetsContainer == null) datasetsContainer = rootTag;
|
||||
@@ -425,14 +438,48 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) {
|
||||
if (id.equals(datasetTag.getAttributeValue("ID"))) {
|
||||
XmlAttribute idAttr = datasetTag.getAttribute("ID");
|
||||
return idAttr != null ? idAttr.getValueElement() : datasetTag;
|
||||
results.add(new PsiElementResolveResult(idAttr != null ? idAttr.getValueElement() : datasetTag));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void findDatasetInIncludesRecursive(PsiFile file, String id, List<ResolveResult> results, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file) || !(file instanceof XmlFile xmlFile)) return;
|
||||
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
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) {
|
||||
findDatasetInFile(includedFile, id, results);
|
||||
if (results.isEmpty()) {
|
||||
findDatasetInIncludesRecursive(includedFile, id, results, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
ResolveResult[] results = multiResolve(false);
|
||||
return results.length == 1 ? results[0].getElement() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @NotNull [] getVariants() {
|
||||
return new Object[0];
|
||||
}
|
||||
}
|
||||
|
||||
private static class DynFormMasterDataFieldReference extends PsiReferenceBase<XmlAttributeValue> {
|
||||
private static class DynFormMasterDataFieldReference extends PsiReferenceBase<XmlAttributeValue> implements PsiPolyVariantReference {
|
||||
private final String fieldName;
|
||||
|
||||
public DynFormMasterDataFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
|
||||
@@ -440,9 +487,9 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
|
||||
List<ResolveResult> results = new ArrayList<>();
|
||||
XmlAttribute attr = (XmlAttribute) myElement.getParent();
|
||||
XmlTag tag = (XmlTag) attr.getParent();
|
||||
String tagName = tag.getName();
|
||||
@@ -450,73 +497,96 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
|
||||
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"));
|
||||
resolveInDataset(tag.getAttributeValue("DATASET-ID"), results);
|
||||
} 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);
|
||||
PsiElement found = findFieldInGrid(gridTag);
|
||||
if (found != null) results.add(new PsiElementResolveResult(found));
|
||||
}
|
||||
}
|
||||
} 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"));
|
||||
resolveInDataset(tag.getAttributeValue("DATASET-ID"), results);
|
||||
} 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);
|
||||
PsiElement found = findFieldInTag(fieldsTag, fieldName);
|
||||
if (found != null) results.add(new PsiElementResolveResult(found));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return filterOpenFiles(results, myElement.getProject());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement resolveInDataset(String datasetId) {
|
||||
if (datasetId == null || datasetId.isEmpty()) return null;
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
ResolveResult[] results = multiResolve(false);
|
||||
return results.length == 1 ? results[0].getElement() : null;
|
||||
}
|
||||
|
||||
private void resolveInDataset(String datasetId, List<ResolveResult> results) {
|
||||
if (datasetId == null || datasetId.isEmpty()) return;
|
||||
|
||||
// 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);
|
||||
List<PsiElement> datasetElements = findDatasetElements(datasetId);
|
||||
for (PsiElement datasetElement : datasetElements) {
|
||||
XmlTag datasetTag = null;
|
||||
if (datasetElement instanceof XmlTag) datasetTag = (XmlTag) datasetElement;
|
||||
else if (datasetElement instanceof XmlAttributeValue) {
|
||||
XmlAttribute attr = (XmlAttribute) datasetElement.getParent();
|
||||
datasetTag = attr.getParent();
|
||||
}
|
||||
|
||||
if (datasetTag != null) {
|
||||
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
|
||||
if (fieldsTag != null) {
|
||||
PsiElement found = findFieldInTag(fieldsTag, fieldName);
|
||||
if (found != null) results.add(new PsiElementResolveResult(found));
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
private List<PsiElement> findDatasetElements(String id) {
|
||||
List<PsiElement> elements = new ArrayList<>();
|
||||
PsiFile currentFile = myElement.getContainingFile();
|
||||
|
||||
// ใช้ Logic เดียวกับ DynFormDatasetReference เพื่อความถูกต้อง
|
||||
List<ResolveResult> results = new ArrayList<>();
|
||||
|
||||
// 1. Local
|
||||
findDatasetInFile(currentFile, id, results);
|
||||
|
||||
// 2. Includers
|
||||
if (results.isEmpty()) {
|
||||
for (PsiFile includer : DynFormPathUtils.findIncluders(currentFile)) {
|
||||
findDatasetInFile(includer, id, results);
|
||||
}
|
||||
}
|
||||
if (found instanceof XmlTag) return found;
|
||||
return null;
|
||||
|
||||
// 3. Ajax
|
||||
if (results.isEmpty()) {
|
||||
PsiFile ajaxXml = DynFormPathUtils.findAjaxXml(currentFile);
|
||||
if (ajaxXml != null) findDatasetInFile(ajaxXml, id, results);
|
||||
}
|
||||
|
||||
for (ResolveResult res : results) {
|
||||
elements.add(res.getElement());
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
private void findDatasetInFile(PsiFile file, String id, List<ResolveResult> results) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return null;
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
|
||||
if (datasetsContainer == null) datasetsContainer = rootTag;
|
||||
@@ -524,97 +594,94 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) {
|
||||
if (id.equals(datasetTag.getAttributeValue("ID"))) {
|
||||
XmlAttribute idAttr = datasetTag.getAttribute("ID");
|
||||
return idAttr != null ? idAttr.getValueElement() : datasetTag;
|
||||
results.add(new PsiElementResolveResult(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 (listTag != null) dataId = listTag.getAttributeValue("DATAID");
|
||||
if (dataId == null) {
|
||||
XmlTag editorTag = gridTag.findFirstSubTag("GRID-EDITOR");
|
||||
if (editorTag != null) {
|
||||
dataId = editorTag.getAttributeValue("DATAID");
|
||||
}
|
||||
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);
|
||||
}
|
||||
List<ResolveResult> results = new ArrayList<>();
|
||||
resolveInDataset(dataId, results);
|
||||
return !results.isEmpty() ? results.get(0).getElement() : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @NotNull [] getVariants() {
|
||||
return new Object[0];
|
||||
}
|
||||
}
|
||||
|
||||
private static class DynFormGridReference extends PsiReferenceBase<XmlAttributeValue> {
|
||||
private static class DynFormGridReference extends PsiReferenceBase<XmlAttributeValue> implements PsiPolyVariantReference {
|
||||
private final String gridId;
|
||||
|
||||
public DynFormGridReference(@NotNull XmlAttributeValue element, TextRange range, String gridId) {
|
||||
super(element, range, true);
|
||||
this.gridId = gridId;
|
||||
}
|
||||
@Nullable @Override public PsiElement resolve() {
|
||||
return findGridInFileRecursive(myElement.getContainingFile(), gridId, new HashSet<>());
|
||||
|
||||
@Override
|
||||
public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
|
||||
List<ResolveResult> results = new ArrayList<>();
|
||||
PsiFile currentFile = myElement.getContainingFile();
|
||||
|
||||
// 1. ค้นหาในไฟล์ปัจจุบัน
|
||||
findGridInFile(currentFile, gridId, results);
|
||||
if (!results.isEmpty()) return filterOpenFiles(results, myElement.getProject());
|
||||
|
||||
// 2. ค้นหาในไฟล์ที่ INCLUDE ไฟล์นี้ (Parent/Main Files)
|
||||
List<PsiFile> includers = DynFormPathUtils.findIncluders(currentFile);
|
||||
for (PsiFile includer : includers) {
|
||||
findGridInFile(includer, gridId, results);
|
||||
}
|
||||
if (!results.isEmpty()) return filterOpenFiles(results, myElement.getProject());
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement findGridInFileRecursive(PsiFile file, String id, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file)) return null;
|
||||
if (!(file instanceof XmlFile xmlFile)) return null;
|
||||
private void findGridInFile(PsiFile file, String id, List<ResolveResult> results) {
|
||||
if (!(file instanceof XmlFile xmlFile)) return;
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return null;
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag dataGridsTag = rootTag.findFirstSubTag("DATA-GRIDS");
|
||||
if (dataGridsTag != null) {
|
||||
for (XmlTag gridTag : dataGridsTag.findSubTags("DATA-GRID")) {
|
||||
if (id.equals(gridTag.getAttributeValue("ID"))) {
|
||||
XmlAttribute idAttr = gridTag.getAttribute("ID");
|
||||
return idAttr != null ? idAttr.getValueElement() : gridTag;
|
||||
results.add(new PsiElementResolveResult(idAttr != null ? idAttr.getValueElement() : gridTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findGridInIncludesRecursive(PsiFile file, String id, List<ResolveResult> results, Set<PsiFile> visited) {
|
||||
if (file == null || !visited.add(file) || !(file instanceof XmlFile xmlFile)) return;
|
||||
|
||||
XmlTag rootTag = xmlFile.getRootTag();
|
||||
if (rootTag == null) return;
|
||||
|
||||
XmlTag includesTag = rootTag.findFirstSubTag("INCLUDES");
|
||||
if (includesTag != null) {
|
||||
@@ -622,12 +689,27 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
String path = includeTag.getAttributeValue("FILE");
|
||||
if (path != null) {
|
||||
PsiFile includedFile = DynFormPathUtils.findIncludedFile(file, path);
|
||||
PsiElement found = findGridInFileRecursive(includedFile, id, visited);
|
||||
if (found != null) return found;
|
||||
if (includedFile != null) {
|
||||
findGridInFile(includedFile, id, results);
|
||||
if (results.isEmpty()) {
|
||||
findGridInIncludesRecursive(includedFile, id, results, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
ResolveResult[] results = multiResolve(false);
|
||||
return results.length == 1 ? results[0].getElement() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @NotNull [] getVariants() {
|
||||
return new Object[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,4 +820,23 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ResolveResult[] filterOpenFiles(List<ResolveResult> results, Project project) {
|
||||
if (results.size() <= 1) return results.toArray(new ResolveResult[0]);
|
||||
FileEditorManager editorManager = FileEditorManager.getInstance(project);
|
||||
List<ResolveResult> openFiles = new ArrayList<>();
|
||||
for (ResolveResult result : results) {
|
||||
PsiElement element = result.getElement();
|
||||
if (element != null) {
|
||||
PsiFile file = element.getContainingFile();
|
||||
if (file != null && file.getVirtualFile() != null && editorManager.isFileOpen(file.getVirtualFile())) {
|
||||
openFiles.add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!openFiles.isEmpty()) {
|
||||
return openFiles.toArray(new ResolveResult[0]);
|
||||
}
|
||||
return results.toArray(new ResolveResult[0]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user