[coroutines-debugger] Minor improvements of Coroutines View Panel

* Group coroutines by parent Jobs instead of Dispatchers by default (registry:  `coroutine.panel.show.jobs.hierarchy`)
* Print the name of the parent Job in the hierarchy when a given Job is a coroutine
* Mark the running coroutine and the current group (current Dispatcher, or Job) with a red tick

IDEA-332363


Merge-request: IJ-MR-126015
Merged-by: Maria Sokolova <maria.sokolova@jetbrains.com>

GitOrigin-RevId: 13564b1dfbb2327bf773d64c9644a2af637b1e1e
This commit is contained in:
Maria Sokolova
2024-03-01 19:34:35 +00:00
committed by intellij-monorepo-bot
parent c2db9ae649
commit a61ab0f5ad
5 changed files with 54 additions and 35 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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<CoroutineInfoData>
) : 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<String, JobContainer>()
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<JobContainer>()
private val coroutines = mutableListOf<CoroutineInfoData>()
@@ -245,12 +248,15 @@ class CoroutineView(project: Project, javaDebugProcess: JavaDebugProcess) :
inner class DispatchersContainer(
val suspendContext: SuspendContextImpl,
val coroutines: List<CoroutineInfoData>
) : 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<CoroutineInfoData>?
) : 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)

View File

@@ -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)
}