Debugger: Add 193 bunch for coroutines debugger
This commit is contained in:
committed by
Vladimir Ilmov
parent
1dc44b4000
commit
3ca5d2d64f
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.debugger.DebuggerInvocationUtil
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.actions.ThreadDumpAction
|
||||
import com.intellij.debugger.impl.DebuggerSession
|
||||
import com.intellij.execution.RunConfigurationExtension
|
||||
import com.intellij.execution.configurations.DebuggingRunnerData
|
||||
import com.intellij.execution.configurations.JavaParameters
|
||||
import com.intellij.execution.configurations.RunConfigurationBase
|
||||
import com.intellij.execution.configurations.RunnerSettings
|
||||
import com.intellij.execution.ui.RunnerLayoutUi
|
||||
import com.intellij.execution.ui.layout.PlaceInGrid
|
||||
import com.intellij.execution.ui.layout.impl.RunnerContentUi
|
||||
import com.intellij.execution.ui.layout.impl.RunnerLayoutUiImpl
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.ui.content.ContentManagerAdapter
|
||||
import com.intellij.ui.content.ContentManagerEvent
|
||||
import com.intellij.util.messages.MessageBusConnection
|
||||
import com.intellij.xdebugger.XDebugProcess
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.XDebuggerManagerListener
|
||||
import org.jetbrains.kotlin.psi.UserDataProperty
|
||||
|
||||
/**
|
||||
* Installs coroutines debug agent and coroutines tab if `kotlinx.coroutines.debug` dependency is found
|
||||
*/
|
||||
@Suppress("IncompatibleAPI")
|
||||
class CoroutinesDebugConfigurationExtension : RunConfigurationExtension() {
|
||||
private var Project.listenerCreated: Boolean? by UserDataProperty(Key.create("COROUTINES_DEBUG_TAB_CREATE_LISTENER"))
|
||||
|
||||
override fun isApplicableFor(configuration: RunConfigurationBase<*>): Boolean {
|
||||
return Registry.`is`("kotlin.debugger.coroutines")
|
||||
}
|
||||
|
||||
override fun <T : RunConfigurationBase<*>?> updateJavaParameters(
|
||||
configuration: T,
|
||||
params: JavaParameters,
|
||||
runnerSettings: RunnerSettings?
|
||||
) {
|
||||
if (!Registry.`is`("kotlin.debugger.coroutines")) return
|
||||
if (runnerSettings is DebuggingRunnerData
|
||||
&& params.classPath != null
|
||||
&& params.classPath.pathList.isNotEmpty()
|
||||
) {
|
||||
params.classPath.pathList.forEach {
|
||||
if (!it.contains("kotlinx-coroutines-debug")) return@forEach
|
||||
// if debug library is included into project, add agent which installs probes
|
||||
params.vmParametersList?.add("-javaagent:$it")
|
||||
params.vmParametersList?.add("-ea")
|
||||
val project = (configuration as RunConfigurationBase<*>).project
|
||||
// add listener to put coroutines tab into debugger tab
|
||||
if (project.listenerCreated != true) { // prevent multiple listeners creation
|
||||
val connection = project.messageBus.connect()
|
||||
connection.subscribe(
|
||||
XDebuggerManager.TOPIC, createListener(project, connection)
|
||||
)
|
||||
project.listenerCreated = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun createListener(project: Project, connection: MessageBusConnection): XDebuggerManagerListener {
|
||||
return object : XDebuggerManagerListener {
|
||||
override fun processStarted(debugProcess: XDebugProcess) {
|
||||
DebuggerInvocationUtil.swingInvokeLater(project) {
|
||||
val session = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession
|
||||
val ui = session?.xDebugSession?.ui
|
||||
if (ui != null)
|
||||
registerCoroutinesPanel(ui, session)
|
||||
}
|
||||
}
|
||||
|
||||
override fun processStopped(debugProcess: XDebugProcess) {
|
||||
connection.disconnect()
|
||||
project.listenerCreated = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds panel to XDebugSessionTab
|
||||
*/
|
||||
private fun registerCoroutinesPanel(ui: RunnerLayoutUi, session: DebuggerSession) {
|
||||
val panel = CoroutinesPanel(session.project, session.contextManager)
|
||||
val content = ui.createContent(
|
||||
"CoroutinesContent", panel, "Coroutines", // TODO(design)
|
||||
AllIcons.Debugger.ThreadGroup, null
|
||||
)
|
||||
content.isCloseable = false
|
||||
ui.addContent(content, 0, PlaceInGrid.left, true)
|
||||
ui.addListener(object : ContentManagerAdapter() {
|
||||
override fun selectionChanged(event: ContentManagerEvent) {
|
||||
if (event.content === content) {
|
||||
if (content.isSelected) {
|
||||
panel.setUpdateEnabled(true)
|
||||
if (panel.isRefreshNeeded) {
|
||||
panel.rebuildIfVisible(DebuggerSession.Event.CONTEXT)
|
||||
}
|
||||
} else {
|
||||
panel.setUpdateEnabled(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, content)
|
||||
// add coroutine dump button: due to api problem left toolbar is copied, modified and reset to tab
|
||||
val runnerContent = (ui.options as RunnerLayoutUiImpl).getData(RunnerContentUi.KEY.name) as RunnerContentUi
|
||||
val modifiedActions = runnerContent.getActions(true)
|
||||
val pos = modifiedActions.indexOfLast { it is ThreadDumpAction }
|
||||
modifiedActions.add(pos + 1, ActionManager.getInstance().getAction("Kotlin.XDebugger.CoroutinesDump"))
|
||||
ui.options.setLeftToolbar(DefaultActionGroup(modifiedActions), ActionPlaces.DEBUGGER_TOOLBAR)
|
||||
}
|
||||
|
||||
}
|
||||
+8
-6
@@ -41,13 +41,15 @@ object CoroutinesDebugProbesProxy {
|
||||
@Synchronized
|
||||
fun dumpCoroutines(context: ExecutionContext): Either<Throwable, List<CoroutineState>> {
|
||||
try {
|
||||
if (context.debugProcess.references == null) {
|
||||
context.debugProcess.references = ProcessReferences(context)
|
||||
var refs = context.debugProcess.references
|
||||
if (refs == null) {
|
||||
refs = ProcessReferences(context)
|
||||
context.debugProcess.references = refs
|
||||
}
|
||||
val refs = context.debugProcess.references!! // already initialized if it was null
|
||||
|
||||
// get dump
|
||||
val infoList = context.invokeMethod(refs.instance, refs.dumpMethod, emptyList()) as ObjectReference
|
||||
val infoList = context.invokeMethod(refs.instance, refs.dumpMethod, emptyList()) as? ObjectReference
|
||||
?: return Either.right(emptyList())
|
||||
|
||||
context.keepReference(infoList)
|
||||
val size = (context.invokeMethod(infoList, refs.getSize, emptyList()) as IntegerValue).value()
|
||||
|
||||
@@ -132,7 +134,7 @@ object CoroutinesDebugProbesProxy {
|
||||
mergedFrameList, refs.getElement,
|
||||
listOf(context.vm.virtualMachine.mirrorOf(it))
|
||||
) as ObjectReference
|
||||
val clazz = (frame.getValue(refs.className) as StringReference).value()
|
||||
val clazz = (frame.getValue(refs.className) as? StringReference)?.value()
|
||||
list.add(
|
||||
0, // add in the beginning
|
||||
StackTraceElement(
|
||||
|
||||
-4
@@ -164,10 +164,6 @@ class SuspendStackFrameDescriptor(
|
||||
override fun getName(): String {
|
||||
return frame.methodName
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon {
|
||||
return IconLoader.getIcon("org/jetbrains/kotlin/idea/icons/suspendCall.${IconExtensionChooser.iconExtension()}")
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncStackFrameDescriptor(val state: CoroutineState, val frame: StackFrameItem, proxy: StackFrameProxyImpl) :
|
||||
|
||||
Reference in New Issue
Block a user