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 super Un
started = true;
LOGGER.info("Started Extra Icons license checker");
- int check_delay_1 = 60_000; // 1 min
- int check_delay_2 = 3_600_000; // 1 hr
- int check_period = 3 * 3_600_000; // 3 hrs
+ int fiest_check_delay = 60_000; // 1 min
+ int second_check_delay = 3_600_000; // 1 hr
+ int other_checks_period = 3 * 3_600_000; // 3 hrs
if ("true".equals(System.getProperty("extra.icons.license.check.fast", "false"))) {
- check_delay_1 = 3_000; // 3 sec
- check_delay_2 = 30_000; // 30 sec
- check_period = 180_000; // 3 min
+ LOGGER.warn("Detected property extra.icons.license.check.fast = true, which will dramatically " +
+ "increase Extra Icons license check frequency. " +
+ "Reminder: you should enable this property for testing purpose only");
+ fiest_check_delay = 3_000; // 3 sec
+ second_check_delay = 30_000; // 30 sec
+ other_checks_period = 180_000; // 3 min
}
long t1 = System.currentTimeMillis();
@@ -65,11 +68,11 @@ public Object execute(@NotNull Project project, @NotNull Continuation super Un
if (installedPluginType.isRequiresLicense()) {
try {
ExtraIconsLicenseStatus.setLicenseActivated(true);
- LOGGER.info("Will check Extra Icons license in " + check_delay_1 / 1000 + " sec, " +
- "in " + check_delay_2 / 1000 + " sec, " +
- "then every " + check_period / 1000 + " sec");
- new Timer().schedule(createLicenseCheckerTimerTask(installedPluginType), check_delay_1);
- new Timer().scheduleAtFixedRate(createLicenseCheckerTimerTask(installedPluginType), check_delay_2, check_period);
+ LOGGER.info("Will check Extra Icons license in " + fiest_check_delay / 1000 + " sec, " +
+ "in " + second_check_delay / 1000 + " sec, " +
+ "then every " + other_checks_period / 1000 + " sec");
+ new Timer().schedule(createLicenseCheckerTimerTask(installedPluginType), fiest_check_delay);
+ new Timer().scheduleAtFixedRate(createLicenseCheckerTimerTask(installedPluginType), second_check_delay, other_checks_period);
} catch (Exception e) {
LOGGER.warn(e);
}
@@ -106,19 +109,19 @@ public void run() {
private ExtraIconsPluginType findInstalledPluginType() {
PluginDescriptor pluginDesc = PluginManager.getPluginByClass(ExtraIconsLicenseCheckIDEActivity.class);
- if (pluginDesc == null) {
+ if (pluginDesc == null) { // should never happen, as two plugins with same service class names will crash the IDE
LOGGER.warn("Failed to find installed Extra Icons plugin by class, will list all installed plugins and try to find it");
Set 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 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