mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[compose] IJPL-208258 Isolate Compose UI Preview application on rendering from the host IDE
GitOrigin-RevId: fe2ed7d0684897037f112c848aa5a2810d18fb35
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9e6bbb34cf
commit
4da98930a0
@@ -643,11 +643,21 @@ abstract class ComponentManagerImpl(
|
||||
return getOrCreateInstanceBlocking(holder = adapter.holder, debugString = key.name, keyClass = key) as T
|
||||
}
|
||||
|
||||
private fun isDevelopmentTime(): Boolean {
|
||||
return Thread.currentThread().contextClassLoader is DevTimeClassLoader
|
||||
}
|
||||
|
||||
final override fun <T : Any> getService(serviceClass: Class<T>): T? {
|
||||
if (isDevelopmentTime()) return null
|
||||
|
||||
return doGetService(serviceClass, true) ?: return postGetService(serviceClass, createIfNeeded = true)
|
||||
}
|
||||
|
||||
final override suspend fun <T : Any> getServiceAsync(keyClass: Class<T>): T {
|
||||
if (isDevelopmentTime()) {
|
||||
throw IllegalStateException("Getting services is not allowed from development tools threads")
|
||||
}
|
||||
|
||||
return serviceContainer.instance(keyClass)
|
||||
}
|
||||
|
||||
@@ -660,6 +670,8 @@ abstract class ComponentManagerImpl(
|
||||
protected open fun <T : Any> postGetService(serviceClass: Class<T>, createIfNeeded: Boolean): T? = null
|
||||
|
||||
final override fun <T : Any> getServiceIfCreated(serviceClass: Class<T>): T? {
|
||||
if (isDevelopmentTime()) return null
|
||||
|
||||
return doGetService(serviceClass, createIfNeeded = false) ?: postGetService(serviceClass, createIfNeeded = false)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.BundleBase.SHOW_LOCALIZED_MESSAGES
|
||||
import com.intellij.BundleBase.appendLocalizationSuffix
|
||||
import com.intellij.BundleBase.getDefaultMessage
|
||||
import com.intellij.BundleBase.replaceMnemonicAmpersand
|
||||
import com.intellij.openapi.application.DevTimeClassLoader
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
@@ -61,7 +62,7 @@ object BundleBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs partial application of the pattern message from the bundle leaving some parameters unassigned.
|
||||
* Performs partial application of the pattern message from the bundle, leaving some parameters unassigned.
|
||||
* It's expected that the message contains `params.length + unassignedParams` placeholders. Parameters
|
||||
* `{0}..{params.length-1}` will be substituted using a passed params array. The remaining parameters
|
||||
* will be renumbered: `{params.length}` will become `{0}` and so on, so the resulting template
|
||||
@@ -284,8 +285,9 @@ internal fun useDefaultValue(bundle: ResourceBundle, @NlsSafe key: String): @Nls
|
||||
return "!$key!"
|
||||
}
|
||||
|
||||
private fun isDevelopmentTime(classLoader: ClassLoader): Boolean {
|
||||
return classLoader.javaClass.simpleName == "DevKitClassLoader"
|
||||
private fun isDevelopmentTime(bundleClassLoader: ClassLoader): Boolean {
|
||||
return bundleClassLoader is DevTimeClassLoader
|
||||
|| Thread.currentThread().contextClassLoader is DevTimeClassLoader
|
||||
}
|
||||
|
||||
internal fun postProcessResolvedValue(
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Magic delegating class loader for UI previews.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
interface DevTimeClassLoader
|
||||
@@ -28,6 +28,9 @@ internal class ComposableFunctionFinder(private val classLoader: ClassLoader) {
|
||||
fun findPreviewFunctions(clazzFqn: String, composableMethodNames: Collection<String>): List<ComposablePreviewFunction> {
|
||||
val previewFunctions = mutableListOf<ComposablePreviewFunction>()
|
||||
|
||||
val contextClassLoader = Thread.currentThread().contextClassLoader
|
||||
Thread.currentThread().contextClassLoader = classLoader
|
||||
|
||||
try {
|
||||
val clazz = classLoader.loadClass(clazzFqn)
|
||||
val functions = findPreviewFunctionsInClass(clazz, composableMethodNames)
|
||||
@@ -39,6 +42,9 @@ internal class ComposableFunctionFinder(private val classLoader: ClassLoader) {
|
||||
catch (e: Exception) {
|
||||
logger.error("Error processing class: $clazzFqn", e)
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().contextClassLoader = contextClassLoader
|
||||
}
|
||||
|
||||
return previewFunctions
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.AnnotationUtil
|
||||
import com.intellij.debugger.ui.HotSwapUIImpl
|
||||
import com.intellij.devkit.compose.hasCompose
|
||||
import com.intellij.ide.plugins.PluginManager
|
||||
import com.intellij.openapi.application.DevTimeClassLoader
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
@@ -36,12 +37,18 @@ internal data class ModulePaths(val module: Module, val paths: List<String>)
|
||||
|
||||
internal data class ContentProvider(val function: Method, val classLoader: URLClassLoader) {
|
||||
fun build(currentComposer: Composer, currentCompositeKeyHashCode: Long) {
|
||||
val contextClassLoader = Thread.currentThread().contextClassLoader
|
||||
Thread.currentThread().contextClassLoader = classLoader
|
||||
|
||||
try {
|
||||
function.invoke(null, currentComposer, currentCompositeKeyHashCode.toInt())
|
||||
}
|
||||
catch (t: Throwable) {
|
||||
thisLogger().warn("Unable to build preview", t)
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().contextClassLoader = contextClassLoader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,17 +82,15 @@ internal suspend fun compileCode(fileToCompile: VirtualFile, project: Project):
|
||||
val pluginByClass = PluginManager.getPluginByClass(ComposePreviewToolWindowFactory::class.java)
|
||||
val filteringClassLoader = FilteringClassLoader(pluginByClass!!.classLoader)
|
||||
|
||||
val loader = DevKitClassLoader(diskPaths, filteringClassLoader)
|
||||
val loader = ComposeUIPreviewClassLoader(diskPaths, filteringClassLoader)
|
||||
val functions = ComposableFunctionFinder(loader).findPreviewFunctions(analysis.targetClassName, analysis.composableMethodNames)
|
||||
|
||||
return functions.firstOrNull()?.method
|
||||
?.let { ContentProvider(it, loader) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Here the magic name `DevKitClassLoader` is used in the IDE process to check if we are in development time classloader.
|
||||
*/
|
||||
internal class DevKitClassLoader(urls: Array<URL>, parent: ClassLoader) : URLClassLoader("ComposeUIPreview", urls, parent)
|
||||
internal class ComposeUIPreviewClassLoader(urls: Array<URL>, parent: ClassLoader)
|
||||
: URLClassLoader("ComposeUIPreview", urls, parent), DevTimeClassLoader
|
||||
|
||||
private suspend fun compileFiles(fileToCompile: VirtualFile, project: Project): List<VirtualFile> {
|
||||
val taskManager = ProjectTaskManager.getInstance(project) as ProjectTaskManagerImpl
|
||||
@@ -145,7 +150,7 @@ private fun analyzeClass(project: Project, vFile: VirtualFile): FileAnalysisResu
|
||||
}
|
||||
|
||||
/**
|
||||
* Isolates project code from attempts to load unrelated classes via parent classloader.
|
||||
* Isolates project code from attempts to load unrelated classes via the parent classloader.
|
||||
*/
|
||||
private class FilteringClassLoader(parent: ClassLoader) : ClassLoader(parent) {
|
||||
override fun loadClass(name: String, resolve: Boolean): Class<*>? {
|
||||
|
||||
Reference in New Issue
Block a user