[coroutine][debugger] implementation basing on X-* approach
XCoroutine view added for direct comparison with Coroutines
This commit is contained in:
committed by
Vladimir Ilmov
parent
61c5ef61cc
commit
b1b0817336
+13
-4
@@ -8,10 +8,11 @@ package org.jetbrains.kotlin.idea.debugger.coroutines
|
||||
import com.intellij.debugger.engine.*
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineAsyncStackFrameItem
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.AsyncStackTraceContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
@@ -21,7 +22,12 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
return null
|
||||
}
|
||||
|
||||
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: SuspendContext): List<CoroutineAsyncStackFrameItem> {
|
||||
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem>? {
|
||||
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList()
|
||||
return null
|
||||
}
|
||||
|
||||
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem> {
|
||||
val defaultResult = emptyList<CoroutineAsyncStackFrameItem>()
|
||||
|
||||
val location = frameProxy.location()
|
||||
@@ -31,7 +37,7 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
val method = location.safeMethod() ?: return defaultResult
|
||||
val threadReference = frameProxy.threadProxy().threadReference
|
||||
|
||||
if (threadReference == null || !threadReference.isSuspended || !(suspendContext.debugProcess as DebugProcessImpl).canRunEvaluation)
|
||||
if (threadReference == null || !threadReference.isSuspended || !canRunEvaluation(suspendContext))
|
||||
return defaultResult
|
||||
|
||||
|
||||
@@ -41,7 +47,7 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
|
||||
private fun createAsyncStackTraceContext(
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
suspendContext: SuspendContext,
|
||||
suspendContext: XSuspendContext,
|
||||
method: Method
|
||||
): AsyncStackTraceContext {
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext as SuspendContextImpl, frameProxy)
|
||||
@@ -49,5 +55,8 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
// DebugMetadataKt not found, probably old kotlin-stdlib version
|
||||
return AsyncStackTraceContext(context, method)
|
||||
}
|
||||
|
||||
fun canRunEvaluation(suspendContext: XSuspendContext) =
|
||||
(suspendContext as SuspendContextImpl).debugProcess.canRunEvaluation
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl
|
||||
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
|
||||
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
open class BuildCoroutineNodeCommand(
|
||||
val node: DebuggerTreeNodeImpl,
|
||||
debuggerContext: DebuggerContextImpl,
|
||||
|
||||
+3
-1
@@ -11,6 +11,7 @@ import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CreationFramesDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineCreatedStackFrameDescriptor
|
||||
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutineBuildCreationFrameCommand(
|
||||
node: DebuggerTreeNodeImpl,
|
||||
val descriptor: CreationFramesDescriptor,
|
||||
@@ -21,10 +22,11 @@ class CoroutineBuildCreationFrameCommand(
|
||||
val threadProxy = debuggerContext.suspendContext?.thread ?: return
|
||||
val evalContext = debuggerContext.createEvaluationContext() ?: return
|
||||
val proxy = threadProxy.forceFrames().first()
|
||||
descriptor.frames.forEach {
|
||||
for(it in descriptor.frames) {
|
||||
val descriptor = myNodeManager.createNode(
|
||||
CoroutineCreatedStackFrameDescriptor(it, proxy), evalContext)
|
||||
myChildren.add(descriptor)
|
||||
|
||||
}
|
||||
updateUI(true)
|
||||
}
|
||||
|
||||
+2
-2
@@ -89,13 +89,13 @@ class CoroutineBuildFrameCommand(
|
||||
debugProcess: DebugProcessImpl,
|
||||
evalContext: EvaluationContextImpl
|
||||
): Boolean {
|
||||
if (descriptor.infoData.thread == null) {
|
||||
if (descriptor.infoData.activeThread == null) {
|
||||
myChildren.add(myNodeManager.createMessageNode("Frames are not available"))
|
||||
return true
|
||||
}
|
||||
val proxy = ThreadReferenceProxyImpl(
|
||||
debugProcess.virtualMachineProxy,
|
||||
descriptor.infoData.thread
|
||||
descriptor.infoData.activeThread
|
||||
)
|
||||
val frames = proxy.forceFrames()
|
||||
var endRange = findResumeWithMethodFrameIndex(frames)
|
||||
|
||||
+79
-64
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutines.command
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.*
|
||||
import com.intellij.debugger.jdi.ClassesByNameProvider
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
@@ -13,40 +14,46 @@ import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.frame.XExecutionStack
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.ThreadReference
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.getPosition
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SuspendStackFrameDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SyntheticStackFrame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.AsyncStackTraceContext
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.EmptyStackFrameDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
|
||||
class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
|
||||
class CoroutineBuilder(val suspendContext: XSuspendContext) {
|
||||
private val methodsTracker = MethodsTracker()
|
||||
private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
|
||||
val virtualMachineProxy = suspendContext.debugProcess.virtualMachineProxy
|
||||
val debugProcess = (suspendContext as SuspendContextImpl).debugProcess
|
||||
val virtualMachineProxy = debugProcess.virtualMachineProxy
|
||||
val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses())
|
||||
|
||||
companion object {
|
||||
val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used for creation stacktrace separator in kotlinx.coroutines
|
||||
}
|
||||
|
||||
fun build(ci: CoroutineInfoData): List<CoroutineStackFrameItem> {
|
||||
fun build(coroutine: CoroutineInfoData): List<CoroutineStackFrameItem> {
|
||||
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
||||
val suspendedThreadProxy = suspendedThreadProxy()
|
||||
val firstSuspendedStackFrameProxyImpl = firstSuspendedThreadFrame()
|
||||
val creationFrameSeparatorIndex = findCreationFrameIndex(ci.stackTrace)
|
||||
val runningThreadReferenceProxyImpl = currentRunningThreadProxy(ci.thread ?: suspendedThreadProxy().threadReference)
|
||||
val positionManager = suspendContext.debugProcess.positionManager
|
||||
val creationFrameSeparatorIndex = findCreationFrameIndex(coroutine.stackTrace)
|
||||
val positionManager = debugProcess.positionManager
|
||||
|
||||
if (ci.state == CoroutineInfoData.State.RUNNING && runningThreadReferenceProxyImpl is ThreadReferenceProxyImpl) {
|
||||
val executionStack = JavaExecutionStack(runningThreadReferenceProxyImpl, suspendContext.debugProcess, suspendContext.thread == runningThreadReferenceProxyImpl)
|
||||
if (coroutine.state == CoroutineInfoData.State.RUNNING && coroutine.activeThread is ThreadReference) {
|
||||
val threadReferenceProxyImpl = runningThreadProxy(coroutine.activeThread)
|
||||
val executionStack = JavaExecutionStack(threadReferenceProxyImpl, debugProcess, suspendedSameThread(coroutine.activeThread))
|
||||
|
||||
val frames = runningThreadReferenceProxyImpl.forceFrames()
|
||||
val frames = threadReferenceProxyImpl.forceFrames()
|
||||
var resumeMethodIndex = findResumeMethodIndex(frames)
|
||||
for (frameIndex in 0..frames.lastIndex) {
|
||||
val runningStackFrameProxy = frames[frameIndex]
|
||||
@@ -55,47 +62,46 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
|
||||
val previousJavaFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true)
|
||||
val asyncStackTrace = coroutineStackFrameProvider
|
||||
.getAsyncStackTrace(previousJavaFrame, suspendContext)
|
||||
asyncStackTrace?.forEach {
|
||||
// val xStackFrame = positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, it.location)
|
||||
asyncStackTrace?.forEach { asyncFrame ->
|
||||
val xStackFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true)
|
||||
// val itStackFrame = null
|
||||
coroutineStackFrameList.add(AsyncCoroutineStackFrameItem(runningStackFrameProxy, "some label", it, xStackFrame))
|
||||
coroutineStackFrameList.add(AsyncCoroutineStackFrameItem(runningStackFrameProxy, asyncFrame, xStackFrame))
|
||||
}
|
||||
} else {
|
||||
// val xStackFrame = stackFrame(positionManager, runningStackFrameProxy, runningStackFrameProxy.location())
|
||||
val xStackFrame = executionStack.createStackFrame(runningStackFrameProxy)
|
||||
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, "", xStackFrame))
|
||||
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, xStackFrame))
|
||||
}
|
||||
}
|
||||
} else if (ci.state == CoroutineInfoData.State
|
||||
.SUSPENDED || runningThreadReferenceProxyImpl == null
|
||||
) { // to get frames from CoroutineInfo anyway
|
||||
} else if (coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) {
|
||||
// to get frames from CoroutineInfo anyway
|
||||
// the thread is paused on breakpoint - it has at least one frame
|
||||
ci.stackTrace.subList(0, creationFrameSeparatorIndex).forEach {
|
||||
val xStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, it)
|
||||
// val xStackFrame = null
|
||||
coroutineStackFrameList.add(SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, "suspended frame", it, xStackFrame))
|
||||
val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex + 1)
|
||||
for (suspendedFrame in suspendedStackTrace) {
|
||||
val suspendedXStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, suspendedFrame)
|
||||
coroutineStackFrameList.add(
|
||||
SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val executionStack = JavaExecutionStack(suspendedThreadProxy(), suspendContext.debugProcess, false)
|
||||
val executionStack = JavaExecutionStack(suspendedThreadProxy(), debugProcess, false)
|
||||
val xStackFrame = executionStack.createStackFrame(firstSuspendedStackFrameProxyImpl)
|
||||
|
||||
ci.stackTrace.subList(creationFrameSeparatorIndex + 1, ci.stackTrace.size).forEach {
|
||||
coroutine.stackTrace.subList(creationFrameSeparatorIndex + 1, coroutine.stackTrace.size).forEach {
|
||||
var location = createLocation(it)
|
||||
coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, "creation frame", it, xStackFrame, location))
|
||||
coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, it, xStackFrame, location))
|
||||
}
|
||||
ci.stackFrameList.addAll(coroutineStackFrameList)
|
||||
coroutine.stackFrameList.addAll(coroutineStackFrameList)
|
||||
return coroutineStackFrameList
|
||||
}
|
||||
|
||||
fun createLocation(stackTraceElement: StackTraceElement): Location {
|
||||
return findLocation(
|
||||
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
|
||||
stackTraceElement.methodName,
|
||||
stackTraceElement.lineNumber
|
||||
)
|
||||
}
|
||||
private fun suspendedSameThread(activeThread: ThreadReference) =
|
||||
activeThread == suspendedThreadProxy().threadReference
|
||||
|
||||
private fun createLocation(stackTraceElement: StackTraceElement): Location = findLocation(
|
||||
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
|
||||
stackTraceElement.methodName,
|
||||
stackTraceElement.lineNumber
|
||||
)
|
||||
|
||||
private fun findLocation(
|
||||
type: ReferenceType?,
|
||||
@@ -113,17 +119,16 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
|
||||
} catch (ignored: AbsentInformationException) {
|
||||
}
|
||||
}
|
||||
return GeneratedLocation(suspendContext.debugProcess, type, methodName, line)
|
||||
return GeneratedLocation(debugProcess, type, methodName, line)
|
||||
}
|
||||
|
||||
fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, location: Location): XStackFrame {
|
||||
return positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, location)!!
|
||||
return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!!
|
||||
}
|
||||
|
||||
fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, stackTraceElement: StackTraceElement) : XStackFrame {
|
||||
val location = createLocation(stackTraceElement)
|
||||
// val sourcePosition = getPosition(stackTraceElement)
|
||||
return positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, location)!!
|
||||
return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!!
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,28 +148,29 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
|
||||
private fun firstSuspendedThreadFrame(): StackFrameProxyImpl =
|
||||
suspendedThreadProxy().forceFrames().first()
|
||||
|
||||
// retrieves currently suspended but active and executing corouting thread proxy
|
||||
fun currentRunningThreadProxy(threadReference: ThreadReference?): ThreadReferenceProxyImpl? =
|
||||
ThreadReferenceProxyImpl(suspendContext.debugProcess.virtualMachineProxy, threadReference)
|
||||
// retrieves currently suspended but active and executing coroutine thread proxy
|
||||
fun runningThreadProxy(threadReference: ThreadReference) =
|
||||
ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, threadReference)
|
||||
|
||||
// retrieves current suspended thread proxy
|
||||
fun suspendedThreadProxy(): ThreadReferenceProxyImpl =
|
||||
suspendContext.thread!! // @TODO hash replace !!
|
||||
private fun suspendedThreadProxy(): ThreadReferenceProxyImpl =
|
||||
(suspendContext as SuspendContextImpl).thread!! // @TODO hash replace !!
|
||||
|
||||
private fun findResumeMethodIndex(frames: List<StackFrameProxyImpl>): Int {
|
||||
for (j: Int in frames.lastIndex downTo 0)
|
||||
if (isResumeMethodFrame(frames[j])) {
|
||||
return j
|
||||
for (i: Int in frames.lastIndex downTo 0)
|
||||
if (isResumeMethodFrame(frames[i])) {
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun isResumeMethodFrame(frame: StackFrameProxyImpl) = frame.location().method().name() == "resumeWith"
|
||||
private fun isResumeMethodFrame(frame: StackFrameProxyImpl) =
|
||||
frame.safeLocation()?.safeMethod()?.name() == "resumeWith"
|
||||
|
||||
/**
|
||||
* Should be invoked on manager thread
|
||||
* This code was migrated from previous implementation, has to be refactored @TODO
|
||||
*/
|
||||
/*
|
||||
private fun createSyntheticStackFrame(
|
||||
descriptor: SuspendStackFrameDescriptor,
|
||||
pos: XSourcePosition,
|
||||
@@ -191,50 +197,59 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
|
||||
} ?: return null
|
||||
return executionStack to SyntheticStackFrame(descriptor, vars, pos)
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
class CreationCoroutineStackFrameItem(
|
||||
frame: StackFrameProxyImpl,
|
||||
label: String = "",
|
||||
val stackTraceElement: StackTraceElement,
|
||||
stackFrame: XStackFrame,
|
||||
val location: Location
|
||||
) : CoroutineStackFrameItem(frame, label, stackFrame) {
|
||||
) : CoroutineStackFrameItem(frame, stackFrame) {
|
||||
override fun location() = location
|
||||
fun emptyDescriptor() =
|
||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||
}
|
||||
|
||||
class SuspendCoroutineStackFrameItem(
|
||||
frame: StackFrameProxyImpl,
|
||||
label: String = "",
|
||||
val stackTraceElement: StackTraceElement,
|
||||
stackFrame: XStackFrame
|
||||
) : CoroutineStackFrameItem(frame, label, stackFrame) {
|
||||
) : CoroutineStackFrameItem(frame, stackFrame) {
|
||||
override fun location() = frame.location()
|
||||
}
|
||||
|
||||
class AsyncCoroutineStackFrameItem(
|
||||
frame: StackFrameProxyImpl,
|
||||
label: String = "",
|
||||
val frameItem: StackFrameItem,
|
||||
stackFrame: XStackFrame
|
||||
) : CoroutineStackFrameItem(frame, label, stackFrame) {
|
||||
) : CoroutineStackFrameItem(frame, stackFrame) {
|
||||
override fun location() : Location = frame.location()
|
||||
}
|
||||
|
||||
class RunningCoroutineStackFrameItem(
|
||||
frame: StackFrameProxyImpl,
|
||||
label: String = "",
|
||||
stackFrame: XStackFrame
|
||||
) : CoroutineStackFrameItem(frame, label, stackFrame) {
|
||||
) : CoroutineStackFrameItem(frame, stackFrame) {
|
||||
val location = frame.location() // it should be invoked in manager thread
|
||||
|
||||
override fun location() = location
|
||||
}
|
||||
|
||||
abstract class CoroutineStackFrameItem(val frame: StackFrameProxyImpl, val label: String = "", val stackFrame: XStackFrame) {
|
||||
abstract class CoroutineStackFrameItem(val frame: StackFrameProxyImpl, val stackFrame: XStackFrame) {
|
||||
val log by logger
|
||||
|
||||
fun sourcePosition() : XSourcePosition? = stackFrame.sourcePosition
|
||||
|
||||
abstract fun location(): Location
|
||||
|
||||
fun uniqueId(): String {
|
||||
val location = location()
|
||||
try {
|
||||
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
|
||||
location.safeLineNumber() + ":" + location.safeSourceLineNumber()
|
||||
} catch (e: Exception) {
|
||||
log.error(e)
|
||||
return location.method().toString() + ":" + location.lineNumber()
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-13
@@ -17,7 +17,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.openapi.util.Disposer
|
||||
@@ -29,10 +28,10 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutineDumpPanel
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.coroutineDebuggerEnabled
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
|
||||
|
||||
@Suppress("ComponentNotRegistered")
|
||||
class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
|
||||
private val logger = Logger.getInstance(this::class.java)
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project ?: return
|
||||
@@ -42,20 +41,15 @@ class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
|
||||
val process = context.debugProcess ?: return
|
||||
process.managerThread.schedule(object : SuspendContextCommandImpl(context.suspendContext) {
|
||||
override fun contextAction() {
|
||||
val states = CoroutinesDebugProbesProxy(context.suspendContext!!).dumpCoroutines() ?: return
|
||||
val states = CoroutinesDebugProbesProxy(context.suspendContext ?: return)
|
||||
.dumpCoroutines()
|
||||
if (states.isOk()) {
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(
|
||||
KotlinBundle.message("debugger.session.tab.coroutine.message.error"),
|
||||
MessageType.ERROR
|
||||
).notify(project)
|
||||
val message = KotlinBundle.message("debugger.session.tab.coroutine.message.error")
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message,MessageType.ERROR).notify(project)
|
||||
} else {
|
||||
val f = fun() {
|
||||
addCoroutineDump(
|
||||
project,
|
||||
states.cache,
|
||||
session.xDebugSession?.ui ?: return,
|
||||
session.searchScope
|
||||
)
|
||||
val ui = session.xDebugSession?.ui ?: return
|
||||
addCoroutineDump(project, states.cache, ui, session.searchScope)
|
||||
}
|
||||
ApplicationManager.getApplication().invokeLater(f, ModalityState.NON_MODAL)
|
||||
}
|
||||
|
||||
+18
-21
@@ -13,60 +13,57 @@ import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerSession
|
||||
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl
|
||||
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineDescriptorData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ManagerThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.createEvaluationContext
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.ProjectNotification
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutinesDebuggerTree
|
||||
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class RefreshCoroutinesTreeCommand(
|
||||
val context: DebuggerContextImpl,
|
||||
private val debuggerTree: CoroutinesDebuggerTree
|
||||
) : SuspendContextCommandImpl(context.suspendContext) {
|
||||
val notification = ProjectNotification(debuggerTree.project)
|
||||
|
||||
override fun contextAction() {
|
||||
val nf = debuggerTree.nodeFactory
|
||||
val root = nf.defaultNode
|
||||
val sc: SuspendContextImpl? = suspendContext
|
||||
if (context.debuggerSession is DebuggerSession && sc is SuspendContextImpl && !sc.isResumed) {
|
||||
val infoCache = CoroutinesDebugProbesProxy(sc).dumpCoroutines()
|
||||
val nodeManagerImpl = debuggerTree.nodeFactory
|
||||
val root = nodeManagerImpl.defaultNode
|
||||
val suspendContext: SuspendContextImpl? = suspendContext
|
||||
if (context.debuggerSession is DebuggerSession && suspendContext is SuspendContextImpl && !suspendContext.isResumed) {
|
||||
var infoCache = CoroutinesDebugProbesProxy(suspendContext).dumpCoroutines()
|
||||
if (infoCache.isOk()) {
|
||||
val evaluationContext = sc.createEvaluationContext()
|
||||
|
||||
val evaluationContext = evaluationContext(suspendContext)
|
||||
for (state in infoCache.cache) {
|
||||
val descriptor = createCoroutineDescriptorNode(nf, state, evaluationContext)
|
||||
val descriptor = createCoroutineDescriptorNode(nodeManagerImpl, state, evaluationContext)
|
||||
root.add(descriptor)
|
||||
}
|
||||
setRoot(root)
|
||||
} else {
|
||||
debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.failure"))
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(
|
||||
KotlinBundle.message("debugger.session.tab.coroutine.message.error"),
|
||||
MessageType.ERROR
|
||||
)
|
||||
.notify(debuggerTree.project)
|
||||
notification.error(KotlinBundle.message("debugger.session.tab.coroutine.message.error"))
|
||||
}
|
||||
} else {
|
||||
} else
|
||||
debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.resume"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluationContext(suspendContext : SuspendContextImpl) =
|
||||
EvaluationContextImpl(suspendContext, suspendContext.frameProxy)
|
||||
|
||||
private fun createCoroutineDescriptorNode(
|
||||
nodeFactory: NodeManagerImpl,
|
||||
coroutineInfoData: CoroutineInfoData,
|
||||
evaluationContext: EvaluationContextImpl
|
||||
) =
|
||||
nodeFactory.createNode(
|
||||
): DebuggerTreeNodeImpl {
|
||||
return nodeFactory.createNode(
|
||||
nodeFactory.getDescriptor(
|
||||
null,
|
||||
CoroutineDescriptorData(coroutineInfoData)
|
||||
),
|
||||
evaluationContext
|
||||
)
|
||||
}
|
||||
|
||||
private fun setRoot(root: DebuggerTreeNodeImpl) {
|
||||
DebuggerInvocationUtil.swingInvokeLater(debuggerTree.project) {
|
||||
|
||||
+1
-3
@@ -12,6 +12,4 @@ import com.intellij.xdebugger.frame.XNamedValue
|
||||
class CoroutineAsyncStackFrameItem(
|
||||
val location: GeneratedLocation,
|
||||
spilledVariables: List<XNamedValue>
|
||||
) : StackFrameItem(location, spilledVariables) {
|
||||
|
||||
}
|
||||
) : StackFrameItem(location, spilledVariables)
|
||||
+1
-3
@@ -15,12 +15,10 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineStackFrame
|
||||
data class CoroutineInfoData(
|
||||
val name: String,
|
||||
val state: State,
|
||||
val threadName: String,
|
||||
val threadStatus: Int,
|
||||
|
||||
val stackTrace: List<StackTraceElement>,
|
||||
// links to jdi.* references
|
||||
val thread: ThreadReference? = null, // for suspended coroutines should be null
|
||||
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
||||
val frame: ObjectReference?
|
||||
) {
|
||||
var stackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
||||
|
||||
+18
-11
@@ -19,6 +19,7 @@ import com.intellij.icons.AllIcons
|
||||
import com.sun.jdi.ObjectReference
|
||||
import javax.swing.Icon
|
||||
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
|
||||
lateinit var icon: Icon
|
||||
|
||||
@@ -26,10 +27,11 @@ class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorI
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
|
||||
val thread = infoData.thread
|
||||
val thread = infoData.activeThread
|
||||
val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: ""
|
||||
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
|
||||
return "${infoData.name}: ${infoData.state}${if (name.isNotEmpty()) " on thread \"$name\":$threadState" else ""}"
|
||||
val threadName = if (name.isNotEmpty()) " on thread \"$name\":$threadState" else ""
|
||||
return "${infoData.name}: ${infoData.state} $threadName"
|
||||
}
|
||||
|
||||
override fun isExpandable() = infoData.state != CoroutineInfoData.State.CREATED
|
||||
@@ -45,8 +47,9 @@ class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorI
|
||||
}
|
||||
}
|
||||
|
||||
class CreationFramesDescriptor(val frames: List<StackTraceElement>)
|
||||
: MessageDescriptor("Coroutine creation stack trace", INFORMATION) {
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CreationFramesDescriptor(val frames: List<StackTraceElement>) :
|
||||
MessageDescriptor("Coroutine creation stack trace", INFORMATION) {
|
||||
|
||||
override fun isExpandable() = true
|
||||
}
|
||||
@@ -54,6 +57,7 @@ class CreationFramesDescriptor(val frames: List<StackTraceElement>)
|
||||
/**
|
||||
* Descriptor for suspend functions
|
||||
*/
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class SuspendStackFrameDescriptor(
|
||||
val infoData: CoroutineInfoData,
|
||||
val frame: StackTraceElement,
|
||||
@@ -64,8 +68,8 @@ class SuspendStackFrameDescriptor(
|
||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
|
||||
return with(frame) {
|
||||
val pack = className.substringBeforeLast(".", "")
|
||||
"$methodName:$lineNumber, ${className.substringAfterLast(".")} " +
|
||||
if (pack.isNotEmpty()) "{$pack}" else ""
|
||||
val packDisplay = if (pack.isNotEmpty()) "{$pack}" else ""
|
||||
"$methodName:$lineNumber, ${className.substringAfterLast(".")} $packDisplay"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,32 +80,35 @@ class SuspendStackFrameDescriptor(
|
||||
/**
|
||||
* For the case when no data inside frame is available
|
||||
*/
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutineCreatedStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
||||
CoroutineStackFrameDescriptor(proxy) {
|
||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
|
||||
return with(frame) {
|
||||
val pack = className.substringBeforeLast(".", "")
|
||||
"$methodName:$lineNumber, ${className.substringAfterLast(".")} ${if (pack.isNotEmpty()) "{$pack}" else ""}"
|
||||
val packDisplay = if (pack.isNotEmpty()) "{$pack}" else ""
|
||||
"$methodName:$lineNumber, ${className.substringAfterLast(".")} $packDisplay"
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName() = null
|
||||
}
|
||||
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class AsyncStackFrameDescriptor(val infoData: CoroutineInfoData, val frame: StackFrameItem, proxy: StackFrameProxyImpl) :
|
||||
CoroutineStackFrameDescriptor(proxy) {
|
||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
|
||||
return with(frame) {
|
||||
val pack = path().substringBeforeLast(".", "")
|
||||
"${method()}:${line()}, ${path().substringAfterLast(".")} ${if (pack.isNotEmpty()) "{$pack}" else ""}"
|
||||
val packDisplay = if (pack.isNotEmpty()) "{$pack}" else ""
|
||||
"${method()}:${line()}, ${path().substringAfterLast(".")} $packDisplay"
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName() = frame.method()
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
open class CoroutineStackFrameDescriptor(proxy: StackFrameProxyImpl) : StackFrameDescriptorImpl(proxy, MethodsTracker()) {
|
||||
override fun isExpandable() = false
|
||||
}
|
||||
}
|
||||
|
||||
+23
-7
@@ -17,8 +17,13 @@ import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.LookupContinuation
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
class CoroutineStackTraceData(infoData: CoroutineInfoData, proxy: StackFrameProxyImpl, evalContext: EvaluationContextImpl, val frame: StackTraceElement)
|
||||
: CoroutineStackDescriptorData(infoData, proxy, evalContext) {
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutineStackTraceData(
|
||||
infoData: CoroutineInfoData,
|
||||
proxy: StackFrameProxyImpl,
|
||||
evalContext: EvaluationContextImpl,
|
||||
val frame: StackTraceElement
|
||||
) : CoroutineStackDescriptorData(infoData, proxy, evalContext) {
|
||||
|
||||
override fun hashCode() = frame.hashCode()
|
||||
|
||||
@@ -34,13 +39,19 @@ class CoroutineStackTraceData(infoData: CoroutineInfoData, proxy: StackFrameProx
|
||||
|
||||
return if (continuation is ObjectReference)
|
||||
SuspendStackFrameDescriptor(infoData, frame, proxy, continuation)
|
||||
else
|
||||
else
|
||||
CoroutineCreatedStackFrameDescriptor(frame, proxy)
|
||||
}
|
||||
}
|
||||
|
||||
class CoroutineStackFrameData(infoData: CoroutineInfoData, proxy: StackFrameProxyImpl, evalContext: EvaluationContextImpl, val frame: StackFrameItem)
|
||||
: CoroutineStackDescriptorData(infoData, proxy, evalContext) {
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutineStackFrameData(
|
||||
infoData: CoroutineInfoData,
|
||||
proxy: StackFrameProxyImpl,
|
||||
evalContext: EvaluationContextImpl,
|
||||
val frame: StackFrameItem
|
||||
) :
|
||||
CoroutineStackDescriptorData(infoData, proxy, evalContext) {
|
||||
override fun createDescriptorImpl(project: Project): NodeDescriptorImpl =
|
||||
AsyncStackFrameDescriptor(infoData, frame, proxy)
|
||||
|
||||
@@ -49,8 +60,12 @@ class CoroutineStackFrameData(infoData: CoroutineInfoData, proxy: StackFrameProx
|
||||
override fun equals(other: Any?) = other is CoroutineStackFrameData && frame == other.frame
|
||||
}
|
||||
|
||||
abstract class CoroutineStackDescriptorData(val infoData: CoroutineInfoData, val proxy: StackFrameProxyImpl, val evalContext: EvaluationContextImpl)
|
||||
: DescriptorData<NodeDescriptorImpl>() {
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
abstract class CoroutineStackDescriptorData(
|
||||
val infoData: CoroutineInfoData,
|
||||
val proxy: StackFrameProxyImpl,
|
||||
val evalContext: EvaluationContextImpl
|
||||
) : DescriptorData<NodeDescriptorImpl>() {
|
||||
|
||||
override fun getDisplayKey(): DisplayKey<NodeDescriptorImpl> = SimpleDisplayKey(infoData)
|
||||
}
|
||||
@@ -58,6 +73,7 @@ abstract class CoroutineStackDescriptorData(val infoData: CoroutineInfoData, val
|
||||
/**
|
||||
* Describes coroutine itself in the tree (name: STATE), has children if stacktrace is not empty (state = CREATED)
|
||||
*/
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutineDescriptorData(private val infoData: CoroutineInfoData) : DescriptorData<CoroutineDescriptorImpl>() {
|
||||
|
||||
override fun createDescriptorImpl(project: Project) =
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
* 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.coroutines
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutines.proxy
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
@@ -37,7 +37,7 @@ class AsyncStackTraceContext(
|
||||
val continuation = locateContinuation() ?: return emptyList()
|
||||
val frames = mutableListOf<CoroutineAsyncStackFrameItem>()
|
||||
collectFramesRecursively(continuation, frames)
|
||||
return frames;
|
||||
return frames
|
||||
}
|
||||
|
||||
private fun locateContinuation() : ObjectReference? {
|
||||
@@ -105,7 +105,7 @@ class AsyncStackTraceContext(
|
||||
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||
|
||||
val rawSpilledVariables =
|
||||
context.invokeMethodAsArray(debugMetadataKtType, "getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;", continuation) as? ArrayReference ?: return null
|
||||
context.invokeMethodAsArray(debugMetadataKtType, "getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;", continuation) ?: return null
|
||||
|
||||
context.keepReference(rawSpilledVariables)
|
||||
|
||||
+10
-23
@@ -4,12 +4,10 @@
|
||||
*/
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutines.proxy
|
||||
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.PrioritizedTask
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.xdebugger.XDebugProcess
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutineInfoCache
|
||||
@@ -17,16 +15,10 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
fun SuspendContextImpl.createEvaluationContext() =
|
||||
EvaluationContextImpl(this, this.frameProxy)
|
||||
|
||||
class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
class CoroutinesDebugProbesProxy(val suspendContext: XSuspendContext) {
|
||||
private val log by logger
|
||||
|
||||
// @TODO refactor to extract initialization logic
|
||||
private var executionContext: ExecutionContext = ExecutionContext(
|
||||
EvaluationContextImpl(suspendContext, suspendContext.frameProxy),
|
||||
suspendContext.frameProxy as StackFrameProxyImpl)
|
||||
private var executionContext: ExecutionContext = executionContext()
|
||||
// might want to use inner class but also having to monitor order of fields
|
||||
private var refs: ProcessReferences = ProcessReferences(executionContext)
|
||||
|
||||
@@ -89,26 +81,16 @@ class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
val state = getState(instance)
|
||||
val thread = getLastObservedThread(instance, refs.lastObservedThreadFieldRef)
|
||||
val lastObservedFrameFieldRef = instance.getValue(refs.lastObservedFrameFieldRef) as? ObjectReference
|
||||
val stackTrace = getStackTrace(instance)
|
||||
return CoroutineInfoData(
|
||||
name,
|
||||
CoroutineInfoData.State.valueOf(state),
|
||||
getThreadName(instance),
|
||||
getThreadState(instance),
|
||||
|
||||
getStackTrace(instance),
|
||||
stackTrace,
|
||||
thread,
|
||||
lastObservedFrameFieldRef
|
||||
)
|
||||
}
|
||||
|
||||
private fun getThreadName(instance: ObjectReference) : String {
|
||||
return "thread name"
|
||||
}
|
||||
|
||||
private fun getThreadState(instance: ObjectReference) : Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun getName(
|
||||
info: ObjectReference // CoroutineInfo instance
|
||||
): String {
|
||||
@@ -203,6 +185,11 @@ class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
private fun sizeOf(args: ObjectReference): Int =
|
||||
(executionContext.invokeMethod(args, refs.sizeRef, emptyList()) as IntegerValue).value()
|
||||
|
||||
private fun executionContext() : ExecutionContext {
|
||||
val evaluationContextImpl = EvaluationContextImpl(suspendContext as SuspendContextImpl, suspendContext.frameProxy)
|
||||
return ExecutionContext(evaluationContextImpl, suspendContext.frameProxy as StackFrameProxyImpl)
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO refactor later
|
||||
* Holds ClassTypes, Methods, ObjectReferences and Fields for a particular jvm
|
||||
|
||||
+35
-11
@@ -8,25 +8,49 @@ package org.jetbrains.kotlin.idea.debugger.coroutines.proxy
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.intellij.debugger.impl.PrioritizedTask
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import java.awt.Component
|
||||
|
||||
class ManagerThreadExecutor(val debugProcess: DebugProcessImpl, val priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) {
|
||||
fun schedule(f: () -> Unit) {
|
||||
val runnable = object : Runnable {
|
||||
override fun run() {f()}
|
||||
class ManagerThreadExecutor(val debugProcess: DebugProcessImpl) {
|
||||
fun on(suspendContext: SuspendContextImpl, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
|
||||
ManagerThreadExecutorInstance(suspendContext, priority)
|
||||
|
||||
fun on(suspendContext: XSuspendContext, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
|
||||
ManagerThreadExecutorInstance(suspendContext as SuspendContextImpl, priority)
|
||||
|
||||
inner class ManagerThreadExecutorInstance(
|
||||
val suspendContext: SuspendContextImpl,
|
||||
val priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL
|
||||
) {
|
||||
|
||||
fun schedule(f: (SuspendContextImpl) -> Unit) {
|
||||
val suspendContextCommand = object : SuspendContextCommandImpl(suspendContext) {
|
||||
override fun getPriority() = this@ManagerThreadExecutorInstance.priority
|
||||
|
||||
override fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
f(suspendContext)
|
||||
}
|
||||
}
|
||||
debugProcess.managerThread.invoke(suspendContextCommand)
|
||||
}
|
||||
debugProcess.managerThread.schedule(priority, runnable)
|
||||
}
|
||||
|
||||
fun schedule(f: DebuggerCommandImpl) {
|
||||
debugProcess.managerThread.schedule(f)
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationThreadExecutor() {
|
||||
fun <T> invoke(f: () -> T) : T {
|
||||
class ApplicationThreadExecutor {
|
||||
fun <T> readAction(f: () -> T): T {
|
||||
return ApplicationManager.getApplication().runReadAction(Computable(f))
|
||||
}
|
||||
|
||||
fun schedule(f: () -> Unit, component: Component) {
|
||||
return ApplicationManager.getApplication().invokeLater( { f() }, ModalityState.stateForComponent(component))
|
||||
}
|
||||
|
||||
fun schedule(f: () -> Unit) {
|
||||
return ApplicationManager.getApplication().invokeLater { f() }
|
||||
}
|
||||
}
|
||||
|
||||
+8
-73
@@ -5,33 +5,22 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutines.util
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.sun.jdi.ClassType
|
||||
import javaslang.control.Either
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.AsyncStackFrameDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SuspendStackFrameDescriptor
|
||||
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ApplicationThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.LookupContinuation
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSourcePosition? {
|
||||
val psiFacade = JavaPsiFacade.getInstance(project)
|
||||
|
||||
val psiClass = ApplicationThreadExecutor().invoke {
|
||||
val psiClass = ApplicationThreadExecutor().readAction {
|
||||
@Suppress("DEPRECATION")
|
||||
psiFacade.findClass(
|
||||
stackTraceElement.className.substringBefore("$"), // find outer class, for which psi exists TODO
|
||||
@@ -44,64 +33,10 @@ fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSource
|
||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
private fun buildSuspendStackFrameChildren(descriptor: SuspendStackFrameDescriptor, frame: StackTraceElement, project: Project) {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val pos = getPosition(frame) ?: return
|
||||
context.debugProcess?.managerThread?.schedule(object : SuspendContextCommandImpl(context.suspendContext) {
|
||||
override fun contextAction() {
|
||||
val (stack, stackFrame) = createSyntheticStackFrame(descriptor, pos) ?: return
|
||||
val action: () -> Unit = { context.debuggerSession?.xDebugSession?.setCurrentStackFrame(stack, stackFrame) }
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun buildAsyncStackFrameChildren(descriptor: AsyncStackFrameDescriptor, process: DebugProcessImpl) {
|
||||
process.managerThread?.schedule(object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val proxy = ThreadReferenceProxyImpl(
|
||||
process.virtualMachineProxy,
|
||||
descriptor.state.thread // is not null because it's a running coroutine
|
||||
)
|
||||
val executionStack = JavaExecutionStack(proxy, process, false)
|
||||
executionStack.initTopFrame()
|
||||
val frame = descriptor.frame.createFrame(process)
|
||||
DebuggerUIUtil.invokeLater {
|
||||
context.debuggerSession?.xDebugSession?.setCurrentStackFrame(
|
||||
executionStack,
|
||||
frame
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun buildEmptyStackFrameChildren(descriptor: EmptyStackFrameDescriptor, project: Project) {
|
||||
val position = getPosition(descriptor.frame) ?: return
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val suspendContext = context.suspendContext ?: return
|
||||
val proxy = suspendContext.thread ?: return
|
||||
context.debugProcess?.managerThread?.schedule(object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
val executionStack =
|
||||
JavaExecutionStack(proxy, context.debugProcess!!, false)
|
||||
executionStack.initTopFrame()
|
||||
val frame = SyntheticStackFrame(descriptor, emptyList(), position)
|
||||
val action: () -> Unit =
|
||||
{ context.debuggerSession?.xDebugSession?.setCurrentStackFrame(executionStack, frame) }
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
||||
StackFrameDescriptorImpl(proxy, MethodsTracker()) {
|
||||
StackFrameDescriptorImpl(proxy, MethodsTracker())
|
||||
|
||||
class ProjectNotification(val project: Project) {
|
||||
fun error(message: String) =
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project)
|
||||
}
|
||||
|
||||
+20
-10
@@ -5,11 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutines.view
|
||||
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.XDebugSessionListener
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
|
||||
|
||||
class CoroutineViewDebugSessionListener(
|
||||
@@ -19,48 +21,56 @@ class CoroutineViewDebugSessionListener(
|
||||
val log by logger
|
||||
|
||||
override fun sessionPaused() {
|
||||
log.info("XListener: sessionPaused")
|
||||
val suspendContext = session.suspendContext ?: return requestClear()
|
||||
xCoroutineView.forceClear()
|
||||
xCoroutineView.alarm.cancel()
|
||||
renew(suspendContext)
|
||||
}
|
||||
|
||||
override fun sessionResumed() {
|
||||
xCoroutineView.saveState()
|
||||
log.info("XListener: sessionResumed")
|
||||
val suspendContext = session.suspendContext ?: return requestClear()
|
||||
log.warn("sessionResumed ${session}")
|
||||
renew(suspendContext)
|
||||
}
|
||||
|
||||
override fun sessionStopped() {
|
||||
log.info("XListener: sessionStopped")
|
||||
val suspendContext = session.suspendContext ?: return requestClear()
|
||||
log.warn("sessionStopped ${session}")
|
||||
renew(suspendContext)
|
||||
}
|
||||
|
||||
override fun stackFrameChanged() {
|
||||
val suspendContext = session.suspendContext ?: return requestClear()
|
||||
log.warn("stackFrameChanged ${session}")
|
||||
renew(suspendContext)
|
||||
xCoroutineView.saveState()
|
||||
log.info("XListener: stackFrameChanged")
|
||||
// val suspendContext = session.suspendContext ?: return requestClear()
|
||||
// log.warn("stackFrameChanged ${session}")
|
||||
// renew(suspendContext)
|
||||
}
|
||||
|
||||
override fun beforeSessionResume() {
|
||||
log.info("XListener: beforeSessionResume")
|
||||
log.warn("beforeSessionResume ${session}")
|
||||
}
|
||||
|
||||
override fun settingsChanged() {
|
||||
log.info("XListener: settingsChanged")
|
||||
val suspendContext = session.suspendContext ?: return requestClear()
|
||||
log.warn("settingsChanged ${session}")
|
||||
renew(suspendContext)
|
||||
}
|
||||
|
||||
fun renew(suspendContext: XSuspendContext) {
|
||||
DebuggerUIUtil.invokeLater {
|
||||
xCoroutineView.panel.tree.setRoot(xCoroutineView.createRoot(suspendContext), false)
|
||||
if(suspendContext is SuspendContextImpl && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
DebuggerUIUtil.invokeLater {
|
||||
xCoroutineView.renewRoot(suspendContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun requestClear() {
|
||||
private fun requestClear() {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) { // no delay in tests
|
||||
xCoroutineView.clear()
|
||||
xCoroutineView.resetRoot()
|
||||
} else {
|
||||
xCoroutineView.alarm.cancelAndRequest()
|
||||
}
|
||||
|
||||
+11
-9
@@ -28,9 +28,8 @@ import java.util.*
|
||||
/**
|
||||
* Tree of coroutines for [CoroutinesPanel]
|
||||
*/
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
|
||||
private val log by logger
|
||||
|
||||
// called on every step/frame
|
||||
override fun build(context: DebuggerContextImpl) {
|
||||
val session = context.debuggerSession
|
||||
@@ -38,9 +37,11 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
|
||||
val command = RefreshCoroutinesTreeCommand(context, this)
|
||||
val debuggerSessionState = session?.state ?: DebuggerSession.State.DISPOSED
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode || debuggerSessionState in EnumSet.of(DebuggerSession.State.PAUSED, DebuggerSession.State.RUNNING)) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode || debuggerSessionState in EnumSet
|
||||
.of(DebuggerSession.State.PAUSED, DebuggerSession.State.RUNNING)
|
||||
) {
|
||||
showMessage(MessageDescriptor.EVALUATING)
|
||||
ManagerThreadExecutor(context.debugProcess!!).schedule(command)
|
||||
context.debugProcess!!.managerThread.schedule(command)
|
||||
} else {
|
||||
showMessage(session?.stateDescription ?: DebuggerBundle.message("status.debug.stopped"))
|
||||
}
|
||||
@@ -48,7 +49,7 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
|
||||
|
||||
|
||||
override fun getBuildNodeCommand(node: DebuggerTreeNodeImpl): DebuggerCommandImpl? {
|
||||
return when(val descriptor = node.descriptor) {
|
||||
return when (val descriptor = node.descriptor) {
|
||||
is CoroutineDescriptorImpl ->
|
||||
CoroutineBuildFrameCommand(node, descriptor, myNodeManager, debuggerContext)
|
||||
is CreationFramesDescriptor ->
|
||||
@@ -71,7 +72,8 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
|
||||
}
|
||||
}
|
||||
|
||||
class CoroutineInfoCache(val cache: MutableList<CoroutineInfoData> = mutableListOf(), var state: CacheState = CacheState.INIT
|
||||
class CoroutineInfoCache(
|
||||
val cache: MutableList<CoroutineInfoData> = mutableListOf(), var state: CacheState = CacheState.INIT
|
||||
) {
|
||||
fun ok(infoList: List<CoroutineInfoData>) {
|
||||
cache.clear()
|
||||
@@ -84,11 +86,11 @@ class CoroutineInfoCache(val cache: MutableList<CoroutineInfoData> = mutableList
|
||||
state = CacheState.FAIL
|
||||
}
|
||||
|
||||
fun isOk() : Boolean {
|
||||
fun isOk(): Boolean {
|
||||
return state == CacheState.OK
|
||||
}
|
||||
}
|
||||
|
||||
enum class CacheState() {
|
||||
OK,FAIL,INIT
|
||||
enum class CacheState {
|
||||
OK, FAIL, INIT
|
||||
}
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerActions
|
||||
/**
|
||||
* Added into ui in [CoroutineProjectConnectionListener.registerCoroutinesPanel]
|
||||
*/
|
||||
@Deprecated("moved to XCoroutineView")
|
||||
class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : ThreadsPanel(project, stateManager) {
|
||||
|
||||
override fun createTreeView(): DebuggerTree {
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.coroutines.view
|
||||
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.settings.ThreadsViewSettings
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.editor.HighlighterColors
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.ui.ColoredTextContainer
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.xdebugger.frame.presentation.XValuePresentation
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
|
||||
import javax.swing.Icon
|
||||
|
||||
class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
|
||||
val texts = mutableListOf<String>()
|
||||
val textKeyAttributes = mutableListOf<TextAttributesKey>()
|
||||
|
||||
constructor(icon: Icon?, hasChildrens: Boolean, text: String) : this(icon, hasChildrens) {
|
||||
append(text)
|
||||
}
|
||||
|
||||
internal fun append(value: String) {
|
||||
texts.add(value)
|
||||
textKeyAttributes.add(CoroutineDebuggerColors.REGULAR_ATTRIBUTES)
|
||||
}
|
||||
|
||||
internal fun appendValue(value: String) {
|
||||
texts.add(value)
|
||||
textKeyAttributes.add(CoroutineDebuggerColors.VALUE_ATTRIBUTES)
|
||||
}
|
||||
|
||||
fun appendToComponent(component: ColoredTextContainer) {
|
||||
val size: Int = texts.size
|
||||
for (i in 0 until size) {
|
||||
val text: String = texts.get(i)
|
||||
val attribute: TextAttributesKey = textKeyAttributes.get(i)
|
||||
|
||||
component.append(text, when(attribute) {
|
||||
CoroutineDebuggerColors.REGULAR_ATTRIBUTES -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
CoroutineDebuggerColors.VALUE_ATTRIBUTES -> XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES
|
||||
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun forEachTextBlock(f: (Pair<String, TextAttributesKey>) -> Unit) {
|
||||
for (pair in texts zip textKeyAttributes)
|
||||
f(pair)
|
||||
}
|
||||
|
||||
fun simpleString(): String {
|
||||
val component = SimpleColoredComponent()
|
||||
appendToComponent(component)
|
||||
return component.getCharSequence(false).toString()
|
||||
}
|
||||
|
||||
fun valuePresentation(): XValuePresentation {
|
||||
return object : XValuePresentation() {
|
||||
override fun isShowName() = false
|
||||
|
||||
override fun getSeparator() = ""
|
||||
|
||||
override fun renderValue(renderer: XValueTextRenderer) {
|
||||
forEachTextBlock {
|
||||
renderer.renderValue(it.first, it.second)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CoroutineDebuggerColors {
|
||||
companion object {
|
||||
val REGULAR_ATTRIBUTES =
|
||||
TextAttributesKey.createTextAttributesKey("REGULAR_ATTRIBUTES", HighlighterColors.NO_HIGHLIGHTING)
|
||||
val VALUE_ATTRIBUTES =
|
||||
TextAttributesKey.createTextAttributesKey("VALUE_ATTRIBUTES", CodeInsightColors.WARNINGS_ATTRIBUTES)
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleColoredTextIconPresentationRenderer {
|
||||
private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance()
|
||||
|
||||
fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon {
|
||||
val thread = infoData.activeThread
|
||||
val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: ""
|
||||
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
|
||||
|
||||
val icon = when (infoData.state) {
|
||||
CoroutineInfoData.State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended
|
||||
CoroutineInfoData.State.RUNNING -> AllIcons.Debugger.ThreadRunning
|
||||
CoroutineInfoData.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
|
||||
}
|
||||
|
||||
val label = SimpleColoredTextIcon(icon, true)
|
||||
label.append("\"")
|
||||
label.appendValue(infoData.name)
|
||||
label.append("\": ${infoData.state}")
|
||||
if(name.isNotEmpty()) {
|
||||
label.append(" on thread \"")
|
||||
label.appendValue(name)
|
||||
label.append("\": $threadState")
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from #StackFrameDescriptorImpl.calcRepresentation
|
||||
*/
|
||||
fun render(location: Location): SimpleColoredTextIcon {
|
||||
val label = SimpleColoredTextIcon(null, false)
|
||||
DebuggerUIUtil.getColorScheme(null)
|
||||
if (location.method() != null) {
|
||||
val myName = location.method().name()
|
||||
val methodDisplay = if (settings.SHOW_ARGUMENTS_TYPES)
|
||||
DebuggerUtilsEx.methodNameWithArguments(location.method())
|
||||
else
|
||||
myName
|
||||
label.appendValue(methodDisplay)
|
||||
}
|
||||
if (settings.SHOW_LINE_NUMBER) {
|
||||
label.append(":")
|
||||
label.append("" + DebuggerUtilsEx.getLineNumber(location, false))
|
||||
}
|
||||
if (settings.SHOW_CLASS_NAME) {
|
||||
val name: String?
|
||||
name = try {
|
||||
val refType: ReferenceType = location.declaringType()
|
||||
refType.name()
|
||||
} catch (e: InternalError) {
|
||||
e.toString()
|
||||
}
|
||||
if (name != null) {
|
||||
label.append(", ")
|
||||
val dotIndex = name.lastIndexOf('.')
|
||||
if (dotIndex < 0) {
|
||||
label.append(name)
|
||||
} else {
|
||||
label.append(name.substring(dotIndex + 1))
|
||||
if (settings.SHOW_PACKAGE_NAME) {
|
||||
label.append(" (${name.substring( 0, dotIndex)})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (settings.SHOW_SOURCE_NAME) {
|
||||
label.append(", ")
|
||||
val sourceName = DebuggerUtilsEx.getSourceName(location) { e: Throwable? -> "Unknown Source" }
|
||||
label.append(sourceName)
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
fun renderCreationNode(infoData: CoroutineInfoData) =
|
||||
SimpleColoredTextIcon(AllIcons.Debugger.ThreadSuspended, true, "Creation stack frame of ${infoData.name}")
|
||||
|
||||
fun renderErrorNode(error: String) =
|
||||
SimpleColoredTextIcon(AllIcons.Actions.Lightning,false, error)
|
||||
|
||||
fun renderRoorNode(text: String) =
|
||||
SimpleColoredTextIcon(null, true, text)
|
||||
|
||||
fun renderGroup(groupName: String) =
|
||||
SimpleColoredTextIcon(AllIcons.Debugger.ThreadGroup,true, groupName)
|
||||
}
|
||||
+241
-174
@@ -5,54 +5,66 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutines.view
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.JavaDebugProcess
|
||||
import com.intellij.debugger.engine.JavaExecutionStack
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.settings.ThreadsViewSettings
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.ui.DoubleClickListener
|
||||
import com.intellij.ui.OnePixelSplitter
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleColoredText
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.util.SingleAlarm
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.frame.*
|
||||
import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation
|
||||
import com.intellij.xdebugger.frame.presentation.XValuePresentation
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
|
||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
|
||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel
|
||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeRestorer
|
||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState
|
||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode
|
||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.ClassType
|
||||
import javaslang.control.Either
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerContentInfo
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineStackFrameItem
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CreationCoroutineStackFrameItem
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.command.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SuspendStackFrameDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SyntheticStackFrame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ApplicationThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.AsyncStackTraceContext
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ManagerThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.CreateContentParams
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.CreateContentParamsProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.XDebugSessionListenerProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
|
||||
import javax.swing.Icon
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutines.util.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.MouseEvent
|
||||
|
||||
|
||||
class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
Disposable, XDebugSessionListenerProvider, CreateContentParamsProvider {
|
||||
private var needToRestoreState: Boolean = false
|
||||
val log by logger
|
||||
val splitter = OnePixelSplitter("SomeKey", 0.25f)
|
||||
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
|
||||
val alarm = SingleAlarm(Runnable { clear() }, VIEW_CLEAR_DELAY, this)
|
||||
val debugProcess: DebugProcessImpl = (session.debugProcess as JavaDebugProcess).debuggerSession.process
|
||||
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
|
||||
val javaDebugProcess = session.debugProcess as JavaDebugProcess
|
||||
val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
|
||||
val renderer = SimpleColoredTextIconPresentationRenderer()
|
||||
val managerThreadExecutor = ManagerThreadExecutor(debugProcess)
|
||||
val applicationThreadExecutor = ApplicationThreadExecutor()
|
||||
var treeState: XDebuggerTreeState? = null
|
||||
private var restorer: XDebuggerTreeRestorer? = null
|
||||
private var selectedNodeListener = XDebuggerTreeSelectedNodeListener(panel.tree)
|
||||
|
||||
companion object {
|
||||
private val VIEW_CLEAR_DELAY = 100 //ms
|
||||
@@ -60,26 +72,42 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
|
||||
init {
|
||||
splitter.firstComponent = panel.mainPanel
|
||||
selectedNodeListener.installOn()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
|
||||
fun saveState() {
|
||||
DebuggerUIUtil.invokeLater {
|
||||
panel.tree
|
||||
.setRoot(object : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {}) {}, false)
|
||||
if (! (panel.tree.root is EmptyNode)) {
|
||||
treeState = XDebuggerTreeState.saveState(panel.tree)
|
||||
log.info("Tree state saved")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetRoot() {
|
||||
DebuggerUIUtil.invokeLater {
|
||||
panel.tree.setRoot(EmptyNode(), false)
|
||||
}
|
||||
}
|
||||
|
||||
fun renewRoot(suspendContext: XSuspendContext) {
|
||||
panel.tree.setRoot(XCoroutinesRootNode(suspendContext), false)
|
||||
if(treeState != null) {
|
||||
restorer?.dispose()
|
||||
restorer = treeState?.restoreState(panel.tree)
|
||||
log.info("Tree state restored")
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
restorer?.dispose()
|
||||
}
|
||||
|
||||
fun forceClear() {
|
||||
alarm.cancel()
|
||||
clear()
|
||||
}
|
||||
|
||||
fun createRoot(suspendContext: XSuspendContext) =
|
||||
XCoroutinesRootNode(panel.tree, suspendContext)
|
||||
|
||||
override fun debugSessionListener(session: XDebugSession) =
|
||||
CoroutineViewDebugSessionListener(session, this)
|
||||
|
||||
@@ -92,172 +120,211 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
panel.tree
|
||||
)
|
||||
|
||||
}
|
||||
inner class EmptyNode : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {})
|
||||
|
||||
class XCoroutinesRootNode(tree: XDebuggerTree, suspendContext: XSuspendContext) :
|
||||
XValueContainerNode<CoroutineGroupContainer>(tree, null, false, CoroutineGroupContainer(suspendContext)) {
|
||||
}
|
||||
inner class XCoroutinesRootNode(suspendContext: XSuspendContext) :
|
||||
XValueContainerNode<CoroutineGroupContainer>(panel.tree, null, false, CoroutineGroupContainer(suspendContext, "Default group"))
|
||||
|
||||
class CoroutineGroupContainer(
|
||||
val suspendContext: XSuspendContext
|
||||
) : XValueContainer() {
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
val children = XValueChildrenList()
|
||||
children.add("", CoroutineContainer(suspendContext as SuspendContextImpl, "Default group"))
|
||||
node.addChildren(children, true)
|
||||
}
|
||||
}
|
||||
|
||||
class CoroutineContainer(
|
||||
val suspendContext: SuspendContextImpl,
|
||||
val groupName: String
|
||||
) : XValue() {
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
val managerThreadExecutor = ManagerThreadExecutor(suspendContext.debugProcess)
|
||||
managerThreadExecutor.schedule {
|
||||
val debugProbesProxy = CoroutinesDebugProbesProxy(suspendContext)
|
||||
|
||||
var coroutineCache = debugProbesProxy.dumpCoroutines()
|
||||
if(coroutineCache.isOk()) {
|
||||
val children = XValueChildrenList()
|
||||
coroutineCache.cache.forEach {
|
||||
children.add("", FramesContainer(it, debugProbesProxy, managerThreadExecutor))
|
||||
}
|
||||
node.addChildren(children, true)
|
||||
} else
|
||||
node.addChildren(XValueChildrenList.EMPTY, true)
|
||||
inner class CoroutineGroupContainer(val suspendContext: XSuspendContext, val groupName: String) : XValueContainer() {
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
|
||||
node.addChildren(groups, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun computePresentation(node: XValueNode, place: XValuePlace) {
|
||||
node.setPresentation(AllIcons.Debugger.ThreadGroup, XRegularValuePresentation(groupName, null, ""), true)
|
||||
}
|
||||
}
|
||||
inner class CoroutineContainer(
|
||||
val suspendContext: XSuspendContext,
|
||||
val groupName: String
|
||||
) : RendererContainer(renderer.renderGroup(groupName)) {
|
||||
|
||||
class FramesContainer(
|
||||
private val coroutineInfoData: CoroutineInfoData,
|
||||
private val debugProbesProxy: CoroutinesDebugProbesProxy,
|
||||
private val managerThreadExecutor: ManagerThreadExecutor
|
||||
) : XValue() {
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
managerThreadExecutor.schedule {
|
||||
val children = XValueChildrenList()
|
||||
debugProbesProxy.frameBuilder().build(coroutineInfoData)
|
||||
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
|
||||
coroutineInfoData.stackFrameList.forEach {
|
||||
val frameValue = when (it) {
|
||||
is CreationCoroutineStackFrameItem -> {
|
||||
creationStack.add(it)
|
||||
null
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
managerThreadExecutor.on(suspendContext).schedule {
|
||||
val debugProbesProxy = CoroutinesDebugProbesProxy(suspendContext)
|
||||
|
||||
var coroutineCache = debugProbesProxy.dumpCoroutines()
|
||||
if (coroutineCache.isOk()) {
|
||||
val children = XValueChildrenList()
|
||||
coroutineCache.cache.forEach {
|
||||
children.add(FramesContainer(it, suspendContext))
|
||||
}
|
||||
else -> CoroutineFrameValue(it)
|
||||
}
|
||||
frameValue?.let {
|
||||
children.add("", frameValue)
|
||||
node.addChildren(children, true)
|
||||
} else {
|
||||
node.addChildren(XValueChildrenList.singleton(ErrorNode("Error occured while fetching information")), true)
|
||||
}
|
||||
}
|
||||
children.add("", CreationFramesContainer(creationStack))
|
||||
}
|
||||
}
|
||||
|
||||
inner class ErrorNode(val error: String) : RendererContainer(renderer.renderErrorNode(error))
|
||||
|
||||
inner class FramesContainer(
|
||||
private val infoData: CoroutineInfoData,
|
||||
private val suspendContext: XSuspendContext
|
||||
) : RendererContainer(renderer.render(infoData)) {
|
||||
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
managerThreadExecutor.on(suspendContext).schedule {
|
||||
val debugProbesProxy = CoroutinesDebugProbesProxy(suspendContext)
|
||||
val children = XValueChildrenList()
|
||||
debugProbesProxy.frameBuilder().build(infoData)
|
||||
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
|
||||
infoData.stackFrameList.forEach {
|
||||
if (it is CreationCoroutineStackFrameItem)
|
||||
creationStack.add(it)
|
||||
else
|
||||
children.add(CoroutineFrameValue(it))
|
||||
}
|
||||
children.add(CreationFramesContainer(infoData, creationStack))
|
||||
node.addChildren(children, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class CreationFramesContainer(
|
||||
private val infoData: CoroutineInfoData,
|
||||
private val creationFrames: List<CreationCoroutineStackFrameItem>
|
||||
) : RendererContainer(renderer.renderCreationNode(infoData)) {
|
||||
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
val children = XValueChildrenList()
|
||||
|
||||
creationFrames.forEach {
|
||||
children.add(CoroutineFrameValue(it))
|
||||
}
|
||||
node.addChildren(children, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun computePresentation(node: XValueNode, place: XValuePlace) {
|
||||
val icon = when (coroutineInfoData.state) {
|
||||
CoroutineInfoData.State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended
|
||||
CoroutineInfoData.State.RUNNING -> AllIcons.Debugger.ThreadRunning
|
||||
CoroutineInfoData.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
|
||||
inner class CoroutineFrameValue(val frame: CoroutineStackFrameItem
|
||||
) : XNamedValue(frame.uniqueId()) {
|
||||
override fun computePresentation(node: XValueNode, place: XValuePlace) =
|
||||
applyRenderer(node, renderer.render(frame.location()))
|
||||
}
|
||||
|
||||
private fun applyRenderer(node: XValueNode, presentation: SimpleColoredTextIcon) =
|
||||
node.setPresentation(presentation.icon, presentation.valuePresentation(), presentation.hasChildrens)
|
||||
|
||||
open inner class RendererContainer(val presentation: SimpleColoredTextIcon) : XNamedValue(presentation.simpleString()) {
|
||||
override fun computePresentation(node: XValueNode, place: XValuePlace) =
|
||||
applyRenderer(node, presentation)
|
||||
}
|
||||
|
||||
inner class XDebuggerTreeSelectedNodeListener(val tree: XDebuggerTree) {
|
||||
|
||||
fun installOn() {
|
||||
object : DoubleClickListener() {
|
||||
override fun onDoubleClick(e: MouseEvent) =
|
||||
nodeSelected(Either.left(e))
|
||||
}.installOn(tree)
|
||||
|
||||
tree.addKeyListener(object : KeyAdapter() {
|
||||
override fun keyPressed(e: KeyEvent) {
|
||||
val key = e.keyCode
|
||||
if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT)
|
||||
nodeSelected(Either.right(e))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val valuePresentation = customizePresentation(coroutineInfoData)
|
||||
node.setPresentation(icon, valuePresentation, true)
|
||||
}
|
||||
fun nodeSelected(event: Either<MouseEvent, KeyEvent>) : Boolean {
|
||||
val selectedNodes = tree.getSelectedNodes(XValueNodeImpl::class.java, null)
|
||||
if (selectedNodes.size == 1) {
|
||||
val node = selectedNodes[0]
|
||||
val valueContainer = node.valueContainer
|
||||
if (valueContainer is XCoroutineView.CoroutineFrameValue) {
|
||||
val frame = valueContainer.frame
|
||||
val threadSuspendContext = session.suspendContext as SuspendContextImpl
|
||||
when (frame) {
|
||||
is RunningCoroutineStackFrameItem -> {
|
||||
val threadProxy = valueContainer.frame.frame.threadProxy()
|
||||
val isCurrentContext = threadSuspendContext.thread == threadProxy
|
||||
createStackAndSetFrame(threadProxy, { frame.stackFrame }, isCurrentContext)
|
||||
}
|
||||
is CreationCoroutineStackFrameItem -> {
|
||||
val position = getPosition(frame.stackTraceElement) ?: return false
|
||||
val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
|
||||
val stackFrame =
|
||||
createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) })
|
||||
}
|
||||
is SuspendCoroutineStackFrameItem -> {
|
||||
val position = getPosition(frame.stackTraceElement) ?: return false
|
||||
}
|
||||
is AsyncCoroutineStackFrameItem -> {
|
||||
|
||||
private fun customizePresentation(coroutineInfoData: CoroutineInfoData): XRegularValuePresentation {
|
||||
val component = SimpleColoredComponent()
|
||||
val thread = coroutineInfoData.thread
|
||||
val name = thread?.name()?.substringBefore(" @${coroutineInfoData.name}") ?: ""
|
||||
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
|
||||
|
||||
component.append("\"").append(coroutineInfoData.name, XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES)
|
||||
component.append("\": ${coroutineInfoData.state} ${if (name.isNotEmpty()) "on thread \"$name\":$threadState" else ""}")
|
||||
return XRegularValuePresentation(component.getCharSequence(false).toString(), null, "")
|
||||
}
|
||||
}
|
||||
|
||||
class CreationFramesContainer(val creationFrames: List<CreationCoroutineStackFrameItem>) : XValue() {
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
val children = XValueChildrenList()
|
||||
|
||||
creationFrames.forEach {
|
||||
children.add("", CoroutineFrameValue(it))
|
||||
}
|
||||
node.addChildren(children, true)
|
||||
}
|
||||
|
||||
override fun computePresentation(node: XValueNode, place: XValuePlace) {
|
||||
node.setPresentation(AllIcons.Debugger.ThreadSuspended, XRegularValuePresentation("Creation stack frame", null, ""), true)
|
||||
}
|
||||
}
|
||||
|
||||
class CoroutineFrameValue(val frame: CoroutineStackFrameItem) : XValue() {
|
||||
override fun computePresentation(node: XValueNode, place: XValuePlace) {
|
||||
val presentation = customizePresentation(frame)
|
||||
if(node is XValueNodeImpl) {
|
||||
node.setPresentation(null, XRegularValuePresentation("", null, ""), false)
|
||||
presentation.texts.forEachIndexed { i, s ->
|
||||
node.text.append(s, presentation.attributes[i])
|
||||
}
|
||||
} else {
|
||||
val component = SimpleColoredComponent()
|
||||
presentation.appendToComponent(component)
|
||||
val valuePresentation = XRegularValuePresentation(component.getCharSequence(false).toString(), null, "")
|
||||
node.setPresentation(null, valuePresentation, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun customizePresentation(frame: CoroutineStackFrameItem) : SimpleColoredText =
|
||||
calcRepresentation(frame.location(), ThreadsViewSettings.getInstance())
|
||||
|
||||
|
||||
/**
|
||||
* Taken from #StackFrameDescriptorImpl.calcRepresentation
|
||||
*/
|
||||
fun calcRepresentation(location: Location, settings: ThreadsViewSettings): SimpleColoredText {
|
||||
val label = SimpleColoredText()
|
||||
DebuggerUIUtil.getColorScheme(null)
|
||||
if (location.method() != null) {
|
||||
val myName = location.method().name()
|
||||
label.append(if (settings.SHOW_ARGUMENTS_TYPES) DebuggerUtilsEx.methodNameWithArguments(location.method()) else myName, XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES)
|
||||
}
|
||||
if (settings.SHOW_LINE_NUMBER) {
|
||||
label.append(":", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
label.append("" + DebuggerUtilsEx.getLineNumber(location, false), SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
}
|
||||
if (settings.SHOW_CLASS_NAME) {
|
||||
val name: String?
|
||||
name = try {
|
||||
val refType: ReferenceType = location.declaringType()
|
||||
refType?.name()
|
||||
} catch (e: InternalError) {
|
||||
e.toString()
|
||||
}
|
||||
if (name != null) {
|
||||
label.append(", ", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
val dotIndex = name.lastIndexOf('.')
|
||||
if (dotIndex < 0) {
|
||||
label.append(name, SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
} else {
|
||||
label.append(name.substring(dotIndex + 1), SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
if (settings.SHOW_PACKAGE_NAME) {
|
||||
label.append(" (${name.substring( 0, dotIndex)})", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
}
|
||||
// else -> {
|
||||
// val (stack, stackFrame) = createSyntheticStackFrame(descriptor, pos) ?: return
|
||||
// val action: () -> Unit = { context.debuggerSession?.xDebugSession?.setCurrentStackFrame(stack, stackFrame) }
|
||||
// - ApplicationManager.getApplication()
|
||||
// - .invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (settings.SHOW_SOURCE_NAME) {
|
||||
label.append(", ", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
label.append(DebuggerUtilsEx.getSourceName(location) { e: Throwable? -> "Unknown Source" }, SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
|
||||
fun createSyntheticStackFrame() {}
|
||||
|
||||
fun createStackAndSetFrame(threadReferenceProxy: ThreadReferenceProxyImpl, stackFrameProvider: () -> XStackFrame, isCurrentContext: Boolean = false) {
|
||||
val threadSuspendContext = session.suspendContext as SuspendContextImpl
|
||||
managerThreadExecutor.on(threadSuspendContext).schedule {
|
||||
val stackFrame = stackFrameProvider.invoke()
|
||||
val executionStack = createExecutionStack(threadReferenceProxy, isCurrentContext)
|
||||
applicationThreadExecutor.schedule(
|
||||
{
|
||||
session.setCurrentStackFrame(executionStack, stackFrame)
|
||||
}, panel.tree)
|
||||
}
|
||||
}
|
||||
|
||||
fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false) : XExecutionStack {
|
||||
val executionStack = CoroutineDebuggerExecutionStack(proxy, isCurrentContext)
|
||||
executionStack.initTopFrame()
|
||||
return executionStack
|
||||
}
|
||||
|
||||
inner class CoroutineDebuggerExecutionStack(threadReferenceProxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean) :
|
||||
JavaExecutionStack(threadReferenceProxy, debugProcess, isCurrentContext)
|
||||
|
||||
|
||||
private fun getPosition(frame: StackTraceElement): XSourcePosition? {
|
||||
val psiFacade = JavaPsiFacade.getInstance(project)
|
||||
val psiClass = psiFacade.findClass(
|
||||
frame.className.substringBefore("$"), // find outer class, for which psi exists TODO
|
||||
GlobalSearchScope.everythingScope(project)
|
||||
)
|
||||
val classFile = psiClass?.containingFile?.virtualFile
|
||||
// to convert to 0-based line number or '-1' to do not move
|
||||
val lineNumber = if (frame.lineNumber > 0) frame.lineNumber - 1 else return null
|
||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
||||
}
|
||||
|
||||
private fun createSyntheticStackFrame(
|
||||
descriptor: SuspendStackFrameDescriptor,
|
||||
pos: XSourcePosition
|
||||
): Pair<XExecutionStack, SyntheticStackFrame>? {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val suspendContext = context.suspendContext ?: return null
|
||||
val proxy = suspendContext.thread ?: return null
|
||||
val executionStack = JavaExecutionStack(proxy, suspendContext.debugProcess, false)
|
||||
executionStack.initTopFrame()
|
||||
val evalContext = context.createEvaluationContext()
|
||||
val frameProxy = evalContext?.frameProxy ?: return null
|
||||
val execContext = ExecutionContext(evalContext, frameProxy)
|
||||
val continuation = descriptor.continuation // guaranteed that it is a BaseContinuationImpl
|
||||
val aMethod = (continuation.type() as ClassType).concreteMethodByName(
|
||||
"getStackTraceElement",
|
||||
"()Ljava/lang/StackTraceElement;"
|
||||
)
|
||||
val vars = with(CoroutineAsyncStackTraceProvider()) {
|
||||
AsyncStackTraceContext(
|
||||
execContext,
|
||||
aMethod
|
||||
).getSpilledVariables(continuation)
|
||||
} ?: return null
|
||||
return executionStack to SyntheticStackFrame(descriptor, vars, pos)
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user