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

127
DevResources/xsd/ajax.xsd Executable file
View File

@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="DATASETS" type="DATASETSType"/>
<xs:complexType name="DATASETSType">
<xs:sequence>
<xs:element type="DATASETType" name="DATASET" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="FieldDataType">
<xs:restriction base="xs:string">
<xs:enumeration value="AUTO"/>
<xs:enumeration value="NUMBER"/>
<xs:enumeration value="NUMERIC"/>
<xs:enumeration value="VARCHAR"/>
<xs:enumeration value="VARCHAR2"/>
<xs:enumeration value="TEXT"/>
<xs:enumeration value="DATE"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="FieldKind">
<xs:restriction base="xs:string">
<xs:enumeration value="VIEW"/>
<xs:enumeration value="DATA"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="YesNo">
<xs:restriction base="xs:string">
<xs:enumeration value="Y"/>
<xs:enumeration value="N"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="AlignmentType">
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="center"/>
<xs:enumeration value="right"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="SQLType">
<xs:all>
<xs:element type="xs:string" name="SELECT"/>
<xs:element type="xs:string" name="FROM"/>
<xs:element type="xs:string" name="WHERE" minOccurs="0"/>
<xs:element type="xs:string" name="FILTER" minOccurs="0"/>
<xs:element type="xs:string" name="GROUP" minOccurs="0"/>
<xs:element type="xs:string" name="HAVING" minOccurs="0"/>
<xs:element type="xs:string" name="ORDER" minOccurs="0"/>
</xs:all>
</xs:complexType>
<!-- <xs:complexType name="SQLType">-->
<!-- <xs:sequence>-->
<!-- <xs:element type="xs:string" name="SELECT"/>-->
<!-- <xs:element type="xs:string" name="FROM"/>-->
<!-- <xs:element type="xs:string" name="WHERE"/>-->
<!-- <xs:element type="xs:string" name="GROUP"/>-->
<!-- <xs:element type="xs:string" name="ORDER"/>-->
<!-- </xs:sequence>-->
<!-- </xs:complexType>-->
<xs:complexType name="SUBDATASETType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="NAME"/>
<xs:attribute type="xs:string" name="DATASET-ID"/>
<xs:attribute type="xs:string" name="MASTER-FIELDS"/>
<xs:attribute type="xs:string" name="DETAIL-FIELDS"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="FIELD-GROUPType">
<xs:sequence>
<xs:element type="FIELDType" name="FIELD" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="SUBDATASETSType">
<xs:sequence>
<xs:element type="SUBDATASETType" name="SUBDATASET" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="FIELDType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="NAME" use="optional"/>
<xs:attribute type="xs:string" name="LABEL" use="optional"/>
<xs:attribute type="xs:string" name="CAPTION" use="optional"/>
<xs:attribute type="FieldDataType" name="TYPE" use="optional"/>
<xs:attribute type="xs:string" name="SORT" use="optional"/>
<xs:attribute type="FieldKind" name="KIND" use="optional"/>
<xs:attribute type="YesNo" name="WARP" use="optional"/>
<xs:attribute type="xs:string" name="WIDTH" use="optional"/>
<xs:attribute type="AlignmentType" name="ALIGN" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="FIELDSType">
<xs:sequence>
<xs:element type="FIELDType" name="FIELD" maxOccurs="unbounded" minOccurs="0"/>
<xs:element type="FIELD-GROUPType" name="DATA-FIELDS" minOccurs="0" maxOccurs="1"/>
<xs:element type="FIELD-GROUPType" name="VIEW-FIELDS" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DATASETType">
<xs:sequence>
<xs:element type="xs:string" name="SCHEMA"/>
<xs:element type="xs:string" name="TABLEDESC" minOccurs="0"/>
<xs:element type="xs:integer" name="MAXROWS" minOccurs="0"/>
<xs:element type="SQLType" name="SQL"/>
<xs:element type="FIELDSType" name="FIELDS" minOccurs="0"/>
<xs:element type="SUBDATASETSType" name="SUBDATASETS" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="ID" use="required"/>
</xs:complexType>
</xs:schema>

1349
DevResources/xsd/dynf-form-def.xsd Executable file

File diff suppressed because it is too large Load Diff

