feat: implement project-level XSD registration and refactor base package

- Schema Validation (XSD):
  - Added new configuration UI for DynamicForm Tools to manage XSD schemas.
  - Implemented DynFormXsdScanner to automatically map local .xsd files to the ExternalResourceManager using a custom URI prefix.
  - Ensured XSD registration is properly scoped at the Project-level (rather than Application-level) using write-actions to comply with IDE threading policies.

- Architecture & Refactoring:
  - Renamed base package from com.sdk.dynformTools to com.sdk.dynform across the entire project for structural consistency.
  - Converted Application-level settings/configurables to Project-level services.
  - Extracted and restored complex I18n folding and inlay hint logic that was previously overwritten, adapting it to use the new Project-level settings.

- UI & Metadata:
  - Bumped plugin version to 3.2.0.
  - Rebranded settings page to "DynamicForm Tools".
  - Organized settings into logical "Internationalization" and "Schema Validation" groups.
This commit is contained in:
2026-04-10 22:51:47 +07:00
parent c76ca9a293
commit 660c7a058c
22 changed files with 2094 additions and 179 deletions

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.generators.actionmodels;
package com.sdk.dynform.tools.actionbean;
import com.intellij.database.model.DasColumn;
import com.intellij.database.model.ObjectKind;

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.generators.actionmodels;
package com.sdk.dynform.tools.actionbean;
import com.intellij.database.psi.DbTable;
import com.intellij.openapi.actionSystem.AnAction;

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.generators.actionmodels;
package com.sdk.dynform.tools.actionbean;
import com.intellij.database.psi.DbTable;
import com.intellij.openapi.actionSystem.AnAction;

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.generators.actionmodels;
package com.sdk.dynform.tools.actionbean;
import com.intellij.database.psi.DbTable;
import com.intellij.openapi.actionSystem.AnAction;

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.generators.actionmodels;
package com.sdk.dynform.tools.actionbean;
import com.intellij.database.model.DasColumn;
import com.intellij.database.model.DasTableKey;

View File

