IJPL-193994 Project leak at IslandsFeedback

(cherry picked from commit fd6c877de11dacfbe43f24a57d174ec18ece77b3)

IJ-CR-167568

GitOrigin-RevId: bbb19e2ec0889a6b4fdcfec10d2caa62aec0fab9
This commit is contained in:
Alexander Lobas
2025-06-26 21:00:51 +02:00
committed by intellij-monorepo-bot
parent e73fc17187
commit 6b666a0827
8 changed files with 4 additions and 303 deletions

View File

@@ -3211,15 +3211,6 @@ tech.insights.lab.promoter.action=Sign Up\u2026
xnext.comment.unavailable=The setting is unavailable when XNext is enabled
ide.islands.read.more=Read more
ide.islands.switch.theme=Switch theme\u2026
ide.islands.share.feedback=Share feedback
ide.islands.share.feedback.title=Share feedback about the new theme
ide.islands.share.feedback.message=Fill out a survey and get a chance to win a prize. It will take about 5 minutes.
ide.islands.share.feedback.button=Respond
ide.islands.share.feedback.promo.title=The fresh theme is on!
ide.islands.share.feedback.promo.message=We\u2019d love your opinion. A survey with a chance to win a prize will be available in two days.
notification.title.paid.plugins.not.loaded=Plugins not loaded
notification.content.paid.plugins.not.loaded=Application restart is necessary to enable the following plugins:<br>
notification.action.load.paid.plugins.and.restart=Enable plugins and restart

View File

@@ -1,90 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.ide.bootstrap
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.laf.UiThemeProviderListManager
import com.intellij.ide.util.PropertiesComponent
import com.intellij.idea.AppMode
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.Experiments
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.JBColor
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
fun applyIslandsTheme(afterImportSettings: Boolean) {
val application = ApplicationManager.getApplication()
if (Registry.`is`("llm.riderNext.enabled", false) || !application.isEAP || application.isUnitTestMode || application.isHeadlessEnvironment || AppMode.isRemoteDevHost()) {
return
}
val properties = PropertiesComponent.getInstance()
if (afterImportSettings) {
if (properties.getValue("ide.islands.show.feedback") != "show.promo") {
return
}
}
else if (properties.getBoolean("ide.islands.ab", false)) {
return
}
properties.setValue("ide.islands.ab", true)
val experiments = Experiments.getInstance()
if (experiments.isFeatureEnabled("ide.one.island.theme") || System.getProperty("ide.one.island.theme") != null) {
enableTheme(true)
}
else if (experiments.isFeatureEnabled("ide.many.islands.theme") || System.getProperty("ide.many.islands.theme") != null) {
enableTheme(false)
}
}
private fun enableTheme(oneIsland: Boolean) {
val lafManager = LafManager.getInstance()
val colorsManager = EditorColorsManager.getInstance()
val currentTheme = lafManager.currentUIThemeLookAndFeel?.id ?: return
val currentEditorTheme = colorsManager.globalScheme.displayName
if (lafManager.autodetect) {
return
}
if ((currentTheme != "ExperimentalDark" && currentTheme != "ExperimentalLight" && currentTheme != "ExperimentalLightWithLightHeader") ||
(currentEditorTheme != "Light" && currentEditorTheme != "Dark" && currentEditorTheme != "Rider Light" && currentEditorTheme != "Rider Dark")) {
return
}
val id = PluginId.getId("com.chrisrm.idea.MaterialThemeUI")
if (PluginManagerCore.findPlugin(id) != null && !PluginManagerCore.isDisabled(id)) {
return
}
val uiThemeManager = UiThemeProviderListManager.getInstance()
val isLight = JBColor.isBright()
val editorScheme: String
val newTheme = if (oneIsland) {
editorScheme = if (isLight) "Light" else "Island Dark"
uiThemeManager.findThemeById(if (isLight) "One Island Light" else "One Island Dark")
}
else {
editorScheme = if (isLight) "Light" else "Island Dark"
uiThemeManager.findThemeById(if (isLight) "Many Islands Light" else "Many Islands Dark")
}
if (newTheme == null) {
return
}
PropertiesComponent.getInstance().setValue("ide.islands.show.feedback", "show.promo")
lafManager.setCurrentLookAndFeel(newTheme, true)
newTheme.installEditorScheme(colorsManager.getScheme(editorScheme) ?: colorsManager.defaultScheme)
lafManager.updateUI()
}