118
DevResources/xsd/lovdef.xsd Executable file
View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="FieldDataType">
<xs:restriction base="xs:string">
<xs:enumeration value="NUMBER"/>
<xs:enumeration value="STRING"/>
<xs:enumeration value="DATE"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="FieldKind">
<xs:restriction base="xs:string">
<xs:enumeration value="VIEW"/>
<xs:enumeration value="DATA"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="YesNo">
<xs:restriction base="xs:string">
<xs:enumeration value="Y"/>
<xs:enumeration value="N"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="AlignmentType">
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="center"/>
<xs:enumeration value="right"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="SelectorType">
<xs:restriction base="xs:string">
<xs:enumeration value="none"/>
<xs:enumeration value="checkbox"/>
<xs:enumeration value="radio"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="AdvSearchFlagType">
<xs:restriction base="xs:string">
<xs:enumeration value="NO"/>
<xs:enumeration value="EQ"/>
<xs:enumeration value="LT"/>
<xs:enumeration value="GT"/>
<xs:enumeration value="BT"/>
<xs:enumeration value="LTE"/>
<xs:enumeration value="GTE"/>
<xs:enumeration value="LIKE"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="LOVS" type="LOVSType"/>
<xs:complexType name="HEADERType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="YesNo" name="NAVI"/>
<xs:attribute type="YesNo" name="SEARCH"/>
<xs:attribute type="SelectorType" name="SELECTOR"/>
</xs:extension>
</xs:simpleContent> </xs:complexType>
<xs:complexType name="LOVType">
<xs:sequence>
<xs:element type="HEADERType" name="HEADER"/>
<xs:element type="xs:string" name="FOOTER"/>
<xs:element type="xs:string" name="DESCRIPTION"/>
<xs:element type="xs:string" name="KEYFIELDS"/>
<xs:element type="xs:string" name="SORTFIELDS"/>
<xs:element type="xs:string" name="PAGESIZE"/>
<xs:element type="FIELDSType" name="FIELDS"/>
<xs:element name="SCRIPT" minOccurs="0" maxOccurs="1">
<xs:complexType mixed="true">
<xs:sequence>
<xs:element type="xs:string" name="DECLARETION" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="INITIALIZE" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute type="xs:string" name="ID" use="required"/>
<xs:attribute type="xs:string" name="DATAID" use="required"/>
</xs:complexType>
<xs:complexType name="FIELDType">
<xs:sequence>
<xs:element type="xs:string" name="HEADER"/>
<xs:element type="xs:string" name="TEMPLATE" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="NAME" use="required"/>
<xs:attribute type="xs:string" name="LABEL" use="required"/>
<xs:attribute type="FieldDataType" name="TYPE" use="optional"/>
<xs:attribute type="xs:string" name="WIDTH" use="optional"/>
<xs:attribute type="AlignmentType" name="ALIGN" use="optional"/>
<xs:attribute type="xs:string" name="FORMAT" use="optional"/>
<xs:attribute type="YesNo" name="SEARCH" use="optional"/>
<xs:attribute type="xs:string" name="SEARCH_ORIGIN" use="optional"/>
<xs:attribute type="AdvSearchFlagType" name="ADV_SEARCH" use="optional"/>
<xs:attribute type="xs:string" name="ADV_ORIGIN" use="optional"/>
<xs:attribute type="FieldDataType" name="ADV_TYPE" use="optional"/>
<xs:attribute type="YesNo" name="SORT" use="optional"/>
<xs:attribute type="xs:string" name="SORT_ORIGIN" use="optional"/>
</xs:complexType>
<xs:complexType name="FIELDSType">
<xs:sequence>
<xs:element type="FIELDType" name="FIELD" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LOVSType">
<xs:sequence>
<xs:element type="LOVType" name="LOV" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

View File

@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Application" type="ApplicationType"/>
<xs:simpleType name="YesNoType">
<xs:restriction base="xs:string">
<xs:enumeration value="Y"/>
<xs:enumeration value="N"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="LocaleType">
<xs:restriction base="xs:string">
<xs:enumeration value="th"/>
<xs:enumeration value="en"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="FormReloadType">
<xs:restriction base="xs:string">
<xs:enumeration value="AUTO"/>
<xs:enumeration value="FORCE"/>
<xs:enumeration value="OFF"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="DBProviderType">
<xs:restriction base="xs:string">
<xs:enumeration value="ORACLE"/>
<xs:enumeration value="MYSQL"/>
<xs:enumeration value="MSSQL"/>
<xs:enumeration value="PGSQL"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="OracleConnectionMode">
<xs:restriction base="xs:string">
<xs:enumeration value="DIRECT"/>
<xs:enumeration value="TNS-STRING"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="DBTimeZoneType">
<xs:restriction base="xs:string">
<xs:enumeration value="UTC"/>
<xs:enumeration value="Asia/Bangkok"/>
<xs:enumeration value="+1"/>
<xs:enumeration value="+2"/>
<xs:enumeration value="+3"/>
<xs:enumeration value="+4"/>
<xs:enumeration value="+5"/>
<xs:enumeration value="+6"/>
<xs:enumeration value="+7"/>
<xs:enumeration value="+8"/>
<xs:enumeration value="+9"/>
<xs:enumeration value="+10"/>
<xs:enumeration value="+11"/>
<xs:enumeration value="+12"/>
<xs:enumeration value="-1"/>
<xs:enumeration value="-2"/>
<xs:enumeration value="-3"/>
<xs:enumeration value="-4"/>
<xs:enumeration value="-5"/>
<xs:enumeration value="-6"/>
<xs:enumeration value="-7"/>
<xs:enumeration value="-8"/>
<xs:enumeration value="-9"/>
<xs:enumeration value="-10"/>
<xs:enumeration value="-11"/>
<xs:enumeration value="-12"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="ConfigType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="Name"/>
<xs:attribute type="xs:string" name="Title"/>
<xs:attribute type="YesNoType" name="DevMode"/>
<xs:attribute type="xs:anyURI" name="RootUrl"/>
<xs:attribute type="xs:string" name="TokenKey"/>
<xs:attribute type="xs:string" name="SignOnPage"/>
<xs:attribute type="xs:string" name="LandingPage"/>
<xs:attribute type="FormReloadType" name="FormReload"/>
<xs:attribute type="LocaleType" name="DefaultLocale"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="ModuleType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="Name" use="optional"/>
<xs:attribute type="YesNoType" name="NeedAuthen" use="optional"/>
<xs:attribute type="xs:string" name="Renderer" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="ModulesType">
<xs:sequence>
<xs:element type="ModuleType" name="Module" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ConnectorType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="xs:string" name="Schema" use="required"/>
<xs:attribute type="YesNoType" name="UsePools"/>
<xs:attribute type="xs:byte" name="Size"/>
<xs:attribute type="xs:byte" name="Preserv"/>
<xs:attribute type="xs:byte" name="Timeout"/>
<xs:attribute type="YesNoType" name="SendHeartBeat"/>
<xs:attribute type="xs:string" name="Username"/>
<xs:attribute type="xs:string" name="Password"/>
<xs:attribute type="YesNoType" name="Debug"/>
<xs:attribute type="YesNoType" name="PoolDebug"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="DatabaseType">
<xs:sequence>
<xs:element type="xs:string" name="TNS-String" minOccurs="0"/>
<xs:element type="ConnectorType" name="Connector" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Name"/>
<xs:attribute type="xs:string" name="Server"/>
<xs:attribute type="DBTimeZoneType" name="TimeZone"/>
<xs:attribute type="DBProviderType" name="Provider"/>
<xs:attribute type="OracleConnectionMode" name="Mode"/>
<xs:attribute type="xs:string" name="Database"/>
<xs:attribute type="xs:short" name="Port"/>
<xs:attribute type="xs:string" name="Username"/>
<xs:attribute type="xs:string" name="Password"/>
</xs:complexType>
<xs:complexType name="DatabasesType">
<xs:sequence>
<xs:element type="DatabaseType" name="Database" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ParameterType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="name" use="optional"/>
<xs:attribute type="xs:string" name="value" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="APIType">
<xs:sequence>
<xs:element type="ParameterType" name="Parameter" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="Name" use="optional"/>
</xs:complexType>
<xs:complexType name="APISType">
<xs:sequence>
<xs:element type="APIType" name="API" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="UploadFileType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="Name" use="required"/>
<xs:attribute type="xs:string" name="Path" use="required"/>
<xs:attribute type="xs:string" name="MaxPixel" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="UploadsType">
<xs:sequence>
<xs:element type="UploadFileType" name="FileType" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ApplicationType">
<xs:sequence >
<xs:element type="ConfigType" name="Config"/>
<xs:element type="ModulesType" name="Modules"/>
<xs:element type="DatabasesType" name="Databases"/>
<xs:element type="UploadsType" name="Uploads"/>
<xs:element type="APISType" name="APIs"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