@@ -0,0 +1,161 @@
package com.sdk.dynform.tools.config;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.NlsContexts;
import com.sdk.dynform.tools.dynform.DynFormXsdScanner;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
public class DynFormConfigurable implements Configurable {
private final Project project;
private JRadioButton foldingBtn;
private JRadioButton inlayBtn;
private JRadioButton disabledBtn;
private JCheckBox showIconCheck;
private TextFieldWithBrowseButton xsdFolderField;
private JTextField xsdPrefixField;
public DynFormConfigurable(@NotNull Project project) {
this.project = project;
}
@Override
public @NlsContexts.ConfigurableName String getDisplayName() {
return "DynamicForm Tools";
}
@Override
public @Nullable JComponent createComponent() {
JPanel mainPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(5, 5, 5, 5);
// --- I18n Settings Group ---
JPanel i18nPanel = new JPanel();
i18nPanel.setLayout(new BoxLayout(i18nPanel, BoxLayout.Y_AXIS));
i18nPanel.setBorder(BorderFactory.createTitledBorder("Internationalization (i18n)"));
i18nPanel.add(new JLabel("Message Display Mode:"));
foldingBtn = new JRadioButton("Folding (Hide key, show translation)");
inlayBtn = new JRadioButton("Inlay Hints (Show translation next to key)");
disabledBtn = new JRadioButton("Disabled");
ButtonGroup group = new ButtonGroup();
group.add(foldingBtn);
group.add(inlayBtn);
group.add(disabledBtn);
i18nPanel.add(foldingBtn);
i18nPanel.add(inlayBtn);
i18nPanel.add(disabledBtn);
i18nPanel.add(Box.createVerticalStrut(10));
showIconCheck = new JCheckBox("Show icon in code completion");
i18nPanel.add(showIconCheck);
mainPanel.add(i18nPanel, gbc);
// --- XSD Settings Group ---
gbc.gridy++;
JPanel xsdPanel = new JPanel(new GridBagLayout());
xsdPanel.setBorder(BorderFactory.createTitledBorder("Schema Validation (XSD)"));
GridBagConstraints xsdGbc = new GridBagConstraints();
xsdGbc.fill = GridBagConstraints.HORIZONTAL;
xsdGbc.insets = new Insets(2, 2, 2, 2);
xsdGbc.gridx = 0;
xsdGbc.gridy = 0;
xsdGbc.weightx = 0.0;
xsdPanel.add(new JLabel("XSD Namespace Prefix:"), xsdGbc);
xsdGbc.gridx = 1;
xsdGbc.weightx = 1.0;
xsdPrefixField = new JTextField();
xsdPanel.add(xsdPrefixField, xsdGbc);
xsdGbc.gridx = 0;
xsdGbc.gridy = 1;
xsdGbc.weightx = 0.0;
xsdPanel.add(new JLabel("XSD Schemas Folder:"), xsdGbc);
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);
xsdPanel.add(xsdFolderField, xsdGbc);
xsdGbc.gridx = 0;
xsdGbc.gridy = 2;
xsdGbc.gridwidth = 2;
xsdGbc.weightx = 1.0;
JLabel hintLabel = new JLabel("Plugin will map: <prefix>/<filename> -> <folder>/<filename>");
hintLabel.setFont(hintLabel.getFont().deriveFont(Font.ITALIC, 11f));
xsdPanel.add(hintLabel, xsdGbc);
mainPanel.add(xsdPanel, gbc);
// Spacer
gbc.gridy++;
gbc.weighty = 1.0;
mainPanel.add(new JPanel(), gbc);
return mainPanel;
}
@Override
public boolean isModified() {
DynFormSettings settings = DynFormSettings.getInstance(project);
return settings.displayMode != getCurrentModeFromUI() ||
settings.showIcon != showIconCheck.isSelected() ||
!settings.xsdFolderPath.equals(xsdFolderField.getText()) ||
!settings.xsdPrefix.equals(xsdPrefixField.getText());
}
@Override
public void apply() {
DynFormSettings settings = DynFormSettings.getInstance(project);
settings.displayMode = getCurrentModeFromUI();
settings.showIcon = showIconCheck.isSelected();
settings.xsdFolderPath = xsdFolderField.getText();
settings.xsdPrefix = xsdPrefixField.getText();
// Register XSDs to IntelliJ for this project
DynFormXsdScanner.scanAndRegister(project, settings.xsdFolderPath, settings.xsdPrefix);
// Refresh editor
com.intellij.codeInsight.daemon.DaemonCodeAnalyzer.getInstance(project).restart();
}
private DynFormSettings.DisplayMode getCurrentModeFromUI() {
if (foldingBtn.isSelected()) return DynFormSettings.DisplayMode.FOLDING;
if (inlayBtn.isSelected()) return DynFormSettings.DisplayMode.INLAY_HINTS;
return DynFormSettings.DisplayMode.DISABLED;
}
@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;
case DISABLED: disabledBtn.setSelected(true); break;
}
showIconCheck.setSelected(settings.showIcon);
xsdFolderField.setText(settings.xsdFolderPath);
xsdPrefixField.setText(settings.xsdPrefix);
}
}

View File

