IntelliJ Platform calls {@link #getDescriptor(XmlTag)} for each XML tag during parsing
26 | *
This method delegates to {@link ZkDomElementDescriptorHolder} to retrieve cached descriptors
27 | *
The holder provides XSD schema-based descriptors with default namespace support
28 | *
IntelliJ uses these descriptors to provide code completion, validation, and navigation
29 | *
30 | *
31 | *
Registration:
32 | *
This provider is registered in plugin.xml
33 | *
34 | *
Supported Files:
35 | *
36 | *
.zul files (ZK User Interface files)
37 | *
zk.xml files (ZK configuration files)
38 | *
lang-addon.xml files (ZK language addon configuration files)
39 | *
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 super Unit> 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 . 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 |
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 | 
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 super Unit> 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.
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 | *
Service Registration: Registered as a project-level service in plugin.xml
53 | *
File Type Detection: Uses {@link ZulDomUtil#isZKFile} to identify ZK files
54 | *
Schema Resolution: Loads XSD schemas via {@link ExternalResourceManager}
55 | *
Descriptor Caching: Uses {@link CachedValue} for performance optimization
56 | *
Default Namespace: Provides automatic namespace resolution like native XML support
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 | *
Detecting file types based on filename/extension patterns
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 | *
[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.
--------------------------------------------------------------------------------