View File

@@ -5,7 +5,7 @@ plugins {
} }
group = "com.sdk.dynform.tools" group = "com.sdk.dynform.tools"
version = "3.1.0" version = "3.2.0"
repositories { repositories {
mavenCentral() mavenCentral()
@@ -39,6 +39,12 @@ intellijPlatform {
} }
changeNotes = """ changeNotes = """
<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> <h2>[3.1.0]</h2>
<ul> <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> <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>
@@ -55,7 +61,7 @@ intellijPlatform {
</ul> </ul>
<h2>[3.0.1]</h2> <h2>[3.0.1]</h2>
<ul> <ul>
<li><strong>Rebranding:</strong> Renamed plugin to "Dynamic Form Helper" (DynamicFormTools) to better reflect its expanding capabilities.</li> <li><strong>Rebranding:</strong> Renamed plugin to "DynamicForm Tools" to better reflect its expanding capabilities.</li>
<li><strong>I18n Support:</strong> Added comprehensive tools for internationalization: <li><strong>I18n Support:</strong> Added comprehensive tools for internationalization:
<ul> <ul>
<li>Inlay hints and code folding for i18n keys in Java, XML, and JavaScript.</li> <li>Inlay hints and code folding for i18n keys in Java, XML, and JavaScript.</li>

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.DasColumn;
import com.intellij.database.model.ObjectKind; 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.database.psi.DbTable;
import com.intellij.openapi.actionSystem.AnAction; 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.database.psi.DbTable;
import com.intellij.openapi.actionSystem.AnAction; 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.database.psi.DbTable;
import com.intellij.openapi.actionSystem.AnAction; 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.DasColumn;
import com.intellij.database.model.DasTableKey; 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.PersistentStateComponent;
import com.intellij.openapi.components.State; import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import com.intellij.util.xmlb.XmlSerializerUtil; import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@State( @State(
name = "com.sdk.dynform.tools.i18n.I18nSettings", name = "com.sdk.dynform.tools.config.DynFormSettings",
storages = @Storage("DynamicFormTools.xml") storages = @Storage("DynamicFormToolsProject.xml")
) )
public class I18nSettings implements PersistentStateComponent<I18nSettings> { public class DynFormSettings implements PersistentStateComponent<DynFormSettings> {
public enum DisplayMode { public enum DisplayMode {
FOLDING, FOLDING,
@@ -22,19 +22,21 @@ public class I18nSettings implements PersistentStateComponent<I18nSettings> {
public DisplayMode displayMode = DisplayMode.FOLDING; public DisplayMode displayMode = DisplayMode.FOLDING;
public boolean showIcon = true; public boolean showIcon = true;
public String xsdFolderPath = "";
public String xsdPrefix = "/dynf";
public static I18nSettings getInstance() { public static DynFormSettings getInstance(Project project) {
return ApplicationManager.getApplication().getService(I18nSettings.class); return project.getService(DynFormSettings.class);
} }
@Nullable @Nullable
@Override @Override
public I18nSettings getState() { public DynFormSettings getState() {
return this; return this;
} }
@Override @Override
public void loadState(@NotNull I18nSettings state) { public void loadState(@NotNull DynFormSettings state) {
XmlSerializerUtil.copyBean(state, this); 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.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder; 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())) { while (formContainer != null && !"FORM_ENTRY".equals(formContainer.getName()) && !"FORM_BROWSE".equals(formContainer.getName())) {
formContainer = formContainer.getParentTag(); formContainer = formContainer.getParentTag();
} }
if (formContainer == null) { if (formContainer == null) {
XmlFile file = (XmlFile) parameters.getOriginalFile(); XmlFile file = (XmlFile) parameters.getOriginalFile();
formContainer = file.getRootTag(); formContainer = file.getRootTag();
} }
if (formContainer != null) { if (formContainer != null) {
addFieldsInTagRecursive(formContainer, resultSet); addFieldsInTagRecursive(formContainer, resultSet);
} }
@@ -136,7 +136,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
ajaxOptionTag = ajaxOptionTag.getParentTag(); ajaxOptionTag = ajaxOptionTag.getParentTag();
} }
if (ajaxOptionTag == null) return; if (ajaxOptionTag == null) return;
String datasetId = ajaxOptionTag.getAttributeValue("DATASET"); String datasetId = ajaxOptionTag.getAttributeValue("DATASET");
if (datasetId == null) return; if (datasetId == null) return;
@@ -146,7 +146,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
if (rootTag != null) { if (rootTag != null) {
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS"); XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag; if (datasetsContainer == null) datasetsContainer = rootTag;
for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) { for (XmlTag datasetTag : datasetsContainer.findSubTags("DATASET")) {
if (datasetId.equals(datasetTag.getAttributeValue("ID"))) { if (datasetId.equals(datasetTag.getAttributeValue("ID"))) {
XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS"); XmlTag fieldsTag = datasetTag.findFirstSubTag("FIELDS");
@@ -199,7 +199,7 @@ public class DynFormCompletionContributor extends CompletionContributor {
if (!(file instanceof XmlFile xmlFile)) return; if (!(file instanceof XmlFile xmlFile)) return;
XmlTag rootTag = xmlFile.getRootTag(); XmlTag rootTag = xmlFile.getRootTag();
if (rootTag == null) return; if (rootTag == null) return;
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS"); XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag; 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.project.Project;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
@@ -26,7 +26,7 @@ public class DynFormPathUtils {
public static VirtualFile findModuleDir(@NotNull PsiFile file) { public static VirtualFile findModuleDir(@NotNull PsiFile file) {
VirtualFile vFile = file.getVirtualFile(); VirtualFile vFile = file.getVirtualFile();
if (vFile == null) return null; if (vFile == null) return null;
VirtualFile current = vFile.getParent(); VirtualFile current = vFile.getParent();
while (current != null) { while (current != null) {
if (current.getParent() != null && "module".equals(current.getParent().getName())) { 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) { public static PsiFile findIncludedFile(@NotNull PsiFile contextFile, @NotNull String path) {
// จัดการกรณี typo ( แทน /) // จัดการกรณี typo ( แทน /)
path = path.replace("", "/"); path = path.replace("", "/");
VirtualFile moduleBase = getModuleBaseDir(contextFile.getProject()); VirtualFile moduleBase = getModuleBaseDir(contextFile.getProject());
if (moduleBase == null) return null; 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.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns; import com.intellij.patterns.PlatformPatterns;
@@ -60,25 +60,25 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
XmlAttributeValue attrValue = (XmlAttributeValue) element; XmlAttributeValue attrValue = (XmlAttributeValue) element;
String value = attrValue.getValue(); String value = attrValue.getValue();
if (value == null || value.isEmpty()) return PsiReference.EMPTY_ARRAY; if (value == null || value.isEmpty()) return PsiReference.EMPTY_ARRAY;
XmlAttribute attr = (XmlAttribute) attrValue.getParent(); XmlAttribute attr = (XmlAttribute) attrValue.getParent();
XmlTag tag = (XmlTag) attr.getParent(); XmlTag tag = (XmlTag) attr.getParent();
String attrName = attr.getName(); String attrName = attr.getName();
if ("TARGET".equals(attrName)) { if ("TARGET".equals(attrName)) {
return new PsiReference[]{new DynFormLocalFieldReference(attrValue, new TextRange(1, value.length() + 1), value)}; return new PsiReference[]{new DynFormLocalFieldReference(attrValue, new TextRange(1, value.length() + 1), value)};
} }
// เคส 1: อยู่ใน LAYOUT หรือ TITLES -> ลิงก์ไปหา FIELDS (นิยาม) // เคส 1: อยู่ใน LAYOUT หรือ TITLES -> ลิงก์ไปหา FIELDS (นิยาม)
if (hasAncestorWithName(tag, "LAYOUT") || hasAncestorWithName(tag, "TITLES")) { if (hasAncestorWithName(tag, "LAYOUT") || hasAncestorWithName(tag, "TITLES")) {
return new PsiReference[]{new DynFormFieldDefinitionReference(attrValue, new TextRange(1, value.length() + 1), value)}; return new PsiReference[]{new DynFormFieldDefinitionReference(attrValue, new TextRange(1, value.length() + 1), value)};
} }
// เคส 2: อยู่ใน FIELDS (นิยาม) -> ลิงก์กลับไปหา LAYOUT หรือ TITLES (การใช้งาน) // เคส 2: อยู่ใน FIELDS (นิยาม) -> ลิงก์กลับไปหา LAYOUT หรือ TITLES (การใช้งาน)
if (hasAncestorWithName(tag, "FIELDS")) { if (hasAncestorWithName(tag, "FIELDS")) {
return new PsiReference[]{new DynFormFieldUsageReference(attrValue, new TextRange(1, value.length() + 1), value)}; return new PsiReference[]{new DynFormFieldUsageReference(attrValue, new TextRange(1, value.length() + 1), value)};
} }
return PsiReference.EMPTY_ARRAY; return PsiReference.EMPTY_ARRAY;
} }
}); });
@@ -180,7 +180,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormModuleReference extends PsiReferenceBase<PsiElement> { private static class DynFormModuleReference extends PsiReferenceBase<PsiElement> {
private final String moduleName; private final String moduleName;
public DynFormModuleReference(@NotNull PsiElement element, TextRange textRange, String moduleName) { public DynFormModuleReference(@NotNull PsiElement element, TextRange textRange, String moduleName) {
super(element, textRange); super(element, textRange, true);
this.moduleName = moduleName; this.moduleName = moduleName;
} }
@Nullable @Override public PsiElement resolve() { @Nullable @Override public PsiElement resolve() {
@@ -192,7 +192,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private final String moduleName; private final String moduleName;
private final String frmlName; private final String frmlName;
public DynFormFrmlReference(@NotNull PsiElement element, TextRange textRange, String moduleName, String frmlName) { public DynFormFrmlReference(@NotNull PsiElement element, TextRange textRange, String moduleName, String frmlName) {
super(element, textRange); super(element, textRange, true);
this.moduleName = moduleName; this.moduleName = moduleName;
this.frmlName = frmlName; this.frmlName = frmlName;
} }
@@ -204,7 +204,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormLocalFieldReference extends PsiReferenceBase<XmlAttributeValue> { private static class DynFormLocalFieldReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName; private final String fieldName;
public DynFormLocalFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) { public DynFormLocalFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range); super(element, range, true);
this.fieldName = fieldName; this.fieldName = fieldName;
} }
@Nullable @Override public PsiElement resolve() { @Nullable @Override public PsiElement resolve() {
@@ -227,31 +227,31 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormFieldDefinitionReference extends PsiReferenceBase<XmlAttributeValue> { private static class DynFormFieldDefinitionReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName; private final String fieldName;
public DynFormFieldDefinitionReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) { public DynFormFieldDefinitionReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range); super(element, range, true);
this.fieldName = fieldName; this.fieldName = fieldName;
} }
@Nullable @Override public PsiElement resolve() { @Nullable @Override public PsiElement resolve() {
XmlTag tag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class); XmlTag tag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
if (tag == null) return null; if (tag == null) return null;
// หา container (FORM_ENTRY, FORM_BROWSE, GRID-LIST หรือ FILTERS) // หา container (FORM_ENTRY, FORM_BROWSE, GRID-LIST หรือ FILTERS)
XmlTag container = tag; XmlTag container = tag;
while (container != null && while (container != null &&
!"LAYOUT".equals(container.getName()) && !"LAYOUT".equals(container.getName()) &&
!"TITLES".equals(container.getName()) && !"TITLES".equals(container.getName()) &&
!"FORM_ENTRY".equals(container.getName()) && !"FORM_ENTRY".equals(container.getName()) &&
!"FORM_BROWSE".equals(container.getName()) && !"FORM_BROWSE".equals(container.getName()) &&
!"GRID-LIST".equals(container.getName()) && !"GRID-LIST".equals(container.getName()) &&
!"FILTERS".equals(container.getName())) { !"FILTERS".equals(container.getName())) {
container = container.getParentTag(); container = container.getParentTag();
} }
if (container != null && ("LAYOUT".equals(container.getName()) || "TITLES".equals(container.getName()))) { if (container != null && ("LAYOUT".equals(container.getName()) || "TITLES".equals(container.getName()))) {
container = container.getParentTag(); container = container.getParentTag();
} }
if (container == null) return null; if (container == null) return null;
XmlTag fieldsTag = container.findFirstSubTag("FIELDS"); XmlTag fieldsTag = container.findFirstSubTag("FIELDS");
if (fieldsTag == null) return null; if (fieldsTag == null) return null;
@@ -265,30 +265,30 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormFieldUsageReference extends PsiReferenceBase<XmlAttributeValue> { private static class DynFormFieldUsageReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName; private final String fieldName;
public DynFormFieldUsageReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) { public DynFormFieldUsageReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range); super(element, range, true);
this.fieldName = fieldName; this.fieldName = fieldName;
} }
@Nullable @Override public PsiElement resolve() { @Nullable @Override public PsiElement resolve() {
XmlTag tag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class); XmlTag tag = PsiTreeUtil.getParentOfType(myElement, XmlTag.class);
if (tag == null) return null; if (tag == null) return null;
// หา FIELDS container // หา FIELDS container
XmlTag fieldsTag = tag; XmlTag fieldsTag = tag;
while (fieldsTag != null && !"FIELDS".equals(fieldsTag.getName())) { while (fieldsTag != null && !"FIELDS".equals(fieldsTag.getName())) {
fieldsTag = fieldsTag.getParentTag(); fieldsTag = fieldsTag.getParentTag();
} }
if (fieldsTag == null) return null; if (fieldsTag == null) return null;
XmlTag containerTag = fieldsTag.getParentTag(); XmlTag containerTag = fieldsTag.getParentTag();
if (containerTag == null) return null; if (containerTag == null) return null;
// หาใน LAYOUT ก่อน // หาใน LAYOUT ก่อน
XmlTag layoutTag = containerTag.findFirstSubTag("LAYOUT"); XmlTag layoutTag = containerTag.findFirstSubTag("LAYOUT");
if (layoutTag != null) { if (layoutTag != null) {
PsiElement found = findFieldInTag(layoutTag, fieldName); PsiElement found = findFieldInTag(layoutTag, fieldName);
if (found != null) return found; if (found != null) return found;
} }
// ถ้าไม่เจอใน LAYOUT ให้หาใน TITLES // ถ้าไม่เจอใน LAYOUT ให้หาใน TITLES
XmlTag titlesTag = containerTag.findFirstSubTag("TITLES"); XmlTag titlesTag = containerTag.findFirstSubTag("TITLES");
if (titlesTag != null) { if (titlesTag != null) {
@@ -320,7 +320,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormDatasetReference extends PsiReferenceBase<XmlAttributeValue> { private static class DynFormDatasetReference extends PsiReferenceBase<XmlAttributeValue> {
private final String datasetId; private final String datasetId;
public DynFormDatasetReference(@NotNull XmlAttributeValue element, TextRange range, String datasetId) { public DynFormDatasetReference(@NotNull XmlAttributeValue element, TextRange range, String datasetId) {
super(element, range); super(element, range, true);
this.datasetId = datasetId; this.datasetId = datasetId;
} }
@Nullable @Override public PsiElement resolve() { @Nullable @Override public PsiElement resolve() {
@@ -339,7 +339,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
if (!(file instanceof XmlFile xmlFile)) return null; if (!(file instanceof XmlFile xmlFile)) return null;
XmlTag rootTag = xmlFile.getRootTag(); XmlTag rootTag = xmlFile.getRootTag();
if (rootTag == null) return null; if (rootTag == null) return null;
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS"); XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag; if (datasetsContainer == null) datasetsContainer = rootTag;
@@ -356,7 +356,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormGridReference extends PsiReferenceBase<XmlAttributeValue> { private static class DynFormGridReference extends PsiReferenceBase<XmlAttributeValue> {
private final String gridId; private final String gridId;
public DynFormGridReference(@NotNull XmlAttributeValue element, TextRange range, String gridId) { public DynFormGridReference(@NotNull XmlAttributeValue element, TextRange range, String gridId) {
super(element, range); super(element, range, true);
this.gridId = gridId; this.gridId = gridId;
} }
@Nullable @Override public PsiElement resolve() { @Nullable @Override public PsiElement resolve() {
@@ -398,7 +398,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormAjaxSrcFieldReference extends PsiReferenceBase<XmlAttributeValue> { private static class DynFormAjaxSrcFieldReference extends PsiReferenceBase<XmlAttributeValue> {
private final String fieldName; private final String fieldName;
public DynFormAjaxSrcFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) { public DynFormAjaxSrcFieldReference(@NotNull XmlAttributeValue element, TextRange range, String fieldName) {
super(element, range); super(element, range, true);
this.fieldName = fieldName; this.fieldName = fieldName;
} }
@Nullable @Override public PsiElement resolve() { @Nullable @Override public PsiElement resolve() {
@@ -407,16 +407,16 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
ajaxOptionTag = ajaxOptionTag.getParentTag(); ajaxOptionTag = ajaxOptionTag.getParentTag();
} }
if (ajaxOptionTag == null) return null; if (ajaxOptionTag == null) return null;
String datasetId = ajaxOptionTag.getAttributeValue("DATASET"); String datasetId = ajaxOptionTag.getAttributeValue("DATASET");
if (datasetId == null) return null; if (datasetId == null) return null;
PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(myElement.getContainingFile()); PsiFile ajaxFile = DynFormPathUtils.findAjaxXml(myElement.getContainingFile());
if (!(ajaxFile instanceof XmlFile xmlFile)) return null; if (!(ajaxFile instanceof XmlFile xmlFile)) return null;
XmlTag rootTag = xmlFile.getRootTag(); XmlTag rootTag = xmlFile.getRootTag();
if (rootTag == null) return null; if (rootTag == null) return null;
XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS"); XmlTag datasetsContainer = rootTag.findFirstSubTag("DATASETS");
if (datasetsContainer == null) datasetsContainer = rootTag; if (datasetsContainer == null) datasetsContainer = rootTag;
@@ -436,7 +436,7 @@ public class DynFormReferenceContributor extends PsiReferenceContributor {
private static class DynFormFileIncludeReference extends PsiReferenceBase<XmlAttributeValue> { private static class DynFormFileIncludeReference extends PsiReferenceBase<XmlAttributeValue> {
private final String path; private final String path;
public DynFormFileIncludeReference(@NotNull XmlAttributeValue element, TextRange range, String path) { public DynFormFileIncludeReference(@NotNull XmlAttributeValue element, TextRange range, String path) {
super(element, range); super(element, range, true);
this.path = path; this.path = path;
} }
@Nullable @Override public PsiElement resolve() { @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.lang.xml.XMLLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.fileTypes.LanguageFileType;
import org.jetbrains.annotations.NotNull; 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.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlToken;
import com.intellij.util.ProcessingContext; import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -31,7 +30,7 @@ public class I18nCompletionContributor extends CompletionContributor {
@NotNull CompletionResultSet resultSet) { @NotNull CompletionResultSet resultSet) {
PsiElement position = parameters.getPosition(); PsiElement position = parameters.getPosition();
PsiLiteralExpression literal = PsiTreeUtil.getParentOfType(position, PsiLiteralExpression.class); PsiLiteralExpression literal = PsiTreeUtil.getParentOfType(position, PsiLiteralExpression.class);
// ในบางภาษาอาจจะไม่ใช่ PsiLiteralExpression โดยตรง // ในบางภาษาอาจจะไม่ใช่ PsiLiteralExpression โดยตรง
if (literal != null && isMGetArgument(literal)) { if (literal != null && isMGetArgument(literal)) {
String value = literal.getValue() instanceof String ? (String) literal.getValue() : ""; String value = literal.getValue() instanceof String ? (String) literal.getValue() : "";
@@ -44,7 +43,7 @@ public class I18nCompletionContributor extends CompletionContributor {
String textBefore = parameters.getEditor().getDocument().getText( String textBefore = parameters.getEditor().getDocument().getText(
new com.intellij.openapi.util.TextRange(Math.max(0, parameters.getOffset() - 100), parameters.getOffset()) 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); java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\$M\\.get\\([\"']([^\"']*)$").matcher(textBefore);
if (m.find()) { if (m.find()) {
handleMultiKeyCompletion(parameters, resultSet, m.group(1)); handleMultiKeyCompletion(parameters, resultSet, m.group(1));
@@ -93,9 +92,9 @@ public class I18nCompletionContributor extends CompletionContributor {
String text = position.getText(); String text = position.getText();
int offsetInElement = parameters.getOffset() - position.getTextRange().getStartOffset(); int offsetInElement = parameters.getOffset() - position.getTextRange().getStartOffset();
if (offsetInElement < 0 || offsetInElement > text.length()) return; if (offsetInElement < 0 || offsetInElement > text.length()) return;
String before = text.substring(0, offsetInElement); String before = text.substring(0, offsetInElement);
// Find the start of the current @M{ block // Find the start of the current @M{ block
int openBracket = before.lastIndexOf("@M{"); int openBracket = before.lastIndexOf("@M{");
if (openBracket != -1) { if (openBracket != -1) {
@@ -115,9 +114,9 @@ public class I18nCompletionContributor extends CompletionContributor {
int lastPipe = content.lastIndexOf("||"); int lastPipe = content.lastIndexOf("||");
int lastSpace = content.lastIndexOf(' '); int lastSpace = content.lastIndexOf(' ');
int lastHash = content.lastIndexOf('#'); int lastHash = content.lastIndexOf('#');
int lastSeparator = Math.max(lastPlus, Math.max(lastPipe != -1 ? lastPipe + 1 : -1, Math.max(lastSpace, lastHash))); int lastSeparator = Math.max(lastPlus, Math.max(lastPipe != -1 ? lastPipe + 1 : -1, Math.max(lastSpace, lastHash)));
String currentPrefix; String currentPrefix;
if (lastSeparator != -1) { if (lastSeparator != -1) {
// Handle || which is 2 chars // Handle || which is 2 chars
@@ -129,13 +128,13 @@ public class I18nCompletionContributor extends CompletionContributor {
} else { } else {
currentPrefix = content; currentPrefix = content;
} }
addMessageKeys(parameters, resultSet.withPrefixMatcher(currentPrefix)); addMessageKeys(parameters, resultSet.withPrefixMatcher(currentPrefix));
} }
private void addMessageKeys(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) { private void addMessageKeys(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
Set<String> keys = I18nUtils.findAllMessageKeys(parameters.getEditor().getProject()); Set<String> keys = I18nUtils.findAllMessageKeys(parameters.getEditor().getProject());
for (String key : keys) { for (String key : keys) {
String translation = I18nUtils.findMessageValue(parameters.getEditor().getProject(), key); String translation = I18nUtils.findMessageValue(parameters.getEditor().getProject(), key);
resultSet.addElement(LookupElementBuilder.create(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.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlAttributeValue;
import com.sdk.dynform.tools.config.DynFormSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -22,7 +23,7 @@ public class I18nFoldingBuilder extends FoldingBuilderEx {
@NotNull @NotNull
@Override @Override
public FoldingDescriptor @NotNull [] buildFoldRegions(@NotNull PsiElement root, @NotNull Document document, boolean quick) { 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]; return new FoldingDescriptor[0];
} }

View File

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

View File

@@ -2,11 +2,11 @@
<idea-plugin> <idea-plugin>
<!-- Unique id for this plugin. Must stay constant for the life of the plugin. --> <!-- Unique id for this plugin. Must stay constant for the life of the plugin. -->
<id>com.sdk.dynform.tools</id> <id>com.sdk.dynform.tools</id>
<name>Dynamic Form Helper</name> <name>DynamicForm Tools</name>
<vendor>Sakda Sakprapakorn</vendor> <vendor>Sakda Sakprapakorn</vendor>
<description><![CDATA[ <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> <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> <h4>Core Modules:</h4>
@@ -29,11 +29,17 @@
<h4>Usage:</h4> <h4>Usage:</h4>
<ul> <ul>
<li><b>Generation:</b> Right-click a table in the <b>Database tool window</b> -> <b>"Generate Action Models"</b>.</li> <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> </ul>
]]></description> ]]></description>
<change-notes><![CDATA[ <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> <h2>[3.1.0]</h2>
<ul> <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> <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> </ul>
<h2>[3.0.1]</h2> <h2>[3.0.1]</h2>
<ul> <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: <li><strong>I18n Support:</strong> Added comprehensive tools for internationalization:
<ul> <ul>
<li>Inlay hints and code folding for i18n keys in Java, XML, and JavaScript.</li> <li>Inlay hints and code folding for i18n keys in Java, XML, and JavaScript.</li>
@@ -96,20 +102,20 @@
<depends>JavaScript</depends> <depends>JavaScript</depends>
<actions> <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"/> <add-to-group group-id="DatabaseViewPopupMenu" anchor="first"/>
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction" <action id="com.sdk.dynform.tools.actionbean.GenerateBeanAction"
class="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction" class="com.sdk.dynform.tools.actionbean.GenerateBeanAction"
text="Generate Action Models V2" text="Generate Action Models V2"
description="Generates ActionBean classes from a database schema."> description="Generates ActionBean classes from a database schema.">
</action> </action>
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanAction.v3" <action id="com.sdk.dynform.tools.actionbean.GenerateBeanAction.v3"
class="com.sdk.dynform.tools.generators.actionmodels.GenerateBeanActionV3" class="com.sdk.dynform.tools.actionbean.GenerateBeanActionV3"
text="Generate Action Models V3" text="Generate Action Models V3"
description="Generates ActionBean classes from a database schema V3 (sdk.db.xxx)."> description="Generates ActionBean classes from a database schema V3 (sdk.db.xxx).">
</action> </action>
<action id="com.sdk.dynform.tools.generators.actionmodels.GenerateDatasetAction" <action id="com.sdk.dynform.tools.actionbean.GenerateDatasetAction"
class="com.sdk.dynform.tools.generators.actionmodels.GenerateDatasetAction" class="com.sdk.dynform.tools.actionbean.GenerateDatasetAction"
text="Generate Dataset XML" text="Generate Dataset XML"
description="Generates Dataset XML definition from a database table."> description="Generates Dataset XML definition from a database table.">
</action> </action>
@@ -117,12 +123,13 @@
</actions> </actions>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.sdk.dynform.tools.i18n.I18nSettings"/> <projectService serviceImplementation="com.sdk.dynform.tools.config.DynFormSettings"/>
<applicationConfigurable instance="com.sdk.dynform.tools.i18n.I18nConfigurable" <projectConfigurable instance="com.sdk.dynform.tools.config.DynFormConfigurable"
id="com.sdk.dynform.tools.i18n.I18nConfigurable" id="com.sdk.dynform.tools.config.DynFormConfigurable"
displayName="DynForm I18n Tools"/> 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"/> <notificationGroup id="Dynamic-Form-Tools-Notification" displayType="BALLOON" isLogByDefault="true"/>
<!-- Folding Builders --> <!-- Folding Builders -->
@@ -140,18 +147,18 @@
<psi.referenceContributor language="XML" implementation="com.sdk.dynform.tools.i18n.I18nReferenceContributor"/> <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="JavaScript" implementation="com.sdk.dynform.tools.i18n.I18nReferenceContributor"/>
<psi.referenceContributor language="JAVA" 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.helper.DynFormReferenceContributor"/> <psi.referenceContributor language="XML" implementation="com.sdk.dynform.tools.dynform.DynFormReferenceContributor"/>
<psi.referenceContributor language="JavaScript" implementation="com.sdk.dynform.tools.helper.DynFormReferenceContributor"/> <psi.referenceContributor language="JavaScript" implementation="com.sdk.dynform.tools.dynform.DynFormReferenceContributor"/>
<!-- Completion Contributors --> <!-- Completion Contributors -->
<completion.contributor language="JAVA" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/> <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="XML" implementationClass="com.sdk.dynform.tools.i18n.I18nCompletionContributor"/>
<completion.contributor language="JavaScript" 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="JAVA" implementationClass="com.sdk.dynform.tools.dynform.DynFormCompletionContributor"/>
<completion.contributor language="XML" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/> <completion.contributor language="XML" implementationClass="com.sdk.dynform.tools.dynform.DynFormCompletionContributor"/>
<completion.contributor language="JavaScript" implementationClass="com.sdk.dynform.tools.helper.DynFormCompletionContributor"/> <completion.contributor language="JavaScript" implementationClass="com.sdk.dynform.tools.dynform.DynFormCompletionContributor"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>