@@ -1,18 +1,18 @@
package com.sdk.dynform.tools.i18n;
package com.sdk.dynform.tools.config;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@State(
name = "com.sdk.dynform.tools.i18n.I18nSettings",
storages = @Storage("DynamicFormTools.xml")
name = "com.sdk.dynform.tools.config.DynFormSettings",
storages = @Storage("DynamicFormToolsProject.xml")
)
public class I18nSettings implements PersistentStateComponent<I18nSettings> {
public class DynFormSettings implements PersistentStateComponent<DynFormSettings> {
public enum DisplayMode {
FOLDING,
@@ -22,19 +22,21 @@ public class I18nSettings implements PersistentStateComponent<I18nSettings> {
public DisplayMode displayMode = DisplayMode.FOLDING;
public boolean showIcon = true;
public String xsdFolderPath = "";
public String xsdPrefix = "/dynf";
public static I18nSettings getInstance() {
return ApplicationManager.getApplication().getService(I18nSettings.class);
public static DynFormSettings getInstance(Project project) {
return project.getService(DynFormSettings.class);
}
@Nullable
@Override
public I18nSettings getState() {
public DynFormSettings getState() {
return this;
}
@Override
public void loadState(@NotNull I18nSettings state) {
public void loadState(@NotNull DynFormSettings state) {
XmlSerializerUtil.copyBean(state, this);
}
}

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.helper;
package com.sdk.dynform.tools.dynform;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
@@ -76,12 +76,12 @@ public class DynFormCompletionContributor extends CompletionContributor {
while (formContainer != null && !"FORM_ENTRY".equals(formContainer.getName()) && !"FORM_BROWSE".equals(formContainer.getName())) {
formContainer = formContainer.getParentTag();
}
if (formContainer == null) {
XmlFile file = (XmlFile) parameters.getOriginalFile();
formContainer = file.getRootTag();
}
if (formContainer != null) {
addFieldsInTagRecursive(formContainer, resultSet);
}
@@ -136,7 +136,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
ajaxOptionTag = ajaxOptionTag.getParentTag();
}
if (ajaxOptionTag == null) return;
String datasetId = ajaxOptionTag.getAttributeValue("DATASET");
if (datasetId == null) return;
@@ -146,7 +146,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
if (rootTag != null) {
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag;
for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) {
if (datasetId.equals(datasetTag.getAttributeValue("ID"))) {
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
@@ -199,7 +199,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
if (!(file instanceof XmlFile xmlFile)) return;
XmlTag rootTag = xmlFile.getRootTag();
if (rootTag == null) return;
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag;

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.helper;
package com.sdk.dynform.tools.dynform;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
@@ -26,7 +26,7 @@ public class DynFormPathUtils {
public static VirtualFile findModuleDir(@NotNull PsiFile file) {
VirtualFile vFile = file.getVirtualFile();
if (vFile == null) return null;
VirtualFile current = vFile.getParent();
while (current != null) {
if (current.getParent() != null && "module".equals(current.getParent().getName())) {
@@ -53,7 +53,7 @@ public class DynFormPathUtils {
public static PsiFile findIncludedFile(@NotNull PsiFile contextFile, @NotNull String path) {
// จัดการกรณี typo ( แทน /)
path = path.replace("", "/");
VirtualFile moduleBase = getModuleBaseDir(contextFile.getProject());
if (moduleBase == null) return null;

View File

@@ -1,4 +1,4 @@
package com.sdk.dynform.tools.helper;
package com.sdk.dynform.tools.dynform;
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns;
@@ -60,25 +60,25 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
XmlAttributeValue attrValue = (XmlAttributeValue) element;
String value = attrValue.getValue();
if (value == null || value.isEmpty()) return PsiReference.EMPTY_ARRAY;
XmlAttribute attr = (XmlAttribute) attrValue.getParent();
XmlTag tag = (XmlTag) attr.getParent();
String attrName = attr.getName();
if ("TARGET".equals(attrName)) {
return new PsiReference[]{new DynFormLocalFieldReference(attrValue, new TextRange(1, value.length() + 1), value)};
}
// เคส 1: อยู่ใน LAYOUT หรือ TITLES -> ลิงก์ไปหา FIELDS (นิยาม)
if (hasAncestorWithName(tag, "LAYOUT") || hasAncestorWithName(tag, "TITLES")) {
return new PsiReference[]{new DynFormFieldDefinitionReference(attrValue, new TextRange(1, value.length() + 1), value)};
}
// เคส 2: อยู่ใน FIELDS (นิยาม) -> ลิงก์กลับไปหา LAYOUT หรือ TITLES (การใช้งาน)
if (hasAncestorWithName(tag, "FIELDS")) {
return new PsiReference[]{new DynFormFieldUsageReference(attrValue, new TextRange(1, value.length() + 1), value)};
}
return PsiReference.EMPTY_ARRAY;
}
});
@@ -180,7 +180,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormModuleReference extends PsiReferenceBase<PsiElement> {
private final String moduleName;
public DynFormModuleReference(@NotNull PsiElement element, TextRange textRange, String moduleName) {
super(element, textRange);
super(element, textRange, true);
this.moduleName = moduleName;
}
@Nullable @Override public PsiElement resolve() {
@@ -192,7 +192,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private final String moduleName;
private final String frmlName;
public DynFormFrmlReference(@NotNull PsiElement element, TextRange textRange, String moduleName, String frmlName) {
super(element, textRange);
super(element, textRange, true);
this.moduleName = moduleName;
this.frmlName = frmlName;
}
@@ -204,7 +204,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormLocalFieldReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName;
public DynFormLocalFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range);
super(element, range, true);
this.fieldName = fieldName;
}
@Nullable @Override public PsiElement resolve() {
@@ -227,31 +227,31 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormFieldDefinitionReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName;
public DynFormFieldDefinitionReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range);
super(element, range, true);
this.fieldName = fieldName;
}
@Nullable @Override public PsiElement resolve() {
XmlTag tag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
if (tag == null) return null;
// หา container (FORM_ENTRY, FORM_BROWSE, GRID-LIST หรือ FILTERS)
XmlTag container = tag;
while (container != null &&
!"LAYOUT".equals(container.getName()) &&
!"TITLES".equals(container.getName()) &&
!"FORM_ENTRY".equals(container.getName()) &&
!"FORM_BROWSE".equals(container.getName()) &&
while (container != null &&
!"LAYOUT".equals(container.getName()) &&
!"TITLES".equals(container.getName()) &&
!"FORM_ENTRY".equals(container.getName()) &&
!"FORM_BROWSE".equals(container.getName()) &&
!"GRID-LIST".equals(container.getName()) &&
!"FILTERS".equals(container.getName())) {
container = container.getParentTag();
}
if (container != null && ("LAYOUT".equals(container.getName()) || "TITLES".equals(container.getName()))) {
container = container.getParentTag();
}
if (container == null) return null;
XmlTag fieldsTag = container.findFirstSubTag("FIELDS");
if (fieldsTag == null) return null;
@@ -265,30 +265,30 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormFieldUsageReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName;
public DynFormFieldUsageReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range);
super(element, range, true);
this.fieldName = fieldName;
}
@Nullable @Override public PsiElement resolve() {
XmlTag tag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
if (tag == null) return null;
// หา FIELDS container
XmlTag fieldsTag = tag;
while (fieldsTag != null && !"FIELDS".equals(fieldsTag.getName())) {
fieldsTag = fieldsTag.getParentTag();
}
if (fieldsTag == null) return null;
XmlTag containerTag = fieldsTag.getParentTag();
if (containerTag == null) return null;
// หาใน LAYOUT ก่อน
XmlTag layoutTag = containerTag.findFirstSubTag("LAYOUT");
if (layoutTag != null) {
PsiElement found = findFieldInTag(layoutTag, fieldName);
if (found != null) return found;
}
// ถ้าไม่เจอใน LAYOUT ให้หาใน TITLES
XmlTag titlesTag = containerTag.findFirstSubTag("TITLES");
if (titlesTag != null) {
@@ -320,7 +320,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormDatasetReference extends PsiReferenceBase<XmlAttributeValue> {
private final String datasetId;
public DynFormDatasetReference(@NotNull XmlAttributeValue element, TextRange range, String datasetId) {
super(element, range);
super(element, range, true);
this.datasetId = datasetId;
}
@Nullable @Override public PsiElement resolve() {
@@ -339,7 +339,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
if (!(file instanceof XmlFile xmlFile)) return null;
XmlTag rootTag = xmlFile.getRootTag();
if (rootTag == null) return null;
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag;
@@ -356,7 +356,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormGridReference extends PsiReferenceBase<XmlAttributeValue> {
private final String gridId;
public DynFormGridReference(@NotNull XmlAttributeValue element, TextRange range, String gridId) {
super(element, range);
super(element, range, true);
this.gridId = gridId;
}
@Nullable @Override public PsiElement resolve() {
@@ -398,7 +398,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormAjaxSrcFieldReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName;
public DynFormAjaxSrcFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range);
super(element, range, true);
this.fieldName = fieldName;
}
@Nullable @Override public PsiElement resolve() {
@@ -407,16 +407,16 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
ajaxOptionTag = ajaxOptionTag.getParentTag();
}
if (ajaxOptionTag == null) return null;
String datasetId = ajaxOptionTag.getAttributeValue("DATASET");
if (datasetId == null) return null;
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(myElement.getContainingFile());
if (!(ajaxFile instanceof XmlFile xmlFile)) return null;
XmlTag rootTag = xmlFile.getRootTag();
if (rootTag == null) return null;
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag;
@@ -436,7 +436,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormFileIncludeReference extends PsiReferenceBase<XmlAttributeValue> {
private final String path;
public DynFormFileIncludeReference(@NotNull XmlAttributeValue element, TextRange range, String path) {
super(element, range);
super(element, range, true);
this.path = path;
}
@Nullable @Override public PsiElement resolve() {

View File

@@ -0,0 +1,44 @@
package com.sdk.dynform.tools.dynform;
import com.intellij.javaee.ExternalResourceManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import java.io.File;
public class DynFormXsdScanner {
private static final Logger LOG = Logger.getInstance(DynFormXsdScanner.class);
public static void scanAndRegister(Project project, String folderPath, String prefix) {
if (project == null || folderPath == null || folderPath.isEmpty()) return;
File dir = new File(folderPath);
if (!dir.exists() || !dir.isDirectory()) return;
File[] xsdFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".xsd"));
if (xsdFiles == null) return;
ExternalResourceManagerEx manager = ExternalResourceManagerEx.getInstanceEx();
// Ensure prefix ends with a slash if it's not empty
String tempPrefix = prefix != null ? prefix.trim() : "";
if (!tempPrefix.isEmpty() && !tempPrefix.endsWith("/")) {
tempPrefix += "/";
}
final String finalPrefix = tempPrefix;
ApplicationManager.getApplication().runWriteAction(() -> {
for (File file : xsdFiles) {
VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
if (vFile == null) continue;
String uri = finalPrefix + file.getName();
manager.addResource(uri, vFile.getPath(), project);
LOG.info("Registered DynForm XSD for project [" + project.getName() + "]: " + uri + " -> " + vFile.getPath());
}
});
}
}

View File

@@ -1,6 +1,5 @@
package com.sdk.dynform.tools.helper;
package com.sdk.dynform.tools.dynform;
import com.intellij.ide.highlighter.XmlLikeFileType;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType;
import org.jetbrains.annotations.NotNull;

View File

@@ -11,7 +11,6 @@ import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlToken;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
@@ -31,7 +30,7 @@ public class I18nCompletionContributor extends CompletionContributor {
@NotNull CompletionResultSet resultSet) {
PsiElement position = parameters.getPosition();
PsiLiteralExpression literal = PsiTreeUtil.getParentOfType(position, PsiLiteralExpression.class);
// ในบางภาษาอาจจะไม่ใช่ PsiLiteralExpression โดยตรง
if (literal != null && isMGetArgument(literal)) {
String value = literal.getValue() instanceof String ? (String) literal.getValue() : "";
@@ -44,7 +43,7 @@ public class I18nCompletionContributor extends CompletionContributor {
String textBefore = parameters.getEditor().getDocument().getText(
new com.intellij.openapi.util.TextRange(Math.max(0, parameters.getOffset() - 100), parameters.getOffset())
);
java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\$M\\.get\\([\"']([^\"']*)$").matcher(textBefore);
if (m.find()) {
handleMultiKeyCompletion(parameters, resultSet, m.group(1));
@@ -93,9 +92,9 @@ public class I18nCompletionContributor extends CompletionContributor {
String text = position.getText();
int offsetInElement = parameters.getOffset() - position.getTextRange().getStartOffset();
if (offsetInElement < 0 || offsetInElement > text.length()) return;
String before = text.substring(0, offsetInElement);
// Find the start of the current @M{ block
int openBracket = before.lastIndexOf("@M{");
if (openBracket != -1) {
@@ -115,9 +114,9 @@ public class I18nCompletionContributor extends CompletionContributor {
int lastPipe = content.lastIndexOf("||");
int lastSpace = content.lastIndexOf(' ');
int lastHash = content.lastIndexOf('#');
int lastSeparator = Math.max(lastPlus, Math.max(lastPipe != -1 ? lastPipe + 1 : -1, Math.max(lastSpace, lastHash)));
String currentPrefix;
if (lastSeparator != -1) {
// Handle || which is 2 chars
@@ -129,13 +128,13 @@ public class I18nCompletionContributor extends CompletionContributor {
} else {
currentPrefix = content;
}
addMessageKeys(parameters, resultSet.withPrefixMatcher(currentPrefix));
}
private void addMessageKeys(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
Set<String> keys = I18nUtils.findAllMessageKeys(parameters.getEditor().getProject());
for (String key : keys) {
String translation = I18nUtils.findMessageValue(parameters.getEditor().getProject(), key);
resultSet.addElement(LookupElementBuilder.create(key)

View File

@@ -1,86 +0,0 @@
package com.sdk.dynform.tools.i18n;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.util.NlsContexts;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
public class I18nConfigurable implements Configurable {
private JRadioButton foldingBtn;
private JRadioButton inlayBtn;
private JRadioButton disabledBtn;
private JCheckBox showIconCheck;
@Override
public @NlsContexts.ConfigurableName String getDisplayName() {
return "DynForm I18n Tools";
}
@Override
public @Nullable JComponent createComponent() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(new JLabel("Message Display Mode:"));
foldingBtn = new JRadioButton("Folding (Hide key, show translation)");
inlayBtn = new JRadioButton("Inlay Hints (Show translation next to key)");
disabledBtn = new JRadioButton("Disabled");
ButtonGroup group = new ButtonGroup();
group.add(foldingBtn);
group.add(inlayBtn);
group.add(disabledBtn);
panel.add(foldingBtn);
panel.add(inlayBtn);
panel.add(disabledBtn);
panel.add(Box.createVerticalStrut(10));
showIconCheck = new JCheckBox("Show icon in code completion");
panel.add(showIconCheck);
panel.add(Box.createVerticalGlue());
return panel;
}
@Override
public boolean isModified() {
I18nSettings settings = I18nSettings.getInstance();
I18nSettings.DisplayMode currentMode = getCurrentModeFromUI();
return settings.displayMode != currentMode || settings.showIcon != showIconCheck.isSelected();
}
@Override
public void apply() {
I18nSettings settings = I18nSettings.getInstance();
settings.displayMode = getCurrentModeFromUI();
settings.showIcon = showIconCheck.isSelected();
// Refresh all editors to apply changes
com.intellij.codeInsight.daemon.DaemonCodeAnalyzer.getInstance(
com.intellij.openapi.project.ProjectManager.getInstance().getOpenProjects()[0]
).restart();
}
private I18nSettings.DisplayMode getCurrentModeFromUI() {
if (foldingBtn.isSelected()) return I18nSettings.DisplayMode.FOLDING;
if (inlayBtn.isSelected()) return I18nSettings.DisplayMode.INLAY_HINTS;
return I18nSettings.DisplayMode.DISABLED;
}
@Override
public void reset() {
I18nSettings settings = I18nSettings.getInstance();
switch (settings.displayMode) {
case FOLDING: foldingBtn.setSelected(true); break;
case INLAY_HINTS: inlayBtn.setSelected(true); break;
case DISABLED: disabledBtn.setSelected(true); break;
}
showIconCheck.setSelected(settings.showIcon);
}
}

View File

@@ -10,6 +10,7 @@ import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.sdk.dynform.tools.config.DynFormSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -22,7 +23,7 @@ public class I18nFoldingBuilder extends FoldingBuilderEx {
@NotNull
@Override
public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) {
if (I18nSettings.getInstance().displayMode != I18nSettings.DisplayMode.FOLDING) {
if (DynFormSettings.getInstance(root.getProject()).displayMode != DynFormSettings.DisplayMode.FOLDING) {
return new FoldingDescriptor[0];
}

View File

@@ -3,10 +3,12 @@ package com.sdk.dynform.tools.i18n;
import com.intellij.codeInsight.hints.*;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlToken;
import com.sdk.dynform.tools.config.DynFormSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -18,7 +20,8 @@ public class I18nInlayHintsProvider implements InlayHintsProvider<NoSettings> {
@Nullable
@Override
public InlayHintsCollector getCollectorFor(@NotNull PsiFile file, @NotNull Editor editor, @NotNull NoSettings settings, @NotNull InlayHintsSink sink) {
if (I18nSettings.getInstance().displayMode != I18nSettings.DisplayMode.INLAY_HINTS) {
Project project = file.getProject();
if (DynFormSettings.getInstance(project).displayMode != DynFormSettings.DisplayMode.INLAY_HINTS) {
return null;
}

View File

@@ -2,11 +2,11 @@
<idea-plugin>
<!-- Unique id for this plugin. Must stay constant for the life of the plugin. -->
<id>com.sdk.dynform.tools</id>
<name>Dynamic Form Helper</name>
<name>DynamicForm Tools</name>
<vendor>Sakda Sakprapakorn</vendor>
<description><![CDATA[
<h3>Enhance Development with Dynamic Form Tools</h3>
<h3>Enhance Development with DynamicForm Tools</h3>
<p>This plugin is a comprehensive suite of tools designed to streamline the development of Java-based web applications (like <code>vrms-system</code> and <code>teddy-taxi-web</code>) that utilize the DynForm framework.</p>
<h4>Core Modules:</h4>
@@ -29,11 +29,17 @@
<h4>Usage:</h4>
<ul>
<li><b>Generation:</b> Right-click a table in the <b>Database tool window</b> -> <b>"Generate Action Models"</b>.</li>
<li><b>I18n:</b> Type <code>@M{</code> in XML or use supported I18n methods in Java/JS to see suggestions. Configure message file paths in <b>Settings -> DynForm I18n Tools</b>.</li>
<li><b>I18n:</b> Type <code>@M{</code> in XML or use supported I18n methods in Java/JS to see suggestions. Configure message file paths in <b>Settings -> DynamicForm Tools</b>.</li>
</ul>
]]></description>
<change-notes><![CDATA[
<h2>[3.2.0]</h2>
<ul>
<li><strong>Schema Validation:</strong> Introduced project-level automatic XSD registration. Configure a target folder and namespace prefix to seamlessly map <code>.xsd</code> files to the <code>ExternalResourceManager</code>.</li>
<li><strong>Architecture:</strong> Finalized rebranding by standardizing internal packages to <code>com.sdk.dynform.*</code> for better consistency.</li>
<li><strong>UI Improvement:</strong> Streamlined the plugin settings UI into distinct Internationalization and Schema Validation groups.</li>
</ul>
<h2>[3.1.0]</h2>
<ul>
<li><strong>Advanced Navigation:</strong> Implemented bidirectional field referencing between <code>&lt;FIELDS&gt;</code>, <code>&lt;LAYOUT&gt;</code>, and <code>&lt;TITLES&gt;</code> tags in <code>.frml</code> files.</li>
@@ -50,7 +56,7 @@
</ul>
<h2>[3.0.1]</h2>
<ul>
<li><strong>Rebranding:</strong> Renamed plugin to "Dynamic Form Helper" (DynamicFormTools) to better reflect its expanding capabilities beyond code generation.</li>
<li><strong>Rebranding:</strong> Renamed plugin to "DynamicForm Tools" to better reflect its expanding capabilities beyond code generation.</li>
<li><strong>I18n Support:</strong> Added comprehensive tools for internationalization:
<ul>
<li>Inlay hints and code folding for i18n keys in Java, XML, and JavaScript.</li>
@@ -96,20 +102,20 @@
<depends>JavaScript</depends>
<actions>
<group id="com.sdk.dynform.tools.generators.actionmodels.GeneratorGroup" popup="true" text="Generate Action Models">
<group id="com.sdk.dynform.tools.actionbean.actionmodels.GeneratorGroup" popup="true" text="Generate Action Models">
<add-to-group group-id="DatabaseViewPopupMenu" anchor="first"/>
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction"
class="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction"
<action id="com.sdk.dynform.tools.actionbean.GenerateBeanAction"
class="com.sdk.dynform.tools.actionbean.GenerateBeanAction"
text="Generate Action Models V2"
description="Generates ActionBean classes from a database schema.">
</action>
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction.v3"
class="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanActionV3"
<action id="com.sdk.dynform.tools.actionbean.GenerateBeanAction.v3"
class="com.sdk.dynform.tools.actionbean.GenerateBeanActionV3"
text="Generate Action Models V3"
description="Generates ActionBean classes from a database schema V3 (sdk.db.xxx).">
</action>
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateDatasetAction"
class="com.sdk.dynform.tools.generators.actionmodels.GenerateDatasetAction"
<action id="com.sdk.dynform.tools.actionbean.GenerateDatasetAction"
class="com.sdk.dynform.tools.actionbean.GenerateDatasetAction"
text="Generate Dataset XML"
description="Generates Dataset XML definition from a database table.">
</action>
@@ -117,12 +123,13 @@
</actions>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.sdk.dynform.tools.i18n.I18nSettings"/>
<applicationConfigurable instance="com.sdk.dynform.tools.i18n.I18nConfigurable"
id="com.sdk.dynform.tools.i18n.I18nConfigurable"
displayName="DynForm I18n Tools"/>
<projectService serviceImplementation="com.sdk.dynform.tools.config.DynFormSettings"/>
<projectConfigurable instance="com.sdk.dynform.tools.config.DynFormConfigurable"
id="com.sdk.dynform.tools.config.DynFormConfigurable"
displayName="DynamicForm Tools"
nonDefaultProject="true"/>
<fileType name="FRML" implementationClass="com.sdk.dynform.tools.helper.FRMLFileType" extensions="frml" language="XML"/>
<fileType name="FRML" implementationClass="com.sdk.dynform.tools.dynform.FRMLFileType" extensions="frml" language="XML"/>
<notificationGroup id="Dynamic-Form-Tools-Notification" displayType="BALLOON" isLogByDefault="true"/>
<!-- Folding Builders -->
@@ -140,18 +147,18 @@
<psi.referenceContributor language="XML" implementation="com.sdk.dynform.tools.i18n.I18nReferenceContributor"/>
<psi.referenceContributor language="JavaScript" implementation="com.sdk.dynform.tools.i18n.I18nReferenceContributor"/>
<psi.referenceContributor language="JAVA" implementation="com.sdk.dynform.tools.helper.DynFormReferenceContributor"/>
<psi.referenceContributor language="XML" implementation="com.sdk.dynform.tools.helper.DynFormReferenceContributor"/>
<psi.referenceContributor language="JavaScript" implementation="com.sdk.dynform.tools.helper.DynFormReferenceContributor"/>
<psi.referenceContributor language="JAVA" implementation="com.sdk.dynform.tools.dynform.DynFormReferenceContributor"/>
<psi.referenceContributor language="XML" implementation="com.sdk.dynform.tools.dynform.DynFormReferenceContributor"/>
<psi.referenceContributor language="JavaScript" implementation="com.sdk.dynform.tools.dynform.DynFormReferenceContributor"/>
<!-- Completion Contributors -->
<completion.contributor language="JAVA" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/>
<completion.contributor language="XML" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/>
<completion.contributor language="JavaScript" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/>
<completion.contributor language="JAVA" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/>
<completion.contributor language="XML" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/>
<completion.contributor language="JavaScript" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/>
<completion.contributor language="JAVA" implementationClass="com.sdk.dynform.tools.dynform.DynFormCompletionContributor"/>
<completion.contributor language="XML" implementationClass="com.sdk.dynform.tools.dynform.DynFormCompletionContributor"/>
<completion.contributor language="JavaScript" implementationClass="com.sdk.dynform.tools.dynform.DynFormCompletionContributor"/>
</extensions>
</idea-plugin>
</idea-plugin>