From 10e7d4988ce61da1055356aff8b7ff196bf5a846 Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Tue, 23 Dec 2025 16:31:29 +0100 Subject: [PATCH] [devkit] IJPL-210990 Compose UI Preview internal failure when running a preview of Chat Sample App GitOrigin-RevId: 8e9910241d9af11da86517001d2ecd0a9cce6f6b --- platform/core-ui/src/ui/ExperimentalUI.kt | 8 +++++- .../src/preview/ComposeCompiler.kt | 2 +- .../preview/ComposePreviewChangesTracker.kt | 8 +++++- .../ComposePreviewToolWindowFactory.kt | 25 +++++++++++++------ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/platform/core-ui/src/ui/ExperimentalUI.kt b/platform/core-ui/src/ui/ExperimentalUI.kt index 199b259b4c76..5edb4b71a1bb 100644 --- a/platform/core-ui/src/ui/ExperimentalUI.kt +++ b/platform/core-ui/src/ui/ExperimentalUI.kt @@ -5,6 +5,7 @@ package com.intellij.ui import com.intellij.ide.util.PropertiesComponent import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.DevTimeClassLoader import com.intellij.openapi.components.service import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.util.registry.EarlyAccessRegistryManager @@ -54,7 +55,12 @@ abstract class ExperimentalUI { fun getInstance(): ExperimentalUI = ApplicationManager.getApplication().service() @JvmStatic - fun isNewUI(): Boolean = NewUiValue.isEnabled() + fun isNewUI(): Boolean { + // always true for development time tools, e.g., in Compose UI Preview + if (Thread.currentThread().contextClassLoader is DevTimeClassLoader) return true + + return NewUiValue.isEnabled() + } val isNewNavbar: Boolean get() = NewUiValue.isEnabled() && Registry.`is`("ide.experimental.ui.navbar.scroll", true) diff --git a/plugins/devkit/intellij.devkit.compose/src/preview/ComposeCompiler.kt b/plugins/devkit/intellij.devkit.compose/src/preview/ComposeCompiler.kt index f207573b86f2..0d59288a129d 100644 --- a/plugins/devkit/intellij.devkit.compose/src/preview/ComposeCompiler.kt +++ b/plugins/devkit/intellij.devkit.compose/src/preview/ComposeCompiler.kt @@ -83,7 +83,7 @@ internal suspend fun compileCode(fileToCompile: VirtualFile, project: Project): .toTypedArray() val pluginByClass = PluginManager.getPluginByClass(ComposePreviewToolWindowFactory::class.java) - val filteringClassLoader = FilteringClassLoader(pluginByClass!!.classLoader) + val filteringClassLoader = FilteringClassLoader(pluginByClass!!.pluginClassLoader!!) val loader = ComposeUIPreviewClassLoader(diskPaths, filteringClassLoader) val functions = ComposableFunctionFinder(loader).findPreviewFunctions(analysis.targetClassName, analysis.composableMethodNames) diff --git a/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewChangesTracker.kt b/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewChangesTracker.kt index 88a67aeb4083..d5041f10d161 100644 --- a/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewChangesTracker.kt +++ b/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewChangesTracker.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.application.EDT import com.intellij.openapi.application.writeAction import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.fileEditor.* import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile @@ -80,7 +81,12 @@ internal class ComposePreviewChangesTracker(val project: Project, val coroutineS FileDocumentManager.getInstance().saveAllDocuments() } - processor(virtualFile) + try { + processor(virtualFile) + } + catch (e: Throwable) { + thisLogger().error("Error during Compose UI Preview refresh chain", e) + } } } } diff --git a/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewToolWindowFactory.kt b/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewToolWindowFactory.kt index ee4df57bf30a..5e2650986a7a 100644 --- a/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewToolWindowFactory.kt +++ b/plugins/devkit/intellij.devkit.compose/src/preview/ComposePreviewToolWindowFactory.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.application.UI import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.diagnostic.fileLogger import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.openapi.ui.getUserData @@ -27,6 +27,8 @@ import javax.swing.JComponent internal const val TOOLWINDOW_ID = "ComposeUIPreview" +private val LOG = fileLogger() + internal class ComposePreviewToolWindowFactory : ToolWindowFactory, DumbAware { override fun shouldBeAvailable(project: Project): Boolean { @@ -47,6 +49,8 @@ internal class ComposePreviewToolWindowFactory : ToolWindowFactory, DumbAware { toolWindow.setTitleActions(titleActionsGroup.getChildren(actionManager).toList()) project.service().startTracking(project, toolWindowContent) { virtualFile -> + LOG.debug("New incoming change for preview $virtualFile") + val oldContent = withContext(Dispatchers.UI) { val current = wrapperPanel.components.firstOrNull() wrapperPanel.setPaintBusy(true) @@ -58,7 +62,7 @@ internal class ComposePreviewToolWindowFactory : ToolWindowFactory, DumbAware { compileCode(virtualFile, project) } catch (e: Throwable) { - thisLogger().warn("Unable to compile code for preview", e) + LOG.warn("Unable to compile code for preview of $virtualFile", e) return@startTracking } finally { @@ -78,19 +82,26 @@ internal class ComposePreviewToolWindowFactory : ToolWindowFactory, DumbAware { } withContext(Dispatchers.UI) { + LOG.debug("Apply new UI preview for $virtualFile") + // free up the previous content JVM classes, register new try { wrapperPanel.getUserData(PROVIDER_KEY)?.classLoader?.close() } catch (e: Exception) { - thisLogger().warn("Unable to release classloader for UI preview", e) + LOG.warn("Unable to release classloader for UI preview", e) } wrapperPanel.putUserData(PROVIDER_KEY, provider) - wrapperPanel.setPaintBusy(false) - wrapperPanel.setContent(compose(focusOnClickInside = true) { - provider.build(currentComposer, currentCompositeKeyHashCode) - }) + + try { + wrapperPanel.setContent(compose(focusOnClickInside = true) { + provider.build(currentComposer, currentCompositeKeyHashCode) + }) + } + catch (e: Exception) { + LOG.error("Unable to apply content for UI preview of $virtualFile", e) + } } } }