diff --git a/.intellijPlatform/coroutines-javaagent-legacy.jar b/.intellijPlatform/coroutines-javaagent-legacy.jar index 4ec9da5..07e15c9 100644 Binary files a/.intellijPlatform/coroutines-javaagent-legacy.jar and b/.intellijPlatform/coroutines-javaagent-legacy.jar differ diff --git a/.intellijPlatform/self-update.lock b/.intellijPlatform/self-update.lock index 01ad1ac..28f6604 100644 --- a/.intellijPlatform/self-update.lock +++ b/.intellijPlatform/self-update.lock @@ -1 +1 @@ -2026-04-10 \ No newline at end of file +2026-04-18 \ No newline at end of file diff --git a/CheckAPI.java b/CheckAPI.java new file mode 100644 index 0000000..8109dd5 --- /dev/null +++ b/CheckAPI.java @@ -0,0 +1,6 @@ +import com.intellij.javaee.ExternalResourceManagerEx; +import com.intellij.javaee.ExternalResourceManager; +public class CheckAPI { + public static void main(String[] args) { + } +} diff --git a/DevResources/full-examples/bdgt04/view/frm/bdgt-0401020.frml b/DevResources/full-examples/bdgt04/view/frm/bdgt-0401020.frml index 9e0dc83..a7189c1 100644 --- a/DevResources/full-examples/bdgt04/view/frm/bdgt-0401020.frml +++ b/DevResources/full-examples/bdgt04/view/frm/bdgt-0401020.frml @@ -55,6 +55,10 @@ + + + +
@@ -120,7 +124,7 @@ - + diff --git a/FindAPI.java b/FindAPI.java new file mode 100644 index 0000000..e9f1a6b --- /dev/null +++ b/FindAPI.java @@ -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())); + } + } +} diff --git a/InspectXSD.java b/InspectXSD.java new file mode 100644 index 0000000..a1ac60a --- /dev/null +++ b/InspectXSD.java @@ -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()); + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 95deec7..b4f7fa8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,11 +4,10 @@ plugins { id("org.jetbrains.intellij.platform") version "2.7.0" } group = "com.sdk.dynform.tools" -version = "3.2.3" +version = "3.2.4" repositories { mavenCentral() - intellijPlatform { defaultRepositories() } @@ -39,12 +38,20 @@ intellijPlatform { } changeNotes = """ +

[3.2.4]

+
    +
  • Persistent Generation Directories: Generator now remembers the last used directory for Action Beans and Dataset XMLs independently per project.
  • +
  • Auto-Open Generated Files: Added configuration to automatically open newly created files in the editor, with a customizable limit on the number of files.
  • +
  • Strict Project-Relative Paths: I18n and XSD configuration now strictly enforces file selection within the project root and stores paths as relative for maximum portability.
  • +
  • Smart File Browser: 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.
  • +

[3.2.3]

  • Advanced Data Referencing: Implemented comprehensive reference and completion support for <FOREIGN-DATASETS> and <MASTER-DATA> structures.
  • Contextual Field Resolution: Enhanced field resolution logic to resolve fields from datasets specified by DATASET-ID and DATAID attributes within their respective tags.
  • Recursive Resource Scanning: Improved dataset and grid scanning to search across recursively included .frml files, ensuring consistent navigation and completion throughout the project.
+

[3.2.2]

  • UI/UX Improvement: Updated the I18n settings to allow selecting the message bundle XML file directly via a file browser, improving configuration usability.
  • diff --git a/buildSrc.kts b/buildSrc.kts new file mode 100644 index 0000000..89bffba --- /dev/null +++ b/buildSrc.kts @@ -0,0 +1 @@ +println("Checking methods") diff --git a/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanAction.java b/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanAction.java index 266ec63..4c5cb04 100644 --- a/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanAction.java +++ b/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanAction.java @@ -13,9 +13,11 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.sdk.dynform.helper.GUtils; +import com.sdk.dynform.tools.config.DynFormSettings; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -30,12 +32,23 @@ public class GenerateBeanAction extends AnAction { return; } + DynFormSettings settings = DynFormSettings.getInstance(project); FileChooserDescriptor descriptor = new FileChooserDescriptor(false,true,false,false,false,false); PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null); - VirtualFile baseDir = GUtils.findSourceRoot(project); + + VirtualFile initialDir = GUtils.findSourceRoot(project); + 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(baseDir, virtualFiles -> { - String packageName = GUtils.getSelectedPackage(project, virtualFiles.getFirst()); + pathChooser.choose(initialDir, virtualFiles -> { + VirtualFile selectedDir = virtualFiles.getFirst(); + settings.lastBeanPackagePath = selectedDir.getPath(); + + String packageName = GUtils.getSelectedPackage(project, selectedDir); ArrayList tables = new ArrayList<>(); for (PsiElement psiElement : psiElements) { diff --git a/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanActionV3.java b/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanActionV3.java index 535ddf6..fdc60d1 100644 --- a/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanActionV3.java +++ b/src/main/java/com/sdk/dynform/tools/actionbean/GenerateBeanActionV3.java @@ -13,9 +13,11 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.sdk.dynform.helper.GUtils; +import com.sdk.dynform.tools.config.DynFormSettings; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -30,12 +32,23 @@ public class GenerateBeanActionV3 extends AnAction { return; } + DynFormSettings settings = DynFormSettings.getInstance(project); FileChooserDescriptor descriptor = new FileChooserDescriptor(false,true,false,false,false,false); PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null); - VirtualFile baseDir = GUtils.findSourceRoot(project); + + VirtualFile initialDir = GUtils.findSourceRoot(project); + 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(baseDir, virtualFiles -> { - String packageName = GUtils.getSelectedPackage(project, virtualFiles.getFirst()); + pathChooser.choose(initialDir, virtualFiles -> { + VirtualFile selectedDir = virtualFiles.getFirst(); + settings.lastBeanPackagePath = selectedDir.getPath(); + + String packageName = GUtils.getSelectedPackage(project, selectedDir); ArrayList tables = new ArrayList<>(); for (PsiElement psiElement : psiElements) { diff --git a/src/main/java/com/sdk/dynform/tools/actionbean/GenerateDatasetAction.java b/src/main/java/com/sdk/dynform/tools/actionbean/GenerateDatasetAction.java index 57a05f2..9f46e41 100644 --- a/src/main/java/com/sdk/dynform/tools/actionbean/GenerateDatasetAction.java +++ b/src/main/java/com/sdk/dynform/tools/actionbean/GenerateDatasetAction.java @@ -13,9 +13,11 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.sdk.dynform.helper.GUtils; +import com.sdk.dynform.tools.config.DynFormSettings; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -30,15 +32,22 @@ public class GenerateDatasetAction extends AnAction { return; } + DynFormSettings settings = DynFormSettings.getInstance(project); FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false); descriptor.setTitle("Select Target Directory for Dataset XML"); PathChooserDialog pathChooser = FileChooserFactory.getInstance().createPathChooser(descriptor, project, null); - //VirtualFile baseDir = GUtils.findSourceRoot(project); - VirtualFile baseDir = project.getBaseDir(); + VirtualFile initialDir = 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(); + settings.lastDatasetFolderPath = targetDir.getPath(); ArrayList tables = new ArrayList<>(); for (PsiElement psiElement : psiElements) { diff --git a/src/main/java/com/sdk/dynform/tools/actionbean/GeneratorServices.java b/src/main/java/com/sdk/dynform/tools/actionbean/GeneratorServices.java index bcc98b3..f5f6793 100644 --- a/src/main/java/com/sdk/dynform/tools/actionbean/GeneratorServices.java +++ b/src/main/java/com/sdk/dynform/tools/actionbean/GeneratorServices.java @@ -5,11 +5,13 @@ import com.intellij.database.model.DasTableKey; import com.intellij.database.psi.DbTable; import com.intellij.database.util.DasUtil; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.JBIterable; import com.sdk.dynform.helper.GUtils; +import com.sdk.dynform.tools.config.DynFormSettings; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; @@ -39,7 +41,7 @@ public class GeneratorServices { this.version = version; } - private void genDataModel(Template template, Map model, VirtualFile targetDir, String classFile, ProgressIndicator indicator) { + private VirtualFile genDataModel(Template template, Map model, VirtualFile targetDir, String classFile, ProgressIndicator indicator) { try { VirtualFile outputFile = targetDir.findOrCreateChildData(this, classFile); ApplicationManager.getApplication().runWriteAction(() -> { @@ -51,11 +53,20 @@ public class GeneratorServices { indicator.setText2("Generated " + outputFile.getName()); GUtils.showInfo(project, "ActionModels Generator", "Generated " + outputFile.getName()); }); + return outputFile; } catch (Exception 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) { // Use AtomicInteger to safely count files within the lambda expression AtomicInteger fileCount = new AtomicInteger(0); @@ -81,30 +92,43 @@ public class GeneratorServices { Template tmpDTO = cfg.getTemplate("actionDTO.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 model = createModelForTable(table); String className = model.get("className").toString(); String classFile = className + ".java"; - genDataModel(tmpBean, model, beanDir, classFile, indicator); + VirtualFile fBean = genDataModel(tmpBean, model, beanDir, classFile, indicator); fileCount.getAndIncrement(); + VirtualFile fBeanExt = null; if (!Files.exists(Path.of(beanExtDir.toNioPath().toString(), classFile))) { - genDataModel(tmpBeanExt, model, beanExtDir, classFile, indicator); + fBeanExt = genDataModel(tmpBeanExt, model, beanExtDir, classFile, indicator); fileCount.getAndIncrement(); } // FIX: Use a separate variable for the DTO filename to avoid overwriting the wrong file. String dtoClassFile = "DTO_" + className + ".java"; - genDataModel(tmpDTO, model, DTODir, dtoClassFile, indicator); + VirtualFile fDTO = genDataModel(tmpDTO, model, DTODir, dtoClassFile, indicator); fileCount.getAndIncrement(); + VirtualFile fDTOExt = null; if (!Files.exists(Path.of(DTOExtDir.toNioPath().toString(), dtoClassFile))) { - genDataModel(tmpDTOExt, model, DTOExtDir, dtoClassFile, indicator); + fDTOExt = genDataModel(tmpDTOExt, model, DTOExtDir, dtoClassFile, indicator); fileCount.getAndIncrement(); } - }); + + if (shouldOpen) { + openInEditor(fBean); + openInEditor(fBeanExt); + openInEditor(fDTO); + openInEditor(fDTOExt); + } + } // 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()); @@ -125,14 +149,22 @@ public class GeneratorServices { 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 model = createModelForTable(table); String tableName = model.get("tableName").toString(); String fileName = GUtils.capitalize(tableName) + ".xml"; - genDataModel(tmpDataset, model, targetDir, fileName, indicator); + VirtualFile fDataset = genDataModel(tmpDataset, model, targetDir, fileName, indicator); fileCount.getAndIncrement(); - }); + + if (shouldOpen) { + openInEditor(fDataset); + } + } String message = String.format("Generated %d dataset XML files successfully.", fileCount.get()); GUtils.showInfo(project, "Dataset XML Generation Complete", message); diff --git a/src/main/java/com/sdk/dynform/tools/config/DynFormConfigurable.java b/src/main/java/com/sdk/dynform/tools/config/DynFormConfigurable.java index b1d6f7f..e2d563a 100644 --- a/src/main/java/com/sdk/dynform/tools/config/DynFormConfigurable.java +++ b/src/main/java/com/sdk/dynform/tools/config/DynFormConfigurable.java @@ -4,9 +4,12 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.ui.TextComponentAccessor; import com.intellij.openapi.ui.TextFieldWithBrowseButton; 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.sdk.dynform.tools.dynform.DynFormXsdScanner; import org.jetbrains.annotations.NotNull; @@ -14,6 +17,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.io.File; public class DynFormConfigurable implements Configurable { @@ -25,6 +29,8 @@ public class DynFormConfigurable implements Configurable { private TextFieldWithBrowseButton i18nMessageFileField; private TextFieldWithBrowseButton xsdFolderField; private JTextField xsdPrefixField; + private JCheckBox autoOpenCheck; + private JSpinner openLimitSpinner; public DynFormConfigurable(@NotNull Project project) { this.project = project; @@ -87,15 +93,38 @@ public class DynFormConfigurable implements Configurable { i18nGbc.weightx = 1.0; i18nMessageFileField = new TextFieldWithBrowseButton(); + VirtualFile projectDir = ProjectUtil.guessProjectDir(project); FileChooserDescriptor xmlDescriptor = new FileChooserDescriptor(true, false, false, false, false, false) { @Override 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); } - }; + + @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("File must be within the project directory."); + } + } + } + }.withRoots(projectDir); - i18nMessageFileField.addBrowseFolderListener("Select Message XML File", "Select the main message bundle XML file", - project, xmlDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); + 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); @@ -128,8 +157,38 @@ public class DynFormConfigurable implements Configurable { xsdGbc.gridx = 1; xsdGbc.weightx = 1.0; 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); xsdGbc.gridx = 0; @@ -142,6 +201,34 @@ public class DynFormConfigurable implements Configurable { 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 gbc.gridy++; gbc.weighty = 1.0; @@ -153,21 +240,30 @@ public class DynFormConfigurable implements Configurable { @Override public boolean isModified() { DynFormSettings settings = DynFormSettings.getInstance(project); + VirtualFile projectDir = ProjectUtil.guessProjectDir(project); return settings.displayMode != getCurrentModeFromUI() || settings.showIcon != showIconCheck.isSelected() || - !settings.i18nMessageFile.equals(i18nMessageFileField.getText()) || - !settings.xsdFolderPath.equals(xsdFolderField.getText()) || - !settings.xsdPrefix.equals(xsdPrefixField.getText()); + !settings.i18nMessageFile.equals(relativizePath(i18nMessageFileField.getText(), projectDir)) || + !settings.xsdFolderPath.equals(relativizePath(xsdFolderField.getText(), projectDir)) || + !settings.xsdPrefix.equals(xsdPrefixField.getText()) || + settings.autoOpenGeneratedFiles != autoOpenCheck.isSelected() || + settings.openFilesLimit != (int) openLimitSpinner.getValue(); } @Override public void apply() { DynFormSettings settings = DynFormSettings.getInstance(project); + VirtualFile projectDir = ProjectUtil.guessProjectDir(project); + settings.displayMode = getCurrentModeFromUI(); 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.autoOpenGeneratedFiles = autoOpenCheck.isSelected(); + settings.openFilesLimit = (int) openLimitSpinner.getValue(); // Register XSDs to IntelliJ for this project DynFormXsdScanner.scanAndRegister(project, settings.xsdFolderPath, settings.xsdPrefix); @@ -176,6 +272,45 @@ public class DynFormConfigurable implements Configurable { 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() { if (foldingBtn.isSelected()) return DynFormSettings.DisplayMode.FOLDING; if (inlayBtn.isSelected()) return DynFormSettings.DisplayMode.INLAY_HINTS; @@ -185,6 +320,7 @@ public class DynFormConfigurable implements Configurable { @Override public void reset() { DynFormSettings settings = DynFormSettings.getInstance(project); + switch (settings.displayMode) { case FOLDING: foldingBtn.setSelected(true); break; case INLAY_HINTS: inlayBtn.setSelected(true); break; @@ -194,5 +330,7 @@ public class DynFormConfigurable implements Configurable { i18nMessageFileField.setText(settings.i18nMessageFile); xsdFolderField.setText(settings.xsdFolderPath); xsdPrefixField.setText(settings.xsdPrefix); + autoOpenCheck.setSelected(settings.autoOpenGeneratedFiles); + openLimitSpinner.setValue(settings.openFilesLimit); } } diff --git a/src/main/java/com/sdk/dynform/tools/config/DynFormSettings.java b/src/main/java/com/sdk/dynform/tools/config/DynFormSettings.java index 274dd74..8b06557 100644 --- a/src/main/java/com/sdk/dynform/tools/config/DynFormSettings.java +++ b/src/main/java/com/sdk/dynform/tools/config/DynFormSettings.java @@ -26,6 +26,12 @@ public class DynFormSettings implements PersistentStateComponent name.toLowerCase().endsWith(".xsd")); diff --git a/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java b/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java index aee8817..f92c02d 100644 --- a/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java +++ b/src/main/java/com/sdk/dynform/tools/i18n/I18nUtils.java @@ -1,6 +1,7 @@ package com.sdk.dynform.tools.i18n; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -29,10 +30,16 @@ public class I18nUtils { 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); + 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) { String filename = new java.io.File(filePath).getName(); Collection files = FilenameIndex.getVirtualFilesByName(filename, GlobalSearchScope.everythingScope(project)); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 86096ae..bb7a94c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -34,6 +34,13 @@ ]]> [3.2.4] +
      +
    • Persistent Generation Directories: Generator now remembers the last used directory for Action Beans and Dataset XMLs independently per project.
    • +
    • Auto-Open Generated Files: Added configuration to automatically open newly created files in the editor, with a customizable limit on the number of files.
    • +
    • Strict Project-Relative Paths: I18n and XSD configuration now strictly enforces file selection within the project root and stores paths as relative for maximum portability.
    • +
    • Smart File Browser: 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.
    • +

    [3.2.3]

    • Advanced Data Referencing: Implemented comprehensive reference and completion support for <FOREIGN-DATASETS> and <MASTER-DATA> structures.