feat(config): persistent context, path portability, and refined browsing

- Generator now remembers last used directory for Action Beans and Datasets independently per project.
- Added settings to customize automatic file opening and table limits.
- Enforced project-relative paths for i18n/XSD for better project portability.
- Refined File Browser to start at current directory with strict project scope.
- Implemented DynFormAnnotator for structural validation.
- Bumped plugin version to 3.2.7.
This commit is contained in:
2026-04-22 16:04:18 +07:00
parent d42a514f44
commit 8f011e637b
5 changed files with 106 additions and 16 deletions

View File

@@ -2,6 +2,8 @@ package com.sdk.dynform.tools.dynform;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.XmlPatterns;
import com.intellij.psi.*;
@@ -229,6 +231,20 @@ public class DynFormCompletionContributor extends CompletionContributor {
addFormFieldsForNameRecursive(parameters.getOriginalFile(), resultSet, new HashSet<>());
}
});
// XML completion for INCLUDE:FILE
extend(CompletionType.BASIC, XmlPatterns.psiElement()
.inside(XmlPatterns.xmlAttributeValue()
.withParent(XmlPatterns.xmlAttribute().withName("FILE")
.withParent(XmlPatterns.xmlTag().withName("INCLUDE")))),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
@NotNull ProcessingContext context,
@NotNull CompletionResultSet resultSet) {
addFileIncludeCompletions(parameters, resultSet);
}
});
}
private void addFieldsInTagRecursive(XmlTag container, @NotNull CompletionResultSet resultSet) {
@@ -254,7 +270,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
private void addDatasetsInFileRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
if (file == null || !visited.add(file)) return;
// 1. Add datasets from current file
addDatasetsInFile(file, resultSet);
@@ -391,7 +407,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
private PsiElement findDatasetInFileRecursiveForCompletion(PsiFile file, String id, Set<PsiFile> visited) {
if (file == null || !visited.add(file)) return null;
if (!(file instanceof XmlFile xmlFile)) return null;
XmlTag rootTag = xmlFile.getRootTag();
if (rootTag == null) return null;
@@ -455,6 +471,78 @@ public class DynFormCompletionContributor extends CompletionContributor {
return null;
}
private void addFileIncludeCompletions(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
String value = parameters.getPosition().getText().replace(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED, "");
PsiFile contextFile = parameters.getOriginalFile();
Project project = contextFile.getProject();
if (value.startsWith("#")) {
// Suggest files in the current module's view/frm
VirtualFile moduleDir = DynFormPathUtils.findModuleDir(contextFile);
if (moduleDir != null) {
VirtualFile frmDir = moduleDir.findFileByRelativePath("view/frm");
if (frmDir != null) {
addFrmlFilesRecursive(frmDir, "#", "", resultSet);
}
}
} else if (value.startsWith("/")) {
// Suggest modules or files in modules
String path = value.substring(1);
int firstSlash = path.indexOf("/");
if (firstSlash < 0) {
// Suggest modules
List<String> modules = DynFormPathUtils.getAllModules(project);
for (String module : modules) {
resultSet.addElement(LookupElementBuilder.create("/" + module + "/")
.withIcon(com.intellij.icons.AllIcons.Nodes.Module)
.withPresentableText("/" + module));
}
} else {
// Suggest files in the selected module
String moduleName = path.substring(0, firstSlash);
VirtualFile moduleBase = DynFormPathUtils.getModuleBaseDir(project);
if (moduleBase != null) {
VirtualFile frmDir = moduleBase.findFileByRelativePath(moduleName + "/view/frm");
if (frmDir != null) {
addFrmlFilesRecursive(frmDir, "/" + moduleName + "/", "", resultSet);
}
}
}
} else {
// Suggest relative files (basic implementation: current directory)
VirtualFile currentDir = contextFile.getVirtualFile().getParent();
if (currentDir != null) {
for (VirtualFile child : currentDir.getChildren()) {
if (child.isDirectory()) {
resultSet.addElement(LookupElementBuilder.create(child.getName() + "/")
.withIcon(com.intellij.icons.AllIcons.Nodes.Folder));
} else if (child.getName().endsWith(".frml")) {
resultSet.addElement(LookupElementBuilder.create(child.getName())
.withIcon(com.intellij.icons.AllIcons.FileTypes.Xml));
}
}
}
// Also suggest starting with # or /
resultSet.addElement(LookupElementBuilder.create("#")
.withTailText(" (Current Module)"));
resultSet.addElement(LookupElementBuilder.create("/")
.withTailText(" (Cross Module)"));
}
}
private void addFrmlFilesRecursive(VirtualFile dir, String prefix, String subPath, @NotNull CompletionResultSet resultSet) {
for (VirtualFile child : dir.getChildren()) {
String currentSubPath = subPath.isEmpty() ? child.getName() : subPath + "/" + child.getName();
if (child.isDirectory()) {
addFrmlFilesRecursive(child, prefix, currentSubPath, resultSet);
} else if (child.getName().endsWith(".frml")) {
resultSet.addElement(LookupElementBuilder.create(prefix + currentSubPath)
.withIcon(com.intellij.icons.AllIcons.FileTypes.Xml)
.withPresentableText(currentSubPath));
}
}
}
private void addFormFieldsForNameRecursive(PsiFile file, @NotNull CompletionResultSet resultSet, Set<PsiFile> visited) {
if (file == null || !visited.add(file)) return;
if (!(file instanceof XmlFile xmlFile)) return;