View File

@@ -322,7 +322,6 @@ fun CoroutineScope.startApplication(
}.getOrLogException(log)
}
applyIslandsTheme(false)
executeApplicationStarter(starter, args)
}
// no need to use a pool once started

View File

@@ -6,10 +6,13 @@ import com.intellij.application.options.colors.SchemesPanel
import com.intellij.application.options.colors.SchemesPanelFactory
import com.intellij.application.options.editor.CheckboxDescriptor
import com.intellij.application.options.editor.checkBox
import com.intellij.ide.*
import com.intellij.ide.DataManager
import com.intellij.ide.GeneralSettings
import com.intellij.ide.IdeBundle.message
import com.intellij.ide.ProjectWindowCustomizerService
import com.intellij.ide.actions.IdeScaleTransformer
import com.intellij.ide.actions.QuickChangeLookAndFeel
import com.intellij.ide.isSupportScreenReadersOverridden
import com.intellij.ide.plugins.PluginManagerConfigurable
import com.intellij.ide.ui.laf.LafManagerImpl
import com.intellij.ide.ui.search.OptionDescription
@@ -18,7 +21,6 @@ import com.intellij.internal.statistic.service.fus.collectors.UIEventLogger.IdeZ
import com.intellij.internal.statistic.service.fus.collectors.UIEventLogger.ThemeAutodetectSelector
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.impl.islands.IslandsFeedback
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.PlatformEditorBundle
@@ -155,11 +157,9 @@ internal class AppearanceConfigurable : BoundSearchableConfigurable(message("tit
private val propertyGraph = PropertyGraph()
private val lafProperty = propertyGraph.lazyProperty { lafManager.lookAndFeelReference }
private val syncThemeProperty = propertyGraph.lazyProperty { lafManager.autodetect }
private val islandLafProperty = propertyGraph.lazyProperty { IslandsFeedback.isIslandTheme() }
override fun createPanel(): DialogPanel {
lafProperty.afterChange(disposable!!) {
islandLafProperty.set(IslandsFeedback.isIslandTheme(it.themeId))
ApplicationManager.getApplication().invokeLater {
QuickChangeLookAndFeel.switchLafAndUpdateUI(lafManager, lafManager.findLaf(it.themeId), true)
LafManager.getInstance().checkRestart()
@@ -185,14 +185,6 @@ internal class AppearanceConfigurable : BoundSearchableConfigurable(message("tit
theme.component.renderer = lafManager.getLookAndFeelCellRenderer(theme.component)
lafComboBoxModelWrapper.comboBoxComponent = theme.component
browserLink(message("ide.islands.read.more"), IslandsFeedback.getReadMoreUrl()).visibleIf(islandLafProperty)
link(message("ide.islands.share.feedback")) {
BrowserUtil.browse(IslandsFeedback.getFeedbackUrl(IslandsFeedback.isOneIslandTheme(lafProperty.get().themeId)))
}
.visibleIf(islandLafProperty)
.component.setExternalLinkIcon()
checkBox(message("preferred.theme.autodetect.selector"))
.bindSelected(syncThemeProperty)
.visible(lafManager.autodetectSupported)

View File

@@ -1,179 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.application.impl.islands
import com.intellij.CommonBundle
import com.intellij.icons.AllIcons
import com.intellij.ide.BrowserUtil
import com.intellij.ide.IdeBundle
import com.intellij.ide.ui.AppearanceConfigurable
import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.LafManagerListener
import com.intellij.ide.util.PropertiesComponent
import com.intellij.idea.AppMode
import com.intellij.notification.Notification
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.ExperimentalUI
import com.intellij.util.concurrency.AppExecutorUtil
import java.util.concurrent.TimeUnit
internal class IslandsFeedback : ProjectActivity {
internal companion object {
internal fun isIslandTheme(): Boolean {
return isIslandTheme(LafManager.getInstance().currentUIThemeLookAndFeel?.id ?: return false)
}
internal fun isIslandTheme(themeId: String) = isOneIslandTheme(themeId) || isManyIslandTheme(themeId)
internal fun isOneIslandTheme(themeId: String) = themeId == "One Island Dark" || themeId == "One Island Light"
internal fun isManyIslandTheme(themeId: String) = themeId == "Many Islands Dark" || themeId == "Many Islands Light"
internal fun getReadMoreUrl() = "https://blog.jetbrains.com/platform/2025/06/testing-a-fresh-look-for-jetbrains-ides/"
internal fun getFeedbackUrl(oneIsland: Boolean): String {
return if (oneIsland) "https://surveys.jetbrains.com/s3/JetBrains-EAP-UI-Feedback-Survey" else "https://surveys.jetbrains.com/s3/Feedback-Survey-About-UI-EAP"
}
private var myFirstProject = !Registry.`is`("llm.riderNext.enabled", false) && ExperimentalUI.isNewUI() &&
!ApplicationManager.getApplication().isUnitTestMode &&
!ApplicationManager.getApplication().isHeadlessEnvironment &&
!AppMode.isRemoteDevHost()
}
override suspend fun execute(project: Project) {
if (myFirstProject) {
myFirstProject = false
handleFeedback(project)
}
}
private fun handleFeedback(project: Project) {
val properties = PropertiesComponent.getInstance()
val showFeedbackValue = properties.getValue("ide.islands.show.feedback")
if (showFeedbackValue != null) {
if (showFeedbackValue == "show.promo") {
showPromoNotification(project)
}
else {
showNotification(showFeedbackValue == "one")
return
}
}
val showFeedbackTime = properties.getLong("ide.islands.show.feedback.time", 0)
if (showFeedbackTime > 0) {
val themeId = LafManager.getInstance().currentUIThemeLookAndFeel.id
if (!isOneIslandTheme(themeId) && !isManyIslandTheme(themeId)) {
showNotification(properties.getBoolean("ide.islands.show.feedback.theme"))
return
}
scheduleNotification(properties.getBoolean("ide.islands.show.feedback.theme"), showFeedbackTime)
}
else {
val themeId = LafManager.getInstance().currentUIThemeLookAndFeel?.id ?: return
val oneIslandTheme = isOneIslandTheme(themeId)
if (oneIslandTheme || isManyIslandTheme(themeId)) {
val currentTime = System.currentTimeMillis()
properties.setValue("ide.islands.show.feedback.time", currentTime.toString())
properties.setValue("ide.islands.show.feedback.theme", oneIslandTheme)
scheduleNotification(oneIslandTheme, currentTime)
}
}
val connection = ApplicationManager.getApplication().messageBus.connect()
connection.subscribe(LafManagerListener.TOPIC, LafManagerListener { manager ->
val themeId = manager.currentUIThemeLookAndFeel.id
if (properties.getLong("ide.islands.show.feedback.time", 0) == 0L) {
if (isOneIslandTheme(themeId) || isManyIslandTheme(themeId)) {
val currentTime = System.currentTimeMillis()
val oneIslandTheme = isOneIslandTheme(themeId)
properties.setValue("ide.islands.show.feedback.time", currentTime.toString())
properties.setValue("ide.islands.show.feedback.theme", oneIslandTheme)
scheduleNotification(oneIslandTheme, currentTime)
}
}
else {
val oneIslandTheme = isOneIslandTheme(themeId)
if (oneIslandTheme || isManyIslandTheme(themeId)) {
properties.setValue("ide.islands.show.feedback.theme", oneIslandTheme)
}
else {
properties.setValue("ide.islands.show.feedback", if (properties.getBoolean("ide.islands.show.feedback.theme")) "one" else "many")
connection.disconnect()
}
}
})
}
private fun showNotification(oneIsland: Boolean) {
val properties = PropertiesComponent.getInstance()
if (properties.getValue("ide.islands.show.feedback") == "done") {
return
}
clearProperties(properties)
val notification = Notification("Feedback In IDE", IdeBundle.message("ide.islands.share.feedback.title"),
IdeBundle.message("ide.islands.share.feedback.message"), NotificationType.INFORMATION)
notification.addAction(NotificationAction.createSimpleExpiring(IdeBundle.message("ide.islands.share.feedback.button")) {
BrowserUtil.browse(getFeedbackUrl(oneIsland))
})
notification.addAction(NotificationAction.createSimpleExpiring(CommonBundle.message("button.decline")) {})
notification.setSuggestionType(true).setImportantSuggestion(true).setIcon(AllIcons.Ide.Feedback).notify(null)
}
private fun showPromoNotification(project: Project) {
val properties = PropertiesComponent.getInstance()
clearProperties(properties)
properties.unsetValue("ide.islands.show.feedback")
val notification = Notification("STICKY:Feedback In IDE", IdeBundle.message("ide.islands.share.feedback.promo.title"),
IdeBundle.message("ide.islands.share.feedback.promo.message"), NotificationType.INFORMATION)
notification.addAction(NotificationAction.createSimpleExpiring(IdeBundle.message("got.it.button.name")) {})
notification.addAction(NotificationAction.createSimpleExpiring(IdeBundle.message("ide.islands.read.more")) {
BrowserUtil.browse(getReadMoreUrl())
})
notification.addAction(NotificationAction.createSimpleExpiring(IdeBundle.message("ide.islands.switch.theme")) {
ShowSettingsUtil.getInstance().showSettingsDialog(project, AppearanceConfigurable::class.java)
})
notification.setSuggestionType(true).setImportantSuggestion(true).setIcon(AllIcons.Ide.Gift).setAddExtraAction(true).notify(null)
}
private fun clearProperties(properties: PropertiesComponent) {
properties.setValue("ide.islands.show.feedback", "done")
properties.unsetValue("ide.islands.show.feedback.time")
properties.unsetValue("ide.islands.show.feedback.theme")
}
private fun scheduleNotification(oneIsland: Boolean, time: Long) {
val delta = time + 48 * 3600 * 1000 - System.currentTimeMillis()
if (delta <= 0) {
showNotification(oneIsland)
return
}
AppExecutorUtil.getAppScheduledExecutorService().schedule(Runnable { showNotification(oneIsland) }, delta, TimeUnit.MILLISECONDS)
}
}

View File

@@ -181,14 +181,6 @@
</description>
</experimentalFeature>
<experimentalFeature id="ide.one.island.theme" percentOfUsers="25">
<description>Enable One Island UI theme</description>
</experimentalFeature>
<experimentalFeature id="ide.many.islands.theme" percentOfUsers="25">
<description>Enable Many Islands UI theme</description>
</experimentalFeature>
<applicationService serviceInterface="com.intellij.execution.wsl.WslDistributionManager"
serviceImplementation="com.intellij.execution.wsl.WslDistributionManagerImpl"/>
<applicationService serviceInterface="com.intellij.execution.wsl.WslIjentAvailabilityService"

View File

@@ -152,8 +152,6 @@
serviceInterface="com.intellij.facet.pointers.FacetPointersManager"/>
<postStartupActivity implementation="com.intellij.facet.impl.pointers.FacetPointersPostStartupActivity"/>
<postStartupActivity implementation="com.intellij.openapi.application.impl.islands.IslandsFeedback"/>
<applicationService
serviceInterface="com.intellij.formatting.visualLayer.VisualFormattingLayerService"
serviceImplementation="com.intellij.formatting.visualLayer.VisualFormattingLayerServiceImpl" />

View File

@@ -9,7 +9,6 @@ import com.intellij.openapi.components.SettingsCategory
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionNotApplicableException
import com.intellij.openapi.util.io.FileUtil
import com.intellij.platform.ide.bootstrap.applyIslandsTheme
import kotlinx.coroutines.*
import java.nio.file.Path
import kotlin.io.path.*
@@ -47,7 +46,6 @@ private class JbAfterRestartSettingsApplier(private val cs: CoroutineScope) : Ap
cs.launch {
withContext(Dispatchers.EDT) {
importer.importOptionsAfterRestart(options, pluginIds)
applyIslandsTheme(true)
}
}
}