diff --git a/CHANGELOG.md b/CHANGELOG.md index 77f2562c..a15e3c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Extra Icons Change Log -## 2024.1.1 (planned for 2024/01/02) +## 2024.1.1 (2024/01/02) * **INFO**: JetBrains will introduce a new business model for paid/freemium plugins. This model will offer a perpetual license, **allowing users to make a one-time payment for the plugin and use it for a lifetime**. [Get more information here](https://github.com/jonathanlermitage/intellij-extra-icons-plugin/blob/master/docs/LICENSE_FAQ.md#how-to-get-a-lifetime-license). There is no ETA yet. * build script: prepare the work for the introduction of lifetime licenses. +* fix [#175](https://github.com/jonathanlermitage/intellij-extra-icons-plugin/issues/175): ` java.lang.Throwable: Must be precomputed`. UI Scale detection could fail at startup. It should be fixed now. * initial support of [Flutter](https://flutter.dev) icons. * improve Prettier support. * improve Gradle support. @@ -12,7 +13,8 @@ * support `(.)log(s)`folders. * support `.noai` files (which tell the AI Assistant plugin to block AI features for the containing project). * support [Detekt](https://detekt.dev/docs/introduction/configurations/) files ending by `detekt-config.yml` or `detekt.yml` (I may try to pick the configured Detekt filename from the `build.gradle(.kts)` file later). -* plugin's license is now verified occasionally, in addition the verification done by IDE at startup. +* support Qodana `qodana.yaml` files. +* plugin's license is now verified occasionally, in addition to the verification done by IDE at startup. * starting from 2024, I will occasionally send some [coupons](https://github.com/jonathanlermitage/intellij-extra-icons-plugin/blob/master/docs/LICENSE_FAQ.md#i-received-a-coupon-for-a-free-license-how-does-it-work) for Free licenses on [Twitter](https://twitter.com/JLermitage) and [Bluesky](https://bsky.app/profile/jonathanlermitage.bsky.social). Stay tuned. ## 2023.4.2 (2023/12/03) @@ -936,7 +938,7 @@ This way, you simply have to download the latest version offered by the plugin m * support `jenkins`, `NOTICE`, `CONTACT` files (with `.md`, `.adoc`, `.txt` or no extension). ## 0.4 (2018/08/26) -* enabled compatibility with all products (WebStorm, etc). +* enabled compatibility with all products (WebStorm, etc.). * rework Maven wrapper `mvnw`, Gradle wrapper `gradlew` files. * support `README`, `CHANGELOG`, `CHANGES`, `LICENSE`, `COPYING`, `CONTRIBUTING`, `AUTHORS` files (with `.md`, `.adoc`, `.txt` or no extension). diff --git a/Makefile b/Makefile index 0089ae53..b9df9900 100644 --- a/Makefile +++ b/Makefile @@ -59,46 +59,47 @@ runold: intro ## run plugin in oldest supported IntelliJ Community version .PHONY: build build: intro ## build and package a plugin which asks for a paid subscription license to build/distribution/ (see generated ZIP file) - ${gradlew_cmd} clean buildPlugin test modernizer biz-lermitage-oga-gradle-check verifyPlugin showGeneratedPlugin --warning-mode all -PpluginLicenseType=subscription + ${gradlew_cmd} clean buildPlugin test modernizer biz-lermitage-oga-gradle-check verifyPlugin showGeneratedPlugin -PpluginLicenseType=subscription .PHONY: buildfree buildfree: intro ## build and package a plugin which doesn't ask for a paid license to build/distribution/ (see generated ZIP file) - ${gradlew_cmd} clean buildPlugin test modernizer biz-lermitage-oga-gradle-check verifyPlugin showGeneratedPlugin --warning-mode all -PpluginLicenseType=free + ${gradlew_cmd} clean buildPlugin test modernizer biz-lermitage-oga-gradle-check verifyPlugin showGeneratedPlugin -PpluginLicenseType=free .PHONY: buildlifetime buildlifetime: intro ## build and package a plugin which asks for a paid lifetime license to build/distribution/ (see generated ZIP file) - ${gradlew_cmd} clean buildPlugin test modernizer biz-lermitage-oga-gradle-check verifyPlugin showGeneratedPlugin --warning-mode all -PpluginLicenseType=lifetime + ${gradlew_cmd} clean buildPlugin test modernizer biz-lermitage-oga-gradle-check verifyPlugin showGeneratedPlugin -PpluginLicenseType=lifetime .PHONY: lint lint: intro ## run linter(s), for now Modernizer - ${gradlew_cmd} modernizer --warning-mode all + ${gradlew_cmd} modernizer .PHONY: test test: intro ## run unit tests - ${gradlew_cmd} cleanTest test verifyPlugin --warning-mode all + ${gradlew_cmd} cleanTest test verifyPlugin .PHONY: cv cv: intro ## check dependencies and Gradle updates - ${gradlew_cmd} dependencyUpdates --warning-mode all --console=plain + ${gradlew_cmd} dependencyUpdates .PHONY: cvnd cvnd: intro ## check dependencies and Gradle updates (use a single-use Gradle daemon process by using --no-daemon) - ${gradlew_cmd} dependencyUpdates --warning-mode all --no-daemon --console=plain + ${gradlew_cmd} dependencyUpdates --no-daemon .PHONY: oga oga: intro ## check for deprecated groupId and artifactId coordinates - ${gradlew_cmd} biz-lermitage-oga-gradle-check --console=plain + ${gradlew_cmd} biz-lermitage-oga-gradle-check .PHONY: svgo svgo: intro ## optimize SVG icons with SVGO (SVGO must be present, type "npm install -g svgo" if needed) + svgo --folder=misc/ --recursive svgo --folder=src/main/resources/ --recursive @@ -114,7 +115,7 @@ dt: intro ## show dependencies graph .PHONY: publish publish: intro ## publish package to the JetBrains marketplace - ${gradlew_cmd} clean buildPlugin test verifyPlugin publishPlugin --warning-mode all + ${gradlew_cmd} clean buildPlugin test verifyPlugin publishPlugin .PHONY: help diff --git a/build.gradle.kts b/build.gradle.kts index d94f1277..b7096068 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,11 @@ plugins { val pluginXmlFile = projectDir.resolve("src/main/resources/META-INF/plugin.xml") val pluginXmlFileBackup = projectDir.resolve("plugin.backup.xml") +val pluginLogoFile = projectDir.resolve("src/main/resources/META-INF/pluginIcon.svg") +val pluginLogoFileBackup = projectDir.resolve("pluginIcon.backup.svg") +val pluginLogoLifetimeFile = projectDir.resolve("misc/pluginIcon.lifetime.svg") +val pluginLogoFreeFile = projectDir.resolve("misc/pluginIcon.free.svg") + // Import variables from gradle.properties file val pluginDownloadIdeaSources: String by project val pluginVersion: String by project @@ -88,9 +93,21 @@ dependencies { } intellij { + when (pluginLicenseType) { + "free" -> { + pluginName.set("Extra Icons Free") + } + + "lifetime" -> { + pluginName.set("Extra Icons Lifetime") + } + + else -> { + pluginName.set("Extra Icons") + } + } downloadSources.set(pluginDownloadIdeaSources.toBoolean() && !System.getenv().containsKey("CI")) instrumentCode.set(true) - pluginName.set("Extra Icons") sandboxDir.set("${rootProject.projectDir}/.idea-sandbox/${shortenIdeVersion(pluginIdeaVersion)}") updateSinceUntilBuild.set(false) version.set(pluginIdeaVersion) @@ -132,6 +149,75 @@ tasks { } } + register("updatePluginCompatibilityRulesFromPluginXml") { + doLast { + var pluginXmlStr = pluginXmlFile.readText() + when (pluginLicenseType) { + "free" -> { + pluginXmlStr = pluginXmlStr.replace("REPLACED_BY_GRADLE", + "") + } + + "lifetime" -> { + pluginXmlStr = pluginXmlStr.replace("REPLACED_BY_GRADLE", + "lermitage.extra.icons.free") + } + + else -> { + pluginXmlStr = pluginXmlStr.replace("REPLACED_BY_GRADLE", + "lermitage.extra.icons.lifetime" + + "lermitage.extra.icons.free") + } + } + FileUtils.delete(pluginXmlFile) + FileUtils.write(pluginXmlFile, pluginXmlStr, "UTF-8") + } + } + + register("backupPluginLogo") { + doLast { + if (pluginLogoFileBackup.exists()) { + if (!pluginLogoFileBackup.delete()) { + throw GradleException("Failed to remove existing plugin logo file backup '${pluginLogoFileBackup}'") + } + } + FileUtils.copyFile(pluginLogoFile, pluginLogoFileBackup) + } + } + register("restorePluginLogoFromBackup") { + doLast { + if (!pluginLogoFileBackup.exists()) { + throw GradleException("Plugin logo file backup '${pluginLogoFileBackup}' is missing") + } + if (pluginLogoFile.exists()) { + if (!pluginLogoFile.delete()) { + throw GradleException("Failed to remove non-original plugin logo file backup '${pluginLogoFile}'") + } + } + FileUtils.moveFile(pluginLogoFileBackup, pluginLogoFile) + } + } + register("usePluginLogoFree") { + doLast { + if (pluginLogoFile.exists()) { + if (!pluginLogoFile.delete()) { + throw GradleException("Failed to remove existing plugin logo file backup '${pluginLogoFile}'") + } + } + FileUtils.copyFile(pluginLogoFreeFile, pluginLogoFile) + } + } + register("usePluginLogoLifetime") { + doLast { + if (pluginLogoFile.exists()) { + if (!pluginLogoFile.delete()) { + throw GradleException("Failed to remove existing plugin logo file backup '${pluginLogoFile}'") + } + } + FileUtils.copyFile(pluginLogoLifetimeFile, pluginLogoFile) + } + } + register("backupPluginXml") { doLast { if (pluginXmlFileBackup.exists()) { @@ -239,7 +325,6 @@ tasks { } withType { useJUnitPlatform() - maxParallelForks = Runtime.getRuntime().availableProcessors() // avoid JBUIScale "Must be precomputed" error, because IDE is not started (LoadingState.APP_STARTED.isOccurred is false) jvmArgs("-Djava.awt.headless=true") @@ -307,15 +392,15 @@ tasks { patchPluginXml { when (pluginLicenseType) { "free" -> { - dependsOn("backupPluginXml", "removeLicenseRestrictionFromPluginXml") + dependsOn("backupPluginLogo", "usePluginLogoFree", "backupPluginXml", "removeLicenseRestrictionFromPluginXml", "updatePluginCompatibilityRulesFromPluginXml") } "lifetime" -> { - dependsOn("backupPluginXml", "renamePluginInfoToLifetimeInPluginXml", "verifyProductDescriptor") + dependsOn("backupPluginLogo", "usePluginLogoLifetime", "backupPluginXml", "renamePluginInfoToLifetimeInPluginXml", "updatePluginCompatibilityRulesFromPluginXml", "verifyProductDescriptor") } else -> { - dependsOn("verifyProductDescriptor") + dependsOn("backupPluginXml", "updatePluginCompatibilityRulesFromPluginXml", "verifyProductDescriptor") } } changeNotes.set(provider { @@ -327,11 +412,15 @@ tasks { buildPlugin { when (pluginLicenseType) { "free" -> { - finalizedBy("restorePluginXmlFromBackup", "renameDistributionNoLicense") + finalizedBy("restorePluginLogoFromBackup", "restorePluginXmlFromBackup", "renameDistributionNoLicense") } "lifetime" -> { - finalizedBy("restorePluginXmlFromBackup", "renameDistributionLifetimeLicense") + finalizedBy("restorePluginLogoFromBackup", "restorePluginXmlFromBackup", "renameDistributionLifetimeLicense") + } + + else -> { + finalizedBy("restorePluginXmlFromBackup") } } } diff --git a/misc/pluginIcon.free.svg b/misc/pluginIcon.free.svg new file mode 100644 index 00000000..3c38d13d --- /dev/null +++ b/misc/pluginIcon.free.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/misc/pluginIcon.lifetime.svg b/misc/pluginIcon.lifetime.svg new file mode 100644 index 00000000..eb1eb9c3 --- /dev/null +++ b/misc/pluginIcon.lifetime.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/java/lermitage/intellij/extra/icons/activity/ExtraIconsLicenseCheckIDEActivity.java b/src/main/java/lermitage/intellij/extra/icons/activity/ExtraIconsLicenseCheckIDEActivity.java index a0c89c80..4850f108 100644 --- a/src/main/java/lermitage/intellij/extra/icons/activity/ExtraIconsLicenseCheckIDEActivity.java +++ b/src/main/java/lermitage/intellij/extra/icons/activity/ExtraIconsLicenseCheckIDEActivity.java @@ -27,7 +27,7 @@ import java.util.stream.Collectors; /** - * Check licence periodically. + * Check Extra Icons licence periodically. */ public class ExtraIconsLicenseCheckIDEActivity implements ProjectActivity { @@ -48,13 +48,16 @@ public Object execute(@NotNull Project project, @NotNull Continuation registeredIds = PluginId.getRegisteredIds().stream() .map(PluginId::getIdString) .collect(Collectors.toSet()); - Optional extraIconsPluginTypeFound = ExtraIconsPluginType.FINDABLE_TYPES.stream() + Optional extraIconsPluginTypeFound = ExtraIconsPluginType.getFindableTypes().stream() .filter(extraIconsPluginType -> registeredIds.contains(extraIconsPluginType.getPluginId())) .findFirst(); return extraIconsPluginTypeFound.orElse(ExtraIconsPluginType.NOT_FOUND); } else { LOGGER.info("Found installed Extra Icons plugin by class: " + pluginDesc); String installedPluginId = pluginDesc.getPluginId().getIdString(); - Optional extraIconsPluginTypeFound = ExtraIconsPluginType.FINDABLE_TYPES.stream() + Optional extraIconsPluginTypeFound = ExtraIconsPluginType.getFindableTypes().stream() .filter(extraIconsPluginType -> installedPluginId.equals(extraIconsPluginType.getPluginId())) .findFirst(); return extraIconsPluginTypeFound.orElse(ExtraIconsPluginType.NOT_FOUND); diff --git a/src/main/java/lermitage/intellij/extra/icons/lic/ExtraIconsPluginType.java b/src/main/java/lermitage/intellij/extra/icons/lic/ExtraIconsPluginType.java index 0e4867d4..7d1d67ae 100644 --- a/src/main/java/lermitage/intellij/extra/icons/lic/ExtraIconsPluginType.java +++ b/src/main/java/lermitage/intellij/extra/icons/lic/ExtraIconsPluginType.java @@ -2,6 +2,8 @@ package lermitage.intellij.extra.icons.lic; +import org.jetbrains.annotations.NotNull; + import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; @@ -9,10 +11,10 @@ @SuppressWarnings("HardCodedStringLiteral") public enum ExtraIconsPluginType { - SUBSCRIPTION("lermitage.intellij.extra.icons", "PEXTRAICONS",true), - LIFETIME("lermitage.extra.icons.lifetime", "PEXTRAICONSLIFE",true), - FREE("lermitage.extra.icons.free", "PEXTRAICONFREE",false), - NOT_FOUND("lermitage.extra.icons.not.found", "PNOTFOUND",true); + SUBSCRIPTION("lermitage.intellij.extra.icons", "PEXTRAICONS", true), + LIFETIME("lermitage.extra.icons.lifetime", "PEXTRAICONSLIFE", true), + FREE("lermitage.extra.icons.free", "PEXTRAICONFREE", false), + NOT_FOUND("lermitage.extra.icons.not.found", "PNOTFOUND", true); private final String pluginId; private final String productCode; @@ -24,11 +26,11 @@ public enum ExtraIconsPluginType { this.requiresLicense = requiresLicense; } - public String getPluginId() { + public @NotNull String getPluginId() { return pluginId; } - public String getProductCode() { + public @NotNull String getProductCode() { return productCode; } @@ -49,7 +51,9 @@ public String toString() { * Plugin types with code that actually exists. Filter others, otherwise users would be able * to bypass license by installing a plugin with the appropriate code. */ - public static final Set FINDABLE_TYPES = Arrays.stream(ExtraIconsPluginType.values()) - .dropWhile(type -> type == NOT_FOUND) - .collect(Collectors.toUnmodifiableSet()); + public static Set getFindableTypes() { + return Arrays.stream(ExtraIconsPluginType.values()) + .dropWhile(type -> type == NOT_FOUND) + .collect(Collectors.toUnmodifiableSet()); + } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 30b86517..b14efee7 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -5,7 +5,7 @@ JONATHAN_LERMITAGE - + @@ -13,10 +13,10 @@
  • Shows an icon for Git sub-module folders.
  • Highly configurable: see File > Settings > Appearance & Behavior > Extra Icons to select extra icons to (de)activate. This is configurable at IDE and project level.
  • You can also register your own icons in order to override file icons and all IDE icons (including toolbars, menus, etc.).
  • -
  • You can import and export icons from external files. This also means you can easily share icon themes with friends. You can also download online icon packs (example: restore the old file icons when using the new UI).
  • +
  • You can import and export icons from external files. This also means you can easily share icon themes with friends. You can also download online icon packs (example: restore the old file icons when using the new UI).
  • For more information, please see this guide.
  • For questions about the plugin licensing models, please see the license FAQ.
  • -
  • JetBrains Gateway support: please install plugin in Host, or in both Host and Client, but not in Client only. Also, for now, plugin's settings panels are partially broken (some fields are hidden, but become visible when clicked or hovered over), but icons override works.
  • +
  • JetBrains Gateway support is limited. Gateway may fail to register licenses for third-party paid plugins (like Extra Icons), and it may not render some settings GUI forms correctly. Unfortunately, these issues are on the JetBrains side. Installing the plugin on the host and/or client sides can help. As a workaround for licensing issues, you can still build the plugin by yourself and remove the license requirement. The plugin is still free (MIT) and open source. I ask for a modest retribution only if you get the plugin from the marketplace, which, I think, is a good way to support my work. Thank you.
  • 📢 Starting from 2024, I will occasionally send some coupons for Free licenses on Twitter and Bluesky. Stay tuned.

    @@ -53,6 +53,9 @@ on how to target different products --> com.intellij.modules.platform + + REPLACED_BY_GRADLE + diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..98727d14 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,6 @@ +# see https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution-config +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.config.dynamic.factor=1 +junit.jupiter.execution.parallel.mode.classes.default=concurrent +junit.jupiter.execution.parallel.mode.default=same_thread