[debugger] show icon tooltip for thread dump items, IDEA-367627

GitOrigin-RevId: 98a49d442375e4fbff82aa825085c95199851afc
This commit is contained in:
Vladimir Parfinenko
2025-06-06 16:30:08 +02:00
committed by intellij-monorepo-bot
parent de6bff2db3
commit 24ad155a3b
10 changed files with 70 additions and 12 deletions

View File

@@ -71,12 +71,17 @@ private fun truncateIfNeeded(allDumpItems: List<DumpItem>, maxItems: Int): Pair<
private fun dumpItemDtos(allDumpItems: List<DumpItem>, maxItems: Int): ThreadDumpWithAwaitingDependencies {
val (dumpItems, truncatedSize) = truncateIfNeeded(allDumpItems, maxItems)
val attributes = dumpItems.map { it.attributes }.distinct()
val attributesToIndex = attributes.withIndex().associate { it.value to it.index.toByte() }
val icons = dumpItems.map { it.icon }.distinct()
val iconToIndex = icons.withIndex().associate { it.value to it.index.toByte() }
val stateDescriptions = dumpItems.map { it.stateDesc }.distinct()
val stateDescriptionsToIndex = stateDescriptions.withIndex().associate { it.value to it.index }
fun <T> prepareIndex(selector: (DumpItem) -> T): Pair<List<T>, Map<T, Int>> {
val values = dumpItems.map(selector).distinct()
val valueToIndex = values.withIndex().associate { it.value to it.index }
return Pair(values, valueToIndex)
}
val (attributes, attributesToIndex) = prepareIndex { it.attributes }
val (icons, iconToIndex) = prepareIndex { it.icon }
val (stateDescriptions, stateDescriptionToIndex) = prepareIndex { it.stateDesc }
val (iconToolTips, iconToolTipToIndex) = prepareIndex { it.iconToolTip }
val awaiting = hashMapOf<Int, IntArray>()
val itemToIndex = dumpItems.withIndex().associate { it.value to it.index }
@@ -110,12 +115,13 @@ private fun dumpItemDtos(allDumpItems: List<DumpItem>, maxItems: Int): ThreadDum
val items = dumpItems.map {
val (firstLine, stackTraceIndex) = itemToStackTrace[it]!!
JavaThreadDumpItemDto(name = it.name,
stateDescriptionIndex = stateDescriptionsToIndex[it.stateDesc]!!,
stateDescriptionIndex = stateDescriptionToIndex[it.stateDesc]!!,
interestLevel = it.interestLevel,
iconIndex = iconToIndex[it.icon]!!,
attributesIndex = attributesToIndex[it.attributes]!!,
iconIndex = iconToIndex[it.icon]!!.toByte(),
attributesIndex = attributesToIndex[it.attributes]!!.toByte(),
isDeadLocked = it.isDeadLocked,
stackTraceIndex = stackTraceIndex,
iconToolTipIndex = iconToolTipToIndex[it.iconToolTip]!!.toByte(),
firstLine = firstLine)
}
@@ -125,6 +131,7 @@ private fun dumpItemDtos(allDumpItems: List<DumpItem>, maxItems: Int): ThreadDum
stackTraces = stackTraces,
awaitingDependencies = awaiting,
stateDescriptions = stateDescriptions,
iconToolTips = iconToolTips,
truncatedItemsNumber = truncatedSize)
}

View File

