[coroutine][debug] AsyncStackTraceProvider refactored
This commit is contained in:
+152
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
|
import com.intellij.debugger.DebuggerContext
|
||||||
|
import com.intellij.debugger.engine.JavaValue
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
|
import com.intellij.debugger.jdi.GeneratedLocation
|
||||||
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
|
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||||
|
import com.intellij.xdebugger.frame.XNamedValue
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||||
|
|
||||||
|
class AsyncStackTraceContext(
|
||||||
|
val context: ExecutionContext,
|
||||||
|
val method: Method,
|
||||||
|
private val debugMetadataKtType: ClassType
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun getAsyncStackTraceForSuspendLambda() : List<StackFrameItem>? {
|
||||||
|
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val thisObject = context.frameProxy.thisObject() ?: return null
|
||||||
|
val thisType = thisObject.referenceType()
|
||||||
|
|
||||||
|
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectFrames(thisObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
||||||
|
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val frameProxy = context.frameProxy
|
||||||
|
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||||
|
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||||
|
context.keepReference(continuation)
|
||||||
|
|
||||||
|
return collectFrames(continuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
||||||
|
val frames = mutableListOf<StackFrameItem>()
|
||||||
|
collectFramesRecursively(continuation, frames)
|
||||||
|
return frames
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
||||||
|
val continuationType = continuation.referenceType() as? ClassType ?: return
|
||||||
|
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||||
|
|
||||||
|
val location = getLocation(continuation)
|
||||||
|
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||||
|
|
||||||
|
if (location != null) {
|
||||||
|
consumer += StackFrameItem(location, spilledVariables)
|
||||||
|
}
|
||||||
|
|
||||||
|
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
||||||
|
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
||||||
|
collectFramesRecursively(completion, consumer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLocation(continuation: ObjectReference): Location? {
|
||||||
|
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
||||||
|
"getStackTraceElement",
|
||||||
|
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
||||||
|
).firstOrNull() ?: return null
|
||||||
|
|
||||||
|
val args = listOf(continuation)
|
||||||
|
|
||||||
|
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
context.keepReference(stackTraceElement)
|
||||||
|
|
||||||
|
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
fun getValue(name: String, desc: String): Value? {
|
||||||
|
val method = stackTraceElementType.methodsByName(name, desc).single()
|
||||||
|
return context.invokeMethod(stackTraceElement, method, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||||
|
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||||
|
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
||||||
|
|
||||||
|
val locationClass = context.findClassSafe(className) ?: return null
|
||||||
|
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||||
|
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
||||||
|
"getSpilledVariableFieldMapping",
|
||||||
|
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
||||||
|
).firstOrNull() ?: return null
|
||||||
|
|
||||||
|
val args = listOf(continuation)
|
||||||
|
|
||||||
|
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
context.keepReference(rawSpilledVariables)
|
||||||
|
|
||||||
|
val length = rawSpilledVariables.length() / 2
|
||||||
|
val spilledVariables = ArrayList<XNamedValue>(length)
|
||||||
|
|
||||||
|
for (index in 0 until length) {
|
||||||
|
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
||||||
|
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
||||||
|
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
||||||
|
|
||||||
|
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
||||||
|
override fun calcValueName() = variableName
|
||||||
|
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
||||||
|
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||||
|
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
spilledVariables += JavaValue.create(
|
||||||
|
null,
|
||||||
|
valueDescriptor,
|
||||||
|
context.evaluationContext,
|
||||||
|
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spilledVariables
|
||||||
|
}
|
||||||
|
|
||||||
|
private tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
||||||
|
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+18
-149
@@ -5,35 +5,20 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
import com.intellij.debugger.DebuggerContext
|
|
||||||
import com.intellij.debugger.engine.JavaStackFrame
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
import com.intellij.debugger.engine.JavaValue
|
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
import com.intellij.debugger.jdi.GeneratedLocation
|
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
|
||||||
import com.intellij.xdebugger.frame.XNamedValue
|
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||||
|
|
||||||
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||||
private companion object {
|
private companion object {
|
||||||
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
||||||
|
|
||||||
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
|
||||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
|
||||||
return type
|
|
||||||
}
|
|
||||||
|
|
||||||
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||||
return hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) }
|
return hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) }
|
||||||
}
|
}
|
||||||
@@ -51,146 +36,30 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
val context = createExecutionContext(suspendContext, frameProxy)
|
||||||
val context = ExecutionContext(evaluationContext, frameProxy)
|
|
||||||
|
|
||||||
// DebugMetadataKt not found, probably old kotlin-stdlib version
|
// DebugMetadataKt not found, probably old kotlin-stdlib version
|
||||||
val debugMetadataKtType = context.findClassSafe(DEBUG_METADATA_KT) ?: return null
|
val debugMetadataKtType = findDebugMetadata(context) ?: return null
|
||||||
|
|
||||||
val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType)
|
val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType)
|
||||||
return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction()
|
return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
|
private fun findDebugMetadata(context: ExecutionContext): ClassType? = context.findClassSafe(DEBUG_METADATA_KT)
|
||||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val thisObject = context.frameProxy.thisObject() ?: return null
|
private fun createExecutionContext(
|
||||||
val thisType = thisObject.referenceType()
|
suspendContext: SuspendContextImpl,
|
||||||
|
frameProxy: StackFrameProxyImpl
|
||||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
): ExecutionContext {
|
||||||
return null
|
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
||||||
}
|
return ExecutionContext(evaluationContext, frameProxy)
|
||||||
|
}
|
||||||
return collectFrames(thisObject)
|
}
|
||||||
|
|
||||||
|
internal fun ExecutionContext.findClassSafe(className: String): ClassType? {
|
||||||
|
return try {
|
||||||
|
findClass(className) as? ClassType
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
|
||||||
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val frameProxy = context.frameProxy
|
|
||||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
|
||||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
|
||||||
context.keepReference(continuation)
|
|
||||||
|
|
||||||
return collectFrames(continuation)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
|
||||||
val frames = mutableListOf<StackFrameItem>()
|
|
||||||
collectFramesRecursively(continuation, frames)
|
|
||||||
return frames
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AsyncStackTraceContext.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
|
||||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
|
||||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
|
||||||
|
|
||||||
val location = getLocation(continuation)
|
|
||||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
|
||||||
|
|
||||||
if (location != null) {
|
|
||||||
consumer += StackFrameItem(location, spilledVariables)
|
|
||||||
}
|
|
||||||
|
|
||||||
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
|
||||||
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
|
||||||
collectFramesRecursively(completion, consumer)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? {
|
|
||||||
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
|
||||||
"getStackTraceElement",
|
|
||||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
|
||||||
).firstOrNull() ?: return null
|
|
||||||
|
|
||||||
val args = listOf(continuation)
|
|
||||||
|
|
||||||
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
|
||||||
?: return null
|
|
||||||
|
|
||||||
context.keepReference(stackTraceElement)
|
|
||||||
|
|
||||||
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
|
||||||
?: return null
|
|
||||||
|
|
||||||
fun getValue(name: String, desc: String): Value? {
|
|
||||||
val method = stackTraceElementType.methodsByName(name, desc).single()
|
|
||||||
return context.invokeMethod(stackTraceElement, method, emptyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
|
||||||
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
|
||||||
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
|
||||||
|
|
||||||
val locationClass = context.findClassSafe(className) ?: return null
|
|
||||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
|
||||||
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
|
||||||
"getSpilledVariableFieldMapping",
|
|
||||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
|
||||||
).firstOrNull() ?: return null
|
|
||||||
|
|
||||||
val args = listOf(continuation)
|
|
||||||
|
|
||||||
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
|
||||||
?: return null
|
|
||||||
|
|
||||||
context.keepReference(rawSpilledVariables)
|
|
||||||
|
|
||||||
val length = rawSpilledVariables.length() / 2
|
|
||||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
|
||||||
|
|
||||||
for (index in 0 until length) {
|
|
||||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
|
||||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
|
||||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
|
||||||
|
|
||||||
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
|
||||||
override fun calcValueName() = variableName
|
|
||||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
|
||||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
|
||||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
spilledVariables += JavaValue.create(
|
|
||||||
null,
|
|
||||||
valueDescriptor,
|
|
||||||
context.evaluationContext,
|
|
||||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return spilledVariables
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ExecutionContext.findClassSafe(className: String): ClassType? {
|
|
||||||
return try {
|
|
||||||
findClass(className) as? ClassType
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AsyncStackTraceContext(
|
|
||||||
val context: ExecutionContext,
|
|
||||||
val method: Method,
|
|
||||||
val debugMetadataKtType: ClassType
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
+2
-1
@@ -39,6 +39,7 @@ import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
|||||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||||
import com.sun.jdi.ClassType
|
import com.sun.jdi.ClassType
|
||||||
import javaslang.control.Either
|
import javaslang.control.Either
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.AsyncStackTraceContext
|
||||||
import org.jetbrains.kotlin.idea.debugger.KotlinCoroutinesAsyncStackTraceProvider
|
import org.jetbrains.kotlin.idea.debugger.KotlinCoroutinesAsyncStackTraceProvider
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
@@ -241,7 +242,7 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
|||||||
val debugMetadataKtType = execContext
|
val debugMetadataKtType = execContext
|
||||||
.findClass("kotlin.coroutines.jvm.internal.DebugMetadataKt") as ClassType
|
.findClass("kotlin.coroutines.jvm.internal.DebugMetadataKt") as ClassType
|
||||||
val vars = with(KotlinCoroutinesAsyncStackTraceProvider()) {
|
val vars = with(KotlinCoroutinesAsyncStackTraceProvider()) {
|
||||||
KotlinCoroutinesAsyncStackTraceProvider.AsyncStackTraceContext(
|
AsyncStackTraceContext(
|
||||||
execContext,
|
execContext,
|
||||||
aMethod,
|
aMethod,
|
||||||
debugMetadataKtType
|
debugMetadataKtType
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ import java.awt.event.KeyEvent
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actually added into ui in [CoroutinesDebugConfigurationExtension.registerCoroutinesPanel]
|
* Actually added into ui in [CoroutineXDebuggerManagerListener.registerCoroutinesPanel]
|
||||||
* Some methods are copied from [com.intellij.debugger.ui.impl.ThreadsPanel]
|
* Some methods are copied from [com.intellij.debugger.ui.impl.ThreadsPanel]
|
||||||
*/
|
*/
|
||||||
class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : DebuggerTreePanel(project, stateManager) {
|
class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : DebuggerTreePanel(project, stateManager) {
|
||||||
|
|||||||
Reference in New Issue
Block a user