feat(config): persistent directories, auto-open files, and strict project scope

- Implemented persistent directory memory for Action Model and Dataset generators (per project).
- Added configuration to automatically open generated files in the editor with a customizable limit.
- Enforced strict project-scope file selection for i18n and XSD settings.
- Switched to relative path storage for i18n/XSD configuration to enhance project portability.
- Improved File Browser logic to start at the current directory or fallback to project root.
- Fixed compilation errors and optimized imports in configuration classes.
- Bumped plugin version to 3.2.4.
This commit is contained in:
2026-04-18 11:14:26 +07:00
parent 4da00c10e4
commit 475555da83
17 changed files with 317 additions and 36 deletions

View File

@@ -1 +1 @@
2026-04-10 2026-04-18

6
CheckAPI.java Normal file
View File

@@ -0,0 +1,6 @@
import com.intellij.javaee.ExternalResourceManagerEx;
import com.intellij.javaee.ExternalResourceManager;
public class CheckAPI {
public static void main(String[] args) {
}
}

View File

@@ -55,6 +55,10 @@
<FIELD NAME="PROJ_FROM_YEAR" TYPE="NUMBER" WIDTH="4" LABEL="plcp.from_year"/> <FIELD NAME="PROJ_FROM_YEAR" TYPE="NUMBER" WIDTH="4" LABEL="plcp.from_year"/>
</FIELDS> </FIELDS>
</DATASET> </DATASET>
<FOREIGN-DATASETS>
<DATASET DATASET-ID="DS-PROJECT-STTGYS" MASTER-FIELDS="PROJ_ID,ACM_CODE" DETAIL-FIELDS="PROJ_ID" DELETE-WITH-MASTER="Y"/>
<DATASET DATASET-ID="DS-PROJECT-STTGYS-ITEMS" MASTER-FIELDS="PROJ_ID,ACM_CODE" DETAIL-FIELDS="PROJ_ID" DELETE-WITH-MASTER="Y"/>
</FOREIGN-DATASETS>
</DATASETS> </DATASETS>
<FORM> <FORM>
@@ -120,7 +124,7 @@
<FIELD NAME="PROJ_YEAR" CAPTION="plcp.year" INPUTTYPE="TEXT" READONLY="Y" DATATYPE="TEXT"/> <FIELD NAME="PROJ_YEAR" CAPTION="plcp.year" INPUTTYPE="TEXT" READONLY="Y" DATATYPE="TEXT"/>
<FIELD NAME="VPJM_CODE" CAPTION="plcp.pjm_code" INPUTTYPE="TEXT" READONLY="Y"/> <FIELD NAME="VPJM_CODE" CAPTION="plcp.pjm_code" INPUTTYPE="TEXT" READONLY="Y"/>
<FIELD NAME="PJM_CODE" CAPTION="plcp.pjm_name" INPUTTYPE="COMBOBOX" REQUIRE="Y" EDIT-READONLY="Y"> <FIELD NAME="PJM_CODE" CAPTION="plcp.pjm_name" INPUTTYPE="COMBOBOX" REQUIRE="Y" EDIT-READONLY="Y">
<AJAX-OPTION URL="/api-data.jbx" DATASET="DS-PROJECT-02" VALUE-FIELD="PJM_CODE" TEXT-FIELD="PJM_NAME"> <AJAX-OPTION URL="/api-data.jbx" DATASET="DS-PROJECT-02" VIEW-DATASET="DS-PROJECT-03" VALUE-FIELD="PJM_CODE" TEXT-FIELD="PJM_NAME">
<UPDATE-FIELDS> <UPDATE-FIELDS>
<FIELD SRC="PJM_CODE" TARGET="VPJM_CODE"></FIELD> <FIELD SRC="PJM_CODE" TARGET="VPJM_CODE"></FIELD>
</UPDATE-FIELDS> </UPDATE-FIELDS>

9
FindAPI.java Normal file
View File

@@ -0,0 +1,9 @@
import com.intellij.javaee.ExternalResourceManager;
import java.lang.reflect.Method;
public class FindAPI {
public static void main(String[] args) {
for (Method m : ExternalResourceManager.class.getMethods()) {
System.out.println(m.getName() + " " + java.util.Arrays.toString(m.getParameterTypes()));
}
}
}

17
InspectXSD.java Normal file
View File