@@ -31,6 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.Nls
import java.awt.event.InputEvent
import javax.swing.Icon
@@ -114,7 +115,7 @@ private fun ThreadDumpWithAwaitingDependencies.toDumpItems(): List<DumpItem> {
val iconsCache = icons.map { it.icon() }
val attributesCache = attributes.map { it.toSimpleTextAttributes() }
val feDumpItems = items.map { FrontendDumpItem(it, iconsCache, attributesCache, stackTraces, stateDescriptions) }
val feDumpItems = items.map { FrontendDumpItem(it, iconsCache, attributesCache, stackTraces, stateDescriptions, iconToolTips) }
for ((index, awaitingIndices) in awaitingDependencies) {
val awaitingItems = awaitingIndices.map { feDumpItems[it] }.toHashSet()
feDumpItems[index].setAwaitingItems(awaitingItems)
@@ -128,12 +129,14 @@ private class FrontendDumpItem(
private val attributesCache: List<SimpleTextAttributes>,
private val stackTracesCache: List<@NlsSafe String>,
private val stateDescriptionsCache: List<@NlsSafe String>,
private val iconToolTipsCache: List<@Nls String?>,
) : DumpItem {
private var internalAwaitingItems: Set<DumpItem> = emptySet()
override val name: @NlsSafe String get() = itemDto.name
override val stateDesc: @NlsSafe String get() = stateDescriptionsCache[itemDto.stateDescriptionIndex]
override val stackTrace: @NlsSafe String get() = "${itemDto.firstLine}\n${stackTracesCache[itemDto.stackTraceIndex]}"
override val iconToolTip: @Nls String? get() = iconToolTipsCache[itemDto.iconToolTipIndex.toUInt().toInt()]
override val interestLevel: Int get() = itemDto.interestLevel
override val icon: Icon get() = iconsCache[itemDto.iconIndex.toUInt().toInt()]
override val attributes: SimpleTextAttributes get() = attributesCache[itemDto.attributesIndex.toInt().toUInt().toInt()]

View File

@@ -15,6 +15,7 @@ import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
@ApiStatus.Internal
@Rpc
@@ -54,6 +55,7 @@ data class ThreadDumpWithAwaitingDependencies(
val attributes: List<SerializableSimpleTextAttributes>,
val stackTraces: List<@NlsSafe String>,
val stateDescriptions: List<@NlsSafe String>,
val iconToolTips: List<@Nls String?>,
val awaitingDependencies: Map<Int, IntArray>,
val truncatedItemsNumber: Int,
)
@@ -65,6 +67,7 @@ data class JavaThreadDumpItemDto(
val firstLine: @NlsSafe String,
val stateDescriptionIndex: Int,
val stackTraceIndex: Int,
val iconToolTipIndex: Byte,
val interestLevel: Int,
val iconIndex: Byte,
val attributesIndex: Byte,

View File

@@ -102,3 +102,6 @@ listbox.import.static=Static
listbox.import.with.subpackages=With Subpackages
do.not.import.inner.classes.for=Exclude inner classes by short name:
do.not.import.inner.classes.no.classes=No inner classes defined
dump.item.java.thread.icon.tooltip.virtual=Virtual Thread
dump.item.java.thread.icon.tooltip.daemon=Daemon Thread

View File

@@ -2,11 +2,13 @@
package com.intellij.unscramble
import com.intellij.icons.AllIcons
import com.intellij.java.frontback.impl.JavaFrontbackBundle
import com.intellij.openapi.util.NlsSafe
import com.intellij.threadDumpParser.ThreadOperation
import com.intellij.threadDumpParser.ThreadState
import com.intellij.ui.SimpleTextAttributes
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.awt.Color
import java.util.*
import javax.swing.Icon
@@ -23,6 +25,8 @@ interface DumpItem {
val icon: Icon
val iconToolTip: @Nls String?
val attributes: SimpleTextAttributes
val isDeadLocked: Boolean
@@ -175,6 +179,13 @@ private class JavaThreadDumpItem(private val threadState: ThreadState) : Mergeab
}
}
override val iconToolTip: @Nls String?
get() = when {
threadState.isVirtual -> JavaFrontbackBundle.message("dump.item.java.thread.icon.tooltip.virtual")
threadState.isDaemon -> JavaFrontbackBundle.message("dump.item.java.thread.icon.tooltip.daemon")
else -> null
}
override val attributes: SimpleTextAttributes = when {
threadState.isSleeping -> DumpItem.SLEEPING_ATTRIBUTES
threadState.isEmptyStackTrace || this.isServiceThread || threadState.isKnownJDKThread() -> DumpItem.UNINTERESTING_ATTRIBUTES

View File

@@ -243,9 +243,12 @@ public final class ThreadDumpPanel extends JPanel implements NoStackTraceFolding
}
private static class ThreadListCellRenderer extends ColoredListCellRenderer<DumpItem> {
private String iconToolTip;
@Override
protected void customizeCellRenderer(@NotNull JList<? extends DumpItem> list, DumpItem threadState, int index, boolean selected, boolean hasFocus) {
setIcon(threadState.getIcon());
iconToolTip = threadState.getIconToolTip();
if (!selected) {
DumpItem selectedThread = list.getSelectedValue();
setBackground(getBackgroundColor(threadState, selectedThread));
@@ -255,6 +258,11 @@ public final class ThreadDumpPanel extends JPanel implements NoStackTraceFolding
append(threadState.getStateDesc(), attrs);
}
@Override
protected String getIconToolTipText() {
return iconToolTip;
}
private static @NotNull Color getBackgroundColor(DumpItem threadState, DumpItem selectedThread) {
if (threadState.isDeadLocked()) {
return LightColors.RED;

View File

@@ -356,6 +356,11 @@ a:com.intellij.ui.ColoredTreeCellRenderer
- f:setup(javax.swing.JScrollPane,java.util.Set,javax.swing.JComponent):V
- bs:setup$default(com.intellij.ui.ScrollableContentBorder$Companion,javax.swing.JScrollPane,com.intellij.ui.Side,javax.swing.JComponent,I,java.lang.Object):V
- bs:setup$default(com.intellij.ui.ScrollableContentBorder$Companion,javax.swing.JScrollPane,java.util.Set,javax.swing.JComponent,I,java.lang.Object):V
c:com.intellij.ui.SimpleColoredComponent
- javax.swing.JComponent
- com.intellij.ui.ColoredTextContainer
- javax.accessibility.Accessible
- *p:getIconToolTipText():java.lang.String
*c:com.intellij.ui.components.JBHtmlPane
- com.intellij.openapi.Disposable
- javax.swing.JEditorPane

View File

@@ -1174,10 +1174,22 @@ public class SimpleColoredComponent extends JComponent implements Accessible, Co
}
}
/**
* Returns the string to be used as the tooltip at the icon fragment (if there is an icon).
* By default this returns {@link IconWithToolTip#getToolTip(boolean)} if icon is an instance of {@link IconWithToolTip},
* otherwise {@code null}.
*
* @return a string containing the icon tooltip
*/
@ApiStatus.Experimental
protected @Nls @Nullable String getIconToolTipText() {
return myIcon instanceof IconWithToolTip i ? i.getToolTip(false) : null;
}
@Override
public String getToolTipText(MouseEvent event) {
if (myIcon instanceof IconWithToolTip && findFragmentAt(event.getX()) == FRAGMENT_ICON) {
String iconToolTip = ((IconWithToolTip)myIcon).getToolTip(false);
if (myIcon != null && findFragmentAt(event.getX()) == FRAGMENT_ICON) {
String iconToolTip = getIconToolTipText();
if (iconToolTip != null) {
return StringUtil.capitalize(iconToolTip);
}

View File

@@ -29,3 +29,5 @@ coroutine.view.fetching.not_found=No coroutine information found
optimised.variable.message={0} was optimised out
to.enable.information.breakpoint.suspend.policy.should.be.set.to.all.threads=To enable information breakpoint suspend policy should be set to 'All' threads.
dump.item.coroutine.tooltip=Coroutine

View File

@@ -15,6 +15,7 @@ import com.intellij.unscramble.IconsCache
import com.intellij.unscramble.MergeableDumpItem
import com.intellij.unscramble.MergeableToken
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.data.State
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy
@@ -52,6 +53,9 @@ private class CoroutineDumpItem(info: CoroutineInfoData) : MergeableDumpItem {
override val stateDesc: String = " (${info.state.name.lowercase()})"
override val iconToolTip: String
get() = KotlinDebuggerCoroutinesBundle.message("dump.item.coroutine.tooltip")
private val dispatcher = info.dispatcher
override val stackTrace: String =