diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutineAsyncStackTraceProvider.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutineAsyncStackTraceProvider.kt index dca08cc682e..3a40a134d9e 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutineAsyncStackTraceProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/CoroutineAsyncStackTraceProvider.kt @@ -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 { + fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List? { + val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList() + return null + } + + fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List { val defaultResult = emptyList() 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 } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/BuildCoroutineNodeCommand.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/BuildCoroutineNodeCommand.kt index 9e4ad372e51..741cf057165 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/BuildCoroutineNodeCommand.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/BuildCoroutineNodeCommand.kt @@ -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, diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildCreationFrameCommand.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildCreationFrameCommand.kt index bf805774d83..515e4c4b1a5 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildCreationFrameCommand.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildCreationFrameCommand.kt @@ -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) } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildFrameCommand.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildFrameCommand.kt index 36e229ef8fe..86f21b62f4d 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildFrameCommand.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuildFrameCommand.kt @@ -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) diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuilder.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuilder.kt index 51b988c3589..c88960c54cb 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuilder.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineBuilder.kt @@ -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 { + fun build(coroutine: CoroutineInfoData): List { val coroutineStackFrameList = mutableListOf() - 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): 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() + } + } } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineDumpAction.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineDumpAction.kt index 699d841dc75..946139c0160 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineDumpAction.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/CoroutineDumpAction.kt @@ -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) } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/RefreshCoroutinesTreeCommand.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/RefreshCoroutinesTreeCommand.kt index 466bbbc3232..ed58ea1091d 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/RefreshCoroutinesTreeCommand.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/command/RefreshCoroutinesTreeCommand.kt @@ -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) { diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineAsyncStackFrameItem.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineAsyncStackFrameItem.kt index b6c16d8102d..5cc6e16ded2 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineAsyncStackFrameItem.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineAsyncStackFrameItem.kt @@ -12,6 +12,4 @@ import com.intellij.xdebugger.frame.XNamedValue class CoroutineAsyncStackFrameItem( val location: GeneratedLocation, spilledVariables: List -) : StackFrameItem(location, spilledVariables) { - -} \ No newline at end of file +) : StackFrameItem(location, spilledVariables) \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineInfoData.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineInfoData.kt index 1a487558b23..24e6b7b0c37 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineInfoData.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/CoroutineInfoData.kt @@ -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, // 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() diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/TreeNodeDescriptorImpl.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/TreeNodeDescriptorImpl.kt index 0d4355910a3..74bc3963b82 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/TreeNodeDescriptorImpl.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/TreeNodeDescriptorImpl.kt @@ -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) - : MessageDescriptor("Coroutine creation stack trace", INFORMATION) { +@Deprecated("moved to XCoroutineView") +class CreationFramesDescriptor(val frames: List) : + MessageDescriptor("Coroutine creation stack trace", INFORMATION) { override fun isExpandable() = true } @@ -54,6 +57,7 @@ class CreationFramesDescriptor(val frames: List) /** * 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 -} \ No newline at end of file +} diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/descriptors.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/descriptors.kt index c5974535d49..0ca2aea0a4b 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/descriptors.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/data/descriptors.kt @@ -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() { +@Deprecated("moved to XCoroutineView") +abstract class CoroutineStackDescriptorData( + val infoData: CoroutineInfoData, + val proxy: StackFrameProxyImpl, + val evalContext: EvaluationContextImpl +) : DescriptorData() { override fun getDisplayKey(): DisplayKey = 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() { override fun createDescriptorImpl(project: Project) = diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/AsyncStackTraceContext.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/AsyncStackTraceContext.kt similarity index 97% rename from idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/AsyncStackTraceContext.kt rename to idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/AsyncStackTraceContext.kt index 8924e7b566f..f846cf17425 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/AsyncStackTraceContext.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/AsyncStackTraceContext.kt @@ -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() collectFramesRecursively(continuation, frames) - return frames; + return frames } private fun locateContinuation() : ObjectReference? { @@ -105,7 +105,7 @@ class AsyncStackTraceContext( fun getSpilledVariables(continuation: ObjectReference): List? { 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) diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/CoroutinesDebugProbesProxy.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/CoroutinesDebugProbesProxy.kt index 821ec75eb36..23ddc57f70d 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/CoroutinesDebugProbesProxy.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/CoroutinesDebugProbesProxy.kt @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/ManagerThreadExecutor.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/ManagerThreadExecutor.kt index aef1fe3ad1a..c896ba0e158 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/ManagerThreadExecutor.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/proxy/ManagerThreadExecutor.kt @@ -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 invoke(f: () -> T) : T { +class ApplicationThreadExecutor { + fun 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() } + } } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/util/KUtils.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/util/KUtils.kt index 4c66a7a7d90..bc139d83d69 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/util/KUtils.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/util/KUtils.kt @@ -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) } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutineViewDebugSessionListener.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutineViewDebugSessionListener.kt index 5bb9509f17d..dc43631ee9c 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutineViewDebugSessionListener.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutineViewDebugSessionListener.kt @@ -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() } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesDebuggerTree.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesDebuggerTree.kt index ccbb0e9569e..ee899222073 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesDebuggerTree.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesDebuggerTree.kt @@ -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 = mutableListOf(), var state: CacheState = CacheState.INIT +class CoroutineInfoCache( + val cache: MutableList = mutableListOf(), var state: CacheState = CacheState.INIT ) { fun ok(infoList: List) { cache.clear() @@ -84,11 +86,11 @@ class CoroutineInfoCache(val cache: MutableList = 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 } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesPanel.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesPanel.kt index 70fa557c55b..5ffdfe72fd9 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesPanel.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/CoroutinesPanel.kt @@ -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 { diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/SimpleColoredTextIcon.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/SimpleColoredTextIcon.kt new file mode 100644 index 00000000000..95a87897083 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/SimpleColoredTextIcon.kt @@ -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() + val textKeyAttributes = mutableListOf() + + 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) -> 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) +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/XCoroutineView.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/XCoroutineView.kt index 7eb04dec26b..4ae06f00834 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/XCoroutineView.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/coroutines/view/XCoroutineView.kt @@ -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(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(panel.tree, null, true, object : XValueContainer() {}) -class XCoroutinesRootNode(tree: XDebuggerTree, suspendContext: XSuspendContext) : - XValueContainerNode(tree, null, false, CoroutineGroupContainer(suspendContext)) { -} + inner class XCoroutinesRootNode(suspendContext: XSuspendContext) : + XValueContainerNode(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() - 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() + 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 + ) : 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) : 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) : 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? { + 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 } +