├── .gitignore ├── settings.gradle ├── doc ├── notion-help.png ├── newsPopup-0.1.23.png ├── zk-feedback-menu.png ├── news-popup-improvement.md ├── feedback-feature.md └── code-completion-xml.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ ├── org │ │ │ └── zkoss │ │ │ │ └── zkidea │ │ │ │ └── lang │ │ │ │ ├── icons │ │ │ │ └── zul.png │ │ │ │ └── resources │ │ │ │ ├── archetype-catalog.xml │ │ │ │ ├── lang-addon.xsd │ │ │ │ └── zk.xsd │ │ └── META-INF │ │ │ ├── pluginIcon.svg │ │ │ └── plugin.xml │ └── java │ │ └── org │ │ └── zkoss │ │ └── zkidea │ │ ├── debug │ │ ├── XmlComletionContributor.java │ │ ├── MavenSmartCompletionContributorX.java │ │ ├── MavenPropertyCompletionContributorX.java │ │ ├── MavenVersionCompletionContributorX.java │ │ └── MavenPomXmlCompletionTagListenerContributorX.java │ │ ├── feedback │ │ ├── NewsAction.java │ │ ├── CustomerSupportAction.java │ │ ├── ReportBugAction.java │ │ └── DocumentationAction.java │ │ ├── lang │ │ ├── ZulIcons.java │ │ ├── ZulLanguage.java │ │ ├── ZulIconProvider.java │ │ ├── ZulFileType.java │ │ ├── ZkConfigSchemaProvider.java │ │ ├── LangAddonSchemaProvider.java │ │ ├── ZulSchemaProvider.java │ │ └── ZkXmlValidationAnnotator.java │ │ ├── project │ │ ├── ZKPathManager.java │ │ ├── ZulFileTypeRegistrar.java │ │ └── ZKProjectsManager.java │ │ ├── editorActions │ │ ├── ZulTypedHandler.java │ │ ├── MavenRunnerPatcher.java │ │ └── WebBrowserUrlProvider.java │ │ ├── dom │ │ ├── ZulDomUtil.java │ │ ├── ZkDomElementDescriptorProvider.java │ │ └── ZkDomElementDescriptorHolder.java │ │ ├── actions │ │ └── GotoJavaClassHandler.java │ │ ├── maven │ │ └── ZKMavenArchetypesProvider.java │ │ ├── completion │ │ └── MVVMAnnotationCompletionProvider.java │ │ └── newsNotification │ │ └── ZKNews.java └── test │ ├── resources │ ├── test-lang-addon-no-namespace.xml │ ├── test-lang-addon-missing-language-name.xml │ ├── test-lang-addon-missing-required.xml │ ├── test-lang-addon-mixed-order.xml │ ├── test-lang-addon-reverse-order.xml │ ├── test-lang-addon-validation.xml │ └── test-lang-addon.xml │ └── java │ └── org │ └── zkoss │ └── zkidea │ ├── newsNotification │ └── ZKNewsTest.java │ └── lang │ └── LangAddonXmlValidationTest.java ├── contributing.md ├── gradlew.bat ├── CLAUDE.md ├── README.md ├── gradlew ├── LICENSE └── ISSUES.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .idea 3 | .gradle -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'zkidea' -------------------------------------------------------------------------------- /doc/notion-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkoss/zkidea/HEAD/doc/notion-help.png -------------------------------------------------------------------------------- /doc/newsPopup-0.1.23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkoss/zkidea/HEAD/doc/newsPopup-0.1.23.png -------------------------------------------------------------------------------- /doc/zk-feedback-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkoss/zkidea/HEAD/doc/zk-feedback-menu.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkoss/zkidea/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/org/zkoss/zkidea/lang/icons/zul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkoss/zkidea/HEAD/src/main/resources/org/zkoss/zkidea/lang/icons/zul.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/test/resources/test-lang-addon-no-namespace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestAddon 5 | TestLanguage 6 | -------------------------------------------------------------------------------- /src/test/resources/test-lang-addon-missing-language-name.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestAddon 5 | some.dependency 6 | -------------------------------------------------------------------------------- /src/test/resources/test-lang-addon-missing-required.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestLanguage 5 | some.dependency 6 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/debug/XmlComletionContributor.java: -------------------------------------------------------------------------------- 1 | /* XmlComletionContributor.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 3:06 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.debug; 14 | 15 | /** 16 | * @author by jumperchen 17 | */ 18 | public class XmlComletionContributor { 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/test-lang-addon-mixed-order.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test Language Addon 4 | test 5 | zul 6 | org.zkoss.test.MessageLoader 7 | 8 | org.zkoss.test.Version 9 | 1.0.0 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/feedback/NewsAction.java: -------------------------------------------------------------------------------- 1 | package org.zkoss.zkidea.feedback; 2 | 3 | import com.intellij.ide.BrowserUtil; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class NewsAction extends DumbAwareAction { 9 | 10 | private static final String URL = "https://www.zkoss.org/news/"; 11 | 12 | @Override 13 | public void actionPerformed(@NotNull AnActionEvent e) { 14 | BrowserUtil.browse(URL); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/ZulIcons.java: -------------------------------------------------------------------------------- 1 | /* ZulIcons.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 10:23 AM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.lang; 14 | 15 | import com.intellij.openapi.util.IconLoader; 16 | 17 | import javax.swing.*; 18 | 19 | /** 20 | * @author by jumperchen 21 | */ 22 | public class ZulIcons { 23 | public static final Icon FILE = IconLoader.getIcon("/org/zkoss/zkidea/lang/icons/zul.png", ZulIcons.class); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/feedback/CustomerSupportAction.java: -------------------------------------------------------------------------------- 1 | package org.zkoss.zkidea.feedback; 2 | 3 | import com.intellij.ide.BrowserUtil; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class CustomerSupportAction extends DumbAwareAction { 9 | 10 | private static final String URL = "https://potix.freshdesk.com/"; 11 | 12 | @Override 13 | public void actionPerformed(@NotNull AnActionEvent e) { 14 | BrowserUtil.browse(URL); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/feedback/ReportBugAction.java: -------------------------------------------------------------------------------- 1 | package org.zkoss.zkidea.feedback; 2 | 3 | import com.intellij.ide.BrowserUtil; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class ReportBugAction extends DumbAwareAction { 9 | 10 | private static final String BUG_REPORT_URL = "https://tracker.zkoss.org/"; 11 | 12 | @Override 13 | public void actionPerformed(@NotNull AnActionEvent e) { 14 | BrowserUtil.browse(BUG_REPORT_URL); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/ZulLanguage.java: -------------------------------------------------------------------------------- 1 | /* ZulLanguage.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 10:18 AM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.lang; 14 | 15 | import com.intellij.lang.xml.XMLLanguage; 16 | 17 | /** 18 | * @author by jumperchen 19 | */ 20 | public class ZulLanguage extends XMLLanguage { 21 | public static final ZulLanguage INSTANCE = new ZulLanguage(); 22 | 23 | private ZulLanguage() { 24 | super(ZulLanguage.INSTANCE, "ZUL", "text/html"); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/feedback/DocumentationAction.java: -------------------------------------------------------------------------------- 1 | package org.zkoss.zkidea.feedback; 2 | 3 | import com.intellij.ide.BrowserUtil; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class DocumentationAction extends DumbAwareAction { 9 | 10 | private static final String DOCUMENTATION_URL = "https://docs.zkoss.org/zk_dev_ref/"; 11 | 12 | @Override 13 | public void actionPerformed(@NotNull AnActionEvent e) { 14 | BrowserUtil.browse(DOCUMENTATION_URL); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/test-lang-addon-reverse-order.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | testComponent 5 | com.example.TestComponent 6 | 7 | 8 | 9 | prop1 10 | value1 11 | 12 | 13 | 14 | org.zkoss.test.Version 15 | 1.0.0 16 | 17 | 18 | test 19 | Test Language Addon 20 | 21 | zul 22 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/debug/MavenSmartCompletionContributorX.java: -------------------------------------------------------------------------------- 1 | /* MavenSmartCompletionContributorX.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 4:22 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.debug; 14 | 15 | import com.intellij.codeInsight.completion.CompletionParameters; 16 | import com.intellij.codeInsight.completion.CompletionResultSet; 17 | import org.jetbrains.idea.maven.dom.model.completion.MavenSmartCompletionContributor; 18 | 19 | /** 20 | * @author by jumperchen 21 | */ 22 | public class MavenSmartCompletionContributorX extends MavenSmartCompletionContributor { 23 | @Override 24 | public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result) { 25 | super.fillCompletionVariants(parameters, result); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/debug/MavenPropertyCompletionContributorX.java: -------------------------------------------------------------------------------- 1 | /* MavenPropertyCompletionContributorX.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 4:19 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.debug; 14 | 15 | import com.intellij.codeInsight.completion.CompletionParameters; 16 | import com.intellij.codeInsight.completion.CompletionResultSet; 17 | import org.jetbrains.idea.maven.dom.references.MavenPropertyCompletionContributor; 18 | 19 | /** 20 | * @author by jumperchen 21 | */ 22 | public class MavenPropertyCompletionContributorX extends MavenPropertyCompletionContributor { 23 | @Override 24 | public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result) { 25 | super.fillCompletionVariants(parameters, result); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/debug/MavenVersionCompletionContributorX.java: -------------------------------------------------------------------------------- 1 | /* MavenVersionCompletionContributorX.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 4:23 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.debug; 14 | 15 | import com.intellij.codeInsight.completion.CompletionParameters; 16 | import com.intellij.codeInsight.completion.CompletionResultSet; 17 | import org.jetbrains.idea.maven.dom.model.completion.MavenVersionCompletionContributor; 18 | 19 | /** 20 | * @author by jumperchen 21 | */ 22 | public class MavenVersionCompletionContributorX extends MavenVersionCompletionContributor { 23 | @Override 24 | public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result) { 25 | super.fillCompletionVariants(parameters, result); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | 2 | Contributions of all kinds are welcome! 3 | 4 | **Reporting Issues** 5 | 6 | \* Before creating an issue, please review open issues to avoid a duplicate. 7 | 8 | \* If you have a question, not an issue, please discuss it in [ZK Forum](https://forum.zkoss.org/questions/). 9 | 10 | **Contributing codes** 11 | 12 | If you wish to contribute code, please create a corresponding issue and a pull request containing the ID of the issue with your comments. Your request will be reviewed before being merged. 13 | 14 | **License and Copyright** 15 | 16 | This project is created and maintained by Potix Corporation.  17 | 18 | Any contributed content is licensed under the same license and terms as the project license. By contributing your code, you certify that you created the content and agree to surrender your copyright of the contributed code to Potix.  19 | 20 | 21 | For any inquiries and questions, please contact us at info@zkoss.org. 22 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/debug/MavenPomXmlCompletionTagListenerContributorX.java: -------------------------------------------------------------------------------- 1 | /* MavenPomXmlCompletionTagListenerContributorX.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 4:25 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.debug; 14 | 15 | import com.intellij.codeInsight.completion.CompletionParameters; 16 | import com.intellij.codeInsight.completion.CompletionResultSet; 17 | import org.jetbrains.idea.maven.dom.model.completion.MavenPomXmlCompletionTagListenerContributor; 18 | 19 | /** 20 | * @author by jumperchen 21 | */ 22 | public class MavenPomXmlCompletionTagListenerContributorX extends MavenPomXmlCompletionTagListenerContributor { 23 | @Override 24 | public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result) { 25 | super.fillCompletionVariants(parameters, result); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/ZulIconProvider.java: -------------------------------------------------------------------------------- 1 | /* ZulIconProvider.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 3:47 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.lang; 14 | 15 | import javax.swing.*; 16 | 17 | import com.intellij.ide.FileIconProvider; 18 | import com.intellij.openapi.project.DumbAware; 19 | import com.intellij.openapi.project.Project; 20 | import com.intellij.openapi.util.Iconable; 21 | import com.intellij.openapi.vfs.VirtualFile; 22 | import org.jetbrains.annotations.NotNull; 23 | import org.jetbrains.annotations.Nullable; 24 | 25 | /** 26 | * @author by jumperchen 27 | */ 28 | public class ZulIconProvider implements DumbAware, FileIconProvider { 29 | @Nullable 30 | @Override 31 | public Icon getIcon(@NotNull VirtualFile file, @Iconable.IconFlags int flags, @Nullable Project project) { 32 | if(project == null) return null; 33 | if (ZulFileType.EXTENSION.equalsIgnoreCase(file.getExtension())) 34 | return ZulIcons.FILE; 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/project/ZKPathManager.java: -------------------------------------------------------------------------------- 1 | /* ZKPathManager.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 9:11 AM 7/29/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.project; 13 | 14 | import java.io.File; 15 | 16 | import com.intellij.openapi.application.PathManager; 17 | 18 | /** 19 | * @author jumperchen 20 | */ 21 | public class ZKPathManager { 22 | public static String PLUGIN_NAME = "zkidea"; 23 | 24 | /** 25 | * e.g. Mac: ~/Library/Caches/JetBrains/IntelliJIdea2025.1/plugins/zkidea/ 26 | */ 27 | public static String getPluginTempPath() { 28 | return PathManager.getPluginsPath() + File.separator + PLUGIN_NAME; 29 | } 30 | 31 | public static String getPluginResourcePath(String classPath) { 32 | // fix for #5 windows issue 33 | if ("\\".equals(File.separator)) { 34 | classPath = classPath.replaceAll("/", "\\\\"); 35 | } 36 | if (classPath.startsWith("/") || classPath.startsWith("\\")) 37 | return getPluginTempPath() + File.separator + "classes" + classPath; 38 | return getPluginTempPath() + File.separator + "classes" + File.separator + classPath; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/ZulFileType.java: -------------------------------------------------------------------------------- 1 | /* ZulFileType.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 10:27 AM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.lang; 14 | 15 | import com.intellij.openapi.fileTypes.LanguageFileType; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import javax.swing.*; 20 | 21 | /** 22 | * @author by jumperchen 23 | */ 24 | public class ZulFileType extends LanguageFileType { 25 | public static final ZulFileType INSTANCE = new ZulFileType(); 26 | 27 | public static final String EXTENSION = "zul"; 28 | private ZulFileType() { 29 | super(ZulLanguage.INSTANCE, true); 30 | } 31 | 32 | @NotNull 33 | @Override 34 | public String getName() { 35 | return "Zul"; 36 | } 37 | 38 | @NotNull 39 | @Override 40 | public String getDescription() { 41 | return "ZK UI Language file"; 42 | } 43 | 44 | @NotNull 45 | @Override 46 | public String getDefaultExtension() { 47 | return "zul"; 48 | } 49 | 50 | @Nullable 51 | @Override 52 | public Icon getIcon() { 53 | return ZulIcons.FILE; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/editorActions/ZulTypedHandler.java: -------------------------------------------------------------------------------- 1 | /* ZulTypedHandler.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 4:09 PM 7/24/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.editorActions; 13 | 14 | import com.intellij.codeInsight.AutoPopupController; 15 | import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler; 16 | import com.intellij.openapi.editor.Editor; 17 | import com.intellij.openapi.project.Project; 18 | import com.intellij.psi.PsiFile; 19 | 20 | /** 21 | * A typed handler for ZUL files that triggers the code completion popup automatically 22 | * when a user types the '@' character. This is designed to assist with ZK's MVVM 23 | * data binding annotations, which all start with the '@' symbol. 24 | * 25 | * @author jumperchen 26 | */ 27 | public class ZulTypedHandler extends CompletionAutoPopupHandler { 28 | @Override 29 | public Result checkAutoPopup(char charTyped, Project project, Editor editor, PsiFile file) { 30 | if ("@".equals(String.valueOf(charTyped))) { 31 | AutoPopupController.getInstance(project).scheduleAutoPopup(editor); 32 | return Result.STOP; 33 | } else { 34 | return super.checkAutoPopup(charTyped, project, editor, file); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/resources/test-lang-addon-validation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TestAddon 4 | TestLanguage 5 | 6 | 7 | 8 | prop1 9 | value1 10 | 11 | 12 | prop2 13 | value2 14 | 15 | 16 | prop3 17 | value3 18 | 19 | 20 | 21 | 22 | component1 23 | com.example.Component1 24 | 25 | 26 | 27 | component2 28 | div 29 | 30 | default 31 | ~./mold/component2.zul 32 | 33 | 34 | compact 35 | ~./mold/component2-compact.zul 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/ZkConfigSchemaProvider.java: -------------------------------------------------------------------------------- 1 | /* ZkXmlSchemaProvider.java 2 | 3 | Purpose: 4 | Schema provider for ZK configuration files (zk.xml) 5 | 6 | Description: 7 | Provides XSD schema resources for zk.xml files to enable 8 | code completion and validation in IntelliJ IDEA 9 | 10 | History: 11 | Created for ZK XML configuration file support 12 | 13 | Copyright (C) 2023 Potix Corporation. All Rights Reserved. 14 | 15 | */ 16 | package org.zkoss.zkidea.lang; 17 | 18 | import com.intellij.javaee.ResourceRegistrar; 19 | import com.intellij.javaee.StandardResourceProvider; 20 | 21 | /** 22 | * Schema provider for ZK configuration files (zk.xml) 23 | * Enables code completion and validation for zk.xml files 24 | * 25 | * @author Hawk 26 | */ 27 | public class ZkConfigSchemaProvider implements StandardResourceProvider { 28 | 29 | // zk config schema 30 | public static final String ZK_CONFIG_SCHEMA_URL = "http://www.zkoss.org/2005/zk/config"; 31 | public static final String ZK_CONFIG_PROJECT_SCHEMA_URL = "https://www.zkoss.org/2005/zk/config/zk.xsd"; 32 | public static final String ZK_CONFIG_PROJECT_SCHEMA_PATH = "org/zkoss/zkidea/lang/resources/zk.xsd"; 33 | 34 | public void registerResources(ResourceRegistrar registrar) { 35 | 36 | var path = "/" + ZK_CONFIG_PROJECT_SCHEMA_PATH; 37 | // zk config 38 | registrar.addStdResource("zk", path, getClass()); 39 | registrar.addStdResource(ZK_CONFIG_SCHEMA_URL, path, getClass()); 40 | registrar.addStdResource(ZK_CONFIG_PROJECT_SCHEMA_URL, path, getClass()); 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/LangAddonSchemaProvider.java: -------------------------------------------------------------------------------- 1 | /* LangAddonSchemaProvider.java 2 | 3 | Purpose: 4 | Schema provider for ZK language addon configuration files (lang-addon.xml) 5 | 6 | Description: 7 | Provides XSD schema resources for lang-addon.xml files to enable 8 | code completion and validation in IntelliJ IDEA 9 | 10 | History: 11 | Created for ZK language addon configuration file support 12 | 13 | Copyright (C) 2023 Potix Corporation. All Rights Reserved. 14 | 15 | */ 16 | package org.zkoss.zkidea.lang; 17 | 18 | import com.intellij.javaee.ResourceRegistrar; 19 | import com.intellij.javaee.StandardResourceProvider; 20 | 21 | /** 22 | * Schema provider for ZK language addon configuration files (lang-addon.xml) 23 | * Enables code completion and validation for lang-addon.xml files 24 | * 25 | * @author Hawk 26 | */ 27 | public class LangAddonSchemaProvider implements StandardResourceProvider { 28 | 29 | // lang-addon schema 30 | public static final String LANG_ADDON_SCHEMA_URL = "http://www.zkoss.org/2005/zk/lang-addon"; 31 | public static final String LANG_ADDON_PROJECT_SCHEMA_URL = "https://www.zkoss.org/2005/zk/lang-addon/lang-addon.xsd";//non-existed 32 | public static final String LANG_ADDON_PROJECT_SCHEMA_PATH = "org/zkoss/zkidea/lang/resources/lang-addon.xsd"; 33 | 34 | public void registerResources(ResourceRegistrar registrar) { 35 | 36 | var path = "/" + LANG_ADDON_PROJECT_SCHEMA_PATH; 37 | // lang-addon 38 | registrar.addStdResource("lang-addon", path, getClass()); 39 | registrar.addStdResource(LANG_ADDON_SCHEMA_URL, path, getClass()); 40 | registrar.addStdResource(LANG_ADDON_PROJECT_SCHEMA_URL, path, getClass()); 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/dom/ZulDomUtil.java: -------------------------------------------------------------------------------- 1 | /* ZulDomUtil.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 5:48 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.dom; 14 | 15 | import com.intellij.psi.PsiElement; 16 | import com.intellij.psi.PsiFile; 17 | import com.intellij.psi.xml.XmlFile; 18 | import com.intellij.psi.xml.XmlTag; 19 | import org.zkoss.zkidea.lang.ZulFileType; 20 | 21 | /** 22 | * @author by jumperchen 23 | */ 24 | public class ZulDomUtil { 25 | public static String VIEW_MODEL = "viewModel"; 26 | public static boolean isZKFile(PsiFile file) { 27 | if(!(file instanceof XmlFile)) { 28 | return false; 29 | } else { 30 | String name = file.getName(); 31 | return name.endsWith(ZulFileType.EXTENSION) || isZkConfigFile(file) || isLangAddonFile(file); 32 | } 33 | } 34 | 35 | public static boolean isZkConfigFile(PsiFile file) { 36 | if(!(file instanceof XmlFile)) { 37 | return false; 38 | } else { 39 | String name = file.getName(); 40 | return "zk.xml".equals(name); 41 | } 42 | } 43 | 44 | public static boolean isLangAddonFile(PsiFile file) { 45 | if(!(file instanceof XmlFile)) { 46 | return false; 47 | } else { 48 | String name = file.getName(); 49 | return "lang-addon.xml".equals(name); 50 | } 51 | } 52 | 53 | public static boolean isZKFile(String ext) { 54 | return ZulFileType.EXTENSION.equalsIgnoreCase(ext) || "zhtml".equalsIgnoreCase(ext); 55 | } 56 | public static boolean hasViewModel(PsiElement element) { 57 | do { 58 | if (element instanceof XmlTag) { 59 | if (hasAttribute((XmlTag) element, VIEW_MODEL)) { 60 | return true; 61 | } 62 | } 63 | element = element.getParent(); 64 | } while (element != null); 65 | return false; 66 | } 67 | public static boolean hasAttribute(XmlTag tag, String key) { 68 | return tag.getAttribute(key) != null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/dom/ZkDomElementDescriptorProvider.java: -------------------------------------------------------------------------------- 1 | /* ZulDomElementDescriptorProvider.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 5:45 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.dom; 14 | 15 | import com.intellij.psi.impl.source.xml.XmlElementDescriptorProvider; 16 | import com.intellij.psi.xml.XmlTag; 17 | import com.intellij.xml.XmlElementDescriptor; 18 | 19 | /** 20 | * ZK-specific XML element descriptor provider that enables code completion and validation 21 | * for ZUL files and ZK configuration descriptor (zk.xml) in IntelliJ IDEA. 22 | * 23 | *

How it works with IntelliJ Platform:

24 | *
    25 | *
  1. IntelliJ Platform calls {@link #getDescriptor(XmlTag)} for each XML tag during parsing
  2. 26 | *
  3. This method delegates to {@link ZkDomElementDescriptorHolder} to retrieve cached descriptors
  4. 27 | *
  5. The holder provides XSD schema-based descriptors with default namespace support
  6. 28 | *
  7. IntelliJ uses these descriptors to provide code completion, validation, and navigation
  8. 29 | *
30 | * 31 | *

Registration:

32 | *

This provider is registered in plugin.xml 33 | * 34 | *

Supported Files:

35 | * 40 | * 41 | * @see XmlElementDescriptorProvider IntelliJ Platform interface for XML element descriptors 42 | * @see ZkDomElementDescriptorHolder Service that manages and caches element descriptors 43 | * @see IntelliJ Platform XML DOM API 44 | * @see IntelliJ Platform Explorer - XML Element Descriptor Provider 45 | * @author jumperchen 46 | */ 47 | public class ZkDomElementDescriptorProvider implements XmlElementDescriptorProvider { 48 | public ZkDomElementDescriptorProvider() { 49 | } 50 | 51 | public XmlElementDescriptor getDescriptor(XmlTag tag) { 52 | return ZkDomElementDescriptorHolder.getInstance(tag.getProject()).getDescriptor(tag); 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/resources/test-lang-addon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestAddon 5 | TestLanguage 6 | 7 | 8 | some.dependency 9 | 10 | com.example.Version 11 | 1.0 12 | 13 | xul/html 14 | com.example.MessageLoader 15 | 16 | 17 | 18 | prop1 19 | value1 20 | 21 | 22 | prop2 23 | value2 24 | 25 | 26 | 27 | sys.prop 28 | sys.value 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | System.out.println("Test"); 40 | 41 | 42 | 43 | testComponent 44 | com.example.TestComponent 45 | div 46 | com.example.TestWidget 47 | 48 | 49 | default 50 | ~./mold/test.zul 51 | 52 | 53 | 54 | testProp 55 | testValue 56 | 57 | 58 | 59 | customAttr 60 | customValue 61 | 62 | 63 | 64 | TestAnnotation 65 | 66 | attr1 67 | val1 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/actions/GotoJavaClassHandler.java: -------------------------------------------------------------------------------- 1 | /* GotoJavaClassHandler.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 5:54 PM 7/27/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.actions; 13 | 14 | import com.intellij.codeInsight.completion.JavaClassReferenceCompletionContributor; 15 | import com.intellij.codeInsight.lookup.LookupElement; 16 | import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler; 17 | import com.intellij.openapi.actionSystem.DataContext; 18 | import com.intellij.openapi.editor.Editor; 19 | import com.intellij.psi.PsiElement; 20 | import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | /** 24 | * @author jumperchen 25 | */ 26 | public class GotoJavaClassHandler implements GotoDeclarationHandler { 27 | @Nullable 28 | @Override 29 | public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int offset, Editor editor) { 30 | if (psiElement == null || psiElement.getContainingFile() == null) { 31 | return null; 32 | } 33 | JavaClassReference reference = JavaClassReferenceCompletionContributor.findJavaClassReference(psiElement.getContainingFile(), offset); 34 | if (reference != null) { 35 | Object[] variants = reference.getVariants(); 36 | if (variants != null && variants.length > 0) { 37 | String className = reference.getCanonicalText().trim(); 38 | if (className.endsWith("\"") || className.endsWith("'")) 39 | className = className.substring(0, className.length()-1); 40 | LookupElement simulation = null; 41 | for (Object o : variants) { 42 | if (o instanceof LookupElement) { 43 | LookupElement element = (LookupElement) o; 44 | final String lookup = element.getLookupString(); 45 | if (className.equals(lookup)) { 46 | return new PsiElement[]{element.getPsiElement()}; 47 | } 48 | if (className.startsWith(lookup)) { 49 | if (simulation != null) { 50 | // more accurate 51 | if (simulation.getLookupString().length() < lookup.length()) { 52 | simulation = element; 53 | } 54 | } else { 55 | simulation = element; 56 | } 57 | } 58 | } 59 | } 60 | if (simulation != null) { 61 | return new PsiElement[]{simulation.getPsiElement()}; 62 | } 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | @Nullable 69 | @Override 70 | public String getActionText(DataContext dataContext) { 71 | return null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/org/zkoss/zkidea/lang/resources/archetype-catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | org.zkoss 8 | zk-archetype-component 9 | 9.5.0.3 10 | An archetype that generates a starter ZK component project 11 | 12 | 13 | org.zkoss 14 | zk-archetype-extension 15 | 9.5.0.3 16 | An archetype that generates a starter ZK extension project 17 | 18 | 19 | org.zkoss 20 | zk-archetype-webapp 21 | 9.5.0.3 22 | An archetype that generates a starter ZK CE webapp project 23 | 24 | 25 | org.zkoss 26 | zk-ee-eval-archetype-webapp 27 | 9.5.0.3 28 | An archetype that generates a starter ZK EE-eval webapp project 29 | 30 | 31 | org.zkoss 32 | zk-ee-eval-archetype-webapp-spring 33 | 9.5.0.3 34 | An archetype that generates a starter ZK EE-eval webapp project with Spring 35 | 36 | 37 | org.zkoss 38 | zk-ee-eval-archetype-webapp-spring-jpa 39 | 9.5.0.3 40 | An archetype that generates a starter ZK EE-eval webapp project with Spring and JPA 41 | 42 | 43 | org.zkoss 44 | zk-archetype-theme 45 | 9.5.0.3 46 | An archetype that generates a starter ZK theme project 47 | 48 | 49 | org.zkoss 50 | zk-archetype-datahandler 51 | 9.5.0.3 52 | An archetype that generates a starter ZK datahandler project 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/ZulSchemaProvider.java: -------------------------------------------------------------------------------- 1 | /* ZulResourceProvider.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 10:18 AM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.lang; 14 | 15 | import com.intellij.javaee.ResourceRegistrar; 16 | import com.intellij.javaee.StandardResourceProvider; 17 | import com.intellij.openapi.diagnostic.Logger; 18 | 19 | /** 20 | * @author by jumperchen 21 | */ 22 | public class ZulSchemaProvider implements StandardResourceProvider { 23 | 24 | private static final Logger LOG = Logger.getInstance( 25 | ZulSchemaProvider.class); 26 | 27 | // zul schema 28 | public static final String ZUL_SCHEMA_URL = "http://www.zkoss.org/2005/zul"; 29 | public static final String ZUL_PROJECT_SCHEMA_URL = "https://www.zkoss.org/2005/zul/zul.xsd"; 30 | public static final String ZUL_PROJECT_SCHEMA_PATH = "org/zkoss/zkidea/lang/resources/zul.xsd"; 31 | 32 | // zk schema 33 | public static final String ZK_SCHEMA_URL = "http://www.zkoss.org/2005/zk"; 34 | 35 | // native schema 36 | public static final String NATIVE_SCHEMA_URL = "http://www.zkoss.org/2005/zk/native"; 37 | 38 | // annotation schema 39 | public static final String ANNOTATION_SCHEMA_URL = "http://www.zkoss.org/2005/zk/annotation"; 40 | 41 | // client schema 42 | public static final String CLIENT_SCHEMA_URL = "http://www.zkoss.org/2005/zk/client"; 43 | 44 | // client attribute schema 45 | public static final String CLIENT_ATTRIBUTE_SCHEMA_URL = "http://www.zkoss.org/2005/zk/client/attribute"; 46 | 47 | // xhtml schema 48 | public static final String XHTML_SCHEMA_URL = "http://www.w3.org/1999/xhtml"; 49 | 50 | // xml schema 51 | public static final String XML_SCHEMA_URL = "http://www.zkoss.org/2007/xml"; 52 | 53 | // shadow schema 54 | public static final String SHADOW_SCHEMA_URL = "http://www.zkoss.org/2015/shadow"; 55 | 56 | public void registerResources(ResourceRegistrar registrar) { 57 | 58 | var path = "/" + ZUL_PROJECT_SCHEMA_PATH; 59 | // zul 60 | registrar.addStdResource("zul", path, getClass()); 61 | registrar.addStdResource(ZUL_SCHEMA_URL, path, getClass()); 62 | registrar.addStdResource(ZUL_PROJECT_SCHEMA_URL, path, getClass()); 63 | 64 | // registrar.addStdResource("xml", ExternalResourceManagerEx.STANDARD_SCHEMAS + "XMLSchema.xsd", ExternalResourceManagerEx.class); 65 | // registrar.addStdResource(XML_SCHEMA_URL, ExternalResourceManagerEx.STANDARD_SCHEMAS + "XMLSchema.xsd", ExternalResourceManagerEx.class); 66 | // 67 | // 68 | // registrar.addStdResource("xhtml", 69 | // ExternalResourceManagerEx.STANDARD_SCHEMAS + "html5/xhtml5.xsd", ExternalResourceManagerEx.class); 70 | // registrar.addStdResource(XHTML_SCHEMA_URL, 71 | // ExternalResourceManagerEx.STANDARD_SCHEMAS + "html5/xhtml5.xsd", ExternalResourceManagerEx.class); 72 | } 73 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/project/ZulFileTypeRegistrar.java: -------------------------------------------------------------------------------- 1 | package org.zkoss.zkidea.project; 2 | 3 | import com.intellij.ide.highlighter.XmlFileType; 4 | import com.intellij.notification.*; 5 | import com.intellij.openapi.application.ApplicationManager; 6 | import com.intellij.openapi.fileTypes.*; 7 | import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.startup.ProjectActivity; 10 | import kotlin.Unit; 11 | import kotlin.coroutines.Continuation; 12 | import org.jetbrains.annotations.*; 13 | import org.zkoss.zkidea.lang.ZulFileType; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Works around an IntelliJ bug where the *.zul file type association with XML 19 | * is lost after a plugin uninstall/reinstall cycle. (see issue #39) 20 | * 21 | * This activity checks on startup if the association is present and restores it if missing. 22 | * See: https://youtrack.jetbrains.com/issue/IJPL-39443 23 | */ 24 | public class ZulFileTypeRegistrar implements ProjectActivity { 25 | 26 | @Override 27 | public @Nullable Object execute(@NotNull Project project, @NotNull Continuation continuation) { 28 | // Defer to avoid running during startup indexing 29 | ApplicationManager.getApplication().invokeLater(() -> ensureZulFileTypeRegistered(project)); 30 | return null; 31 | } 32 | 33 | private void ensureZulFileTypeRegistered(Project project) { 34 | FileTypeManager fileTypeManager = FileTypeManager.getInstance(); 35 | FileType xmlFileType = XmlFileType.INSTANCE; 36 | 37 | List associations = fileTypeManager.getAssociations(xmlFileType); 38 | boolean isZulAssociated = false; 39 | for (FileNameMatcher matcher : associations) { 40 | if (matcher instanceof ExtensionFileNameMatcher && ZulFileType.EXTENSION.equals(((ExtensionFileNameMatcher) matcher).getExtension())) { 41 | isZulAssociated = true; 42 | break; 43 | } 44 | if ("*.zul".equals(matcher.getPresentableString())) { 45 | isZulAssociated = true; 46 | break; 47 | } 48 | } 49 | 50 | if (!isZulAssociated) { 51 | ApplicationManager.getApplication().runWriteAction(() -> { 52 | // Use the deprecated associate method as it's the safest way to ADD an association 53 | // without replacing all existing ones. This is a targeted fix for a specific bug. 54 | ((FileTypeManagerImpl) fileTypeManager).associate(xmlFileType, new ExtensionFileNameMatcher(ZulFileType.EXTENSION)); 55 | }); 56 | NotificationGroupManager.getInstance() 57 | .getNotificationGroup("news notification") 58 | .createNotification( 59 | "ZK Plugin associated ZUL file type", 60 | "The '*.zul' pattern was automatically associated with the XML file type to work around a known IntelliJ bug.", 61 | NotificationType.INFORMATION 62 | ) 63 | .notify(project); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/maven/ZKMavenArchetypesProvider.java: -------------------------------------------------------------------------------- 1 | /* ZKMavenArchetypesProvider.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 6:08 PM 7/28/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.maven; 13 | 14 | import com.intellij.openapi.diagnostic.Logger; 15 | import org.jetbrains.idea.maven.indices.MavenArchetypesProvider; 16 | import org.w3c.dom.Document; 17 | import org.w3c.dom.Node; 18 | import org.w3c.dom.NodeList; 19 | import org.zkoss.zkidea.project.ZKPathManager; 20 | 21 | import javax.xml.parsers.DocumentBuilder; 22 | import javax.xml.parsers.DocumentBuilderFactory; 23 | import java.io.File; 24 | import java.util.Collection; 25 | import java.util.Collections; 26 | import java.util.LinkedList; 27 | import java.util.List; 28 | 29 | /** 30 | * Provides ZK Maven archetype definitions for IntelliJ IDEA. 31 | *

32 | * This class loads archetype information from a local XML resource file. 33 | * If the archetype catalog file is not found, an empty list is returned. 34 | *

35 | * @author jumperchen 36 | */ 37 | public class ZKMavenArchetypesProvider implements MavenArchetypesProvider { 38 | 39 | 40 | private static final Logger LOG = Logger.getInstance(ZKMavenArchetypesProvider.class); 41 | public static final String MAVEN_ARCHETYPE_PATH = "org/zkoss/zkidea/lang/resources/archetype-catalog.xml"; 42 | public static final String MAVEN_ARCHETYPE_URL = "http://mavensync.zkoss.org/maven2/archetype-catalog.xml"; 43 | 44 | @Override 45 | public Collection getArchetypes() { 46 | File fileSrc = new File(ZKPathManager.getPluginResourcePath(ZKMavenArchetypesProvider.MAVEN_ARCHETYPE_PATH)); 47 | 48 | if (!fileSrc.isFile()) { 49 | LOG.info(fileSrc + " is not found!"); 50 | return Collections.EMPTY_LIST; 51 | } 52 | try { 53 | DocumentBuilderFactory instance = DocumentBuilderFactory.newInstance(); 54 | //Using factory get an instance of document builder 55 | DocumentBuilder db = instance.newDocumentBuilder(); 56 | 57 | //parse using builder to get DOM representation of the XML file 58 | Document parse = db.parse(fileSrc); 59 | NodeList archetypes = parse.getElementsByTagName("archetype"); 60 | List list = new LinkedList(); 61 | for (int i = 0; i < archetypes.getLength(); i++) { 62 | Node item = archetypes.item(i); 63 | 64 | NodeList childNodes = item.getChildNodes(); 65 | String groupId = null; 66 | String artifactId = null; 67 | String version = null; 68 | String description = null; 69 | for (int j = 0; j < childNodes.getLength(); j++) { 70 | Node item1 = childNodes.item(j); 71 | if ("groupId".equals(item1.getNodeName())) { 72 | groupId = item1.getFirstChild().getNodeValue(); 73 | } else if ("artifactId".equals(item1.getNodeName())) { 74 | artifactId = item1.getFirstChild().getNodeValue(); 75 | } else if ("version".equals(item1.getNodeName())) { 76 | version = item1.getFirstChild().getNodeValue(); 77 | } else if ("description".equals(item1.getNodeName())) { 78 | description = item1.getFirstChild().getNodeValue(); 79 | } 80 | } 81 | list.add(new org.jetbrains.idea.maven.model.MavenArchetype(groupId, artifactId, version, "", description)); 82 | } 83 | return list; 84 | }catch(Exception pce) { 85 | LOG.error(pce); 86 | } 87 | return Collections.EMPTY_LIST; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/lang/ZkXmlValidationAnnotator.java: -------------------------------------------------------------------------------- 1 | /* ZkXmlValidationAnnotator.java 2 | 3 | Purpose: 4 | Provides XML structure validation for ZK configuration files 5 | 6 | Description: 7 | Annotator that validates ZK XML files against their schemas, 8 | highlighting missing required elements and structural issues 9 | 10 | History: 11 | Created for enhanced ZK XML validation support 12 | 13 | Copyright (C) 2023 Potix Corporation. All Rights Reserved. 14 | 15 | */ 16 | package org.zkoss.zkidea.lang; 17 | 18 | import com.intellij.codeInspection.ProblemHighlightType; 19 | import com.intellij.lang.annotation.AnnotationHolder; 20 | import com.intellij.lang.annotation.Annotator; 21 | import com.intellij.lang.annotation.HighlightSeverity; 22 | import com.intellij.psi.PsiElement; 23 | import com.intellij.psi.xml.XmlFile; 24 | import com.intellij.psi.xml.XmlTag; 25 | import org.jetbrains.annotations.NotNull; 26 | import org.zkoss.zkidea.dom.ZulDomUtil; 27 | 28 | /** 29 | * Custom XML annotator for ZK Framework files that provides structural validation 30 | * beyond what IntelliJ's built-in XML validation offers. 31 | * 32 | * This annotator specifically checks for required elements in lang-addon.xml 33 | * and highlights missing required elements that might not be caught by standard validation. 34 | */ 35 | public class ZkXmlValidationAnnotator implements Annotator { 36 | 37 | @Override 38 | public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { 39 | if (!(element instanceof XmlTag)) { 40 | return; 41 | } 42 | 43 | XmlTag tag = (XmlTag) element; 44 | 45 | // Only process lang-addon.xml or an XML file with lang-addon namespace 46 | if (!(tag.getContainingFile() instanceof XmlFile) || 47 | (!ZulDomUtil.isLangAddonFile(tag.getContainingFile()) && 48 | !tag.getNamespace().equals(LangAddonSchemaProvider.LANG_ADDON_SCHEMA_URL))) { 49 | return; 50 | } 51 | 52 | validateLangAddonStructure(tag, holder); 53 | } 54 | 55 | private void validateLangAddonStructure(@NotNull XmlTag tag, @NotNull AnnotationHolder holder) { 56 | // Only validate root element 57 | if (!"language-addon".equals(tag.getName()) || tag.getParentTag() != null) { 58 | return; 59 | } 60 | 61 | // Check for required elements 62 | boolean hasAddonName = false; 63 | boolean hasLanguageName = false; 64 | 65 | XmlTag[] subTags = tag.getSubTags(); 66 | for (XmlTag subTag : subTags) { 67 | String name = subTag.getName(); 68 | if ("addon-name".equals(name)) { 69 | hasAddonName = true; 70 | } else if ("language-name".equals(name)) { 71 | hasLanguageName = true; 72 | } 73 | } 74 | 75 | // Report missing required elements 76 | if (!hasAddonName) { 77 | holder.newAnnotation(HighlightSeverity.ERROR, "Missing required element: ") 78 | .range(tag.getTextRange()) 79 | .highlightType(ProblemHighlightType.GENERIC_ERROR) 80 | .create(); 81 | } 82 | 83 | if (!hasLanguageName) { 84 | holder.newAnnotation(HighlightSeverity.ERROR, "Missing required element: ") 85 | .range(tag.getTextRange()) 86 | .highlightType(ProblemHighlightType.GENERIC_ERROR) 87 | .create(); 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /doc/news-popup-improvement.md: -------------------------------------------------------------------------------- 1 | # News Popup Feature Improvement - Enhanced Specification 2 | 3 | ## Feature 1. show news popup more frequently 4 | **Requirements**: Show a new popup when: 5 | 1. The latest news content changed and was different from the cached one 6 | 2. No cached news exists 7 | 3. The latest news content equals to the cached one but it's over 7 days since last shown 8 | 9 | ### File Format 10 | Java Properties format for JDK built-in support 11 | ```properties 12 | lastShown=1234567890000 13 | content=Latest ZK Framework news content here... 14 | ``` 15 | e.g. in mac, the file is at /Users/username/Documents/workspace/PLUGIN/zkidea/build/idea-sandbox/system/plugins/zkidea/zkNews.properties 16 | 17 | **Key Design Decisions**: 18 | - Use Properties format for JDK built-in support (`java.util.Properties`) 19 | - Store timestamps as epoch milliseconds (UTC-agnostic) 20 | 21 | ### Logic Flow 22 | 1. **Cache Check**: Load `zkNews.properties` using `Properties.load()` 23 | 2. **Migration**: ignore old format new cache file. 24 | 3**Time Check**: Compare `lastShown` timestamp with current time 25 | 5. **Display Logic**: Show popup if: 26 | - No cache exists (first run), OR 27 | - More than configured days since `lastShown`, OR 28 | - Content has changed 29 | 6. **Update Cache**: Write new properties with current timestamp and content 30 | 31 | ### Thread Safety & Concurrency 32 | simple design, ignore this case. 33 | 34 | ### File Operations 35 | - **Directory Creation**: Ensure cache directory exists with proper permissions 36 | - **Backup Strategy**: Keep previous cache file as `.bak` during updates 37 | 38 | ### Error Handling Strategy 39 | just write a log 40 | 41 | ### Time Calculations & Configuration 42 | ```java 43 | private static final int INTERVAL_DAYS = 7; 44 | ``` 45 | 46 | ### Testing Strategy 47 | 48 | ### User Experience Enhancements 49 | - **Accessibility**: Screen reader compatible notifications 50 | - **Error Recovery**: Never show error dialogs to users, graceful degradation only 51 | 52 | ## Feature 2. news popup requires users to close 53 | Current popup screenshot at ![newsPopup-0.1.23.png](newsPopup-0.1.23.png). It's closable. 54 | 55 | **Requirements**: Transform auto-dismissing notification to user-controlled popup 56 | - Remove auto-dismiss timer from the current implementation 57 | - Persist popup until user interaction 58 | 59 | 60 | ### Performance Optimizations 61 | from com.intellij.openapi.startup.ProjectActivity javadoc 62 | > Runs an activity after project open. execute gets called inside a coroutine scope spanning from project opening to project closing (or plugin unloading). Flow and any other long-running activities are allowed and natural. 63 | 64 | So a long operation inside doesn't affect intellij. 65 | 66 | ## Implementation Priority (Revised) 67 | 1. **Phase 1**: Core Properties format and enhanced error handling 68 | 2. **Phase 2**: Thread-safe cache service with configuration 69 | 3. **Phase 3**: Sticky popup UI and link functionality 70 | 4. **Phase 4**: Performance optimizations and comprehensive testing 71 | 5. **Phase 5**: Migration support and backward compatibility 72 | 73 | ## Files to Modify 74 | ### Core Implementation 75 | - `src/main/java/org/zkoss/zkidea/newsNotification/ZKNews.java` - Main service logic 76 | 77 | ### Testing 78 | - `src/test/java/org/zkoss/zkidea/newsNotification/ZKNewsTest.java` - Enhanced unit tests 79 | 80 | 81 | ## Security & Compliance Considerations 82 | - no need content validation since it comes from our zk official website 83 | - **File Permissions**: Ensure cache files have appropriate permissions (600) 84 | - **Data Privacy**: No personal data collection, only anonymous usage patterns 85 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | ZKIdea is an IntelliJ IDEA plugin for the ZK Framework, providing enhanced development experience for ZUL files and ZK applications. The plugin extends IntelliJ's XML support to provide ZUL-specific features like code completion, syntax checking, and MVVM annotation support. 8 | 9 | ## Build System & Commands 10 | 11 | This project uses Gradle with the IntelliJ Plugin development plugin. 12 | 13 | ### Common Commands 14 | 15 | - `./gradlew build` - Build the plugin 16 | - `./gradlew test` - Run tests 17 | - `./gradlew runIde` - Launch IntelliJ with the plugin installed for testing 18 | - `./gradlew buildPlugin` - Build distributable plugin ZIP 19 | - `./gradlew publishPlugin` - Publish to JetBrains marketplace 20 | - `./gradlew verifyPlugin` - Validate plugin structure and compatibility 21 | 22 | ### Development Setup 23 | 24 | - Java 17 compatibility (source and target) 25 | - IntelliJ Platform version 2023.3 26 | - Supports IntelliJ versions 233.2 to 251.* 27 | - Dependencies: Maven plugin, Java plugin, JSoup library 28 | 29 | ## Architecture 30 | 31 | ### Core Components 32 | 33 | **Language Support (`lang` package)** 34 | - `ZulLanguage` - Defines ZUL as XML-based language 35 | - `ZulFileType` - File type registration for .zul files 36 | - `ZulSchemaProvider` - Provides XSD schema for validation 37 | - `ZulIconProvider` - Custom icons for ZUL files 38 | 39 | **Code Completion (`completion` package)** 40 | - `MVVMAnnotationCompletionProvider` - Auto-completion for ZK MVVM annotations (@init, @load, @bind, @save, @command, etc.) 41 | 42 | **DOM Support (`dom` package)** 43 | - `ZulDomElementDescriptorProvider` - XML element descriptors for ZUL elements 44 | - `ZulDomElementDescriptorHolder` - Project-level service holding element descriptors 45 | - `ZulDomUtil` - Utilities for ZK file detection and ViewModel analysis 46 | 47 | **Editor Actions (`editorActions` package)** 48 | - `ZulTypedHandler` - Custom typing behavior in ZUL files 49 | - `WebBrowserUrlProvider` - Browser URL generation for ZUL files 50 | - `MavenRunnerPatcher` - Maven integration patches 51 | 52 | **Maven Integration (`maven` package)** 53 | - `ZKMavenArchetypesProvider` - ZK Maven archetypes for project creation 54 | 55 | **Project Management (`project` package)** 56 | - `ZKProjectsManager` - Post-startup activity for project initialization 57 | - `ZKPathManager` - Path management utilities 58 | 59 | **News System (`newsNotification` package)** 60 | - `ZKNews` - Fetches and displays ZK framework news notifications 61 | 62 | ### Plugin Configuration 63 | 64 | The plugin is configured via `plugin.xml` which defines: 65 | - Extension points for XML completion, DOM providers, file types 66 | - Dependencies on Maven and Java plugins 67 | - Post-startup activities for project management and news 68 | - Supported IntelliJ platform versions 69 | 70 | ### Testing 71 | 72 | Tests use JUnit 5 with Mockito for mocking. The test structure mirrors the main source structure. Key test patterns: 73 | - Mock IntelliJ platform components (Project, Application, etc.) 74 | - Use reflection to test private methods when needed 75 | - Test network operations with mocked connections 76 | 77 | ### Key Files 78 | 79 | - `src/main/resources/org/zkoss/zkidea/lang/resources/zul.xsd` - ZUL schema definition 80 | - `src/main/resources/org/zkoss/zkidea/lang/resources/archetype-catalog.xml` - Maven archetypes catalog 81 | - `src/main/resources/META-INF/plugin.xml` - Plugin configuration 82 | 83 | ## Development Notes 84 | 85 | - ZUL files are treated as XML with custom extensions 86 | - The plugin heavily integrates with IntelliJ's XML infrastructure 87 | - MVVM annotations are a key differentiator from standard XML editing 88 | - Maven archetype support enables quick ZK project creation 89 | - Plugin supports news notifications fetched from ZK website -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/completion/MVVMAnnotationCompletionProvider.java: -------------------------------------------------------------------------------- 1 | /* MVVMAnnotationCompletionProvider.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 12:46 PM 7/24/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.completion; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import com.intellij.codeInsight.completion.CompletionContributor; 18 | import com.intellij.codeInsight.completion.CompletionParameters; 19 | import com.intellij.codeInsight.completion.CompletionResultSet; 20 | import com.intellij.codeInsight.completion.CompletionUtilCore; 21 | import com.intellij.codeInsight.lookup.LookupElementBuilder; 22 | import com.intellij.psi.PsiElement; 23 | import com.intellij.psi.PsiFile; 24 | import com.intellij.psi.xml.XmlAttribute; 25 | import com.intellij.psi.xml.XmlAttributeValue; 26 | import com.intellij.psi.xml.XmlFile; 27 | import org.zkoss.zkidea.dom.ZulDomUtil; 28 | 29 | /** 30 | * Provides code completion suggestions for MVVM annotations in ZK XML files. 31 | * This class detects the context of ZK MVVM attributes and offers relevant annotation completions 32 | * (such as @id, @init, @command, etc.) 33 | * 34 | * @author jumperchen 35 | */ 36 | public class MVVMAnnotationCompletionProvider extends CompletionContributor { 37 | @Override 38 | public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result) { 39 | PsiFile psiFile = parameters.getOriginalFile(); 40 | if(psiFile instanceof XmlFile) { 41 | if (ZulDomUtil.isZKFile(psiFile)) { 42 | PsiElement element = parameters.getPosition(); 43 | if (ZulDomUtil.hasViewModel(element)) { 44 | String text = element.getText(); 45 | PsiElement xmlText = element.getParent(); 46 | if (xmlText instanceof XmlAttributeValue) { 47 | // result.stopHere(); 48 | final String name = ((XmlAttribute) xmlText.getParent()).getName(); 49 | final String value = element.getText(); 50 | String query = value.replace(CompletionUtilCore.DUMMY_IDENTIFIER, ""); 51 | String[] strings = query.split(" "); 52 | if (strings.length > 0) { 53 | query = strings[strings.length - 1]; 54 | } 55 | List queryList = Arrays.asList(strings); 56 | CompletionResultSet completionResultSet = result.withPrefixMatcher(query); 57 | 58 | // TODO: support idspace resolver. 59 | // TODO: should ignore different namespace 60 | // get annotation value 61 | String annotVal = ""; 62 | int end = value.indexOf(CompletionUtilCore.DUMMY_IDENTIFIER); 63 | if (end > 0) { 64 | annotVal = value.substring(0, end); 65 | } 66 | String[] split = annotVal.split(" "); 67 | annotVal = split[split.length - 1]; 68 | 69 | if (ZulDomUtil.VIEW_MODEL.equals(name)) { 70 | for (String annot : new String[] {"@id", "@init"}) { 71 | addElement(completionResultSet, queryList, annot); 72 | } 73 | } else { 74 | 75 | // event type 76 | if (name != null && name.startsWith("on")) { 77 | addElement(completionResultSet, queryList, "@command"); 78 | addElement(completionResultSet, queryList, "@global-command"); 79 | } else { 80 | addElement(completionResultSet, queryList, "@init"); 81 | addElement(completionResultSet, queryList, "@load"); 82 | addElement(completionResultSet, queryList, "@bind"); 83 | addElement(completionResultSet, queryList, "@save"); 84 | addElement(completionResultSet, queryList, "@ref"); 85 | addElement(completionResultSet, queryList, "@converter"); 86 | addElement(completionResultSet, queryList, "@validator"); 87 | addElement(completionResultSet, queryList, "@template"); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | private boolean addElement(CompletionResultSet resultSet, List queryList, String annotation) { 96 | for (String query : queryList) 97 | if (query.startsWith(annotation)) 98 | return false; 99 | 100 | resultSet.addElement(LookupElementBuilder.create(annotation + "()")); 101 | return true; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 35 | 37 | 40 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/test/java/org/zkoss/zkidea/newsNotification/ZKNewsTest.java: -------------------------------------------------------------------------------- 1 | /* ZKNewsTest.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Created for testing ZKNews functionality 9 | 10 | Copyright (C) 2021 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.newsNotification; 13 | 14 | import com.intellij.openapi.project.Project; 15 | import org.junit.jupiter.api.BeforeEach; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.extension.ExtendWith; 18 | import org.junit.jupiter.api.io.TempDir; 19 | import org.mockito.Mock; 20 | import org.mockito.MockedStatic; 21 | import org.mockito.junit.jupiter.MockitoExtension; 22 | import org.jsoup.Jsoup; 23 | import org.jsoup.Connection; 24 | import org.jsoup.nodes.Document; 25 | import org.jsoup.nodes.Element; 26 | import org.jsoup.select.Elements; 27 | import org.zkoss.zkidea.project.ZKPathManager; 28 | 29 | import java.io.File; 30 | import java.io.FileInputStream; 31 | import java.io.IOException; 32 | import java.net.SocketTimeoutException; 33 | import java.lang.reflect.Method; 34 | import java.nio.file.Path; 35 | import java.util.Properties; 36 | import kotlin.Unit; 37 | import kotlin.coroutines.Continuation; 38 | 39 | import static org.junit.jupiter.api.Assertions.*; 40 | import static org.mockito.Mockito.*; 41 | 42 | /** 43 | * Unit tests for ZKNews notification functionality. 44 | * Tests the ZK news fetching, caching, and notification display behavior 45 | * using JUnit 5 and Mockito for mocking external dependencies. 46 | */ 47 | @ExtendWith(MockitoExtension.class) 48 | public class ZKNewsTest { 49 | 50 | private ZKNews zkNews; 51 | 52 | @TempDir 53 | Path tempDir; 54 | 55 | @BeforeEach 56 | public void setUp() { 57 | zkNews = new ZKNews(); 58 | } 59 | 60 | @Test 61 | public void testNewsLoaderWithActualWebsite() throws Exception { 62 | // This test makes an actual HTTP request to the ZK website 63 | // It may fail if the website is down or the network is unavailable 64 | 65 | // Act 66 | Method newsLoaderMethod = ZKNews.class.getDeclaredMethod("newsLoader"); 67 | newsLoaderMethod.setAccessible(true); 68 | String result = (String) newsLoaderMethod.invoke(zkNews); 69 | 70 | // Assert 71 | assertNotNull(result, "News content should not be null"); 72 | assertFalse(result.trim().isEmpty(), "News content should not be empty"); 73 | assertTrue(result.endsWith(".") || result.endsWith("!"), "News content should end with proper punctuation"); 74 | 75 | // Basic content validation - should not contain HTML tags 76 | assertFalse(result.contains("<"), "News content should not contain HTML tags"); 77 | assertFalse(result.contains(">"), "News content should not contain HTML tags"); 78 | } 79 | 80 | @Test 81 | public void testLoadCacheFromNonexistentFile() throws Exception { 82 | // Arrange 83 | File nonExistentFile = new File(tempDir.toFile(), "nonexistent.properties"); 84 | 85 | // Act 86 | Method loadCacheMethod = ZKNews.class.getDeclaredMethod("loadCache", File.class); 87 | loadCacheMethod.setAccessible(true); 88 | Properties result = (Properties) loadCacheMethod.invoke(zkNews, nonExistentFile); 89 | 90 | // Assert 91 | assertNotNull(result); 92 | assertTrue(result.isEmpty()); 93 | } 94 | 95 | @Test 96 | public void testUpdateCacheCreatesPropertiesFile() throws Exception { 97 | // Arrange 98 | File cacheFile = new File(tempDir.toFile(), "zkNews.properties"); 99 | String testContent = "Test news content"; 100 | long testTimestamp = System.currentTimeMillis(); 101 | 102 | // Act 103 | Method updateCacheMethod = ZKNews.class.getDeclaredMethod("updateCache", File.class, String.class, long.class); 104 | updateCacheMethod.setAccessible(true); 105 | updateCacheMethod.invoke(zkNews, cacheFile, testContent, testTimestamp); 106 | 107 | // Assert 108 | assertTrue(cacheFile.exists()); 109 | 110 | Properties props = new Properties(); 111 | try (FileInputStream fis = new FileInputStream(cacheFile)) { 112 | props.load(fis); 113 | } 114 | 115 | assertEquals(testContent, props.getProperty("content")); 116 | assertEquals(String.valueOf(testTimestamp), props.getProperty("lastShown")); 117 | } 118 | 119 | @Test 120 | public void testLoadCacheFromExistingFile() throws Exception { 121 | // Arrange 122 | File cacheFile = new File(tempDir.toFile(), "zkNews.properties"); 123 | String testContent = "Cached news content"; 124 | long testTimestamp = System.currentTimeMillis(); 125 | 126 | // Create cache file first 127 | Method updateCacheMethod = ZKNews.class.getDeclaredMethod("updateCache", File.class, String.class, long.class); 128 | updateCacheMethod.setAccessible(true); 129 | updateCacheMethod.invoke(zkNews, cacheFile, testContent, testTimestamp); 130 | 131 | // Act 132 | Method loadCacheMethod = ZKNews.class.getDeclaredMethod("loadCache", File.class); 133 | loadCacheMethod.setAccessible(true); 134 | Properties result = (Properties) loadCacheMethod.invoke(zkNews, cacheFile); 135 | 136 | // Assert 137 | assertNotNull(result); 138 | assertFalse(result.isEmpty()); 139 | assertEquals(testContent, result.getProperty("content")); 140 | assertEquals(String.valueOf(testTimestamp), result.getProperty("lastShown")); 141 | } 142 | 143 | 144 | } -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/editorActions/MavenRunnerPatcher.java: -------------------------------------------------------------------------------- 1 | /* MavenRunnerPatcher.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 11:29 PM 7/31/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.editorActions; 13 | 14 | import java.io.File; 15 | import java.net.MalformedURLException; 16 | import java.net.URL; 17 | 18 | import com.intellij.execution.Executor; 19 | import com.intellij.execution.configurations.JavaParameters; 20 | import com.intellij.execution.configurations.RunProfile; 21 | import com.intellij.execution.runners.JavaProgramPatcher; 22 | import com.intellij.openapi.project.Project; 23 | import com.intellij.openapi.vfs.VfsUtil; 24 | import com.intellij.openapi.vfs.VirtualFile; 25 | import org.jetbrains.idea.maven.execution.MavenRunConfiguration; 26 | import org.jetbrains.idea.maven.project.MavenProject; 27 | import org.jetbrains.idea.maven.project.MavenProjectsManager; 28 | 29 | /** 30 | * This class patches java parameters for maven run configuration to get jetty port 31 | * and set it to system property for {@link WebBrowserUrlProvider} 32 | * 33 | * @author jumperchen 34 | */ 35 | public class MavenRunnerPatcher extends JavaProgramPatcher { 36 | private static String JETTY_ECLIPSE_PLUGIN_GROUPID = "org.eclipse.jetty"; 37 | private static String JETTY_ECLIPSE_PLUGIN_ARTIFACTID = "jetty-maven-plugin"; 38 | 39 | private static String JETTY_PLUGIN_GROUPID = "org.mortbay.jetty"; 40 | private static String JETTY_PLUGIN_ARTIFACTID = JETTY_ECLIPSE_PLUGIN_ARTIFACTID; 41 | 42 | private static String JETTY_OLD_PLUGIN_GROUPID = JETTY_PLUGIN_GROUPID; 43 | private static String JETTY_OLD_PLUGIN_ARTIFACTID = "maven-jetty-plugin"; 44 | 45 | private static String JBOSS_PLUGIN_GROUPID = "org.jboss.as.plugins"; 46 | private static String JBOSS_PLUGIN_ARTIFACTID = "jboss-as-maven-plugin"; 47 | 48 | private static String TOMCAT6_PLUGIN_GROUPID = "org.apache.tomcat.maven"; 49 | private static String TOMCAT6_PLUGIN_ARTIFACTID = "tomcat6-maven-plugin"; 50 | 51 | private static String TOMCAT7_PLUGIN_GROUPID = TOMCAT6_PLUGIN_GROUPID; 52 | private static String TOMCAT7_PLUGIN_ARTIFACTID = "tomcat7-maven-plugin"; 53 | 54 | @Override public void patchJavaParameters(Executor executor, 55 | RunProfile runProfile, JavaParameters javaParameters) { 56 | if (runProfile instanceof MavenRunConfiguration) { 57 | MavenRunConfiguration config = (MavenRunConfiguration) runProfile; 58 | String projectName = null; 59 | Project project = config.getProject(); 60 | 61 | VirtualFile file = null; 62 | try { 63 | file = VfsUtil.findFileByURL( 64 | new URL("file:" + javaParameters.getWorkingDirectory() + File.separator + "pom.xml")); 65 | MavenProject project1 = MavenProjectsManager 66 | .getInstance(project).findProject(file); 67 | if (project1 != null) 68 | projectName = project1.getFinalName(); 69 | } catch (MalformedURLException e) { 70 | } 71 | 72 | for (String key : javaParameters 73 | .getProgramParametersList().getList()) { 74 | 75 | if (key.startsWith("-Djetty.port=")) { 76 | String port = key.substring(13); 77 | System.setProperty("org.zkoss.zkidea.jetty.port." + projectName, port); 78 | break; 79 | } 80 | } 81 | 82 | 83 | // MavenRunConfiguration config = (MavenRunConfiguration) runProfile; 84 | // MavenRunnerParameters parameters = config.getRunnerParameters(); 85 | // if (parameters.isPomExecution()) { 86 | // for (String goal : parameters.getGoals()) { 87 | // // for jetty 88 | // if (goal.contains("jetty:run") || goal.contains("jetty:start")) { 89 | // if (goal.contains(":run")) { 90 | // VirtualFile file = VirtualFileManager 91 | // .getInstance().findFileByUrl( 92 | // "file://" + parameters.getWorkingDirPath() + "/pom.xml"); 93 | // PsiFile directory = PsiManager 94 | // .getInstance(config.getProject()) 95 | // .findFile(file); 96 | //// System.identityHashCode(file); 97 | // MavenProject project = MavenProjectsManager 98 | // .getInstance(directory.getProject()) 99 | // .findProject(file); 100 | // Map modelMap = project 101 | // .getModelMap(); 102 | //// for (MavenPlugin plugin: project.getPlugins()) { 103 | //// plugin.get 104 | //// } 105 | // MavenPlugin plugin = project 106 | // .findPlugin(JETTY_ECLIPSE_PLUGIN_GROUPID, 107 | // JETTY_ECLIPSE_PLUGIN_ARTIFACTID); 108 | // if (plugin == null) { 109 | // plugin = project.findPlugin(JETTY_PLUGIN_GROUPID, 110 | // JETTY_PLUGIN_ARTIFACTID); 111 | // } 112 | // if (plugin == null) { 113 | // plugin = project.findPlugin(JETTY_OLD_PLUGIN_GROUPID, 114 | // JETTY_OLD_PLUGIN_ARTIFACTID); 115 | // } 116 | // String propertyValue = javaParameters 117 | // .getProgramParametersList() 118 | // .getPropertyValue("jetty.port"); 119 | // Element configurationElement = plugin 120 | // .getConfigurationElement(); 121 | // for (Content content : configurationElement 122 | // .getContent()) { 123 | // ((Element)content).getName(); 124 | // } 125 | // System.out.println(); 126 | // } 127 | // } else if (goal.contains("tomcat6:start")) { 128 | // 129 | // } else if (goal.contains("tomcat7:start")) { 130 | // 131 | // } else if (goal.contains("tomcat7:start")) { 132 | // 133 | // } else if (goal.contains("jboss-as:start") || goal.contains("jboss-as:run")) { 134 | // 135 | // } 136 | // } 137 | // } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zkidea 2 | ZK IntelliJ IDEA Plugin 3 | [plugin page on Jetbrains marketplace](https://plugins.jetbrains.com/plugin/7855-zk) 4 | 5 | ## Supported IntelliJ Version 6 | See `patchPluginXml` in [build.gradle](build.gradle) for detail. 7 | 8 | ## Features 9 | 10 | ### ZUL File Support 11 | * **Code Completion**: Provides context-aware suggestions for ZK components, attributes, and events as you type in ZUL files, speeding up development. 12 | * **Go to Declaration**: Quickly navigate from a component tag in a ZUL file to its corresponding Java class (e.g., Composer or ViewModel). 13 | * **Open in Browser**: For Maven projects, this feature intelligently constructs the correct URL to view your ZUL file in a browser, automatically detecting the server port and context path from your `pom.xml`. 14 | 15 | ### ZK Configuration File Support 16 | * **`zk.xml` Code Completion**: Offers code completion for elements and attributes in `zk.xml`, helping you configure your ZK application correctly. 17 | * **`lang-addon.xml` Completion and Validation**: Provides code completion and validates the file to ensure all required elements are present, preventing common configuration errors. 18 | > **Note:** To enable code completion, either use the default filenames (`zk.xml`, `lang-addon.xml`) or add the correct namespace to your custom-named XML file. 19 | > - For `zk.xml`: `xmlns="http://www.zkoss.org/2005/zk/config"` 20 | > - For `lang-addon.xml`: `xmlns="http://www.zkoss.org/2005/zk/lang-addon"` 21 | 22 | ### MVVM Development Support 23 | * **Annotation Completion**: Triggers an auto-popup with a list of ZK MVVM annotations (e.g., `@init`, `@load`, `@bind`) whenever you type the `@` symbol, making it easier to write databinding expressions. 24 | 25 | ### Project Creation 26 | * **Maven Archetypes**: Integrates with IntelliJ's project wizard to let you create new ZK projects from official Maven archetypes, providing a standardized project structure. 27 | 28 | ### ZK News Notification 29 | * **Startup Notification**: Displays a popup with the latest news from the official ZK framework website when you start IntelliJ IDEA, keeping you informed of updates and announcements. 30 | 31 | ### Feedback Menu 32 | * **Easy Access to Support**: Adds a "ZK Feedback" menu under the "Help" menu with quick links to report bugs, request features, or get help from the community. 33 | 34 | ## Getting Started 35 | * [User Guide](https://docs.zkoss.org/zk_installation_guide/create_and_run_your_first_zk_application_with_intellij_and_zkidea) 36 | 37 | ## Development Setup 38 | 39 | ### Running the Plugin in Development Mode 40 | 41 | #### Method 1: Using Gradle runIde Task (Recommended) 42 | ```bash 43 | cd zkidea 44 | ./gradlew runIde 45 | ``` 46 | 47 | This will compile the plugin and launch a new IntelliJ IDEA instance with your plugin pre-installed. 48 | 49 | #### Method 2: Using IDE Development Environment 50 | 51 | 1. **Import Project**: 52 | - Open IntelliJ IDEA 53 | - File → Open → Select the `zkidea` folder 54 | - Import as Gradle project 55 | 56 | 2. **Configure Run Configuration**: 57 | - Go to Run → Edit Configurations 58 | - Click "+" and add new "Gradle" configuration 59 | - Name: "Run Plugin" 60 | - Tasks: `runIde` 61 | - Arguments: (leave empty) 62 | - Gradle project: select your zkidea project 63 | - Working directory: should point to your zkidea folder 64 | 65 | 3. **Run/Debug**: 66 | - Select your "Run Plugin" configuration from the run dropdown 67 | - Click Run (▶) or Debug (🐛) button 68 | - IntelliJ will launch a new instance with the plugin loaded 69 | 70 | #### Development Benefits 71 | - **Hot Reloading**: Make changes and restart the test IDE to see updates 72 | - **Debugging**: Set breakpoints in your plugin code for debugging 73 | - **Live Testing**: Test plugin features immediately without building JARs 74 | - **Rapid Iteration**: Quick development cycle for faster development 75 | 76 | ## License 77 | 78 | * [Apache License version 2](https://github.com/jumperchen/zkidea/blob/master/LICENSE) 79 | 80 | ## Download 81 | 82 | * You can install and update ZK IntelliJ Plugin at IntelliJ Setting > Plugins Marketplace. 83 | * [IntelliJ plugins home](https://plugins.jetbrains.com/plugin/7855) 84 | 85 | ## Demo 86 | TBD 87 | 88 | # Release Process 89 | 90 | 91 | ## 1. Version Updates 92 | Update version numbers in two locations: 93 | - `build.gradle` - Update the `version` property 94 | - `src/main/resources/META-INF/plugin.xml` - Add new version entry to `` 95 | 96 | ## 2. Testing and Validation 97 | ```bash 98 | # Build the plugin 99 | ./gradlew build 100 | 101 | # Run tests 102 | ./gradlew test 103 | 104 | # Verify plugin structure and compatibility 105 | ./gradlew verifyPlugin 106 | 107 | # Test locally in IDE 108 | ./gradlew runIde 109 | ``` 110 | 111 | ## 3. Build Distribution 112 | ```bash 113 | # Create plugin distribution ZIP 114 | ./gradlew buildPlugin 115 | ``` 116 | 117 | ## 4. Post-Release 118 | 1. **Create Git Tag** 119 | ```bash 120 | git tag v0.1.X 121 | git push origin v0.1.X 122 | ``` 123 | 124 | 2. **Update Development Version** 125 | 126 | 3. **Verify Publication** 127 | - Check plugin appears on [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/7855) 128 | - Test installation from marketplace 129 | 130 | 131 | # Troubleshooting 132 | * syntax highlighting in zul doesn't work after restarting IntelliJ IDEA 133 | If you ever uninstalled the plugin before, you might encounter this issue. This is caused by an [IntelliJ bug](https://youtrack.jetbrains.com/issue/IJPL-39443/Plugin-fileType-extensions-will-disappear-after-restart-if-the-plugin-was-uninstalled-once-befores). 134 | The current workaround is to manually add zul file type in IntelliJ IDEA settings: 135 | 1. Go to `Settings` > `Editor` > `File Types` 136 | 2. Under `Recognized File Types` > `XML` 137 | 3. Add `*.zul` to the list of `File name patterns` 138 | 139 | 140 | * For Mac user, if you run this plugin with IntelliJ 14 that crashes on startup, you may refer to [this solution](https://github.com/zkoss/zkidea/issues/10#issuecomment-148628901) -------------------------------------------------------------------------------- /doc/feedback-feature.md: -------------------------------------------------------------------------------- 1 | # Feedback Button 2 | 3 | # Overview 4 | 5 | The IntelliJ IDEA ZK plugin is a valuable tool that helps users to develop applications more quickly and easily. We can use this plugin to collect user feedback. This proposal outlines the benefits of adding a feedback feature to the ZK plugin and how it would be implemented. 6 | 7 | # Difference from Website Feedback 8 | 9 | I suppose those developers who download ZK plugin in their IDEA actually develop a ZK app. So collecting their feedback is more valuable than those visitors who just visit our website. 10 | 11 | # Benefits 12 | 13 | There are several benefits to adding a feedback feature to the ZK plugin. 14 | 15 | First, it would allow users to provide feedback, including bug reports, feature requests, and general feedback. This information would be valuable in improving the plugin or framework and making it more user-friendly. 16 | 17 | Second, the feedback feature would make it easier for users to report bugs and request features. Currently, users have to create tickets on Freshdesk or email feedback. This feedback sends to [info@zkoss.org](mailto:info@zkoss.org). 18 | 19 | Third, the feedback feature would show users that the developers are committed to listening to their feedback and making the plugin better. This would help to build goodwill with users and encourage them to continue using the plugin. 20 | 21 | Encourage user interactivity. 22 | 23 | # Existing Similar Examples 24 | 25 | There are many existing examples of feedback forms in other products. Here are a few examples: 26 | 27 | ## Google Chrome 28 | 29 | Google Chrome has a feedback form that users can use to report bugs, suggest features, or provide other feedback. 30 | 31 | ## Notion 32 | ![notion-help.png](notion-help.png) 33 | 34 | # Feedback feature 35 | 36 | The feedback feature could be implemented by adding a **?** (question mark icon/button) at the bottom-right corner in a zul file like Notion provides. 37 | For non-zul files, do nothing. 38 | When clicking the icon, it shows items: 39 | * Customer Support 40 | * Documentation 41 | * Feedback 42 | * What’s New 43 | 44 | - Customer Support: open the default browser to visit https://potix.freshdesk.com/ 45 | - Documentation: open the default browser to visit zk Developer's Reference website: https://docs.zkoss.org/zk_dev_ref/ 46 | - Feedback: https://www.zkoss.org/support/about/contact 47 | - What’s New: open the default browser to visit the News page 48 | 49 | 50 | # UI design evaluation 51 | The evaluation of potential UI placements for the ZK Plugin feedback feature prioritizes strict adherence to JetBrains UX conventions, which emphasize minimalism and contextuality. Placing the action on the **Main Toolbar** was rejected because this area is reserved for high-frequency, critical developer functions (like VCS and Run/Debug), and integrating a low-frequency utility here violates the principle of UI minimalism and contributes to visual clutter. The option of a **Floating Editor Button** was also rejected as it violates the principle of contextuality; floating elements within the IntelliJ editor are strictly reserved for code-related operations such as refactoring or quick fixes that are triggered by a code selection, not for global plugin support. 52 | 53 | Consequently, the primary recommendation is to integrate the feedback links as a nested Action Group within the main IDE's **Help Menu**. This placement is the canonical standard for auxiliary features, support links, and documentation, consuming zero persistent visual screen real estate. By using a nested group, the plugin can offer multiple clear options (e.g., "Report Bug," "Suggest Feature") while ensuring high discoverability; any action registered here is automatically indexed and searchable via the IDE’s global "Search Everywhere" function, providing predictable access for the user. 54 | 55 | # Design Consideration: Action Implementation Strategy 56 | 57 | A common desire when creating multiple similar actions—like opening different URLs—is to write a single, reusable action class and parameterize it from `plugin.xml`. While this approach seems efficient, it is an anti-pattern in the IntelliJ Action System. The chosen implementation of creating separate, stateless action classes for each menu item is a deliberate and critical design decision based on the following platform constraints: 58 | 59 | ### 1. Action Instantiation from `plugin.xml` 60 | 61 | When the IDE loads the plugin, it parses the `` section in `plugin.xml`. For each `` tag, it instantiates the specified class using its **default, no-argument constructor**. The `plugin.xml` schema provides no mechanism to pass arguments (like a URL string) to the constructor during this process. An attempt to register a reusable `OpenUrlAction` with a `url` field would fail, as the field would never be initialized, leading to a `NullPointerException` when the action is triggered. 62 | 63 | ### 2. Memory Safety and Statelessness 64 | 65 | A core principle of the IntelliJ Action System is that actions must be **stateless**. Action classes should not contain instance fields (non-static variables). The IntelliJ Platform may reuse action instances across different contexts and windows, and holding state can lead to unpredictable behavior and, more importantly, **memory leaks**. An action holding a reference to a URL string or other data prevents that data from being garbage collected, even if the context in which it was created is long gone. 66 | 67 | ### The Correct, Idiomatic Approach 68 | 69 | The correct and recommended pattern is to create a separate, simple class for each action. 70 | 71 | - **Stateless and Safe:** Each action class is completely stateless. The URL is defined as a `private static final String`, which is a compile-time constant and not part of the object's state. This guarantees memory safety. 72 | - **Clear and Explicit:** The `plugin.xml` file clearly maps a specific user action to a dedicated class whose purpose is self-evident. 73 | - **Reliable:** This approach is guaranteed to work correctly with the platform's lifecycle and instantiation logic. 74 | 75 | While it involves a small amount of code repetition, this pattern ensures stability, safety, and compliance with the fundamental design principles of the IntelliJ Platform. -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /src/test/java/org/zkoss/zkidea/lang/LangAddonXmlValidationTest.java: -------------------------------------------------------------------------------- 1 | package org.zkoss.zkidea.lang; 2 | 3 | import com.intellij.codeInspection.ProblemHighlightType; 4 | import com.intellij.lang.annotation.AnnotationBuilder; 5 | import com.intellij.lang.annotation.AnnotationHolder; 6 | import com.intellij.lang.annotation.HighlightSeverity; 7 | import com.intellij.openapi.util.TextRange; 8 | import com.intellij.psi.xml.XmlTag; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.ExtendWith; 12 | import org.mockito.Mock; 13 | import org.mockito.MockedStatic; 14 | import org.mockito.MockitoAnnotations; 15 | import org.mockito.junit.jupiter.MockitoExtension; 16 | import org.zkoss.zkidea.dom.ZulDomUtil; 17 | 18 | import javax.xml.XMLConstants; 19 | import javax.xml.transform.Source; 20 | import javax.xml.transform.stream.StreamSource; 21 | import javax.xml.validation.Schema; 22 | import javax.xml.validation.SchemaFactory; 23 | import javax.xml.validation.Validator; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.StringReader; 27 | import java.nio.file.Files; 28 | import java.nio.file.Paths; 29 | 30 | import static org.junit.jupiter.api.Assertions.*; 31 | import static org.mockito.ArgumentMatchers.any; 32 | import static org.mockito.ArgumentMatchers.anyString; 33 | import static org.mockito.ArgumentMatchers.eq; 34 | import static org.mockito.Mockito.*; 35 | 36 | /** 37 | * Comprehensive test suite for lang-addon.xml validation. 38 | * Tests both XSD schema validation and custom annotator behavior. 39 | */ 40 | @ExtendWith(MockitoExtension.class) 41 | public class LangAddonXmlValidationTest { 42 | private ZkXmlValidationAnnotator annotator; 43 | private Schema langAddonSchema; 44 | 45 | @BeforeEach 46 | public void setUp() { 47 | annotator = new ZkXmlValidationAnnotator(); 48 | 49 | // Load XSD schema for validation tests 50 | loadLangAddonSchema(); 51 | } 52 | 53 | private void loadLangAddonSchema() { 54 | try { 55 | SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 56 | InputStream schemaStream = getClass().getClassLoader() 57 | .getResourceAsStream("org/zkoss/zkidea/lang/resources/lang-addon.xsd"); 58 | if (schemaStream != null) { 59 | Source schemaFile = new StreamSource(schemaStream); 60 | langAddonSchema = factory.newSchema(schemaFile); 61 | } 62 | } catch (Exception e) { 63 | // If schema loading fails, tests will skip XSD validation 64 | langAddonSchema = null; 65 | } 66 | } 67 | 68 | @Test 69 | public void testAnnotatorInitialization() { 70 | assertNotNull(annotator, "Annotator should be initialized"); 71 | } 72 | 73 | @Test 74 | public void testXsdSchemaLoading() { 75 | // Test that the schema loads successfully 76 | assertNotNull(langAddonSchema, "Lang addon schema should load successfully"); 77 | } 78 | 79 | // ================== XSD Schema Validation Tests ================== 80 | 81 | @Test 82 | public void testXsdValidationValidFile() { 83 | if (langAddonSchema == null) { 84 | // Skip test if schema couldn't be loaded 85 | return; 86 | } 87 | 88 | String xmlContent = loadTestFileContent("test-lang-addon.xml"); 89 | assertTrue(validateAgainstSchema(xmlContent), "Valid lang-addon.xml should pass XSD validation"); 90 | } 91 | 92 | @Test 93 | public void testXsdValidationFlexibleOrdering() { 94 | if (langAddonSchema == null) { 95 | return; 96 | } 97 | 98 | // Test mixed order 99 | String mixedOrderContent = loadTestFileContent("test-lang-addon-mixed-order.xml"); 100 | assertTrue(validateAgainstSchema(mixedOrderContent), "Mixed element order should pass XSD validation"); 101 | 102 | // Test reverse order 103 | String reverseOrderContent = loadTestFileContent("test-lang-addon-reverse-order.xml"); 104 | assertTrue(validateAgainstSchema(reverseOrderContent), "Reverse element order should pass XSD validation"); 105 | } 106 | 107 | @Test 108 | public void testXsdValidationMultipleElements() { 109 | if (langAddonSchema == null) { 110 | return; 111 | } 112 | 113 | String xmlContent = loadTestFileContent("test-lang-addon-validation.xml"); 114 | assertTrue(validateAgainstSchema(xmlContent), "Multiple library-property elements should pass XSD validation"); 115 | } 116 | 117 | @Test 118 | public void testXsdValidationNamespaceHandling() { 119 | if (langAddonSchema == null) { 120 | return; 121 | } 122 | 123 | // Test with namespace 124 | String withNamespace = loadTestFileContent("test-lang-addon.xml"); 125 | assertTrue(validateAgainstSchema(withNamespace), "File with namespace should pass XSD validation"); 126 | 127 | // Test without namespace - should fail XSD validation 128 | String withoutNamespace = loadTestFileContent("test-lang-addon-no-namespace.xml"); 129 | assertFalse(validateAgainstSchema(withoutNamespace), "File without namespace should fail XSD validation"); 130 | } 131 | 132 | @Test 133 | public void testXsdValidationInvalidRootElement() { 134 | if (langAddonSchema == null) { 135 | return; 136 | } 137 | 138 | String invalidXml = """ 139 | 140 | 141 | Test 142 | test 143 | 144 | """; 145 | 146 | assertFalse(validateAgainstSchema(invalidXml), "Invalid root element should fail XSD validation"); 147 | } 148 | 149 | /** 150 | * Helper method to validate XML content against the lang-addon schema 151 | */ 152 | private boolean validateAgainstSchema(String xmlContent) { 153 | if (langAddonSchema == null) { 154 | return true; // Skip validation if schema not available 155 | } 156 | 157 | try { 158 | Validator validator = langAddonSchema.newValidator(); 159 | Source xmlFile = new StreamSource(new StringReader(xmlContent)); 160 | validator.validate(xmlFile); 161 | return true; 162 | } catch (Exception e) { 163 | return false; 164 | } 165 | } 166 | 167 | /** 168 | * Helper method to load test XML file content as string 169 | */ 170 | private String loadTestFileContent(String filename) { 171 | try { 172 | return Files.readString(Paths.get("src/test/resources/" + filename)); 173 | } catch (IOException e) { 174 | fail("Could not load test file: " + filename + " - " + e.getMessage()); 175 | return ""; 176 | } 177 | } 178 | 179 | } -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/newsNotification/ZKNews.java: -------------------------------------------------------------------------------- 1 | /* ZKNews.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Thu Sep 09 16:35:58 CST 2021, Created by katherine 9 | 10 | Copyright (C) 2021 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.newsNotification; 13 | 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.nio.file.Files; 19 | import java.nio.file.Path; 20 | import java.nio.file.Paths; 21 | import java.util.Properties; 22 | 23 | import com.intellij.ide.BrowserUtil; 24 | import com.intellij.notification.NotificationAction; 25 | import com.intellij.notification.NotificationGroupManager; 26 | import com.intellij.notification.NotificationType; 27 | import com.intellij.openapi.diagnostic.Logger; 28 | import com.intellij.openapi.project.Project; 29 | import com.intellij.openapi.startup.ProjectActivity; 30 | import kotlin.Unit; 31 | import kotlin.coroutines.Continuation; 32 | import org.jetbrains.annotations.*; 33 | import org.jsoup.Jsoup; 34 | import org.jsoup.nodes.Document; 35 | import org.jsoup.nodes.Element; 36 | 37 | import org.zkoss.zkidea.project.*; 38 | 39 | /** 40 | * Manages ZK Framework news notifications within the IDE. 41 | * Fetches and displays ZK framework news, updates, and announcements 42 | * to keep developers informed about the latest ZK developments. 43 | * 44 | *

This class implements {@link ProjectActivity} instead of the deprecated 45 | * {@code StartupActivity} to provide non-blocking asynchronous execution during 46 | * project startup. This design choice is crucial for maintaining IDE responsiveness 47 | * when the ZK website has slow response times or becomes unreachable.

48 | * 49 | * @since 0.1.23 50 | */ 51 | public class ZKNews implements ProjectActivity { 52 | public static final String ZK_WEBSITE_URL = "https://www.zkoss.org?ide=in"; 53 | public static final String ZK_NEWS_FETCH_URL = ZK_WEBSITE_URL + "&fetch=true"; 54 | private static final Logger logger = Logger.getInstance(ZKNews.class); 55 | private static final int INTERVAL_DAYS = 7; 56 | private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000L; 57 | 58 | /** 59 | * Executes the news fetching process asynchronously when a project starts. 60 | * 61 | *

This method is called by the IntelliJ Platform on a background thread, 62 | * ensuring that network operations don't block the IDE startup process. 63 | * The method safely handles exceptions to prevent startup failures.

64 | * 65 | * @param project the project being opened (not null) 66 | * @param continuation the coroutine continuation (required by ProjectActivity interface) 67 | * @return null (no return value needed for this startup activity) 68 | */ 69 | @Override 70 | public @Nullable Object execute(@NotNull Project project, @NotNull Continuation continuation) { 71 | if (!project.isDefault()) { 72 | try { 73 | newsNotificationPopup(project); 74 | } catch (Exception e) { 75 | logger.debug("Failed to fetch ZK news", e); 76 | } 77 | } 78 | return null; 79 | } 80 | 81 | /** 82 | * Handles the news notification display logic with Properties-based caching. 83 | * 84 | *

Shows popup when:

85 | *
    86 | *
  • No cached news exists (first run)
  • 87 | *
  • Latest news content differs from cached content
  • 88 | *
  • Same content but over 7 days since last shown
  • 89 | *
90 | * 91 | * @param project the current project for notification context 92 | * @throws IOException if file operations or network requests fail 93 | */ 94 | private void newsNotificationPopup(Project project) throws IOException { 95 | Path cacheDir = Paths.get(ZKPathManager.getPluginTempPath()); 96 | Files.createDirectories(cacheDir); 97 | 98 | File zkNewsFile = new File(cacheDir.toFile(), "zkNews.properties"); 99 | 100 | String currentNews = newsLoader(); 101 | long currentTime = System.currentTimeMillis(); 102 | 103 | boolean shouldShow = shouldShowNotification(zkNewsFile, currentNews, currentTime); 104 | 105 | if (shouldShow) { 106 | updateCache(zkNewsFile, currentNews, currentTime); 107 | NotificationGroupManager.getInstance().getNotificationGroup("news notification") 108 | .createNotification(currentNews, NotificationType.INFORMATION) 109 | .addAction(NotificationAction.createSimple("Visit zkoss.org for detail.", 110 | () -> BrowserUtil.browse(ZK_WEBSITE_URL + "&read=more#news-sec"))) 111 | .notify(project); 112 | } 113 | } 114 | 115 | /** 116 | * Determines whether a notification should be shown based on cache state and news content. 117 | * 118 | * @param zkNewsFile 119 | * @param currentNews the latest news content 120 | * @param currentTime the current timestamp 121 | * @return true if notification should be shown 122 | */ 123 | private boolean shouldShowNotification(File zkNewsFile, String currentNews, long currentTime) { 124 | Properties cache = loadCache(zkNewsFile); 125 | String cachedNews = cache.getProperty("content", ""); 126 | String lastShownStr = cache.getProperty("lastShown", "0"); 127 | 128 | if (cache.isEmpty() || cachedNews.isEmpty()) { 129 | return true; 130 | } 131 | 132 | if (!currentNews.equals(cachedNews)) { 133 | return true; 134 | } 135 | 136 | long lastShown = Long.parseLong(lastShownStr); 137 | long daysSinceLastShown = (currentTime - lastShown) / MILLIS_PER_DAY; 138 | 139 | return daysSinceLastShown >= INTERVAL_DAYS; 140 | } 141 | 142 | /** 143 | * Loads cache properties from the specified file. 144 | * Returns empty properties if file doesn't exist or can't be read. 145 | * 146 | * @param cacheFile the cache file to read from 147 | * @return Properties object with cached data 148 | */ 149 | private Properties loadCache(File cacheFile) { 150 | Properties cache = new Properties(); 151 | if (cacheFile.exists()) { 152 | try (FileInputStream fis = new FileInputStream(cacheFile)) { 153 | cache.load(fis); 154 | } catch (IOException e) { 155 | logger.debug("Failed to load cache file", e); 156 | } 157 | } 158 | return cache; 159 | } 160 | 161 | /** 162 | * Updates cache with new content and timestamp. 163 | * 164 | * @param cacheFile the cache file to update 165 | * @param content the news content to cache 166 | * @param timestamp the timestamp when shown 167 | */ 168 | private void updateCache(File cacheFile, String content, long timestamp) { 169 | Properties cache = new Properties(); 170 | cache.setProperty("content", content); 171 | cache.setProperty("lastShown", String.valueOf(timestamp)); 172 | 173 | try { 174 | Files.createDirectories(cacheFile.getParentFile().toPath()); 175 | } catch (IOException e) { 176 | logger.debug("Failed to create cache directory", e); 177 | return; 178 | } 179 | 180 | try (FileOutputStream fos = new FileOutputStream(cacheFile)) { 181 | cache.store(fos, "ZK News Cache - lastShown timestamp in epoch milliseconds"); 182 | } catch (IOException e) { 183 | logger.debug("Failed to update cache file", e); 184 | } 185 | } 186 | 187 | /** 188 | * Fetches the latest news content from the ZK website. 189 | * 190 | *

Uses JSoup to scrape news content with a 5-second timeout to prevent 191 | * hanging when the website is slow or unreachable. The timeout ensures 192 | * that this background operation doesn't impact IDE responsiveness.

193 | * 194 | * @return the formatted news text 195 | * @throws IOException if the network request fails or times out 196 | */ 197 | private String newsLoader() throws IOException { 198 | Document doc = Jsoup.connect(ZK_NEWS_FETCH_URL).timeout(5000).get(); 199 | Element element = doc.select("#row-news-inner-box .desc span").get(0); 200 | String tagName = element.tagName(); 201 | String news = element.toString().replace("<" + tagName + ">","").replace("",""); 202 | if (!news.endsWith("!") && !news.endsWith(".")) 203 | news = news.concat("."); 204 | return news; 205 | } 206 | } -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/dom/ZkDomElementDescriptorHolder.java: -------------------------------------------------------------------------------- 1 | /* ZulDomElementDescriptorHolder.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | Jul 10 5:46 PM 2015, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | 12 | */ 13 | package org.zkoss.zkidea.dom; 14 | 15 | import com.intellij.javaee.ExternalResourceManager; 16 | import com.intellij.openapi.diagnostic.Logger; 17 | import com.intellij.openapi.project.Project; 18 | import com.intellij.openapi.vfs.VfsUtil; 19 | import com.intellij.openapi.vfs.VirtualFile; 20 | import com.intellij.psi.PsiFile; 21 | import com.intellij.psi.PsiManager; 22 | import com.intellij.psi.util.CachedValue; 23 | import com.intellij.psi.util.CachedValueProvider; 24 | import com.intellij.psi.util.CachedValuesManager; 25 | import com.intellij.psi.util.PsiModificationTracker; 26 | import com.intellij.psi.xml.XmlFile; 27 | import com.intellij.psi.xml.XmlTag; 28 | import com.intellij.xml.XmlElementDescriptor; 29 | import com.intellij.xml.impl.schema.XmlNSDescriptorImpl; 30 | import org.jetbrains.annotations.NotNull; 31 | import org.jetbrains.annotations.Nullable; 32 | import org.zkoss.zkidea.lang.*; 33 | import org.zkoss.zkidea.lang.ZkConfigSchemaProvider; 34 | import org.zkoss.zkidea.lang.LangAddonSchemaProvider; 35 | 36 | import java.net.MalformedURLException; 37 | import java.net.URL; 38 | import java.util.HashMap; 39 | import java.util.Map; 40 | 41 | /** 42 | * Project-level service that manages and caches XML element descriptors for ZK Framework files. 43 | * This service provides schema-based validation and code completion for both ZUL files and 44 | * ZK configuration files (zk.xml) with automatic default namespace support. 45 | * 46 | *

This class acts as a bridge between IntelliJ's XML infrastructure and ZK-specific schemas, 47 | * enabling seamless code completion and validation without requiring explicit namespace declarations 48 | * in ZK files.

49 | * 50 | *

Architecture and IntelliJ Integration:

51 | *
    52 | *
  1. Service Registration: Registered as a project-level service in plugin.xml
  2. 53 | *
  3. File Type Detection: Uses {@link ZulDomUtil#isZKFile} to identify ZK files
  4. 54 | *
  5. Schema Resolution: Loads XSD schemas via {@link ExternalResourceManager}
  6. 55 | *
  7. Descriptor Caching: Uses {@link CachedValue} for performance optimization
  8. 56 | *
  9. Default Namespace: Provides automatic namespace resolution like native XML support
  10. 57 | *
58 | * 59 | *

Supported File Types:

60 | *
    61 | *
  • ZUL_FILE: .zul files using zul.xsd schema with namespace "http://www.zkoss.org/2005/zul"
  • 62 | *
  • ZK_CONFIG_FILE: zk.xml files using zk.xsd schema with namespace "http://www.zkoss.org/2005/zk/config"
  • 63 | *
  • LANG_ADDON_FILE: lang-addon.xml files using lang-addon.xsd schema with namespace "http://www.zkoss.org/2005/zk/lang-addon"
  • 64 | *
65 | * 66 | *

How Default Namespace Works:

67 | *

Similar to how IntelliJ handles HTML files without explicit namespace declarations, 68 | * this service enables ZK files to work without {@code xmlns} declarations by:

69 | *
    70 | *
  1. Detecting file types based on filename/extension patterns
  2. 71 | *
  3. Loading appropriate XSD schema descriptors
  4. 72 | *
  5. Calling {@code XmlNSDescriptorImpl.getElementDescriptor(tagName, defaultNamespace)}
  6. 73 | *
  7. Providing schema-aware completion and validation automatically
  8. 74 | *
75 | * 76 | *

Performance Considerations:

77 | *
    78 | *
  • XSD descriptors are cached per project using {@link CachedValuesManager}
  • 79 | *
  • Cache invalidation tied to {@link PsiModificationTracker#MODIFICATION_COUNT}
  • 80 | *
  • Thread-safe descriptor creation with synchronized access
  • 81 | *
82 | * 83 | * @see ExternalResourceManager IntelliJ service for resolving schema locations 84 | * @see XmlNSDescriptorImpl IntelliJ's XSD-based namespace descriptor implementation 85 | * @see IntelliJ Platform XML DOM API 86 | * @see IntelliJ Platform Plugin Services 87 | * @author jumperchen 88 | */ 89 | public class ZkDomElementDescriptorHolder { 90 | private static final Logger LOG = Logger.getInstance(ZkDomElementDescriptorHolder.class); 91 | private final Project myProject; 92 | private final Map> myDescriptorsMap = new HashMap<>(); 93 | 94 | public ZkDomElementDescriptorHolder(Project project) { 95 | this.myProject = project; 96 | } 97 | 98 | public static ZkDomElementDescriptorHolder getInstance(@NotNull Project project) { 99 | return project.getService(ZkDomElementDescriptorHolder.class); 100 | } 101 | 102 | @Nullable 103 | public XmlElementDescriptor getDescriptor(@NotNull XmlTag tag) { 104 | ZkDomElementDescriptorHolder.FileKind kind = getFileKind(tag.getContainingFile()); 105 | if (kind == null) { 106 | return null; 107 | } else { 108 | XmlNSDescriptorImpl desc; 109 | synchronized (this) { 110 | desc = this.tryGetOrCreateDescriptor(kind); 111 | if (desc == null) { 112 | return null; 113 | } 114 | } 115 | 116 | LOG.assertTrue(tag.isValid()); 117 | LOG.assertTrue(desc.isValid()); 118 | return desc.getElementDescriptor(tag.getName(), desc.getDefaultNamespace()); 119 | } 120 | } 121 | 122 | @Nullable 123 | private XmlNSDescriptorImpl tryGetOrCreateDescriptor(final ZkDomElementDescriptorHolder.FileKind kind) { 124 | CachedValue result = this.myDescriptorsMap.get(kind); 125 | if (result == null) { 126 | result = CachedValuesManager.getManager(this.myProject) 127 | .createCachedValue(() -> 128 | CachedValueProvider.Result.create(ZkDomElementDescriptorHolder.this.doCreateDescriptor(kind), 129 | PsiModificationTracker.MODIFICATION_COUNT), false); 130 | this.myDescriptorsMap.put(kind, result); 131 | } 132 | 133 | return result.getValue(); 134 | } 135 | 136 | @Nullable 137 | private XmlNSDescriptorImpl doCreateDescriptor(ZkDomElementDescriptorHolder.FileKind kind) { 138 | String schemaUrl = kind.getSchemaUrl(); 139 | String location = ExternalResourceManager.getInstance().getResourceLocation(schemaUrl, ""); 140 | if (schemaUrl.equals(location)) { 141 | return null; 142 | } else { 143 | VirtualFile schema; 144 | try { 145 | schema = VfsUtil.findFileByURL(new URL(location)); 146 | } catch (MalformedURLException var7) { 147 | return null; 148 | } 149 | if (schema == null) { 150 | return null; 151 | } else { 152 | PsiFile psiFile = PsiManager.getInstance(this.myProject).findFile(schema); 153 | if (!(psiFile instanceof XmlFile)) { 154 | return null; 155 | } else { 156 | XmlNSDescriptorImpl result = new XmlNSDescriptorImpl(); 157 | result.init(psiFile); 158 | return result; 159 | } 160 | } 161 | } 162 | } 163 | 164 | @Nullable 165 | private static ZkDomElementDescriptorHolder.FileKind getFileKind(PsiFile file) { 166 | if (ZulDomUtil.isLangAddonFile(file)) { 167 | return FileKind.LANG_ADDON_FILE; 168 | } else if (ZulDomUtil.isZkConfigFile(file)) { 169 | return FileKind.ZK_CONFIG_FILE; 170 | } else if (ZulDomUtil.isZKFile(file)) { 171 | return FileKind.ZUL_FILE; 172 | } else { 173 | return null; 174 | } 175 | } 176 | 177 | private enum FileKind { 178 | ZUL_FILE { 179 | public String getSchemaUrl() { 180 | return ZulSchemaProvider.ZUL_PROJECT_SCHEMA_URL; 181 | } 182 | }, 183 | ZK_CONFIG_FILE { 184 | public String getSchemaUrl() { 185 | return ZkConfigSchemaProvider.ZK_CONFIG_PROJECT_SCHEMA_URL; 186 | } 187 | }, 188 | LANG_ADDON_FILE { 189 | public String getSchemaUrl() { 190 | return LangAddonSchemaProvider.LANG_ADDON_SCHEMA_URL; 191 | } 192 | }; 193 | 194 | FileKind() { 195 | } 196 | 197 | public abstract String getSchemaUrl(); 198 | } 199 | } -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/editorActions/WebBrowserUrlProvider.java: -------------------------------------------------------------------------------- 1 | /* WebBrowserUrlProvider.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 4:56 PM 7/31/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.editorActions; 13 | 14 | import com.intellij.execution.ExecutionManager; 15 | import com.intellij.execution.process.ProcessHandler; 16 | import com.intellij.ide.browsers.OpenInBrowserRequest; 17 | import com.intellij.openapi.fileTypes.FileType; 18 | import com.intellij.openapi.project.Project; 19 | import com.intellij.openapi.vfs.VirtualFile; 20 | import com.intellij.util.Url; 21 | import com.intellij.util.Urls; 22 | import org.jdom.Content; 23 | import org.jdom.Element; 24 | import org.jetbrains.annotations.NotNull; 25 | import org.jetbrains.idea.maven.model.MavenPlugin; 26 | import org.jetbrains.idea.maven.project.MavenProject; 27 | import org.jetbrains.idea.maven.project.MavenProjectsManager; 28 | import org.zkoss.zkidea.dom.ZulDomUtil; 29 | 30 | /** 31 | * Provides a URL for ZUL files for the "Open in Browser" feature. 32 | *

33 | * This class is responsible for generating the correct URL when a user right-clicks on a ZUL file 34 | * and selects "Open in Browser." It intelligently constructs the URL by analyzing the Maven `pom.xml` 35 | * to determine the web application's context path and server port. 36 | *

37 | * Specifically, it parses the configuration of various Jetty Maven plugins to find the correct port and context path. 38 | * It also works in conjunction with {@link MavenRunnerPatcher} to get the port from a running Jetty instance, 39 | * ensuring that even dynamically assigned ports are handled correctly. This provides a seamless development 40 | * experience for ZK developers using Maven. 41 | * 42 | * @author jumperchen 43 | */ 44 | public class WebBrowserUrlProvider 45 | extends com.intellij.ide.browsers.WebBrowserUrlProvider { 46 | 47 | private static String MAVEN_STANDARD_WEBAPP_PATH = "src/main/webapp/"; 48 | protected Url getUrl(@NotNull OpenInBrowserRequest request, @NotNull VirtualFile file) throws WebBrowserUrlProvider.BrowserException { 49 | Project project = request.getProject(); 50 | MavenProjectsManager instance = MavenProjectsManager 51 | .getInstance(project); 52 | if (instance.isMavenizedProject()) { 53 | MavenProject project1 = instance.findProject(file); 54 | 55 | String port = "8080"; 56 | 57 | FileType fileType = file.getFileType(); 58 | if (ZulDomUtil.isZKFile(file.getExtension())) { 59 | ProcessHandler[] runningProcesses = ExecutionManager 60 | .getInstance(request.getProject()) 61 | .getRunningProcesses(); 62 | // PsiFile file1 = PsiManager.getInstance(project).findFile(file); 63 | MavenProject maven = instance 64 | .findContainingProject(file); 65 | if (maven != null) { 66 | String contextPath = maven.getMavenId().getArtifactId(); 67 | String filePath = file.getPath(); 68 | if (filePath.contains(MAVEN_STANDARD_WEBAPP_PATH)) { 69 | filePath = trim(filePath, MAVEN_STANDARD_WEBAPP_PATH); 70 | } else { 71 | MavenPlugin plugin = maven 72 | .findPlugin("org.mortbay.jetty", 73 | "jetty-maven-plugin"); 74 | if (plugin != null) { 75 | for (Content content : plugin.getConfigurationElement().getContent()) { 76 | if (content instanceof Element) { 77 | Element element = (Element) content; 78 | if ("webAppSourceDirectory".equals(element.getName())) { 79 | String value = element.getValue(); 80 | filePath = trim(filePath, value); 81 | } else if ("webAppConfig".equals(element.getName())) { 82 | for (Content c : element.getContent()) { 83 | if (c instanceof Element) { 84 | Element el = (Element) c; 85 | 86 | if ("contextPath".equals(el.getName())) { 87 | contextPath = el.getValue(); 88 | break; 89 | } 90 | } 91 | } 92 | } else if ("webApp".equals(element.getName())) { 93 | for (Content c : element.getContent()) { 94 | if (c instanceof Element) { 95 | Element el = (Element) c; 96 | 97 | if ("contextPath".equals(el.getName())) { 98 | contextPath = el.getValue(); 99 | break; 100 | } 101 | } 102 | } 103 | } else if ("connectors".equals(element.getName())) { 104 | for (Content c : element.getContent()) { 105 | if (c instanceof Element) { 106 | Element el = (Element) c; 107 | 108 | if ("connectors".equals(el.getName())) { 109 | for (Content c0 : element.getContent()) { 110 | if (c instanceof Element) { 111 | Element el0 = (Element) c0; 112 | 113 | if ("port".equals(el0.getName())) { 114 | port = el0.getValue(); 115 | break; 116 | } 117 | } 118 | } 119 | break; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } else { 127 | plugin = maven 128 | .findPlugin("org.eclipse.jetty", 129 | "jetty-maven-plugin"); 130 | if (plugin != null) { 131 | for (Content content : plugin 132 | .getConfigurationElement().getContent()) { 133 | if (content instanceof Element) { 134 | Element element = (Element) content; 135 | if ("webAppSourceDirectory" 136 | .equals(element.getName())) { 137 | String value = element.getValue(); 138 | filePath = trim(filePath, value); 139 | } else if ("webApp".equals(element.getName())) { 140 | for (Content c : element.getContent()) { 141 | if (c instanceof Element) { 142 | Element el = (Element) c; 143 | 144 | if ("contextPath".equals(el.getName())) { 145 | contextPath = el.getValue(); 146 | break; 147 | } 148 | } 149 | } 150 | } else if ("httpConnector".equals(element.getName())) { 151 | for (Content c : element.getContent()) { 152 | if (c instanceof Element) { 153 | Element el = (Element) c; 154 | 155 | if ("port".equals(el.getName())) { 156 | port = el.getValue(); 157 | break; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } else { 165 | plugin = maven 166 | .findPlugin("org.mortbay.jetty", 167 | "maven-jetty-plugin"); 168 | if (plugin != null) { 169 | for (Content content : plugin 170 | .getConfigurationElement().getContent()) { 171 | if (content instanceof Element) { 172 | Element element = (Element) content; 173 | if ("webAppSourceDirectory" 174 | .equals(element.getName())) { 175 | String value = element.getValue(); 176 | filePath = trim(filePath, value); 177 | } else if ("webAppConfig".equals(element.getName())) { 178 | for (Content c : element.getContent()) { 179 | if (c instanceof Element) { 180 | Element el = (Element) c; 181 | 182 | if ("contextPath".equals(el.getName())) { 183 | contextPath = el.getValue(); 184 | break; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | } 191 | } 192 | } 193 | } 194 | } 195 | if (contextPath.startsWith("/")) 196 | contextPath = contextPath.substring(1); 197 | if (filePath.startsWith("/")) 198 | filePath = filePath.substring(1); 199 | if (System.getProperty("org.zkoss.zkidea.jetty.port." + maven.getFinalName()) != null) 200 | port = System.getProperty("org.zkoss.zkidea.jetty.port." + maven.getFinalName()); 201 | return Urls.newHttpUrl("localhost:" + port + "/", contextPath + "/" + filePath); 202 | } 203 | } 204 | } 205 | return null; 206 | } 207 | 208 | private String trim(String filePath, String directory) { 209 | if (filePath.contains(directory)) { 210 | return filePath 211 | .substring(filePath.indexOf(directory) + (directory.length())); 212 | } 213 | return filePath; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/org/zkoss/zkidea/project/ZKProjectsManager.java: -------------------------------------------------------------------------------- 1 | /* ZKProjectsManager.java 2 | 3 | Purpose: 4 | 5 | Description: 6 | 7 | History: 8 | 12:14 PM 7/24/15, Created by jumperchen 9 | 10 | Copyright (C) 2015 Potix Corporation. All Rights Reserved. 11 | */ 12 | package org.zkoss.zkidea.project; 13 | 14 | import com.intellij.javaee.ExternalResourceManager; 15 | import com.intellij.openapi.application.ApplicationManager; 16 | import com.intellij.openapi.diagnostic.Logger; 17 | import com.intellij.openapi.project.*; 18 | import com.intellij.openapi.startup.StartupActivity; 19 | import com.intellij.openapi.util.io.FileUtil; 20 | import org.jetbrains.annotations.NotNull; 21 | import org.jetbrains.idea.maven.utils.MavenUtil; 22 | import org.w3c.dom.*; 23 | import org.zkoss.zkidea.lang.ZulSchemaProvider; 24 | import org.zkoss.zkidea.maven.ZKMavenArchetypesProvider; 25 | 26 | import javax.xml.parsers.*; 27 | import java.io.*; 28 | import java.net.*; 29 | import java.util.Date; 30 | import java.util.concurrent.atomic.AtomicBoolean; 31 | 32 | /** 33 | * This class implements StartupActivity.DumbAware to perform initialization tasks 34 | * when a project is opened in the IDE. 35 | * 36 | * Key responsibilities: 37 | * - Initializes ZK-related resources for new projects 38 | * - Updates and manages ZUL schema files for XML validation 39 | * - Updates ZK Maven archetypes with the latest one from the remote repository 40 | * - Handles resource synchronization with remote ZK repositories 41 | * 42 | * The manager ensures that: 43 | * 1. The latest ZUL schema (zul.xsd) is downloaded and maintained 44 | * 2. Maven archetypes are kept up to date 45 | * 3. Resources are properly registered with IntelliJ's ExternalResourceManager 46 | * 47 | * @author jumperchen 48 | */ 49 | public class ZKProjectsManager implements StartupActivity.DumbAware { 50 | 51 | private static final Logger LOG = Logger.getInstance(ZKProjectsManager.class); 52 | 53 | private final AtomicBoolean isInitialized = new AtomicBoolean(); 54 | 55 | public void runActivity(@NotNull Project project) { 56 | // fix for issue #20 57 | if (!project.isDefault()) { 58 | ZKProjectsManager.this.doInit(project); 59 | } 60 | } 61 | 62 | private void doInit(@NotNull Project project) { 63 | synchronized (this.isInitialized) { 64 | if (!this.isInitialized.getAndSet(true)) { 65 | // fetch the lastes zul.xsd file 66 | MavenUtil.runWhenInitialized(project, new DumbAwareRunnable() { 67 | public void run() { 68 | // TODO: support with project's ZK version 69 | updateZulSchema(); 70 | updateMavenArchetype(); 71 | } 72 | }); 73 | } 74 | } 75 | } 76 | 77 | private void copyResourceToFile(String path, File toFile) { 78 | InputStream in = getClass().getClassLoader().getResourceAsStream(path); 79 | FileOutputStream out = null; 80 | try { 81 | if (!toFile.exists()) { 82 | // Bug fixed #7 83 | toFile.getParentFile().mkdirs(); 84 | toFile.createNewFile(); 85 | } 86 | 87 | out = new FileOutputStream(toFile); 88 | if (in != null) { 89 | FileUtil.copy(in, out); 90 | } 91 | } catch (Exception e) { 92 | LOG.error(e); 93 | } finally { 94 | if (in != null) { 95 | try { 96 | in.close(); 97 | } catch (IOException e) { 98 | } 99 | } 100 | if (out != null) { 101 | try { 102 | out.close(); 103 | } catch (IOException e) { 104 | } 105 | } 106 | } 107 | } 108 | 109 | private static boolean equals(Object a, Object b) { 110 | return (a == b) || (a != null && a.equals(b)); 111 | } 112 | 113 | private void updateZulSchema() { 114 | try { 115 | final String pluginResourcePath = "file:" + ZKPathManager.getPluginResourcePath(ZulSchemaProvider.ZUL_PROJECT_SCHEMA_PATH); 116 | LOG.debug("PluginResourcePath: " + pluginResourcePath); 117 | 118 | ExternalResourceManager instance = ExternalResourceManager.getInstance(); 119 | if (!equals(pluginResourcePath, instance.getResourceLocation(ZulSchemaProvider.ZUL_PROJECT_SCHEMA_PATH, ""))) { 120 | ApplicationManager.getApplication().invokeLater(new Runnable() { 121 | @Override 122 | public void run() { 123 | ApplicationManager.getApplication().runWriteAction(new Runnable() { 124 | @Override 125 | public void run() { 126 | ExternalResourceManager.getInstance().addResource(ZulSchemaProvider.ZUL_PROJECT_SCHEMA_URL, pluginResourcePath); 127 | } 128 | }); 129 | } 130 | }); 131 | } 132 | 133 | File fileSrc = new File(ZKPathManager.getPluginResourcePath(ZulSchemaProvider.ZUL_PROJECT_SCHEMA_PATH)); 134 | if (!fileSrc.isFile() || fileSrc.length() < 20) { 135 | // copy from jar file. 136 | copyResourceToFile(ZulSchemaProvider.ZUL_PROJECT_SCHEMA_PATH, fileSrc); 137 | } 138 | if (fileSrc.lastModified() > new Date().getTime()) { 139 | return;// won't support this case 140 | } 141 | 142 | LOG.info("Downloading latest zul file: " + ZulSchemaProvider.ZUL_PROJECT_SCHEMA_URL); 143 | 144 | File fileTmp = new File(fileSrc.getAbsolutePath() + ".tmp"); 145 | byte[] download = download( 146 | new URL(ZulSchemaProvider.ZUL_PROJECT_SCHEMA_URL)); 147 | if (download != null && download.length == 0) 148 | return; // try next time. 149 | FileUtil.writeToFile(fileTmp, download); 150 | // HttpRequests.request(ZulSchemaProvider.ZUL_PROJECT_SCHEMA_URL).saveToFile(fileTmp, ProgressManager.getGlobalProgressIndicator()); 151 | double origin = getSchemaVersion(fileSrc); 152 | double newone = getSchemaVersion(fileTmp); 153 | if (newone > origin) { 154 | FileUtil.copy(fileTmp, fileSrc); 155 | fileTmp.deleteOnExit(); 156 | } 157 | fileSrc.setLastModified(new Date().getTime() + 7 * 24 * 60 * 60 * 1000); 158 | } catch (IOException e) { 159 | } 160 | } 161 | private byte[] download(URL url) throws IOException { 162 | URLConnection uc = url.openConnection(); 163 | int len = uc.getContentLength(); 164 | if (len < 0) return new byte[0]; 165 | InputStream is = new BufferedInputStream(uc.getInputStream()); 166 | try { 167 | byte[] data = new byte[len]; 168 | int offset = 0; 169 | while (offset < len) { 170 | int read = is.read(data, offset, data.length - offset); 171 | if (read < 0) { 172 | break; 173 | } 174 | offset += read; 175 | } 176 | if (offset < len) { 177 | throw new IOException( 178 | String.format("Read %d bytes; expected %d", offset, len)); 179 | } 180 | return data; 181 | } finally { 182 | is.close(); 183 | } 184 | } 185 | private void updateMavenArchetype() { 186 | try { 187 | File fileSrc = new File(ZKPathManager.getPluginResourcePath(ZKMavenArchetypesProvider.MAVEN_ARCHETYPE_PATH)); 188 | if (!fileSrc.isFile() || fileSrc.length() < 20) { 189 | // copy from jar file. 190 | copyResourceToFile(ZKMavenArchetypesProvider.MAVEN_ARCHETYPE_PATH, fileSrc); 191 | } 192 | 193 | if (fileSrc.lastModified() > new Date().getTime()) { 194 | return;// won't support this case 195 | } 196 | LOG.info("Downloading latest maven archetype file: " + ZKMavenArchetypesProvider.MAVEN_ARCHETYPE_PATH); 197 | 198 | File fileTmp = new File(fileSrc.getAbsolutePath() + ".tmp"); 199 | 200 | byte[] download = download(new URL(ZKMavenArchetypesProvider.MAVEN_ARCHETYPE_URL)); 201 | if (download != null && download.length == 0) 202 | return; // try next time. 203 | FileUtil.writeToFile(fileTmp, download); 204 | // HttpRequests.request(ZKMavenArchetypesProvider.MAVEN_ARCHETYPE_URL).saveToFile(fileTmp, ProgressManager.getGlobalProgressIndicator()); 205 | // to compare with length is not correct 206 | // if (fileTmp.length() > fileSrc.length()) { 207 | FileUtil.copy(fileTmp, fileSrc); 208 | fileTmp.deleteOnExit(); 209 | // } 210 | fileSrc.setLastModified(new Date().getTime() + 7 * 24 * 60 * 60 * 1000); 211 | } catch (IOException e) { 212 | LOG.debug(e); 213 | } 214 | } 215 | 216 | private double getSchemaVersion(File file) { 217 | try { 218 | DocumentBuilderFactory instance = DocumentBuilderFactory.newInstance(); 219 | //Using factory get an instance of document builder 220 | DocumentBuilder db = instance.newDocumentBuilder(); 221 | 222 | //parse using builder to get DOM representation of the XML file 223 | Document parse = db.parse(file); 224 | NodeList elementsByTagName = parse.getElementsByTagName("xs:schema"); 225 | if (elementsByTagName.getLength() > 0) { 226 | NamedNodeMap attributes = elementsByTagName.item(0).getAttributes(); 227 | Node version = attributes.getNamedItem("version"); 228 | if (version != null) { 229 | String nodeValue = version.getNodeValue().replace(".", ""); 230 | return Double.parseDouble(nodeValue); 231 | } 232 | } 233 | }catch(Exception pce) { 234 | LOG.error(pce); 235 | } 236 | return 0; 237 | } 238 | } -------------------------------------------------------------------------------- /src/main/resources/org/zkoss/zkidea/lang/resources/lang-addon.xsd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | org.zkoss.zkidea 3 | ZK 4 | ZK Framework 5 | org.jetbrains.idea.maven 6 | com.intellij.modules.java 7 | 8 | A ZK framework plugin for IntelliJ IDEA that helps developers work with the ZK Java Web Framework. The plugin provides powerful, schema-aware code completion and validation to accelerate development.

10 | Features: 11 |
    12 |
  • 13 | ZUL File Support 14 |
      15 |
    • ZUL code completion for ZK components, attributes, and events
    • 16 |
    • ZUL to class declaration navigation
    • 17 |
    • ZUL syntax check and validation
    • 18 |
    19 |
  • 20 |
  • 21 | ZK Configuration File Support 22 |
      23 |
    • ZK configuration file (zk.xml) code completion and validation
    • 24 |
    • Language addon configuration file (lang-addon.xml) code completion and validation
    • 25 |
    • Note: To enable code completion, either use the default filenames (zk.xml, lang-addon.xml) or add the correct namespace to your custom-named XML file.
    • 26 |
    27 |
  • 28 |
  • 29 | MVVM Development Support 30 |
      31 |
    • ZK MVVM annotation code completion (@init, @load, @bind, @save, @command, etc.)
    • 32 |
    33 |
  • 34 |
  • 35 | Project Creation 36 |
      37 |
    • Provide ZK Maven archetypes for creating ZK projects
    • 38 |
    39 |
  • 40 |
  • 41 | Feedback Menu 42 |
      43 |
    • a feedback menu under the Help menu for feedback and documentation
    • 44 |
    45 |
  • 46 |
47 | See ZK Developer's Reference / Intellij IDEA ZK Plugin for details. 48 | ]]> 49 | 50 | 0.4.1

52 |
    53 |
  • update plugin icon and description
  • 54 |
55 |

0.4.0

56 | 64 |

0.2.0

65 |
    66 |
  • Refined news notification to help users stay better informed by keeping important messages accessible until they are reviewed
  • 67 |
68 |

0.1.23

69 | 73 |

0.1.22

74 |
    75 |
  • Upgrade for Intellij 2024.1 version
  • 76 |
77 |
78 |

0.1.21

79 |
    80 |
  • [bug] #40 Cannot run under IU-233.11799.241
  • 81 |
82 |
83 |

0.1.20

84 |
    85 |
  • [bug] #38 ZK news popup has an error in Linux env. for 2023-2
  • 86 |
87 |
88 |

0.1.19

89 |
    90 |
  • [bug] #36 Add support for Intellij 2023.2
  • 91 |
92 |
93 |

0.1.18

94 |
    95 |
  • [bug] #34 Add support for Intellij 2023.1
  • 96 |
97 |
98 |

0.1.17

99 |
    100 |
  • [bug] #33 ZK Plugin not compatible with Intellij 2022.3
  • 101 |
102 |
103 |

0.1.16

104 |
    105 |
  • [bug] #32 support latest intellij version 222.*
  • 106 |
107 |
108 |

0.1.15

109 |
    110 |
  • [bug] #31 Plugin error for IC-221.5.080.210 with Version 0.1.14
  • 111 |
112 |
113 |

0.1.14

114 |
    115 |
  • [bug] #30 No compatible with v2021.3
  • 116 |
117 |
118 |

0.1.13

119 |
    120 |
  • fix deprecated API usage
  • 121 |
  • add news notification
  • 122 |
123 |
124 |

0.1.12

125 |
    126 |
  • [bug] #28 - Plugin dont work in Intellij 2021.2
  • 127 |
128 |
129 |

0.1.11

130 |
    131 |
  • [bug] #25 - failed with RuntimeExceptionWithAttachments in IntellijJ 2020.1
  • 132 |
133 |
134 |

0.1.10

135 |
    136 |
  • [bug] #20 - Error when starting IntelliJ IDEA 2017.2
  • 137 |
138 |
139 |

0.1.9

140 |
    141 |
  • [bug] #19 - Exception on Intellij IU-163.12024.16
  • 142 |
143 |
144 |

0.1.8

145 |
    146 |
  • [bug] #14 - Worker exited due to exception
  • 147 |
  • [bug] #17 - Assertion on load of IDE/Project
  • 148 |
149 |
150 |

0.1.7

151 |
    152 |
  • [bug] #10 - Plugin crashes on latest Idea 14 with JDK 6
  • 153 |
  • [bug] #13 - Worker exited due to exception
  • 154 |
155 |
156 |

0.1.6

157 |
    158 |
  • [enhancement] #8 - Support to open zul file in browser when run project with Jetty maven plugin
  • 159 |
  • [bug] #7 - IOException on IDEA startup
  • 160 |
161 |
162 |

0.1.5

163 |
    164 |
  • [enhancement] #6 - JDK 6 or 7 support
  • 165 |
  • [bug] #5 - v0.1.4 cannot work well on Windows environment
  • 166 |
167 |
168 |

0.1.4

169 |
    170 |
  • Fixed an issue about stackoverflow exception
  • 171 |
172 |
173 |

0.1.3

174 |
    175 |
  • Support ZK Maven archetypes to create project
  • 176 |
  • some bugs fixed
  • 177 |
178 |
179 |

0.1.2

180 |
    181 |
  • Support MVVM annotation content assistant
  • 182 |
  • Support to upgrade zul.xsd file automatically
  • 183 |
  • Support MVVM annotation to go to declaration for Java class
  • 184 |
185 |
186 |

0.1.1

187 |
    188 |
  • Update zul.xsd to ZK 7.0.6-FL version
  • 189 |
190 |
191 |

0.1.0

192 |
    193 |
  • ZUL editor supports content assistant.
  • 194 |
  • ZUL editor supports syntax checking.
  • 195 |
196 |
197 | ]]>
198 | 199 | 201 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 247 | 248 | 252 | 256 | 260 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /doc/code-completion-xml.md: -------------------------------------------------------------------------------- 1 | # ZKIdea XML Code Completion Architecture 2 | 3 | ## Overview 4 | 5 | The ZKIdea plugin provides comprehensive code completion and validation for three types of XML files in the ZK Framework ecosystem: 6 | 7 | - **ZUL files** (`.zul`) - ZK User Interface markup files 8 | - **ZK configuration files** (`zk.xml`) - ZK application configuration 9 | - **Language addon files** (`lang-addon.xml`) - ZK component library definitions 10 | 11 | This document describes the architectural patterns, key components, and integration mechanisms that enable IntelliJ IDEA to provide schema-aware completion, validation, and navigation for these file types. 12 | 13 | ## Architectural Principles 14 | 15 | ### Dual-Layer Architecture 16 | 17 | The code completion system operates on two complementary layers: 18 | 19 | 1. **Schema Provider Layer** - Explicit namespace-based completion using `StandardResourceProvider` 20 | 2. **DOM Descriptor Layer** - Default namespace completion for files without explicit `xmlns` declarations 21 | 22 | This dual approach ensures that files work seamlessly whether developers include explicit namespace declarations or rely on filename-based detection. 23 | 24 | ### IntelliJ Integration Strategy 25 | 26 | The architecture leverages IntelliJ Platform's XML infrastructure rather than creating custom completion mechanisms: 27 | 28 | - **XSD Schema Integration** - Uses standard XML Schema files for element definitions 29 | - **Standard Extension Points** - Implements IntelliJ's `StandardResourceProvider` and `XmlElementDescriptorProvider` interfaces 30 | - **Built-in Caching** - Relies on IntelliJ's `CachedValue` system for performance optimization 31 | - **Namespace Resolution** - Uses `XmlNSDescriptorImpl` for schema-based validation and completion 32 | 33 | ## Core Components 34 | 35 | ### Schema Provider Layer 36 | 37 | #### StandardResourceProvider Implementation 38 | 39 | Each XML file type has a dedicated schema provider that registers XSD resources: 40 | 41 | **ZulSchemaProvider** 42 | - Registers `zul.xsd` for namespace `http://www.zkoss.org/2005/zul` 43 | - Handles multiple ZK-related namespaces (native, client, annotation, shadow) 44 | - Maps HTTP and HTTPS schema URLs to local XSD resources 45 | 46 | **ZkConfigSchemaProvider** 47 | - Registers `zk.xsd` for namespace `http://www.zkoss.org/2005/zk/config` 48 | - Provides schema support for ZK application configuration elements 49 | 50 | **LangAddonSchemaProvider** 51 | - Registers `lang-addon.xsd` for namespace `http://www.zkoss.org/2005/zk/lang-addon` 52 | - Enables completion for component library and language addon definitions 53 | 54 | #### Schema Provider Pattern 55 | 56 | All schema providers follow a consistent implementation pattern: 57 | 58 | ```java 59 | public class [Type]SchemaProvider implements StandardResourceProvider { 60 | public static final String SCHEMA_URL = "http://www.zkoss.org/..."; 61 | public static final String PROJECT_SCHEMA_URL = "https://www.zkoss.org/.../schema.xsd"; 62 | public static final String PROJECT_SCHEMA_PATH = "org/zkoss/zkidea/lang/resources/schema.xsd"; 63 | 64 | public void registerResources(ResourceRegistrar registrar) { 65 | var path = "/" + PROJECT_SCHEMA_PATH; 66 | registrar.addStdResource("schema-name", path, getClass()); 67 | registrar.addStdResource(SCHEMA_URL, path, getClass()); 68 | registrar.addStdResource(PROJECT_SCHEMA_URL, path, getClass()); 69 | } 70 | } 71 | ``` 72 | 73 | ### DOM Descriptor Layer 74 | 75 | #### File Type Detection 76 | 77 | **ZulDomUtil** serves as the central utility for identifying ZK-related XML files: 78 | 79 | - `isZKFile(PsiFile)` - Detects all ZK-related XML files 80 | - `isZkConfigFile(PsiFile)` - Identifies `zk.xml` files by filename 81 | - `isLangAddonFile(PsiFile)` - Identifies `lang-addon.xml` files by filename 82 | 83 | The detection logic uses filename patterns rather than content analysis for performance. 84 | 85 | #### Element Descriptor Management 86 | 87 | **ZkDomElementDescriptorProvider** implements `XmlElementDescriptorProvider`: 88 | - Entry point for IntelliJ's XML completion system 89 | - Delegates to `ZkDomElementDescriptorHolder` for actual descriptor resolution 90 | - Registered in `plugin.xml` as `xml.elementDescriptorProvider` 91 | 92 | **ZkDomElementDescriptorHolder** manages schema descriptors per project: 93 | - Project-level service that caches `XmlNSDescriptorImpl` instances 94 | - Maps file types to appropriate XSD schemas 95 | - Uses `CachedValue` with `PsiModificationTracker` for cache invalidation 96 | - Provides default namespace context for schema-based completion 97 | 98 | #### FileKind Enumeration 99 | 100 | The system uses an internal `FileKind` enum to map file types to schema URLs: 101 | 102 | ```java 103 | enum FileKind { 104 | ZUL_FILE { getSchemaUrl() → ZulSchemaProvider.ZUL_PROJECT_SCHEMA_URL }, 105 | ZK_CONFIG_FILE { getSchemaUrl() → ZkConfigSchemaProvider.ZK_CONFIG_PROJECT_SCHEMA_URL }, 106 | LANG_ADDON_FILE { getSchemaUrl() → LangAddonSchemaProvider.LANG_ADDON_PROJECT_SCHEMA_URL } 107 | } 108 | ``` 109 | 110 | ## Integration Flow 111 | 112 | ### Schema-Based Completion (With Namespace) 113 | 114 | 1. Developer types `<` in an XML file with explicit namespace (`xmlns="http://www.zkoss.org/..."`) 115 | 2. IntelliJ Platform's XML parser identifies the namespace 116 | 3. `ExternalResourceManager` resolves namespace to local XSD via registered `StandardResourceProvider` 117 | 4. IntelliJ provides completion based on XSD schema elements and attributes 118 | 5. Real-time validation occurs against the schema 119 | 120 | ### Default Namespace Completion (Without Namespace) 121 | 122 | 1. Developer types `<` in a ZK XML file without explicit namespace 123 | 2. `ZkDomElementDescriptorProvider.getDescriptor()` is called by IntelliJ 124 | 3. `ZulDomUtil` identifies file type by filename pattern 125 | 4. `ZkDomElementDescriptorHolder` maps file type to appropriate schema URL 126 | 5. Cached `XmlNSDescriptorImpl` provides schema-based completion with default namespace 127 | 6. Elements complete as if explicit namespace were present 128 | 129 | ## Activating Code Completion 130 | 131 | For the plugin to provide code completion and validation for `zk.xml` and `lang-addon.xml` files, one of the following two conditions must be met: 132 | 133 | 1. **Filename Convention**: The file must be named exactly `zk.xml` or `lang-addon.xml`. The plugin's **DOM Descriptor Layer** automatically detects these filenames and applies the correct schema, even if no XML namespace is declared. 134 | 135 | 2. **Explicit XML Namespace**: If you use a custom filename (e.g., `my-zk-config.xml`), you **must** declare the correct XML namespace in the root element. The plugin's **Schema Provider Layer** uses this namespace to associate the file with the proper XSD. 136 | 137 | - For **ZK configuration files**, the required namespace is: 138 | ```xml 139 | 140 | ... 141 | 142 | ``` 143 | - For **language addon files**, the required namespace is: 144 | ```xml 145 | 146 | ... 147 | 148 | ``` 149 | 150 | This dual-system ensures flexibility while providing a reliable mechanism for activating the plugin's features. 151 | 152 | ## Schema Architecture 153 | 154 | ### XSD Schema Structure 155 | 156 | Each XML file type is backed by a comprehensive XSD schema: 157 | 158 | **zul.xsd** - ZK UI components 159 | - Root `` elements and ZK component hierarchy 160 | - Event handlers, data binding attributes 161 | - Layout and styling properties 162 | 163 | **zk.xsd** - ZK configuration 164 | - Application-level configuration sections 165 | - Client, desktop, session, and system configuration elements 166 | - Library properties and preference settings 167 | 168 | **lang-addon.xsd** - Language addons 169 | - Component definitions and inheritance 170 | - JavaScript and CSS resource declarations 171 | - Custom properties and annotation support 172 | 173 | ### Schema Location Resolution 174 | 175 | Schema resolution follows IntelliJ's standard pattern: 176 | 177 | 1. **Resource Registration** - Schema providers register local XSD paths 178 | 2. **URL Mapping** - External URLs (HTTP/HTTPS) map to local resources 179 | 3. **Classpath Loading** - XSD files loaded from plugin JAR resources 180 | 4. **Namespace Resolution** - `ExternalResourceManager` handles URL-to-file mapping 181 | 182 | ## Performance Considerations 183 | 184 | ### Caching Strategy 185 | 186 | - **Project-Level Caching** - One descriptor cache per IntelliJ project 187 | - **Modification Tracking** - Cache invalidation tied to `PsiModificationTracker.MODIFICATION_COUNT` 188 | - **Lazy Loading** - Descriptors created on-demand, not at plugin startup 189 | - **Thread Safety** - Synchronized descriptor creation prevents race conditions 190 | 191 | ### Resource Management 192 | 193 | - **Local Schema Storage** - XSD files bundled in plugin resources 194 | - **Minimal Memory Footprint** - Reuses IntelliJ's existing XML infrastructure 195 | - **No Network Dependency** - All schemas resolved locally 196 | 197 | ## Plugin Configuration 198 | 199 | ### Extension Point Registration 200 | 201 | The system registers through standard IntelliJ extension points: 202 | 203 | ```xml 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | ``` 220 | 221 | ## Extensibility Patterns 222 | 223 | ### Adding New XML File Types 224 | 225 | To add support for additional ZK XML file types: 226 | 227 | 1. **Create Schema Provider** - Implement `StandardResourceProvider` following existing patterns 228 | 2. **Add XSD Schema** - Place schema file in `resources/org/zkoss/zkidea/lang/resources/` 229 | 3. **Update File Detection** - Add detection method to `ZulDomUtil` 230 | 4. **Extend FileKind Enum** - Add new enum value in `ZkDomElementDescriptorHolder` 231 | 5. **Register Extensions** - Update `plugin.xml` with provider and file type registrations 232 | 233 | ### Schema Evolution 234 | 235 | - **Backward Compatibility** - New XSD versions should maintain element compatibility 236 | - **Automatic Updates** - Schema changes automatically flow through to completion 237 | - **Version Management** - Consider schema versioning for breaking changes 238 | 239 | ## Development Guidelines 240 | 241 | ### Code Organization 242 | 243 | - **Package Structure** - Schema providers in `.lang` package, DOM descriptors in `.dom` package 244 | - **Naming Conventions** - `[Type]SchemaProvider` and clear, descriptive class names 245 | - **Documentation** - Comprehensive JavaDoc explaining integration points 246 | 247 | ### Testing Considerations 248 | 249 | - **Test Files** - Provide sample XML files for manual testing 250 | - **Namespace Variants** - Test both explicit and implicit namespace scenarios 251 | - **Error Conditions** - Verify behavior with malformed or incomplete XML 252 | - **Performance** - Test caching behavior under various modification scenarios 253 | 254 | ### Maintenance Notes 255 | 256 | - **IntelliJ Version Compatibility** - Monitor IntelliJ Platform API changes 257 | - **Schema Updates** - Coordinate XSD updates with ZK Framework releases 258 | - **Error Handling** - Graceful degradation when schemas unavailable 259 | - **Logging** - Appropriate logging for debugging completion issues 260 | 261 | This architecture provides a robust, maintainable foundation for XML code completion in ZK Framework development environments while following IntelliJ Platform best practices and patterns. -------------------------------------------------------------------------------- /src/main/resources/org/zkoss/zkidea/lang/resources/zk.xsd: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | -------------------------------------------------------------------------------- /ISSUES.md: -------------------------------------------------------------------------------- 1 | # Cannot init component state (componentName=GradleJvmSupportMatrix, componentClass=GradleJvmSupportMatrix) 2 | ## Steps to reproduce 3 | * run gradle task "runIde" 4 | 5 | ## Current result 6 | IDE starts with errors: 7 | ```text 8 | 2025-09-30 22:49:43,706 [ 5009] SEVERE - #c.i.c.ComponentStoreImpl - Cannot init component state (componentName=GradleJvmSupportMatrix, componentClass=GradleJvmSupportMatrix) [Plugin: com.intellij.gradle] 9 | com.intellij.diagnostic.PluginException: Cannot init component state (componentName=GradleJvmSupportMatrix, componentClass=GradleJvmSupportMatrix) [Plugin: com.intellij.gradle] 10 | at com.intellij.configurationStore.ComponentStoreImpl.initComponent(ComponentStoreImpl.kt:174) 11 | at com.intellij.configurationStore.ComponentStoreWithExtraComponents.initComponent(ComponentStoreWithExtraComponents.kt:48) 12 | at com.intellij.serviceContainer.ComponentManagerImpl.initializeComponent$intellij_platform_serviceContainer(ComponentManagerImpl.kt:931) 13 | at com.intellij.serviceContainer.ServiceInstanceInitializer.createInstance$suspendImpl(ServiceInstanceInitializer.kt:41) 14 | at com.intellij.serviceContainer.ServiceInstanceInitializer.createInstance(ServiceInstanceInitializer.kt) 15 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1$1.invokeSuspend(LazyInstanceHolder.kt:162) 16 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1$1.invoke(LazyInstanceHolder.kt) 17 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1$1.invoke(LazyInstanceHolder.kt) 18 | at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:78) 19 | at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:167) 20 | at kotlinx.coroutines.BuildersKt.withContext(Unknown Source) 21 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1.invokeSuspend(LazyInstanceHolder.kt:160) 22 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1.invoke(LazyInstanceHolder.kt) 23 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1.invoke(LazyInstanceHolder.kt) 24 | at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:44) 25 | at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:112) 26 | at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) 27 | at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56) 28 | at kotlinx.coroutines.BuildersKt.launch(Unknown Source) 29 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.initialize(LazyInstanceHolder.kt:145) 30 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.access$initialize(LazyInstanceHolder.kt:13) 31 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.tryInitialize(LazyInstanceHolder.kt:135) 32 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.getInstance(LazyInstanceHolder.kt:95) 33 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.getInstanceInCallerContext$suspendImpl(LazyInstanceHolder.kt:87) 34 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.getInstanceInCallerContext(LazyInstanceHolder.kt) 35 | at com.intellij.serviceContainer.ComponentManagerImplKt$getOrCreateInstanceBlocking$3.invokeSuspend(ComponentManagerImpl.kt:2337) 36 | at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 37 | at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108) 38 | at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280) 39 | at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85) 40 | at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59) 41 | at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source) 42 | at com.intellij.serviceContainer.ComponentManagerImplKt$runBlockingInitialization$1.invoke(ComponentManagerImpl.kt:2406) 43 | at com.intellij.serviceContainer.ComponentManagerImplKt$runBlockingInitialization$1.invoke(ComponentManagerImpl.kt:2397) 44 | at com.intellij.openapi.progress.ContextKt.prepareThreadContext(context.kt:83) 45 | at com.intellij.serviceContainer.ComponentManagerImplKt.runBlockingInitialization(ComponentManagerImpl.kt:2397) 46 | at com.intellij.serviceContainer.ComponentManagerImplKt.getOrCreateInstanceBlocking(ComponentManagerImpl.kt:2336) 47 | at com.intellij.serviceContainer.ComponentManagerImpl.doGetService(ComponentManagerImpl.kt:1057) 48 | at com.intellij.serviceContainer.ComponentManagerImpl.getService(ComponentManagerImpl.kt:988) 49 | at org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix$Companion.getInstance(GradleJvmSupportMatrix.kt:220) 50 | at org.jetbrains.plugins.gradle.jvmcompat.GradleCompatibilitySupportUpdater.(GradleCompatibilitySupportUpdater.kt:14) 51 | at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 52 | at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) 53 | at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) 54 | at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) 55 | at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) 56 | at com.intellij.platform.instanceContainer.instantiation.InstantiateKt$instantiate$4.invoke(instantiate.kt:74) 57 | at com.intellij.platform.instanceContainer.instantiation.InstantiateKt$instantiate$4.invoke(instantiate.kt:72) 58 | at com.intellij.platform.instanceContainer.instantiation.InstantiateKt.instantiate(instantiate.kt:278) 59 | at com.intellij.platform.instanceContainer.instantiation.InstantiateKt.instantiate(instantiate.kt:72) 60 | at com.intellij.serviceContainer.InstantiateKt.instantiateWithContainer(instantiate.kt:19) 61 | at com.intellij.serviceContainer.ServiceInstanceInitializer.createInstance$suspendImpl(ServiceInstanceInitializer.kt:26) 62 | at com.intellij.serviceContainer.ServiceInstanceInitializer.createInstance(ServiceInstanceInitializer.kt) 63 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1$1.invokeSuspend(LazyInstanceHolder.kt:162) 64 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1$1.invoke(LazyInstanceHolder.kt) 65 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1$1.invoke(LazyInstanceHolder.kt) 66 | at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:78) 67 | at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:167) 68 | at kotlinx.coroutines.BuildersKt.withContext(Unknown Source) 69 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1.invokeSuspend(LazyInstanceHolder.kt:160) 70 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1.invoke(LazyInstanceHolder.kt) 71 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder$initialize$1.invoke(LazyInstanceHolder.kt) 72 | at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:44) 73 | at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:112) 74 | at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126) 75 | at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56) 76 | at kotlinx.coroutines.BuildersKt.launch(Unknown Source) 77 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.initialize(LazyInstanceHolder.kt:145) 78 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.access$initialize(LazyInstanceHolder.kt:13) 79 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.tryInitialize(LazyInstanceHolder.kt:135) 80 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.getInstance(LazyInstanceHolder.kt:95) 81 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.getInstanceInCallerContext$suspendImpl(LazyInstanceHolder.kt:87) 82 | at com.intellij.platform.instanceContainer.internal.LazyInstanceHolder.getInstanceInCallerContext(LazyInstanceHolder.kt) 83 | at com.intellij.serviceContainer.ComponentManagerImplKt$getOrCreateInstanceBlocking$3.invokeSuspend(ComponentManagerImpl.kt:2337) 84 | at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 85 | at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108) 86 | at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280) 87 | at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85) 88 | at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59) 89 | at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source) 90 | at com.intellij.serviceContainer.ComponentManagerImplKt$runBlockingInitialization$1.invoke(ComponentManagerImpl.kt:2406) 91 | at com.intellij.serviceContainer.ComponentManagerImplKt$runBlockingInitialization$1.invoke(ComponentManagerImpl.kt:2397) 92 | at com.intellij.openapi.progress.ContextKt.prepareThreadContext(context.kt:86) 93 | at com.intellij.serviceContainer.ComponentManagerImplKt.runBlockingInitialization(ComponentManagerImpl.kt:2397) 94 | at com.intellij.serviceContainer.ComponentManagerImplKt.getOrCreateInstanceBlocking(ComponentManagerImpl.kt:2336) 95 | at com.intellij.serviceContainer.ComponentManagerImpl.doGetService(ComponentManagerImpl.kt:1057) 96 | at com.intellij.serviceContainer.ComponentManagerImpl.getService(ComponentManagerImpl.kt:988) 97 | at org.jetbrains.plugins.gradle.jvmcompat.GradleCompatibilitySupportUpdater$Companion.getInstance(GradleCompatibilitySupportUpdater.kt:26) 98 | at org.jetbrains.plugins.gradle.service.project.GradleVersionUpdateStartupActivity.execute(GradleVersionUpdateStartupActivity.kt:12) 99 | at com.intellij.ide.startup.impl.StartupManagerImplKt$launchActivity$1.invokeSuspend(StartupManagerImpl.kt:482) 100 | at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 101 | at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108) 102 | at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584) 103 | at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793) 104 | at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697) 105 | at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684) 106 | Caused by: java.lang.IllegalArgumentException: 25 107 | at com.intellij.util.lang.JavaVersion.parse(JavaVersion.java:307) 108 | at org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix$getCompatibilityRanges$1$javaRange$1.invoke(GradleJvmSupportMatrix.kt:44) 109 | at org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix$getCompatibilityRanges$1$javaRange$1.invoke(GradleJvmSupportMatrix.kt:44) 110 | at org.jetbrains.plugins.gradle.jvmcompat.IdeVersionedDataParser$Companion.parseVersion(IdeVersionedDataParser.kt:21) 111 | at org.jetbrains.plugins.gradle.jvmcompat.IdeVersionedDataParser$Companion.parseRange(IdeVersionedDataParser.kt:31) 112 | at org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix.getCompatibilityRanges(GradleJvmSupportMatrix.kt:44) 113 | at org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix.applyState(GradleJvmSupportMatrix.kt:28) 114 | at org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix.onStateChanged(GradleJvmSupportMatrix.kt:50) 115 | at org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix.onStateChanged(GradleJvmSupportMatrix.kt:13) 116 | at org.jetbrains.plugins.gradle.jvmcompat.IdeVersionedDataStorage.loadState(IdeVersionedDataStorage.kt:38) 117 | at org.jetbrains.plugins.gradle.jvmcompat.IdeVersionedDataStorage.loadState(IdeVersionedDataStorage.kt:7) 118 | at com.intellij.configurationStore.ComponentStoreImpl.doInitComponent(ComponentStoreImpl.kt:490) 119 | at com.intellij.configurationStore.ComponentStoreImpl.initComponent(ComponentStoreImpl.kt:409) 120 | at com.intellij.configurationStore.ComponentStoreImpl.initComponent(ComponentStoreImpl.kt:131) 121 | ... 95 more 122 | ``` 123 | 124 | ## Root cause 125 | The IntelliJ IDEA version you're using has a Gradle plugin that doesn't recognize Java 25. The error happens when: 126 | 127 | The GradleJvmSupportMatrix component tries to initialize 128 | It attempts to parse Java version compatibility data 129 | The parser encounters "25" (Java 25) but the JavaVersion.parse() method doesn't recognize it as a valid version 130 | 131 | Why this happens: 132 | 133 | Java 25 is a relatively new version (early access/preview) 134 | Your IntelliJ IDEA version was released before Java 25 support was added 135 | The Gradle plugin's compatibility matrix includes Java 25, but the version parser hasn't been updated to handle it 136 | 137 | ## Solution 138 | Upgrade the IntelliJ platform version in `build.gradle` 139 | ```gradle 140 | intellij { 141 | version = '2024.2' // or later 142 | plugins = ['maven', 'com.intellij.java'] 143 | } 144 | ``` 145 | but it also needs to adjust the compatible version range: 146 | 147 | ```xml 148 | 149 | sinceBuild = '241' // Start from 2024.1 150 | untilBuild = '253.*' 151 | 152 | ``` 153 | 154 | That might affect some users who are on older versions of IntelliJ IDEA, so consider the trade-offs. --------------------------------------------------------------------------------