Browse Source

Improve reporting of preview errors (#1807)

Resolves #1686
pull/1820/head
Alexey Tsvetkov 3 years ago committed by GitHub
parent
commit
9331c66301
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewErrorReporter.kt
  2. 8
      gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewListener.kt
  3. 18
      gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewManager.kt
  4. 4
      gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/commands.kt
  5. 27
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/IdePreviewErrorReporter.kt
  6. 43
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt
  7. 3
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt
  8. 89
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/PreviewPanel.kt
  9. 39
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/uiUtils.kt

21
gradle-plugins/preview-rpc/src/main/kotlin/org/jetbrains/compose/desktop/ui/tooling/preview/rpc/PreviewErrorReporter.kt

@ -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)
}
}

8
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 onFinishedBuild(success: Boolean)
fun onNewRenderRequest(previewRequest: FrameRequest) fun onNewRenderRequest(previewRequest: FrameRequest)
fun onRenderedFrame(frame: RenderedFrame) fun onRenderedFrame(frame: RenderedFrame)
fun onIncompatibleProtocolVersions(versionServer: Int, versionClient: Int) fun onError(error: String)
} }
open class PreviewListenerBase : PreviewListener { open class PreviewListenerBase : PreviewListener {
@ -20,7 +20,7 @@ open class PreviewListenerBase : PreviewListener {
override fun onNewRenderRequest(previewRequest: FrameRequest) {} override fun onNewRenderRequest(previewRequest: FrameRequest) {}
override fun onRenderedFrame(frame: RenderedFrame) {} override fun onRenderedFrame(frame: RenderedFrame) {}
override fun onIncompatibleProtocolVersions(versionServer: Int, versionClient: Int) {} override fun onError(error: String) {}
} }
class CompositePreviewListener : PreviewListener { class CompositePreviewListener : PreviewListener {
@ -42,8 +42,8 @@ class CompositePreviewListener : PreviewListener {
forEachListener { it.onRenderedFrame(frame) } forEachListener { it.onRenderedFrame(frame) }
} }
override fun onIncompatibleProtocolVersions(versionServer: Int, versionClient: Int) { override fun onError(error: String) {
forEachListener { it.onIncompatibleProtocolVersions(versionServer, versionClient) } forEachListener { it.onError(error) }
} }
@Synchronized @Synchronized

18
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( class PreviewManagerImpl(
private val previewListener: PreviewListener = PreviewListenerBase(), private val previewListener: PreviewListener
private val errorReporter: PreviewErrorReporter = StderrPreviewErrorReporter
) : PreviewManager { ) : PreviewManager {
// todo: add quiet mode // todo: add quiet mode
private val log = PrintStreamLogger("SERVER") private val log = PrintStreamLogger("SERVER")
@ -125,7 +124,7 @@ class PreviewManagerImpl(
appendLine(exception) appendLine(exception)
} }
} }
errorReporter.report(PreviewException(errorMessage), details = processLogLines.joinToString("\n")) onError(errorMessage)
} }
} }
} }
@ -160,10 +159,10 @@ class PreviewManagerImpl(
previewListener.onRenderedFrame(renderedFrame) previewListener.onRenderedFrame(renderedFrame)
}, },
onError = { error -> onError = { error ->
errorReporter.report(PreviewException(error))
previewHostConfig.set(null) previewHostConfig.set(null)
previewClasspath.set(null) previewClasspath.set(null)
inProcessRequest.set(null) inProcessRequest.set(null)
onError(error)
} }
) )
} }
@ -292,9 +291,18 @@ class PreviewManagerImpl(
} }
}.also { }.also {
it.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { thread, e -> it.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { thread, e ->
errorReporter.report(e) onError(e)
} }
threads.add(it) threads.add(it)
it.start() it.start()
} }
private fun onError(e: Throwable) {
onError(e.stackTraceString)
}
private fun onError(error: String) {
log.error { error }
previewListener.onError(error)
}
} }

4
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) { if (type == Command.Type.ATTACH) {
val version = args.firstOrNull()?.toIntOrNull() ?: 0 val version = args.firstOrNull()?.toIntOrNull() ?: 0
if (PROTOCOL_VERSION != version) { if (PROTOCOL_VERSION != version) {
listener?.onIncompatibleProtocolVersions(PROTOCOL_VERSION, version) listener?.onError(
"Compose Multiplatform Gradle plugin version is not compatible with Intellij plugin version"
)
} }
fn() fn()
} }

27
idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/IdePreviewErrorReporter.kt

@ -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()
}
}

43
idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewPanel.kt

@ -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()
}

3
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.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.JBLoadingPanel import com.intellij.ui.components.JBLoadingPanel
import org.jetbrains.compose.desktop.ide.preview.ui.PreviewPanel
import java.awt.BorderLayout import java.awt.BorderLayout
class PreviewToolWindow : ToolWindowFactory, DumbAware { class PreviewToolWindow : ToolWindowFactory, DumbAware {
@ -24,7 +25,7 @@ class PreviewToolWindow : ToolWindowFactory, DumbAware {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
toolWindow.contentManager.let { content -> toolWindow.contentManager.let { content ->
val panel = PreviewPanel() val panel = PreviewPanel(project)
val loadingPanel = JBLoadingPanel(BorderLayout(), project) val loadingPanel = JBLoadingPanel(BorderLayout(), project)
loadingPanel.add(panel, BorderLayout.CENTER) loadingPanel.add(panel, BorderLayout.CENTER)
content.addContent(content.factory.createContent(loadingPanel, null, false)) content.addContent(content.factory.createContent(loadingPanel, null, false))

89
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<PreviewPanel>() {
sealed class PreviewPanelState {
data class Image(val image: BufferedImage, val dimension: Dimension) : PreviewPanelState()
class Error(val error: String) : PreviewPanelState()
}
private val myState = AtomicReference<PreviewPanelState>()
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()
}

39
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()
}
Loading…
Cancel
Save