diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 71c87c1d293b..31929023d48f 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1144,6 +1144,9 @@ debugger.filter.breakpoints.by.coroutine.id.description=When enabled uses unique debugger.transfer.context.to.suspend.all.with.method.breakpoint=false debugger.transfer.context.to.suspend.all.with.method.breakpoint.description=Use method breakpoint to suspend all threads for a breakpoint with suspend all threads +coroutine.panel.show.jobs.hierarchy=true +coroutine.panel.show.jobs.hierarchy.description=When enabled, Coroutine View displays the hierarchy of coroutine jobs; otherwise, it shows coroutines grouped by dispatchers. + wolf.the.problem.solver=true wolf.the.problem.solver.description=Disable 'global highlighting' to ease debug ui.disable.dimension.service.keys=false diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt index bd9f538aaecc..751790de1248 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt @@ -3,7 +3,9 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data import com.intellij.debugger.engine.JavaValue +import com.intellij.debugger.engine.SuspendContext import com.sun.jdi.ThreadReference +import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData.Companion.DEFAULT_COROUTINE_NAME import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData.Companion.DEFAULT_COROUTINE_STATE import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.MirrorOfCoroutineInfo @@ -23,6 +25,9 @@ abstract class CoroutineInfoData(val descriptor: CoroutineDescriptor) { fun isCreated() = descriptor.state == State.CREATED fun isRunning() = descriptor.state == State.RUNNING + + fun isRunningOnCurrentThread(suspendContext: SuspendContext) = + activeThread == suspendContext.thread?.threadReference companion object { const val DEFAULT_COROUTINE_NAME = "coroutine" diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt index 31c036801161..5686aa52ac0e 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt @@ -190,7 +190,7 @@ class CoroutineDumpPanel( override fun customizeCellRenderer(list: JList<*>, value: Any, index: Int, selected: Boolean, hasFocus: Boolean) { val infoData = value as CompleteCoroutineInfoData val state = infoData.descriptor - icon = fromState(state.state) + icon = fromState(state.state, false) val attrs = getAttributes(infoData) append(state.name + " (", attrs) var detail: String? = state.state.name diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineView.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineView.kt index dd4fa6a9c67a..61ed7feb3188 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineView.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineView.kt @@ -3,7 +3,6 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.view import com.intellij.debugger.engine.DebugProcessImpl -import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree import com.intellij.debugger.engine.JavaDebugProcess import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.engine.events.SuspendContextCommandImpl @@ -15,6 +14,7 @@ import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl import com.intellij.openapi.project.Project import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.util.registry.Registry import com.intellij.ui.SimpleListCellRenderer import com.intellij.ui.border.CustomLineBorder import com.intellij.ui.components.JBLabel @@ -25,6 +25,7 @@ import com.intellij.xdebugger.XDebugSessionListener import com.intellij.xdebugger.frame.* import com.intellij.xdebugger.impl.actions.XDebuggerActions import com.intellij.xdebugger.impl.ui.DebuggerUIUtil +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 @@ -36,6 +37,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBund import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem import org.jetbrains.kotlin.idea.debugger.coroutine.data.CreationCoroutineStackFrameItem +import org.jetbrains.kotlin.idea.debugger.coroutine.data.LazyCoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy import org.jetbrains.kotlin.idea.debugger.coroutine.util.* import java.awt.BorderLayout @@ -48,7 +50,7 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : private val EMPTY_DISPATCHER_NAME = KotlinDebuggerCoroutinesBundle.message("coroutine.view.dispatcher.empty") val log by logger } - + val alarm = SingleAlarm({ resetRoot() }, VIEW_CLEAR_DELAY_MS, this) private val debugProcess = javaDebugProcess.debuggerSession.process private val renderer = SimpleColoredTextIconPresentationRenderer() @@ -127,17 +129,8 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : ) inner class CoroutineTopGroupContainer(val suspendContext: SuspendContextImpl) : XValueContainer() { - override fun computeChildren(node: XCompositeNode) { - val children = XValueChildrenList() - children.add(CoroutineGroupContainer(suspendContext)) - node.addChildren(children, true) - } - } - - inner class CoroutineGroupContainer(val suspendContext: SuspendContextImpl) : RendererContainer(renderer.renderGroup(KotlinDebuggerCoroutinesBundle.message("coroutine.view.node.root"))) { override fun computeChildren(node: XCompositeNode) { node.setAlreadySorted(true) - if (suspendContext.suspendPolicy != EventRequest.SUSPEND_ALL) { node.addChildren( XValueChildrenList.singleton( @@ -147,7 +140,6 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : ) return } - debugProcess.invokeInSuspendContext(suspendContext) { suspendContext -> val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext) val coroutineCache = debugProbesProxy.dumpCoroutines() @@ -158,8 +150,11 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : } val children = XValueChildrenList() - children.add(JobsContainer(suspendContext, coroutineCache.cache)) - children.add(DispatchersContainer(suspendContext, coroutineCache.cache)) + if (Registry.`is`("coroutine.panel.show.jobs.hierarchy")) { + children.add(JobsContainer(suspendContext, coroutineCache.cache)) + } else { + children.add(DispatchersContainer(suspendContext, coroutineCache.cache)) + } node.addChildren(children, true) } } @@ -168,19 +163,26 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : inner class JobsContainer( val suspendContext: SuspendContextImpl, val coroutines: List - ) : RendererContainer(renderer.renderGroup(KotlinDebuggerCoroutinesBundle.message("coroutine.view.node.jobs"))) { + ) : RendererContainer(renderer.renderNoIconNode(KotlinDebuggerCoroutinesBundle.message("coroutine.view.node.jobs"))) { + + private val jobToCoroutineInfo = + coroutines + .filter { it.jobHierarchy.isNotEmpty()} + .associateBy({ it.jobHierarchy.first()}, { it }) + override fun computeChildren(node: XCompositeNode) { debugProcess.invokeInSuspendContext(suspendContext) { suspendContext -> val jobNodes = mutableMapOf() - val jobs = XValueChildrenList() val coroutines = XValueChildrenList() this.coroutines.forEach { coroutine -> if (coroutine.jobHierarchy.isNotEmpty()) { var parent: JobContainer? = null coroutine.jobHierarchy.reversed().forEach { - parent = jobNodes.computeIfAbsent(it) { jobName -> - JobContainer(suspendContext, jobName).also { jobContainer -> + parent = jobNodes.computeIfAbsent(it) { jobDetails -> + val coroutineName = jobToCoroutineInfo[jobDetails]?.descriptor?.formatName() + val jobName = (if (coroutineName != null) "\"$coroutineName\":" else "") + jobDetails + JobContainer(suspendContext, jobName, coroutine.isRunningOnCurrentThread(suspendContext)).also { jobContainer -> if (parent == null) { jobs.add(jobContainer) } @@ -208,8 +210,9 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : inner class JobContainer( private val suspendContext: SuspendContextImpl, - private val jobName: String - ) : RendererContainer(renderer.renderGroup(jobName)) { + private val jobName: String, + isCurrent: Boolean + ) : RendererContainer(renderer.renderThreadGroup(jobName, isCurrent)) { private val jobs = mutableListOf() private val coroutines = mutableListOf() @@ -245,12 +248,15 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : inner class DispatchersContainer( val suspendContext: SuspendContextImpl, val coroutines: List - ) : RendererContainer(renderer.renderGroup(KotlinDebuggerCoroutinesBundle.message("coroutine.view.node.dispatchers"))) { + ) : RendererContainer(renderer.renderNoIconNode(KotlinDebuggerCoroutinesBundle.message("coroutine.view.node.dispatchers"))) { override fun computeChildren(node: XCompositeNode) { val children = XValueChildrenList() val groups = coroutines.groupBy { it.descriptor.dispatcher } for (dispatcher in groups.keys) { - children.add(CoroutineContainer(suspendContext, dispatcher ?: EMPTY_DISPATCHER_NAME, groups[dispatcher])) + // Mark the group that contains a running coroutine with a tick + val coroutines = groups[dispatcher] + val isCurrent = coroutines?.any { it.isRunningOnCurrentThread(suspendContext) } ?: false + children.add(CoroutineContainer(suspendContext, dispatcher ?: EMPTY_DISPATCHER_NAME, isCurrent, coroutines)) } if (children.size() > 0) { @@ -264,20 +270,19 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : inner class CoroutineContainer( private val suspendContext: SuspendContextImpl, private val groupName: String, + isCurrent: Boolean, private val coroutines: List? - ) : RendererContainer(renderer.renderGroup(groupName)) { + ) : RendererContainer(renderer.renderThreadGroup(groupName, isCurrent)) { override fun computeChildren(node: XCompositeNode) { val children = XValueChildrenList() coroutines?.forEach { children.add(FramesContainer(it, suspendContext, groupName)) } - if (children.size() > 0) { node.addChildren(children, true) } else { node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true) } - } } @@ -289,7 +294,7 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) : private val infoData: CoroutineInfoData, private val suspendContext: SuspendContextImpl, parentGroupNameToHideFromContext: String, - ) : RendererContainer(renderer.render(infoData, parentGroupNameToHideFromContext)) { + ) : RendererContainer(renderer.render(infoData, infoData.isRunningOnCurrentThread(suspendContext), parentGroupNameToHideFromContext)) { override fun computeChildren(node: XCompositeNode) { node.setAlreadySorted(true) diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt index c4a4c989817e..7984bb5d821e 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt @@ -91,10 +91,10 @@ interface CoroutineDebuggerColors { } } -fun fromState(state: State): Icon = +fun fromState(state: State, isCurrent: Boolean): Icon = when (state) { State.SUSPENDED -> AllIcons.Debugger.ThreadFrozen - State.RUNNING -> AllIcons.Debugger.ThreadRunning + State.RUNNING -> if (isCurrent) AllIcons.Debugger.ThreadCurrent else AllIcons.Debugger.ThreadRunning State.CREATED -> AllIcons.Debugger.ThreadStates.Idle else -> AllIcons.Debugger.ThreadStates.Daemon_sign } @@ -105,13 +105,13 @@ class SimpleColoredTextIconPresentationRenderer { } private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance() - - fun render(infoData: CoroutineInfoData, textToHideFromContext: String): SimpleColoredTextIcon { + + fun render(infoData: CoroutineInfoData, isCurrent: Boolean, textToHideFromContext: String): SimpleColoredTextIcon { val thread = infoData.activeThread val name = thread?.name()?.substringBefore(" @${infoData.descriptor.name}") ?: "" val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else "" - - val icon = fromState(infoData.descriptor.state) + + val icon = fromState(infoData.descriptor.state, isCurrent) val label = SimpleColoredTextIcon(icon, !infoData.isCreated()) label.append("\"") @@ -197,6 +197,12 @@ class SimpleColoredTextIconPresentationRenderer { fun renderInfoNode(text: String) = SimpleColoredTextIcon(AllIcons.General.Information, false, KotlinDebuggerCoroutinesBundle.message(text)) - fun renderGroup(groupName: String) = - SimpleColoredTextIcon(AllIcons.Debugger.ThreadGroup, true, groupName) + fun renderThreadGroup(groupName: String, isCurrent: Boolean) = + SimpleColoredTextIcon(if (isCurrent) AllIcons.Debugger.ThreadGroupCurrent else AllIcons.Debugger.ThreadGroup, true, groupName) + + fun renderExpandHover(groupName: String) = + SimpleColoredTextIcon(AllIcons.Ide.Notification.ExpandHover, true, groupName) + + fun renderNoIconNode(groupName: String) = + SimpleColoredTextIcon(null, true, groupName) }