mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[compose] IJPL-208258 Refresh indication for Compose UI Preview
GitOrigin-RevId: 87e929b554642a79ca27a39303743b99f300fb91
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5c36d1af57
commit
49fd96e756
@@ -34,9 +34,10 @@
|
||||
|
||||
<registryKey key="devkit.compose.desktop.preview" defaultValue="false"
|
||||
restartRequired="true" description="Enables Compose UI previews tool window"/>
|
||||
<toolWindow id="ComposeUIPreview" anchor="right"
|
||||
factoryClass="com.intellij.devkit.compose.preview.ComposePreviewToolWindowFactory"
|
||||
icon="org.jetbrains.idea.devkit.DevKitIcons.ComposeToolWindow"/>
|
||||
<library.toolWindow id="ComposeUIPreview" anchor="right"
|
||||
factoryClass="com.intellij.devkit.compose.preview.ComposePreviewToolWindowFactory"
|
||||
icon="org.jetbrains.idea.devkit.DevKitIcons.ComposeToolWindow"
|
||||
librarySearchClass="com.intellij.devkit.compose.preview.ComposePreviewSearcher"/>
|
||||
</extensions>
|
||||
|
||||
<actions>
|
||||
@@ -62,10 +63,14 @@
|
||||
<group id="ComposePreviewActions.Title" searchable="false">
|
||||
<action id="ComposePreviewRefresh"
|
||||
class="com.intellij.devkit.compose.preview.ComposePreviewRefreshAction"
|
||||
icon="AllIcons.General.Refresh"/>
|
||||
icon="AllIcons.General.Refresh">
|
||||
<override-text place="GoToAction"/>
|
||||
</action>
|
||||
<action id="ComposePreviewRefreshAuto"
|
||||
class="com.intellij.devkit.compose.preview.ComposePreviewRefreshAutoAction"
|
||||
icon="AllIcons.General.RefreshAuto"/>
|
||||
icon="AllIcons.General.RefreshAuto">
|
||||
<override-text place="GoToAction"/>
|
||||
</action>
|
||||
</group>
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
|
||||
+9
-2
@@ -44,6 +44,7 @@ jewel.swing.not.editable.disabled=Not editable + disabled
|
||||
jewel.swing.editable=Editable
|
||||
jewel.swing.editable.disabled=Editable + Disabled
|
||||
jewel.swing.text.areas=Text areas:
|
||||
|
||||
compose.sandbox=Compose Sandbox
|
||||
compose.sandbox.show.automatically.on.project.open=Show automatically on project open
|
||||
configurable.name.settings.page.on.compose=Settings Page on Compose
|
||||
@@ -52,11 +53,17 @@ link.label.enable.hot.reload=Enable Hot Reload
|
||||
link.label.dismiss=Dismiss
|
||||
|
||||
toolwindow.stripe.ComposeUIPreview=Compose UI Preview
|
||||
compose.preview.empty.text.top=Preview will appear here.
|
||||
compose.preview.empty.text.top=Preview will appear here \u1F300.
|
||||
compose.preview.empty.text.compile=Make sure you compile the project at least once!
|
||||
compose.preview.build=Build project
|
||||
compose.preview.build=\u2014 <Build project>
|
||||
compose.preview.refresh=\u2014 <Refresh>
|
||||
compose.preview.unsupported.file=This file is not supported:
|
||||
compose.preview.enable.composable=\u2014 Use @Composable and @Preview to enable UI preview
|
||||
|
||||
action.ComposePreviewRefresh.text=Refresh
|
||||
action.ComposePreviewRefresh.GoToAction.text=Refresh Compose UI Preview
|
||||
action.ComposePreviewRefresh.description=Refreshes UI preview
|
||||
|
||||
action.ComposePreviewRefreshAuto.text=Refresh Automatically
|
||||
action.ComposePreviewRefreshAuto.GoToAction.text=Refresh Compose UI Preview Automatically
|
||||
action.ComposePreviewRefreshAuto.description=Toggles automatic refresh on changes in code
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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.devkit.compose.preview
|
||||
|
||||
import com.intellij.devkit.compose.DevkitComposeBundle
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUiKind
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT
|
||||
import com.intellij.openapi.actionSystem.Presentation
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.keymap.KeymapUtil
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.JBPanelWithEmptyText
|
||||
import com.intellij.util.ui.AsyncProcessIcon
|
||||
import com.intellij.util.ui.StatusText
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
internal class ComposePreviewBusyPanel(private val project: Project) : JBPanelWithEmptyText(BorderLayout()), DumbAware {
|
||||
@Volatile
|
||||
private var busy: Boolean = false
|
||||
private var busyIcon: AsyncProcessIcon? = null
|
||||
|
||||
init {
|
||||
emptyText.isCenterAlignText = false
|
||||
emptyText.clear()
|
||||
emptyText.appendLine(DevkitComposeBundle.message("compose.preview.empty.text.top"))
|
||||
emptyText.appendLine(DevkitComposeBundle.message("compose.preview.empty.text.compile"))
|
||||
emptyText.appendLine("")
|
||||
appendBuildHintText(project)
|
||||
}
|
||||
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
private fun appendBuildHintText(project: Project) {
|
||||
val text = emptyText
|
||||
|
||||
val buildLine = DevkitComposeBundle.message("compose.preview.build")
|
||||
text.appendLine(buildLine.substringBefore("<"))
|
||||
text.appendText(buildLine.substringAfter('<').substringBefore('>'), SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES) { _ ->
|
||||
val compileAction = ActionManager.getInstance().getAction("CompileDirty")!!
|
||||
val dataContext = SimpleDataContext.builder().add(PROJECT, project).build()
|
||||
ActionUtil.performAction(compileAction, AnActionEvent.createEvent(dataContext, Presentation(), "", ActionUiKind.NONE, null))
|
||||
}
|
||||
text.appendText(buildLine.substringAfter(">"))
|
||||
val shortcut = ActionManager.getInstance().getKeyboardShortcut("CompileDirty")
|
||||
val shortcutText = shortcut?.let { " (${KeymapUtil.getShortcutText(shortcut)})" } ?: ""
|
||||
text.appendText(shortcutText)
|
||||
|
||||
addRefreshHintText(text, project)
|
||||
}
|
||||
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
private fun addRefreshHintText(text: StatusText, project: Project) {
|
||||
val refreshLine = DevkitComposeBundle.message("compose.preview.refresh")
|
||||
text.appendLine(refreshLine.substringBefore("<"))
|
||||
text.appendText(refreshLine.substringAfter('<').substringBefore('>'), SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES) { _ ->
|
||||
project.service<ComposePreviewChangesTracker>().refresh()
|
||||
}
|
||||
text.appendText(refreshLine.substringAfter(">"))
|
||||
}
|
||||
|
||||
fun setPaintBusy(paintBusy: Boolean) {
|
||||
if (busy == paintBusy) return
|
||||
|
||||
busy = paintBusy
|
||||
updateBusy()
|
||||
}
|
||||
|
||||
private fun updateBusy() {
|
||||
if (busy) {
|
||||
if (busyIcon == null) {
|
||||
busyIcon = AsyncProcessIcon.Big(toString())
|
||||
busyIcon!!.setOpaque(false)
|
||||
busyIcon!!.setPaintPassiveIcon(false)
|
||||
add(busyIcon!!, BorderLayout.CENTER)
|
||||
}
|
||||
}
|
||||
|
||||
if (busyIcon != null) {
|
||||
if (busy) {
|
||||
busyIcon!!.resume()
|
||||
}
|
||||
else {
|
||||
busyIcon!!.suspend()
|
||||
SwingUtilities.invokeLater(Runnable {
|
||||
if (busyIcon != null) {
|
||||
repaint()
|
||||
}
|
||||
})
|
||||
}
|
||||
if (busyIcon != null) {
|
||||
busyIcon!!.updateLocation(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun displayUnsupportedFile() {
|
||||
removeAll()
|
||||
|
||||
emptyText.clear()
|
||||
emptyText.appendLine(AllIcons.Ide.FatalErrorRead, DevkitComposeBundle.message("compose.preview.unsupported.file"),
|
||||
StatusText.DEFAULT_ATTRIBUTES, null)
|
||||
emptyText.appendLine(DevkitComposeBundle.message("compose.preview.enable.composable"))
|
||||
|
||||
addRefreshHintText(emptyText, project)
|
||||
}
|
||||
}
|
||||
+74
-10
@@ -1,6 +1,7 @@
|
||||
// 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.devkit.compose.preview
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.editor.Editor
|
||||
@@ -17,28 +18,91 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
private val manualRefreshCounter = AtomicInteger(0)
|
||||
|
||||
enum class RefreshReason {
|
||||
INITIAL,
|
||||
CHANGE,
|
||||
MANUAL {
|
||||
override fun toHash(): Int {
|
||||
return manualRefreshCounter.addAndGet(1)
|
||||
}
|
||||
};
|
||||
|
||||
open fun toHash(): Int = ordinal
|
||||
}
|
||||
|
||||
private data class RefreshSignal(
|
||||
val text: String?,
|
||||
val file: VirtualFile,
|
||||
val reason: RefreshReason,
|
||||
val hash: Int,
|
||||
) {
|
||||
constructor(text: String?, file: VirtualFile, reason: RefreshReason) : this(text, file, reason, reason.toHash())
|
||||
}
|
||||
|
||||
private const val REFRESH_SETTING_KEY = "compose.preview.refresh.auto"
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class EditorSelectionAndTextChangesTracker(val project: Project, val coroutineScope: CoroutineScope) {
|
||||
internal class ComposePreviewChangesTracker(val project: Project, val coroutineScope: CoroutineScope) {
|
||||
@Volatile
|
||||
private var refreshCallback: (() -> Unit)? = null
|
||||
|
||||
private val autoRefresh: MutableStateFlow<Boolean> =
|
||||
MutableStateFlow(PropertiesComponent.getInstance().getBoolean(REFRESH_SETTING_KEY, false))
|
||||
|
||||
fun setAutoRefresh(value: Boolean) {
|
||||
autoRefresh.value = value
|
||||
PropertiesComponent.getInstance().setValue(REFRESH_SETTING_KEY, value)
|
||||
}
|
||||
|
||||
fun isAutoRefresh(): Boolean {
|
||||
return autoRefresh.value
|
||||
}
|
||||
|
||||
fun startTracking(project: Project, disposable: Disposable, processor: suspend (VirtualFile) -> Unit) {
|
||||
coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
|
||||
observeEditorContentChanges(project, disposable)
|
||||
val changesFlow = observeEditorContentChanges(project, disposable)
|
||||
.debounce(1000L)
|
||||
.distinctUntilChanged()
|
||||
.filter { autoRefresh.value }
|
||||
|
||||
val manualRefreshFlow = observeManualRefresh()
|
||||
|
||||
merge(changesFlow, manualRefreshFlow)
|
||||
.conflate() // drop older refreshes, there is no need to process them
|
||||
.collect { (_, virtualFile) ->
|
||||
processor(virtualFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeEditorContentChanges(project: Project, disposable: Disposable): Flow<Pair<String, VirtualFile>> {
|
||||
fun refresh() {
|
||||
refreshCallback?.invoke()
|
||||
}
|
||||
|
||||
private fun observeManualRefresh(): Flow<RefreshSignal> {
|
||||
return callbackFlow {
|
||||
refreshCallback = {
|
||||
val fileEditorManager = FileEditorManager.getInstance(project)
|
||||
val selectedEditor = fileEditorManager.selectedEditor as? TextEditor
|
||||
val selectedFile = fileEditorManager.selectedFiles.firstOrNull()
|
||||
|
||||
if (selectedEditor != null && selectedFile != null) {
|
||||
trySend(RefreshSignal(null, selectedFile, RefreshReason.MANUAL))
|
||||
}
|
||||
}
|
||||
|
||||
awaitClose { }
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeEditorContentChanges(project: Project, disposable: Disposable): Flow<RefreshSignal> {
|
||||
return callbackFlow {
|
||||
var currentEditor: Editor? = null
|
||||
var documentListener: DocumentListener? = null
|
||||
@@ -63,7 +127,7 @@ internal class EditorSelectionAndTextChangesTracker(val project: Project, val co
|
||||
documentListener = object : DocumentListener {
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
val text = event.document.text
|
||||
trySend(text to file)
|
||||
trySend(RefreshSignal(text, file, RefreshReason.CHANGE))
|
||||
}
|
||||
}
|
||||
currentEditor!!.document.addDocumentListener(documentListener!!, disposable)
|
||||
@@ -77,7 +141,7 @@ internal class EditorSelectionAndTextChangesTracker(val project: Project, val co
|
||||
|
||||
if (newFile != null) {
|
||||
val text = newEditor.document.text
|
||||
trySend(text to newFile)
|
||||
trySend(RefreshSignal(text, newFile, RefreshReason.CHANGE))
|
||||
addListenersToCurrentEditor(newEditor, newFile)
|
||||
}
|
||||
else {
|
||||
@@ -97,7 +161,7 @@ internal class EditorSelectionAndTextChangesTracker(val project: Project, val co
|
||||
val selectedFile = fileEditorManager.selectedFiles.firstOrNull()
|
||||
|
||||
if (selectedEditor != null && selectedFile != null) {
|
||||
trySend("" to selectedFile)
|
||||
trySend(RefreshSignal(null, selectedFile, RefreshReason.INITIAL))
|
||||
addListenersToCurrentEditor(selectedEditor.editor, selectedFile)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
// 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.devkit.compose.preview
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
|
||||
internal class ComposePreviewRefreshAction : DumbAwareAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = e.project != null
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val p = e.project ?: return
|
||||
|
||||
p.service<ComposePreviewChangesTracker>().refresh()
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -3,18 +3,31 @@ package com.intellij.devkit.compose.preview
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||
|
||||
internal class ComposePreviewRefreshAutoAction : DumbAwareToggleAction() {
|
||||
private var refresh: Boolean = true
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
super.update(e)
|
||||
|
||||
e.presentation.isEnabledAndVisible = e.project != null
|
||||
}
|
||||
|
||||
override fun isSelected(e: AnActionEvent): Boolean {
|
||||
return this.refresh
|
||||
val p = e.project ?: return false
|
||||
|
||||
return p.service<ComposePreviewChangesTracker>().isAutoRefresh()
|
||||
}
|
||||
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
this.refresh = state
|
||||
val p = e.project ?: return
|
||||
|
||||
val tracker = p.service<ComposePreviewChangesTracker>()
|
||||
tracker.setAutoRefresh(state)
|
||||
if (state) {
|
||||
tracker.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.devkit.compose.preview
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ext.LibrarySearchHelper
|
||||
import org.jetbrains.idea.devkit.util.PsiUtil
|
||||
|
||||
internal class ComposePreviewSearcher : LibrarySearchHelper {
|
||||
override fun isLibraryExists(project: Project): Boolean {
|
||||
// only in plugin projects to not conflict with other previews
|
||||
return PsiUtil.isPluginProject(project)
|
||||
}
|
||||
}
|
||||
+37
-51
@@ -5,15 +5,11 @@ package com.intellij.devkit.compose.preview
|
||||
|
||||
import androidx.compose.runtime.currentComposer
|
||||
import androidx.compose.runtime.currentCompositeKeyHashCode
|
||||
import com.intellij.devkit.compose.DevkitComposeBundle
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.keymap.KeymapUtil
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.getUserData
|
||||
@@ -22,9 +18,6 @@ import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ToolWindowFactory
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.JBPanelWithEmptyText
|
||||
import com.intellij.util.ui.StatusText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -40,17 +33,7 @@ internal class ComposePreviewToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||
|
||||
@OptIn(ExperimentalJewelApi::class)
|
||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||
val wrapperPanel = object : JBPanelWithEmptyText(), DumbAware {
|
||||
}
|
||||
wrapperPanel.setLayout(BorderLayout())
|
||||
|
||||
val emptyText = wrapperPanel.emptyText
|
||||
emptyText.isCenterAlignText = false
|
||||
emptyText.clear()
|
||||
emptyText.appendLine(DevkitComposeBundle.message("compose.preview.empty.text.top"))
|
||||
emptyText.appendLine(DevkitComposeBundle.message("compose.preview.empty.text.compile"))
|
||||
emptyText.appendLine("")
|
||||
appendBuildHintText(project, emptyText)
|
||||
val wrapperPanel = ComposePreviewBusyPanel(project)
|
||||
|
||||
val contentManager = toolWindow.contentManager
|
||||
val toolWindowContent = contentManager.factory.createContent(wrapperPanel, "", false)
|
||||
@@ -61,43 +44,46 @@ internal class ComposePreviewToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||
val titleActionsGroup = actionManager.getAction("ComposePreviewActions.Title") as DefaultActionGroup
|
||||
toolWindow.setTitleActions(titleActionsGroup.getChildren(actionManager).toList())
|
||||
|
||||
project.service<EditorSelectionAndTextChangesTracker>().startTracking(project, toolWindowContent) { virtualFile ->
|
||||
try {
|
||||
val provider = compileCode(virtualFile, project) ?: return@startTracking
|
||||
project.service<ComposePreviewChangesTracker>().startTracking(project, toolWindowContent) { virtualFile ->
|
||||
wrapperPanel.setPaintBusy(true)
|
||||
|
||||
withContext(Dispatchers.EDT) {
|
||||
// free up the previous content JVM classes, register new
|
||||
try {
|
||||
wrapperPanel.getUserData(PROVIDER_KEY)?.classLoader?.close()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
thisLogger().error("Unable to release classloader for UI preview", e)
|
||||
}
|
||||
|
||||
wrapperPanel.putUserData(PROVIDER_KEY, provider)
|
||||
wrapperPanel.removeAll()
|
||||
|
||||
wrapperPanel.add(compose(focusOnClickInside = true) {
|
||||
provider.build(currentComposer, currentCompositeKeyHashCode)
|
||||
}, BorderLayout.CENTER)
|
||||
}
|
||||
val provider = try {
|
||||
compileCode(virtualFile, project)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
thisLogger().error(e)
|
||||
thisLogger().warn("Unable to compile code for preview", e)
|
||||
return@startTracking
|
||||
}
|
||||
finally {
|
||||
wrapperPanel.setPaintBusy(false)
|
||||
}
|
||||
|
||||
if (provider == null) {
|
||||
withContext(Dispatchers.EDT) {
|
||||
wrapperPanel.displayUnsupportedFile()
|
||||
}
|
||||
return@startTracking
|
||||
}
|
||||
|
||||
withContext(Dispatchers.EDT) {
|
||||
// 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)
|
||||
}
|
||||
wrapperPanel.putUserData(PROVIDER_KEY, provider)
|
||||
|
||||
wrapperPanel.setPaintBusy(false)
|
||||
wrapperPanel.removeAll()
|
||||
|
||||
wrapperPanel.add(compose(focusOnClickInside = true) {
|
||||
provider.build(currentComposer, currentCompositeKeyHashCode)
|
||||
}, BorderLayout.CENTER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendBuildHintText(project: Project, text: StatusText) {
|
||||
val shortcut = ActionManager.getInstance().getKeyboardShortcut("CompileDirty")
|
||||
text.appendLine(DevkitComposeBundle.message("compose.preview.build"), SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES) { _ ->
|
||||
val compileAction = ActionManager.getInstance().getAction("CompileDirty")!!
|
||||
val dataContext = SimpleDataContext.builder().add(PROJECT, project).build()
|
||||
ActionUtil.performAction(compileAction, AnActionEvent.createEvent(dataContext, Presentation(), "", ActionUiKind.NONE, null))
|
||||
}
|
||||
val shortcutText = shortcut?.let { " (${KeymapUtil.getShortcutText(shortcut)})" } ?: ""
|
||||
text.appendText(shortcutText)
|
||||
}
|
||||
|
||||
private val PROVIDER_KEY = Key.create<ContentProvider>("ComposePreviewToolWindowFactory.ContentProvider")
|
||||
|
||||
Reference in New Issue
Block a user