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 deleted file mode 100644 index 7025c37e12..0000000000 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewErrorReporter.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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/PreviewListener.kt b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt index cce79d1f65..b831b84c0d 100644 --- a/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt +++ b/gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt @@ -10,7 +10,7 @@ interface PreviewListener { fun onFinishedBuild(success: Boolean) fun onNewRenderRequest(previewRequest: FrameRequest) fun onRenderedFrame(frame: RenderedFrame) - fun onIncompatibleProtocolVersions(versionServer: Int, versionClient: Int) + fun onError(error: String) } open class PreviewListenerBase : PreviewListener { @@ -20,7 +20,7 @@ open class PreviewListenerBase : PreviewListener { override fun onNewRenderRequest(previewRequest: FrameRequest) {} override fun onRenderedFrame(frame: RenderedFrame) {} - override fun onIncompatibleProtocolVersions(versionServer: Int, versionClient: Int) {} + override fun onError(error: String) {} } class CompositePreviewListener : PreviewListener { @@ -42,8 +42,8 @@ class CompositePreviewListener : PreviewListener { forEachListener { it.onRenderedFrame(frame) } } - override fun onIncompatibleProtocolVersions(versionServer: Int, versionClient: Int) { - forEachListener { it.onIncompatibleProtocolVersions(versionServer, versionClient) } + override fun onError(error: String) { + forEachListener { it.onError(error) } } @Synchronized 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 2d2d8bc2c2..7e5309e74b 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 @@ -50,8 +50,7 @@ private data class RunningPreview( } class PreviewManagerImpl( - private val previewListener: PreviewListener = PreviewListenerBase(), - private val errorReporter: PreviewErrorReporter = StderrPreviewErrorReporter + private val previewListener: PreviewListener ) : PreviewManager { // todo: add quiet mode private val log = PrintStreamLogger("SERVER") @@ -125,7 +124,7 @@ class PreviewManagerImpl( appendLine(exception) } } - errorReporter.report(PreviewException(errorMessage), details = processLogLines.joinToString("\n")) + onError(errorMessage) } } } @@ -160,10 +159,10 @@ class PreviewManagerImpl( previewListener.onRenderedFrame(renderedFrame) }, onError = { error -> - errorReporter.report(PreviewException(error)) previewHostConfig.set(null) previewClasspath.set(null) inProcessRequest.set(null) + onError(error) } ) } @@ -292,9 +291,18 @@ class PreviewManagerImpl( } }.also { it.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { thread, e -> - errorReporter.report(e) + onError(e) } threads.add(it) it.start() } + + private fun onError(e: Throwable) { + onError(e.stackTraceString) + } + + private fun onError(error: String) { + log.error { error } + previewListener.onError(error) + } } 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 dc314d5a23..1f765cff0b 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 @@ -20,7 +20,9 @@ internal fun RemoteConnection.receiveAttach( if (type == Command.Type.ATTACH) { val version = args.firstOrNull()?.toIntOrNull() ?: 0 if (PROTOCOL_VERSION != version) { - listener?.onIncompatibleProtocolVersions(PROTOCOL_VERSION, version) + listener?.onError( + "Compose Multiplatform Gradle plugin version is not compatible with Intellij plugin version" + ) } fn() } 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 deleted file mode 100644 index d86655c909..0000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/IdePreviewErrorReporter.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 deleted file mode 100644 index e4787d10eb..0000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 java.awt.Color -import java.awt.Dimension -import java.awt.Graphics -import java.awt.image.BufferedImage -import javax.swing.JPanel - -internal class PreviewPanel : JPanel() { - private var image: BufferedImage? = null - private var imageDimension: Dimension? = null - - override fun paintComponent(g: Graphics) { - super.paintComponent(g) - - synchronized(this) { - image?.let { image -> - val w = imageDimension!!.width - val h = imageDimension!!.height - g.color = Color.WHITE - g.fillRect(0, 0, w, h) - g.drawImage(image, 0, 0, w, h, null) - } - } - } - - fun previewImage(image: BufferedImage?, imageDimension: Dimension?) { - synchronized(this) { - this.image = image - this.imageDimension = imageDimension - } - - repaint() - } - - override fun getPreferredSize(): Dimension? = - imageDimension ?: super.getPreferredSize() -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt index b75fd4f6b6..b5f6beb5fc 100644 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.ui.components.JBLoadingPanel +import org.jetbrains.compose.desktop.ide.preview.ui.PreviewPanel import java.awt.BorderLayout class PreviewToolWindow : ToolWindowFactory, DumbAware { @@ -24,7 +25,7 @@ class PreviewToolWindow : ToolWindowFactory, DumbAware { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { toolWindow.contentManager.let { content -> - val panel = PreviewPanel() + val panel = PreviewPanel(project) val loadingPanel = JBLoadingPanel(BorderLayout(), project) loadingPanel.add(panel, BorderLayout.CENTER) content.addContent(content.factory.createContent(loadingPanel, null, false)) diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/PreviewPanel.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/PreviewPanel.kt new file mode 100644 index 0000000000..4fe1241fb4 --- /dev/null +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/PreviewPanel.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2020-2022 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.ui + +import com.intellij.icons.AllIcons +import com.intellij.openapi.project.Project +import com.intellij.ui.SimpleTextAttributes +import com.intellij.ui.components.JBPanel +import com.intellij.util.ui.StatusText +import java.awt.Color +import java.awt.Dimension +import java.awt.Graphics +import java.awt.image.BufferedImage +import java.util.concurrent.atomic.AtomicReference +import javax.swing.SwingUtilities + +internal class PreviewPanel(private val myProject: Project) : JBPanel() { + sealed class PreviewPanelState { + data class Image(val image: BufferedImage, val dimension: Dimension) : PreviewPanelState() + class Error(val error: String) : PreviewPanelState() + } + private val myState = AtomicReference() + private val myStatusText = object : StatusText(this) { + override fun isStatusVisible(): Boolean { + return myState.get() is PreviewPanelState.Error + } + } + + init { + SwingUtilities.invokeLater { + myStatusText.initStatusText() + } + } + + fun StatusText.initStatusText() { + clear() + appendLine( + AllIcons.General.Error, + "Preview rendering encountered an error", + SimpleTextAttributes.REGULAR_ATTRIBUTES, + null + ) + appendLine( + "Show details", + SimpleTextAttributes.LINK_ATTRIBUTES + ) { + val errorText = (myState.get() as? PreviewPanelState.Error)?.error + showTextDialog("Preview Error Details", errorText.orEmpty(), myProject) + } + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + + when (val state = myState.get()) { + is PreviewPanelState.Image -> { + val (image, dimension) = state + val w = dimension.width + val h = dimension.height + g.color = Color.WHITE + g.fillRect(0, 0, w, h) + g.drawImage(image, 0, 0, w, h, null) + } + is PreviewPanelState.Error -> { + myStatusText.paint(this, g) + } + } + } + + fun previewImage(image: BufferedImage, imageDimension: Dimension) { + myState.set(PreviewPanelState.Image(image, imageDimension)) + SwingUtilities.invokeLater { + repaint() + } + } + + fun error(error: String) { + myState.set(PreviewPanelState.Error(error)) + SwingUtilities.invokeLater { + repaint() + } + } + + override fun getPreferredSize(): Dimension? = + (myState.get() as? PreviewPanelState.Image)?.dimension ?: super.getPreferredSize() +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/uiUtils.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/uiUtils.kt new file mode 100644 index 0000000000..a31528f11e --- /dev/null +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/uiUtils.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2020-2022 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.ui + +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.ScrollPaneFactory +import java.awt.BorderLayout +import javax.swing.JComponent +import javax.swing.JPanel +import javax.swing.JTextArea + +fun showTextDialog( + title: String, + text: String, + project: Project? = null + ) { + val wrapper: DialogWrapper = object : DialogWrapper(project, false) { + init { + init() + } + + override fun createCenterPanel(): JComponent { + val textArea = JTextArea(text).apply { + isEditable = false + rows = 40 + columns = 70 + } + return JPanel(BorderLayout()).apply { + add(ScrollPaneFactory.createScrollPane(textArea)) + } + } + } + wrapper.title = title + wrapper.show() +}