@@ -0,0 +1,17 @@
import com.intellij.javaee.ExternalResourceManager;
import com.intellij.javaee.ExternalResourceManagerEx;
import java.lang.reflect.Method;
public class InspectXSD {
public static void main(String[] args) throws Exception {
System.out.println("Methods in ExternalResourceManager:");
for (Method m : ExternalResourceManager.class.getMethods()) {
System.out.println(m.getName());
}
System.out.println("---");
System.out.println("Methods in ExternalResourceManagerEx:");
for (Method m : ExternalResourceManagerEx.class.getMethods()) {
System.out.println(m.getName());
}
}
}

View File

@@ -4,11 +4,10 @@ plugins {
id("org.jetbrains.intellij.platform") version "2.7.0" id("org.jetbrains.intellij.platform") version "2.7.0"
} }
group = "com.sdk.dynform.tools" group = "com.sdk.dynform.tools"
version = "3.2.3" version = "3.2.4"
repositories { repositories {
mavenCentral() mavenCentral()
intellijPlatform { intellijPlatform {
defaultRepositories() defaultRepositories()
} }
@@ -39,12 +38,20 @@ intellijPlatform {
} }
changeNotes = """ changeNotes = """
<h2>[3.2.4]</h2>
<ul>
<li><strong>Persistent Generation Directories:</strong> Generator now remembers the last used directory for Action Beans and Dataset XMLs independently per project.</li>
<li><strong>Auto-Open Generated Files:</strong> Added configuration to automatically open newly created files in the editor, with a customizable limit on the number of files.</li>
<li><strong>Strict Project-Relative Paths:</strong> I18n and XSD configuration now strictly enforces file selection within the project root and stores paths as relative for maximum portability.</li>
<li><strong>Smart File Browser:</strong> Improved file picker logic to automatically start at the current configured directory if it's within the project, falling back to the project root otherwise.</li>
</ul>
<h2>[3.2.3]</h2> <h2>[3.2.3]</h2>
<ul> <ul>
<li><strong>Advanced Data Referencing:</strong> Implemented comprehensive reference and completion support for <code>&lt;FOREIGN-DATASETS&gt;</code> and <code>&lt;MASTER-DATA&gt;</code> structures.</li> <li><strong>Advanced Data Referencing:</strong> Implemented comprehensive reference and completion support for <code>&lt;FOREIGN-DATASETS&gt;</code> and <code>&lt;MASTER-DATA&gt;</code> structures.</li>
<li><strong>Contextual Field Resolution:</strong> Enhanced field resolution logic to resolve fields from datasets specified by <code>DATASET-ID</code> and <code>DATAID</code> attributes within their respective tags.</li> <li><strong>Contextual Field Resolution:</strong> Enhanced field resolution logic to resolve fields from datasets specified by <code>DATASET-ID</code> and <code>DATAID</code> attributes within their respective tags.</li>
<li><strong>Recursive Resource Scanning:</strong> Improved dataset and grid scanning to search across recursively included <code>.frml</code> files, ensuring consistent navigation and completion throughout the project.</li> <li><strong>Recursive Resource Scanning:</strong> Improved dataset and grid scanning to search across recursively included <code>.frml</code> files, ensuring consistent navigation and completion throughout the project.</li>
</ul> </ul>
<h2>[3.2.2]</h2> <h2>[3.2.2]</h2>
<ul> <ul>
<li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li> <li><strong>UI/UX Improvement:</strong> Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.</li>

1
buildSrc.kts Normal file
View File

@@ -0,0 +1 @@
println("Checking methods")

View File

@@ -13,9 +13,11 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.sdk.dynform.helper.GUtils; import com.sdk.dynform.helper.GUtils;
import com.sdk.dynform.tools.config.DynFormSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@@ -30,12 +32,23 @@ public class GenerateBeanAction extends AnAction {
return; return;
} }
DynFormSettings settings = DynFormSettings.getInstance(project);
FileChooserDescriptor descriptor = new FileChooserDescriptor(false,true,false,false,false,false); FileChooserDescriptor descriptor = new FileChooserDescriptor(false,true,false,false,false,false);
PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null); PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null);
VirtualFile baseDir = GUtils.findSourceRoot(project);
pathChooser.choose(baseDir, virtualFiles -> { VirtualFile initialDir = GUtils.findSourceRoot(project);
String packageName = GUtils.getSelectedPackage(project, virtualFiles.getFirst()); if (settings.lastBeanPackagePath != null && !settings.lastBeanPackagePath.isEmpty()) {
VirtualFile lastDir = com.intellij.openapi.vfs.LocalFileSystem.getInstance().findFileByPath(settings.lastBeanPackagePath);
if (lastDir != null && lastDir.isValid()) {
initialDir = lastDir;
}
}
pathChooser.choose(initialDir, virtualFiles -> {
VirtualFile selectedDir = virtualFiles.getFirst();
settings.lastBeanPackagePath = selectedDir.getPath();
String packageName = GUtils.getSelectedPackage(project, selectedDir);
ArrayList<DbTable> tables = new ArrayList<>(); ArrayList<DbTable> tables = new ArrayList<>();
for (PsiElement psiElement : psiElements) { for (PsiElement psiElement : psiElements) {

View File

@@ -13,9 +13,11 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.sdk.dynform.helper.GUtils; import com.sdk.dynform.helper.GUtils;
import com.sdk.dynform.tools.config.DynFormSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@@ -30,12 +32,23 @@ public class GenerateBeanActionV3 extends AnAction {
return; return;
} }
DynFormSettings settings = DynFormSettings.getInstance(project);
FileChooserDescriptor descriptor = new FileChooserDescriptor(false,true,false,false,false,false); FileChooserDescriptor descriptor = new FileChooserDescriptor(false,true,false,false,false,false);
PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null); PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null);
VirtualFile baseDir = GUtils.findSourceRoot(project);
pathChooser.choose(baseDir, virtualFiles -> { VirtualFile initialDir = GUtils.findSourceRoot(project);
String packageName = GUtils.getSelectedPackage(project, virtualFiles.getFirst()); if (settings.lastBeanPackagePath != null && !settings.lastBeanPackagePath.isEmpty()) {
VirtualFile lastDir = com.intellij.openapi.vfs.LocalFileSystem.getInstance().findFileByPath(settings.lastBeanPackagePath);
if (lastDir != null && lastDir.isValid()) {
initialDir = lastDir;
}
}
pathChooser.choose(initialDir, virtualFiles -> {
VirtualFile selectedDir = virtualFiles.getFirst();
settings.lastBeanPackagePath = selectedDir.getPath();
String packageName = GUtils.getSelectedPackage(project, selectedDir);
ArrayList<DbTable> tables = new ArrayList<>(); ArrayList<DbTable> tables = new ArrayList<>();
for (PsiElement psiElement : psiElements) { for (PsiElement psiElement : psiElements) {

View File

@@ -13,9 +13,11 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.sdk.dynform.helper.GUtils; import com.sdk.dynform.helper.GUtils;
import com.sdk.dynform.tools.config.DynFormSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@@ -30,15 +32,22 @@ public class GenerateDatasetAction extends AnAction {
return; return;
} }
DynFormSettings settings = DynFormSettings.getInstance(project);
FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false); FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false);
descriptor.setTitle("Select Target Directory for Dataset XML"); descriptor.setTitle("Select Target Directory for Dataset XML");
PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null); PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null);
//VirtualFile baseDir = GUtils.findSourceRoot(project); VirtualFile initialDir = project.getBaseDir();
VirtualFile baseDir = project.getBaseDir(); if (settings.lastDatasetFolderPath != null && !settings.lastDatasetFolderPath.isEmpty()) {
VirtualFile lastDir = com.intellij.openapi.vfs.LocalFileSystem.getInstance().findFileByPath(settings.lastDatasetFolderPath);
if (lastDir != null && lastDir.isValid()) {
initialDir = lastDir;
}
}
pathChooser.choose(baseDir, virtualFiles -> { pathChooser.choose(initialDir, virtualFiles -> {
VirtualFile targetDir = virtualFiles.getFirst(); VirtualFile targetDir = virtualFiles.getFirst();
settings.lastDatasetFolderPath = targetDir.getPath();
ArrayList<DbTable> tables = new ArrayList<>(); ArrayList<DbTable> tables = new ArrayList<>();
for (PsiElement psiElement : psiElements) { for (PsiElement psiElement : psiElements) {

View File

@@ -5,11 +5,13 @@ import com.intellij.database.model.DasTableKey;
import com.intellij.database.psi.DbTable; import com.intellij.database.psi.DbTable;
import com.intellij.database.util.DasUtil; import com.intellij.database.util.DasUtil;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.JBIterable; import com.intellij.util.containers.JBIterable;
import com.sdk.dynform.helper.GUtils; import com.sdk.dynform.helper.GUtils;
import com.sdk.dynform.tools.config.DynFormSettings;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.Template; import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler; import freemarker.template.TemplateExceptionHandler;
@@ -39,7 +41,7 @@ public class GeneratorServices {
this.version = version; this.version = version;
} }
private void genDataModel(Template template, Map<String, Object> model, VirtualFile targetDir, String classFile, ProgressIndicator indicator) { private VirtualFile genDataModel(Template template, Map<String, Object> model, VirtualFile targetDir, String classFile, ProgressIndicator indicator) {
try { try {
VirtualFile outputFile = targetDir.findOrCreateChildData(this, classFile); VirtualFile outputFile = targetDir.findOrCreateChildData(this, classFile);
ApplicationManager.getApplication().runWriteAction(() -> { ApplicationManager.getApplication().runWriteAction(() -> {
@@ -51,11 +53,20 @@ public class GeneratorServices {
indicator.setText2("Generated " + outputFile.getName()); indicator.setText2("Generated " + outputFile.getName());
GUtils.showInfo(project, "ActionModels Generator", "Generated " + outputFile.getName()); GUtils.showInfo(project, "ActionModels Generator", "Generated " + outputFile.getName());
}); });
return outputFile;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private void openInEditor(VirtualFile file) {
if (file != null) {
ApplicationManager.getApplication().invokeLater(() -> {
FileEditorManager.getInstance(project).openFile(file, true);
});
}
}
public void execute(@NotNull ProgressIndicator indicator) { public void execute(@NotNull ProgressIndicator indicator) {
// Use AtomicInteger to safely count files within the lambda expression // Use AtomicInteger to safely count files within the lambda expression
AtomicInteger fileCount = new AtomicInteger(0); AtomicInteger fileCount = new AtomicInteger(0);
@@ -81,30 +92,43 @@ public class GeneratorServices {
Template tmpDTO = cfg.getTemplate("actionDTO.ftl"); Template tmpDTO = cfg.getTemplate("actionDTO.ftl");
Template tmpDTOExt = cfg.getTemplate("actionDTO.extend.ftl"); Template tmpDTOExt = cfg.getTemplate("actionDTO.extend.ftl");
tables.forEach(table -> { DynFormSettings settings = DynFormSettings.getInstance(project);
for (int i = 0; i < tables.size(); i++) {
DbTable table = tables.get(i);
final boolean shouldOpen = settings.autoOpenGeneratedFiles && i < settings.openFilesLimit;
Map<String, Object> model = createModelForTable(table); Map<String, Object> model = createModelForTable(table);
String className = model.get("className").toString(); String className = model.get("className").toString();
String classFile = className + ".java"; String classFile = className + ".java";
genDataModel(tmpBean, model, beanDir, classFile, indicator); VirtualFile fBean = genDataModel(tmpBean, model, beanDir, classFile, indicator);
fileCount.getAndIncrement(); fileCount.getAndIncrement();
VirtualFile fBeanExt = null;
if (!Files.exists(Path.of(beanExtDir.toNioPath().toString(), classFile))) { if (!Files.exists(Path.of(beanExtDir.toNioPath().toString(), classFile))) {
genDataModel(tmpBeanExt, model, beanExtDir, classFile, indicator); fBeanExt = genDataModel(tmpBeanExt, model, beanExtDir, classFile, indicator);
fileCount.getAndIncrement(); fileCount.getAndIncrement();
} }
// FIX: Use a separate variable for the DTO filename to avoid overwriting the wrong file. // FIX: Use a separate variable for the DTO filename to avoid overwriting the wrong file.
String dtoClassFile = "DTO_" + className + ".java"; String dtoClassFile = "DTO_" + className + ".java";
genDataModel(tmpDTO, model, DTODir, dtoClassFile, indicator); VirtualFile fDTO = genDataModel(tmpDTO, model, DTODir, dtoClassFile, indicator);
fileCount.getAndIncrement(); fileCount.getAndIncrement();
VirtualFile fDTOExt = null;
if (!Files.exists(Path.of(DTOExtDir.toNioPath().toString(), dtoClassFile))) { if (!Files.exists(Path.of(DTOExtDir.toNioPath().toString(), dtoClassFile))) {
genDataModel(tmpDTOExt, model, DTOExtDir, dtoClassFile, indicator); fDTOExt = genDataModel(tmpDTOExt, model, DTOExtDir, dtoClassFile, indicator);
fileCount.getAndIncrement(); fileCount.getAndIncrement();
} }
});
if (shouldOpen) {
openInEditor(fBean);
openInEditor(fBeanExt);
openInEditor(fDTO);
openInEditor(fDTOExt);
}
}
// After the loop finishes, show a single, helpful summary notification. // After the loop finishes, show a single, helpful summary notification.
String message = String.format("Generated %d files for %d tables successfully.", fileCount.get(), tables.size()); String message = String.format("Generated %d files for %d tables successfully.", fileCount.get(), tables.size());
@@ -125,14 +149,22 @@ public class GeneratorServices {
Template tmpDataset = cfg.getTemplate("dataset.ftl"); Template tmpDataset = cfg.getTemplate("dataset.ftl");
tables.forEach(table -> { DynFormSettings settings = DynFormSettings.getInstance(project);
for (int i = 0; i < tables.size(); i++) {
DbTable table = tables.get(i);
final boolean shouldOpen = settings.autoOpenGeneratedFiles && i < settings.openFilesLimit;
Map<String, Object> model = createModelForTable(table); Map<String, Object> model = createModelForTable(table);
String tableName = model.get("tableName").toString(); String tableName = model.get("tableName").toString();
String fileName = GUtils.capitalize(tableName) + ".xml"; String fileName = GUtils.capitalize(tableName) + ".xml";
genDataModel(tmpDataset, model, targetDir, fileName, indicator); VirtualFile fDataset = genDataModel(tmpDataset, model, targetDir, fileName, indicator);
fileCount.getAndIncrement(); fileCount.getAndIncrement();
});
if (shouldOpen) {
openInEditor(fDataset);
}
}
String message = String.format("Generated %d dataset XML files successfully.", fileCount.get()); String message = String.format("Generated %d dataset XML files successfully.", fileCount.get());
GUtils.showInfo(project, "Dataset XML Generation Complete", message); GUtils.showInfo(project, "Dataset XML Generation Complete", message);

View File

@@ -4,9 +4,12 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.TextComponentAccessor; import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.sdk.dynform.tools.dynform.DynFormXsdScanner; import com.sdk.dynform.tools.dynform.DynFormXsdScanner;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -14,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.File;
public class DynFormConfigurable implements Configurable { public class DynFormConfigurable implements Configurable {
@@ -25,6 +29,8 @@ public class DynFormConfigurable implements Configurable {
private TextFieldWithBrowseButton i18nMessageFileField; private TextFieldWithBrowseButton i18nMessageFileField;
private TextFieldWithBrowseButton xsdFolderField; private TextFieldWithBrowseButton xsdFolderField;
private JTextField xsdPrefixField; private JTextField xsdPrefixField;
private JCheckBox autoOpenCheck;
private JSpinner openLimitSpinner;
public DynFormConfigurable(@NotNull Project project) { public DynFormConfigurable(@NotNull Project project) {
this.project = project; this.project = project;
@@ -87,15 +93,38 @@ public class DynFormConfigurable implements Configurable {
i18nGbc.weightx = 1.0; i18nGbc.weightx = 1.0;
i18nMessageFileField = new TextFieldWithBrowseButton(); i18nMessageFileField = new TextFieldWithBrowseButton();
VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
FileChooserDescriptor xmlDescriptor = new FileChooserDescriptor(true, false, false, false, false, false) { FileChooserDescriptor xmlDescriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
@Override @Override
public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
return super.isFileVisible(file, showHiddenFiles) && (file.isDirectory() || "xml".equalsIgnoreCase(file.getExtension())); if (!super.isFileVisible(file, showHiddenFiles)) return false;
if (projectDir == null) return true;
return VfsUtil.isAncestor(projectDir, file, false);
} }
};
i18nMessageFileField.addBrowseFolderListener("Select Message XML File", "Select the main message bundle XML file", @Override
project, xmlDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); public void validateSelectedFiles(VirtualFile @NotNull [] files) throws Exception {
super.validateSelectedFiles(files);
for (VirtualFile file : files) {
if (projectDir != null && !VfsUtil.isAncestor(projectDir, file, false)) {
throw new Exception("File must be within the project directory.");
}
}
}
}.withRoots(projectDir);
i18nMessageFileField.addBrowseFolderListener("Select Message XML File", "Select the main message bundle XML file (within project)",
project, xmlDescriptor, new TextComponentAccessor<>() {
@Override
public String getText(JTextField textField) {
return resolvePath(textField.getText(), projectDir);
}
@Override
public void setText(JTextField textField, @NotNull String text) {
textField.setText(relativizePath(text, projectDir));
}
});
i18nPanel.add(i18nMessageFileField, i18nGbc); i18nPanel.add(i18nMessageFileField, i18nGbc);
@@ -128,8 +157,38 @@ public class DynFormConfigurable implements Configurable {
xsdGbc.gridx = 1; xsdGbc.gridx = 1;
xsdGbc.weightx = 1.0; xsdGbc.weightx = 1.0;
xsdFolderField = new TextFieldWithBrowseButton(); xsdFolderField = new TextFieldWithBrowseButton();
xsdFolderField.addBrowseFolderListener("Select XSD Folder", "Select the folder containing DynForm .xsd schemas",
project, FileChooserDescriptorFactory.createSingleFolderDescriptor(), TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); FileChooserDescriptor folderDescriptor = new FileChooserDescriptor(false, true, false, false, false, false) {
@Override
public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
if (!super.isFileVisible(file, showHiddenFiles)) return false;
if (projectDir == null) return true;
return VfsUtil.isAncestor(projectDir, file, false);
}
@Override
public void validateSelectedFiles(VirtualFile @NotNull [] files) throws Exception {
super.validateSelectedFiles(files);
for (VirtualFile file : files) {
if (projectDir != null && !VfsUtil.isAncestor(projectDir, file, false)) {
throw new Exception("Folder must be within the project directory.");
}
}
}
}.withRoots(projectDir);
xsdFolderField.addBrowseFolderListener("Select XSD Folder", "Select the folder containing DynForm .xsd schemas (within project)",
project, folderDescriptor, new TextComponentAccessor<>() {
@Override
public String getText(JTextField textField) {
return resolvePath(textField.getText(), projectDir);
}
@Override
public void setText(JTextField textField, @NotNull String text) {
textField.setText(relativizePath(text, projectDir));
}
});
xsdPanel.add(xsdFolderField, xsdGbc); xsdPanel.add(xsdFolderField, xsdGbc);
xsdGbc.gridx = 0; xsdGbc.gridx = 0;
@@ -142,6 +201,34 @@ public class DynFormConfigurable implements Configurable {
mainPanel.add(xsdPanel, gbc); mainPanel.add(xsdPanel, gbc);
// --- Generator Settings Group ---
gbc.gridy++;
JPanel generatorPanel = new JPanel(new GridBagLayout());
generatorPanel.setBorder(BorderFactory.createTitledBorder("Action Models Generator"));
GridBagConstraints genGbc = new GridBagConstraints();
genGbc.fill = GridBagConstraints.HORIZONTAL;
genGbc.insets = new Insets(2, 2, 2, 2);
genGbc.gridx = 0;
genGbc.gridy = 0;
genGbc.weightx = 1.0;
genGbc.gridwidth = 2;
autoOpenCheck = new JCheckBox("Automatically open generated files in editor");
generatorPanel.add(autoOpenCheck, genGbc);
genGbc.gridy++;
genGbc.gridwidth = 1;
genGbc.weightx = 0.0;
generatorPanel.add(new JLabel("Limit of tables to open:"), genGbc);
genGbc.gridx = 1;
genGbc.weightx = 1.0;
openLimitSpinner = new JSpinner(new SpinnerNumberModel(3, 0, 100, 1));
generatorPanel.add(openLimitSpinner, genGbc);
mainPanel.add(generatorPanel, gbc);
// Spacer // Spacer
gbc.gridy++; gbc.gridy++;
gbc.weighty = 1.0; gbc.weighty = 1.0;
@@ -153,21 +240,30 @@ public class DynFormConfigurable implements Configurable {
@Override @Override
public boolean isModified() { public boolean isModified() {
DynFormSettings settings = DynFormSettings.getInstance(project); DynFormSettings settings = DynFormSettings.getInstance(project);
VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
return settings.displayMode != getCurrentModeFromUI() || return settings.displayMode != getCurrentModeFromUI() ||
settings.showIcon != showIconCheck.isSelected() || settings.showIcon != showIconCheck.isSelected() ||
!settings.i18nMessageFile.equals(i18nMessageFileField.getText()) || !settings.i18nMessageFile.equals(relativizePath(i18nMessageFileField.getText(), projectDir)) ||
!settings.xsdFolderPath.equals(xsdFolderField.getText()) || !settings.xsdFolderPath.equals(relativizePath(xsdFolderField.getText(), projectDir)) ||
!settings.xsdPrefix.equals(xsdPrefixField.getText()); !settings.xsdPrefix.equals(xsdPrefixField.getText()) ||
settings.autoOpenGeneratedFiles != autoOpenCheck.isSelected() ||
settings.openFilesLimit != (int) openLimitSpinner.getValue();
} }
@Override @Override
public void apply() { public void apply() {
DynFormSettings settings = DynFormSettings.getInstance(project); DynFormSettings settings = DynFormSettings.getInstance(project);
VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
settings.displayMode = getCurrentModeFromUI(); settings.displayMode = getCurrentModeFromUI();
settings.showIcon = showIconCheck.isSelected(); settings.showIcon = showIconCheck.isSelected();
settings.i18nMessageFile = i18nMessageFileField.getText();
settings.xsdFolderPath = xsdFolderField.getText(); settings.i18nMessageFile = relativizePath(i18nMessageFileField.getText(), projectDir);
settings.xsdFolderPath = relativizePath(xsdFolderField.getText(), projectDir);
settings.xsdPrefix = xsdPrefixField.getText(); settings.xsdPrefix = xsdPrefixField.getText();
settings.autoOpenGeneratedFiles = autoOpenCheck.isSelected();
settings.openFilesLimit = (int) openLimitSpinner.getValue();
// Register XSDs to IntelliJ for this project // Register XSDs to IntelliJ for this project
DynFormXsdScanner.scanAndRegister(project, settings.xsdFolderPath, settings.xsdPrefix); DynFormXsdScanner.scanAndRegister(project, settings.xsdFolderPath, settings.xsdPrefix);
@@ -176,6 +272,45 @@ public class DynFormConfigurable implements Configurable {
com.intellij.codeInsight.daemon.DaemonCodeAnalyzer.getInstance(project).restart(); com.intellij.codeInsight.daemon.DaemonCodeAnalyzer.getInstance(project).restart();
} }
private String relativizePath(String path, VirtualFile projectDir) {
if (projectDir == null || path == null || path.isEmpty()) return path;
File file = new File(path);
if (!file.isAbsolute()) return path; // Already relative or placeholder
VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
if (vFile != null) {
String relativePath = VfsUtil.getRelativePath(vFile, projectDir);
if (relativePath != null) return relativePath;
}
return path;
}
private String resolvePath(String path, VirtualFile projectDir) {
if (projectDir == null) return path;
if (path == null || path.isEmpty()) return projectDir.getPath();
VirtualFile vFile;
File file = new File(path);
if (file.isAbsolute()) {
vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
} else {
vFile = projectDir.findFileByRelativePath(path);
}
if (vFile != null && VfsUtil.isAncestor(projectDir, vFile, false)) {
return vFile.getPath();
}
return projectDir.getPath();
}
private boolean isWithinProject(String path, VirtualFile projectDir) {
if (projectDir == null || path == null || path.isEmpty()) return true;
File file = new File(path);
String absolutePath = file.getAbsolutePath();
String projectPath = projectDir.getPath();
return absolutePath.startsWith(projectPath);
}
private DynFormSettings.DisplayMode getCurrentModeFromUI() { private DynFormSettings.DisplayMode getCurrentModeFromUI() {
if (foldingBtn.isSelected()) return DynFormSettings.DisplayMode.FOLDING; if (foldingBtn.isSelected()) return DynFormSettings.DisplayMode.FOLDING;
if (inlayBtn.isSelected()) return DynFormSettings.DisplayMode.INLAY_HINTS; if (inlayBtn.isSelected()) return DynFormSettings.DisplayMode.INLAY_HINTS;
@@ -185,6 +320,7 @@ public class DynFormConfigurable implements Configurable {
@Override @Override
public void reset() { public void reset() {
DynFormSettings settings = DynFormSettings.getInstance(project); DynFormSettings settings = DynFormSettings.getInstance(project);
switch (settings.displayMode) { switch (settings.displayMode) {
case FOLDING: foldingBtn.setSelected(true); break; case FOLDING: foldingBtn.setSelected(true); break;
case INLAY_HINTS: inlayBtn.setSelected(true); break; case INLAY_HINTS: inlayBtn.setSelected(true); break;
@@ -194,5 +330,7 @@ public class DynFormConfigurable implements Configurable {
i18nMessageFileField.setText(settings.i18nMessageFile); i18nMessageFileField.setText(settings.i18nMessageFile);
xsdFolderField.setText(settings.xsdFolderPath); xsdFolderField.setText(settings.xsdFolderPath);
xsdPrefixField.setText(settings.xsdPrefix); xsdPrefixField.setText(settings.xsdPrefix);
autoOpenCheck.setSelected(settings.autoOpenGeneratedFiles);
openLimitSpinner.setValue(settings.openFilesLimit);
} }
} }

View File

@@ -26,6 +26,12 @@ public class DynFormSettings implements PersistentStateComponent<DynFormSettings
public String xsdFolderPath = ""; public String xsdFolderPath = "";
public String xsdPrefix = "/dynf"; public String xsdPrefix = "/dynf";
// Action Models Generator Settings
public boolean autoOpenGeneratedFiles = true;
public int openFilesLimit = 3;
public String lastBeanPackagePath = "";
public String lastDatasetFolderPath = "";
public static DynFormSettings getInstance(Project project) { public static DynFormSettings getInstance(Project project) {
return project.getService(DynFormSettings.class); return project.getService(DynFormSettings.class);
} }

View File

@@ -4,6 +4,7 @@ import com.intellij.javaee.ExternalResourceManagerEx;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
@@ -16,6 +17,17 @@ public class DynFormXsdScanner {
if (project == null || folderPath == null || folderPath.isEmpty()) return; if (project == null || folderPath == null || folderPath.isEmpty()) return;
File dir = new File(folderPath); File dir = new File(folderPath);
if (!dir.exists() || !dir.isDirectory()) {
// Try relative path from project root
VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
if (projectDir != null) {
VirtualFile vDir = projectDir.findFileByRelativePath(folderPath);
if (vDir != null && vDir.isDirectory()) {
dir = new File(vDir.getPath());
}
}
}
if (!dir.exists() || !dir.isDirectory()) return; if (!dir.exists() || !dir.isDirectory()) return;
File[] xsdFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".xsd")); File[] xsdFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".xsd"));

View File

@@ -1,6 +1,7 @@
package com.sdk.dynform.tools.i18n; package com.sdk.dynform.tools.i18n;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
@@ -29,10 +30,16 @@ public class I18nUtils {
return result; return result;
} }
// Try to find the file using its absolute path // Try to find the file using its absolute path or relative path from project root
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(filePath); VirtualFile file = LocalFileSystem.getInstance().findFileByPath(filePath);
if (file == null) {
VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
if (projectDir != null) {
file = projectDir.findFileByRelativePath(filePath);
}
}
// If not found by absolute path, fallback to searching by filename in the project // If not found by path, fallback to searching by filename in the project
if (file == null) { if (file == null) {
String filename = new java.io.File(filePath).getName(); String filename = new java.io.File(filePath).getName();
Collection<VirtualFile> files = FilenameIndex.getVirtualFilesByName(filename, GlobalSearchScope.everythingScope(project)); Collection<VirtualFile> files = FilenameIndex.getVirtualFilesByName(filename, GlobalSearchScope.everythingScope(project));

View File

@@ -34,6 +34,13 @@
]]></description> ]]></description>
<change-notes><![CDATA[ <change-notes><![CDATA[
<h2>[3.2.4]</h2>
<ul>
<li><strong>Persistent Generation Directories:</strong> Generator now remembers the last used directory for Action Beans and Dataset XMLs independently per project.</li>
<li><strong>Auto-Open Generated Files:</strong> Added configuration to automatically open newly created files in the editor, with a customizable limit on the number of files.</li>
<li><strong>Strict Project-Relative Paths:</strong> I18n and XSD configuration now strictly enforces file selection within the project root and stores paths as relative for maximum portability.</li>
<li><strong>Smart File Browser:</strong> Improved file picker logic to automatically start at the current configured directory if it's within the project, falling back to the project root otherwise.</li>
</ul>
<h2>[3.2.3]</h2> <h2>[3.2.3]</h2>
<ul> <ul>
<li><strong>Advanced Data Referencing:</strong> Implemented comprehensive reference and completion support for <code>&lt;FOREIGN-DATASETS&gt;</code> and <code>&lt;MASTER-DATA&gt;</code> structures.</li> <li><strong>Advanced Data Referencing:</strong> Implemented comprehensive reference and completion support for <code>&lt;FOREIGN-DATASETS&gt;</code> and <code>&lt;MASTER-DATA&gt;</code> structures.</li>