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:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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><FIELDS></code>, <code><LAYOUT></code>, and <code><TITLES></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>
|
||||
|
||||
Reference in New Issue
Block a user