From 9686eb2acd25220b18cfa6a624646b4b0aa165c0 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov <654232+AlexeyTsvetkov@users.noreply.github.com> Date: Mon, 29 Nov 2021 20:13:05 +0300 Subject: [PATCH] Improve handling of preview errors (#1502) --- .../desktop/ui/tooling/preview/rpc/Command.kt | 1 + .../ui/tooling/preview/rpc/ExitCodes.kt | 2 + .../preview/rpc/PreviewErrorReporter.kt | 21 ++++++ .../tooling/preview/rpc/PreviewException.kt | 8 +++ .../ui/tooling/preview/rpc/PreviewManager.kt | 70 +++++++++++++++---- .../tooling/preview/rpc/RemotePreviewHost.kt | 55 +++++++++------ .../ui/tooling/preview/rpc/commands.kt | 28 ++++++-- .../ui/tooling/preview/rpc/constants.kt | 1 + .../ui/tooling/preview/rpc/protocolVersion.kt | 2 +- .../tooling/preview/rpc/utils/RingBuffer.kt | 51 ++++++++++++++ .../preview/rpc/utils/RingBufferTest.kt | 60 ++++++++++++++++ .../ide/preview/IdePreviewErrorReporter.kt | 27 +++++++ .../desktop/ide/preview/PreviewPanel.kt | 2 +- .../ide/preview/PreviewStateService.kt | 17 ++++- 14 files changed, 303 insertions(+), 42 deletions(-) create mode 100644 gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewErrorReporter.kt create mode 100644 gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewException.kt create mode 100644 gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBuffer.kt create mode 100644 gradle-plugins/preview-rpc/src/test/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBufferTest.kt create mode 100644 idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/IdePreviewErrorReporter.kt diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/Command.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/Command.kt index 93bf2f916f..357c472acd 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/Command.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/Command.kt @@ -9,6 +9,7 @@ data class Command(val type: Type, val args: List) { enum class Type { ATTACH, FRAME, + ERROR, PREVIEW_CONFIG, PREVIEW_CLASSPATH, PREVIEW_FQ_NAME, diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/ExitCodes.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/ExitCodes.kt index 14366c228f..1a892c3a1b 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/ExitCodes.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/ExitCodes.kt @@ -8,4 +8,6 @@ package org.jetbrains.compose.desktop.ui.tooling.preview.rpc internal object ExitCodes { const val OK = 0 const val COULD_NOT_CONNECT_TO_PREVIEW_MANAGER = 1 + const val RECEIVER_FATAL_ERROR = 2 + const val SENDER_FATAL_ERROR = 3 } \ No newline at end of file diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewErrorReporter.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewErrorReporter.kt new file mode 100644 index 0000000000..7025c37e12 --- /dev/null +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewErrorReporter.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.ui.tooling.preview.rpc + +interface PreviewErrorReporter { + fun report(e: Throwable, details: String? = null) + fun report(e: String, details: String? = null) +} + +object StderrPreviewErrorReporter : PreviewErrorReporter { + override fun report(e: Throwable, details: String?) { + report(e.stackTraceString) + } + + override fun report(e: String, details: String?) { + System.err.println(e) + } +} \ No newline at end of file diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewException.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewException.kt new file mode 100644 index 0000000000..d9a48c7541 --- /dev/null +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewException.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.ui.tooling.preview.rpc + +class PreviewException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt index 80f8d8ccba..2d2d8bc2c2 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt @@ -5,6 +5,7 @@ package org.jetbrains.compose.desktop.ui.tooling.preview.rpc +import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.utils.RingBuffer import java.io.IOException import java.net.ServerSocket import java.net.SocketTimeoutException @@ -49,8 +50,10 @@ private data class RunningPreview( } class PreviewManagerImpl( - private val previewListener: PreviewListener = PreviewListenerBase() + private val previewListener: PreviewListener = PreviewListenerBase(), + private val errorReporter: PreviewErrorReporter = StderrPreviewErrorReporter ) : PreviewManager { + // todo: add quiet mode private val log = PrintStreamLogger("SERVER") private val previewSocket = newServerSocket() private val gradleCallbackSocket = newServerSocket() @@ -78,9 +81,9 @@ class PreviewManagerImpl( PREVIEW_HOST_CLASS_NAME, previewSocket.localPort.toString() ).apply { - // todo: non verbose mode - redirectOutput(ProcessBuilder.Redirect.INHERIT) - redirectError(ProcessBuilder.Redirect.INHERIT) + redirectOutput(ProcessBuilder.Redirect.PIPE) + redirectError(ProcessBuilder.Redirect.PIPE) + redirectErrorStream(true) }.start() val runningPreview = runningPreview.get() @@ -91,6 +94,39 @@ class PreviewManagerImpl( connection?.receiveAttach(listener = previewListener) { this.runningPreview.set(RunningPreview(connection, process)) } + val processLogLines = RingBuffer(512) + val exception = StringBuilder() + var exceptionMarker = false + process.inputStream.bufferedReader().forEachLine { line -> + if (exceptionMarker) { + exception.appendLine(line) + } else { + if (line.startsWith(PREVIEW_START_OF_STACKTRACE_MARKER)) { + exceptionMarker = true + } else { + processLogLines.add(line) + } + } + } + while (process.isAlive) { + process.waitFor(5, TimeUnit.SECONDS) + if (process.isAlive) { + process.destroyForcibly() + process.waitFor(5, TimeUnit.SECONDS) + } + } + if (process.isAlive) error("Preview process does not finish!") + + val exitCode = process.exitValue() + if (exitCode != ExitCodes.OK) { + val errorMessage = buildString { + appendLine("Preview process exited unexpectedly: exitCode=$exitCode") + if (exceptionMarker) { + appendLine(exception) + } + } + errorReporter.report(PreviewException(errorMessage), details = processLogLines.joinToString("\n")) + } } } @@ -115,13 +151,21 @@ class PreviewManagerImpl( private val receivePreviewResponseThread = repeatWhileAliveThread("receivePreviewResponse") { withLivePreviewConnection { - receiveFrame { renderedFrame -> - inProcessRequest.get()?.let { request -> - processedRequest.set(request) - inProcessRequest.compareAndSet(request, null) + receiveFrame( + onFrame = { renderedFrame -> + inProcessRequest.get()?.let { request -> + processedRequest.set(request) + inProcessRequest.compareAndSet(request, null) + } + previewListener.onRenderedFrame(renderedFrame) + }, + onError = { error -> + errorReporter.report(PreviewException(error)) + previewHostConfig.set(null) + previewClasspath.set(null) + inProcessRequest.set(null) } - previewListener.onRenderedFrame(renderedFrame) - } + ) } } @@ -244,12 +288,12 @@ class PreviewManagerImpl( Thread.sleep(sleepDelayMs) } catch (e: InterruptedException) { continue - } catch (e: Throwable) { - e.printStackTrace(System.err) - break } } }.also { + it.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { thread, e -> + errorReporter.report(e) + } threads.add(it) it.start() } diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RemotePreviewHost.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RemotePreviewHost.kt index 7b71c283aa..8d96ab8b16 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RemotePreviewHost.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/RemotePreviewHost.kt @@ -69,31 +69,46 @@ internal class PreviewHost(private val log: PreviewLogger, connection: RemoteCon Thread.sleep(DEFAULT_SLEEP_DELAY_MS) } catch (e: InterruptedException) { continue + } catch (e: Exception) { + if (connection.isAlive) { + connection.sendError(e) + } else { + throw IllegalStateException("Could not report an exception: IDE connection is not alive", e) + } } } - } + }.setUpUnhandledExceptionHandler(ExitCodes.SENDER_FATAL_ERROR) val receiverThread = thread { - try { - while (connection.isAlive) { - try { - connection.receivePreviewRequest( - onPreviewClasspath = { - previewClasspath.set(it) - senderThread.interrupt() - }, - onFrameRequest = { - previewRequest.set(it) - senderThread.interrupt() - } - ) - } catch (e: SocketTimeoutException) { - continue - } + while (connection.isAlive) { + try { + connection.receivePreviewRequest( + onPreviewClasspath = { + previewClasspath.set(it) + senderThread.interrupt() + }, + onFrameRequest = { + previewRequest.set(it) + senderThread.interrupt() + } + ) + } catch (e: SocketTimeoutException) { + continue + } catch (e: InterruptedException) { + continue + } + } + }.setUpUnhandledExceptionHandler(ExitCodes.RECEIVER_FATAL_ERROR) + + private fun Thread.setUpUnhandledExceptionHandler(exitCode: Int): Thread = apply { + uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e -> + try { + System.err.println() + System.err.println(PREVIEW_START_OF_STACKTRACE_MARKER) + e.printStackTrace(System.err) + } finally { + exitProcess(exitCode) } - } catch (e: Throwable) { - e.printStackTrace(System.err) - exitProcess(1) } } diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/commands.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/commands.kt index bd12ec1828..dc314d5a23 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/commands.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/commands.kt @@ -32,14 +32,30 @@ internal fun RemoteConnection.sendFrame(frame: RenderedFrame) { sendData(frame.bytes) } -internal fun RemoteConnection.receiveFrame(fn: (RenderedFrame) -> Unit) { +internal fun RemoteConnection.sendError(e: Exception) { + sendCommand(Command.Type.ERROR) + sendUtf8StringData(e.stackTraceString) +} + +internal fun RemoteConnection.receiveFrame( + onFrame: (RenderedFrame) -> Unit, + onError: (String) -> Unit +) { receiveCommand { (type, args) -> - if (type == Command.Type.FRAME) { - receiveData { bytes -> - val (w, h) = args - val frame = RenderedFrame(bytes, width = w.toInt(), height = h.toInt()) - fn(frame) + when (type) { + Command.Type.FRAME -> { + receiveData { bytes -> + val (w, h) = args + val frame = RenderedFrame(bytes, width = w.toInt(), height = h.toInt()) + onFrame(frame) + } + } + Command.Type.ERROR -> { + receiveUtf8StringData { stacktrace -> + onError(stacktrace) + } } + else -> error("Received unexpected command type: $type") } } } diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/constants.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/constants.kt index f6a98d384f..b83ee410c8 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/constants.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/constants.kt @@ -11,3 +11,4 @@ internal const val MAX_CMD_SIZE = 8 * 1024 // 100 Mb should be enough even for 8K screenshots internal const val MAX_BINARY_SIZE = 100 * 1024 * 1024 internal const val MAX_BUF_SIZE = 8 * 1024 +internal const val PREVIEW_START_OF_STACKTRACE_MARKER = "" diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/protocolVersion.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/protocolVersion.kt index c91b9b2852..fd18084c66 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/protocolVersion.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/protocolVersion.kt @@ -5,4 +5,4 @@ package org.jetbrains.compose.desktop.ui.tooling.preview.rpc -const val PROTOCOL_VERSION = 1 \ No newline at end of file +const val PROTOCOL_VERSION = 2 \ No newline at end of file diff --git a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBuffer.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBuffer.kt new file mode 100644 index 0000000000..24f414b4b2 --- /dev/null +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBuffer.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.ui.tooling.preview.rpc.utils + +internal class RingBuffer(internal val maxSize: Int) : Iterable { + private var start = 0 + private var size = 0 + private val values = arrayOfNulls(maxSize) + + init { + check(maxSize > 0) { "Max size should be a positive number: $maxSize" } + } + + fun add(element: T) { + if (size < maxSize) { + size++ + } else { + start = (start + 1) % maxSize + } + values[(start + size - 1) % maxSize] = element + } + + fun addAll(elements: Iterable) { + elements.forEach { add(it) } + } + + fun clear() { + start = 0 + size = 0 + for (i in values.indices) { + values[i] = null + } + } + + override fun iterator(): Iterator = + object : Iterator { + private var i = 0 + + override fun hasNext(): Boolean = i < size + + override fun next(): T { + if (!hasNext()) throw NoSuchElementException() + + @Suppress("UNCHECKED_CAST") + return values[(start + i++) % maxSize] as T + } + } +} \ No newline at end of file diff --git a/gradle-plugins/preview-rpc/src/test/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBufferTest.kt b/gradle-plugins/preview-rpc/src/test/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBufferTest.kt new file mode 100644 index 0000000000..0cb89ff4cd --- /dev/null +++ b/gradle-plugins/preview-rpc/src/test/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/utils/RingBufferTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.ui.tooling.preview.rpc.utils + +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +internal class RingBufferTest { + private fun numbers(size: Int): IntArray = IntArray(size) { it } + private fun testBuffer() = RingBuffer(4) + + @Test + fun empty() { + val it = testBuffer().iterator() + assertFalse(it.hasNext()) + } + + @Test + fun addSmall() { + val buf = testBuffer() + val expected = numbers(buf.maxSize / 2) + buf.addAll(expected.asIterable()) + assertArrayEquals(expected, buf.toList().toIntArray()) + } + + @Test + fun addMedium() { + val buf = testBuffer() + val expected = numbers(buf.maxSize + buf.maxSize / 2) + buf.addAll(expected.asIterable()) + checkAllEquals(expected.takeLast(buf.maxSize), buf.toList()) + } + + @Test + fun addBig() { + val buf = testBuffer() + val expected = numbers(buf.maxSize * 3) + buf.addAll(expected.asIterable()) + checkAllEquals(expected.takeLast(buf.maxSize), buf.toList()) + } + + @Test + fun testClear() { + val buf = testBuffer() + val expected = numbers(buf.maxSize * 3) + buf.addAll(expected.asIterable()) + buf.clear() + checkAllEquals(emptyList(), buf.toList()) + } + + private fun checkAllEquals(expected: Collection, actual: Collection) { + val expectedString = expected.joinToString(", ", prefix = "[", postfix = "]") + val actualString = actual.joinToString(", ", prefix = "[", postfix = "]") + assertEquals(expectedString, actualString) + } +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/IdePreviewErrorReporter.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/IdePreviewErrorReporter.kt new file mode 100644 index 0000000000..d86655c909 --- /dev/null +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/IdePreviewErrorReporter.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.ide.preview + +import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewErrorReporter + +internal class IdePreviewErrorReporter( + private val logger: Logger, + private val previewStateService: PreviewStateService +) : PreviewErrorReporter { + override fun report(e: Throwable, details: String?) { + report(e.stackTraceToString(), details) + } + + override fun report(e: String, details: String?) { + if (details != null) { + logger.error(e, details) + } else { + logger.error(e) + } + previewStateService.clearPreviewOnError() + } +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt index 0087a2d610..e4787d10eb 100644 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt @@ -29,7 +29,7 @@ internal class PreviewPanel : JPanel() { } } - fun previewImage(image: BufferedImage, imageDimension: Dimension) { + fun previewImage(image: BufferedImage?, imageDimension: Dimension?) { synchronized(this) { this.image = image this.imageDimension = imageDimension diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt index 3a85c90f84..e5d40420b7 100644 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt @@ -9,6 +9,7 @@ import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType import com.intellij.openapi.Disposable import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.externalSystem.model.task.* import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager import com.intellij.openapi.module.Module @@ -24,12 +25,16 @@ import javax.swing.event.AncestorListener @Service class PreviewStateService(private val myProject: Project) : Disposable { + private val idePreviewLogger = Logger.getInstance("org.jetbrains.compose.desktop.ide.preview") private val previewListener = CompositePreviewListener() - private val previewManager: PreviewManager = PreviewManagerImpl(previewListener) + private val errorReporter = IdePreviewErrorReporter(idePreviewLogger, this) + private val previewManager: PreviewManager = PreviewManagerImpl(previewListener, errorReporter) val gradleCallbackPort: Int get() = previewManager.gradleCallbackPort private val configurePreviewTaskNameCache = ConfigurePreviewTaskNameCache(ConfigurePreviewTaskNameProviderImpl()) + private var previewPanel: PreviewPanel? = null + private var loadingPanel: JBLoadingPanel? = null init { val projectRefreshListener = ConfigurePreviewTaskNameCacheInvalidator(configurePreviewTaskNameCache) @@ -44,12 +49,17 @@ class PreviewStateService(private val myProject: Project) : Disposable { override fun dispose() { previewManager.close() configurePreviewTaskNameCache.invalidate() + previewPanel = null + loadingPanel = null } internal fun registerPreviewPanels( previewPanel: PreviewPanel, loadingPanel: JBLoadingPanel ) { + this.previewPanel = previewPanel + this.loadingPanel = loadingPanel + val previewResizeListener = PreviewResizeListener(previewManager) previewPanel.addAncestorListener(previewResizeListener) Disposer.register(this) { previewPanel.removeAncestorListener(previewResizeListener) } @@ -73,6 +83,11 @@ class PreviewStateService(private val myProject: Project) : Disposable { }) } + internal fun clearPreviewOnError() { + loadingPanel?.stopLoading() + previewPanel?.previewImage(null, null) + } + internal fun buildStarted() { previewListener.onNewBuildRequest() }