coroutine debugger toolbar added
This commit is contained in:
committed by
Vladimir Ilmov
parent
e570450c59
commit
cac4a991f5
+2
-2
@@ -19,12 +19,12 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<CoroutineAsyncStackFrameItem>? {
|
||||
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList()
|
||||
return null
|
||||
return stackFrameList
|
||||
}
|
||||
|
||||
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem>? {
|
||||
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList()
|
||||
return null
|
||||
return stackFrameList
|
||||
}
|
||||
|
||||
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem> {
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.coroutine
|
||||
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.SimpleListCellRenderer
|
||||
import javax.swing.ListCellRenderer
|
||||
|
||||
class VersionedImplementationProvider() {
|
||||
fun comboboxListCellRenderer(): ListCellRenderer<in String>? =
|
||||
SimpleListCellRenderer.create<String> { label: JBLabel, value: String?, index: Int ->
|
||||
if (value != null) {
|
||||
label.text = value
|
||||
} else if (index >= 0) {
|
||||
label.text = "Loading..."
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.coroutine
|
||||
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.override.com.intellij.ui.SimpleListCellRenderer
|
||||
import javax.swing.ListCellRenderer
|
||||
|
||||
class VersionedImplementationProvider() {
|
||||
fun comboboxListCellRenderer(): ListCellRenderer<in String>? =
|
||||
SimpleListCellRenderer.create<String> { label: JBLabel, value: String?, index: Int ->
|
||||
if (value != null) {
|
||||
label.text = value
|
||||
} else if (index >= 0) {
|
||||
label.text = "Loading..."
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -71,14 +71,15 @@ class CoroutineBuilder(val suspendContext: XSuspendContext) {
|
||||
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, xStackFrame))
|
||||
}
|
||||
}
|
||||
} else if (coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) {
|
||||
} else if ((coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference) {
|
||||
// to get frames from CoroutineInfo anyway
|
||||
// the thread is paused on breakpoint - it has at least one frame
|
||||
val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex + 1)
|
||||
for (suspendedFrame in suspendedStackTrace) {
|
||||
val suspendedXStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, suspendedFrame)
|
||||
|
||||
coroutineStackFrameList.add(
|
||||
SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame)
|
||||
SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame, coroutine.lastObservedFrameFieldRef)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -167,10 +168,6 @@ class CoroutineBuilder(val suspendContext: XSuspendContext) {
|
||||
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,
|
||||
@@ -206,6 +203,7 @@ class CreationCoroutineStackFrameItem(
|
||||
val location: Location
|
||||
) : CoroutineStackFrameItem(frame, stackFrame) {
|
||||
override fun location() = location
|
||||
|
||||
fun emptyDescriptor() =
|
||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||
}
|
||||
@@ -213,9 +211,13 @@ class CreationCoroutineStackFrameItem(
|
||||
class SuspendCoroutineStackFrameItem(
|
||||
frame: StackFrameProxyImpl,
|
||||
val stackTraceElement: StackTraceElement,
|
||||
stackFrame: XStackFrame
|
||||
stackFrame: XStackFrame,
|
||||
val lastObservedFrameFieldRef: ObjectReference
|
||||
) : CoroutineStackFrameItem(frame, stackFrame) {
|
||||
override fun location() = frame.location()
|
||||
|
||||
fun emptyDescriptor() =
|
||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||
}
|
||||
|
||||
class AsyncCoroutineStackFrameItem(
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ data class CoroutineInfoData(
|
||||
val stackTrace: List<StackTraceElement>,
|
||||
// links to jdi.* references
|
||||
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
||||
val frame: ObjectReference?
|
||||
val lastObservedFrameFieldRef: ObjectReference?
|
||||
) {
|
||||
var stackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class CoroutineStackTraceData(
|
||||
val lookupContinuation = LookupContinuation(context, frame)
|
||||
|
||||
// retrieve continuation only if suspend method
|
||||
val continuation = lookupContinuation.findContinuation(infoData)
|
||||
val continuation = lookupContinuation.findContinuation(infoData.lastObservedFrameFieldRef)
|
||||
|
||||
return if (continuation is ObjectReference)
|
||||
SuspendStackFrameDescriptor(infoData, frame, proxy, continuation)
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.coroutine.override.com.intellij.ui;
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import sun.swing.DefaultLookup;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.plaf.basic.BasicHTML;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* JBLabel-based (text and icon) list cell renderer.
|
||||
*
|
||||
* @see ColoredListCellRenderer for more complex SimpleColoredComponent-based variant.
|
||||
*
|
||||
* @author gregsh
|
||||
*/
|
||||
public abstract class SimpleListCellRenderer<T> extends JBLabel implements ListCellRenderer<T> {
|
||||
|
||||
@NotNull
|
||||
public static <T> SimpleListCellRenderer<T> create(@NotNull String nullValue, @NotNull Function<? super T, String> getText) {
|
||||
return new SimpleListCellRenderer<T>() {
|
||||
@Override
|
||||
public void customize(JList<? extends T> list, T value, int index, boolean selected, boolean hasFocus) {
|
||||
setText(value == null ? nullValue : getText.fun(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <T> SimpleListCellRenderer<T> create(@NotNull Customizer<? super T> customizer) {
|
||||
return new SimpleListCellRenderer<T>() {
|
||||
@Override
|
||||
public void customize(JList<? extends T> list, T value, int index, boolean selected, boolean hasFocus) {
|
||||
customizer.customize(this, value, index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends T> list, T value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
setComponentOrientation(list.getComponentOrientation());
|
||||
setBorder(JBUI.Borders.empty(1));
|
||||
Color bg, fg;
|
||||
JList.DropLocation dropLocation = list.getDropLocation();
|
||||
if (dropLocation != null && !dropLocation.isInsert() && dropLocation.getIndex() == index) {
|
||||
bg = DefaultLookup.getColor(this, ui, "List.dropCellBackground");
|
||||
fg = DefaultLookup.getColor(this, ui, "List.dropCellForeground");
|
||||
isSelected = true;
|
||||
}
|
||||
else {
|
||||
bg = isSelected ? list.getSelectionBackground() : list.getBackground();
|
||||
fg = isSelected ? list.getSelectionForeground() : list.getForeground();
|
||||
}
|
||||
setBackground(bg);
|
||||
setForeground(fg);
|
||||
setFont(list.getFont());
|
||||
setText("");
|
||||
setIcon(null);
|
||||
customize(list, value, index, isSelected, cellHasFocus);
|
||||
setOpaque(isSelected);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract void customize(JList<? extends T> list, T value, int index, boolean selected, boolean hasFocus);
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
if (StringUtil.isNotEmpty(getText())) {
|
||||
return super.getPreferredSize();
|
||||
}
|
||||
setText(" ");
|
||||
Dimension size = super.getPreferredSize();
|
||||
setText("");
|
||||
return size;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Customizer<T> {
|
||||
void customize(@NotNull JBLabel label, T value, int index);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
@Override public void validate() {}
|
||||
@Override public void invalidate() {}
|
||||
@Override public void repaint() {}
|
||||
@Override public void revalidate() {}
|
||||
@Override public void repaint(long tm, int x, int y, int width, int height) {}
|
||||
@Override public void repaint(Rectangle r) {}
|
||||
@Override public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {}
|
||||
@Override public void firePropertyChange(String propertyName, char oldValue, char newValue) {}
|
||||
@Override public void firePropertyChange(String propertyName, short oldValue, short newValue) {}
|
||||
@Override public void firePropertyChange(String propertyName, int oldValue, int newValue) {}
|
||||
@Override public void firePropertyChange(String propertyName, long oldValue, long newValue) {}
|
||||
@Override public void firePropertyChange(String propertyName, float oldValue, float newValue) {}
|
||||
@Override public void firePropertyChange(String propertyName, double oldValue, double newValue) {}
|
||||
@Override public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {}
|
||||
|
||||
@Override
|
||||
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
|
||||
if (propertyName == "text"
|
||||
|| ((propertyName == "font" || propertyName == "foreground")
|
||||
&& oldValue != newValue
|
||||
&& getClientProperty(BasicHTML.propertyKey) != null)) {
|
||||
super.firePropertyChange(propertyName, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
}
|
||||
+9
-3
@@ -36,7 +36,11 @@ class AsyncStackTraceContext(
|
||||
fun getAsyncStackTraceIfAny() : List<CoroutineAsyncStackFrameItem> {
|
||||
val continuation = locateContinuation() ?: return emptyList()
|
||||
val frames = mutableListOf<CoroutineAsyncStackFrameItem>()
|
||||
collectFramesRecursively(continuation, frames)
|
||||
try {
|
||||
collectFramesRecursively(continuation, frames)
|
||||
} catch (e: Exception) {
|
||||
log.error("Error while looking for variables.", e)
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
@@ -71,9 +75,9 @@ class AsyncStackTraceContext(
|
||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||
|
||||
val location = createLocation(continuation)
|
||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||
|
||||
location?.let {
|
||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||
consumer.add(CoroutineAsyncStackFrameItem(location, spilledVariables))
|
||||
}
|
||||
|
||||
@@ -86,7 +90,9 @@ class AsyncStackTraceContext(
|
||||
val instance = invokeGetStackTraceElement(continuation) ?: return null
|
||||
val className = context.invokeMethodAsString(instance, "getClassName") ?: return null
|
||||
val methodName = context.invokeMethodAsString(instance, "getMethodName") ?: return null
|
||||
val lineNumber = context.invokeMethodAsInt(instance,"getLineNumber") ?: return null
|
||||
val lineNumber = context.invokeMethodAsInt(instance,"getLineNumber")?.takeIf {
|
||||
it >= 0
|
||||
} ?: return null
|
||||
val locationClass = context.findClassSafe(className) ?: return null
|
||||
log.warn("Got location of ${className}.${methodName}:${lineNumber} in ${locationClass}")
|
||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||
|
||||
+13
-2
@@ -35,11 +35,11 @@ class LookupContinuation(val context: ExecutionContext, val frame: StackTraceEle
|
||||
* Gets current CoroutineInfo.lastObservedFrame and finds next frames in it until null or needed stackTraceElement is found
|
||||
* @return null if matching continuation is not found or is not BaseContinuationImpl
|
||||
*/
|
||||
fun findContinuation(infoData: CoroutineInfoData): ObjectReference? {
|
||||
fun findContinuation(initialContinuation: ObjectReference?): ObjectReference? {
|
||||
if (!isApplicable())
|
||||
return null
|
||||
|
||||
var continuation = infoData.frame ?: return null
|
||||
var continuation = initialContinuation ?: return null
|
||||
val baseType = "kotlin.coroutines.jvm.internal.BaseContinuationImpl"
|
||||
val getTrace = (continuation.type() as ClassType).concreteMethodByName(
|
||||
"getStackTraceElement",
|
||||
@@ -81,4 +81,15 @@ class LookupContinuation(val context: ExecutionContext, val frame: StackTraceEle
|
||||
val next = type.concreteMethodByName("getCompletion", "()Lkotlin/coroutines/Continuation;")
|
||||
return context.invokeMethod(continuation, next, emptyList()) as? ObjectReference
|
||||
}
|
||||
|
||||
fun findGetStackTraceElementMethodRef(continuation: ObjectReference): Method =
|
||||
(continuation.type() as ClassType).concreteMethodByName("getStackTraceElement", "()Ljava/lang/StackTraceElement;")
|
||||
|
||||
fun createAsyncStackTraceContext(stackTraceElementMethodRef: Method) =
|
||||
AsyncStackTraceContext(context, stackTraceElementMethodRef)
|
||||
|
||||
fun createAsyncStackTraceContext(continuation: ObjectReference): AsyncStackTraceContext? {
|
||||
val getStackTraceElementMethodRef = findGetStackTraceElementMethodRef(continuation)
|
||||
return createAsyncStackTraceContext(getStackTraceElementMethodRef)
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -13,7 +13,7 @@ public class Utils {
|
||||
Object instance,
|
||||
String methodName,
|
||||
Object... args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class[] classes = new Class[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if(args[i] instanceof ReflectionSpecificType)
|
||||
@@ -23,10 +23,10 @@ public class Utils {
|
||||
}
|
||||
Method method = instance.getClass().getDeclaredMethod(methodName, classes);
|
||||
method.setAccessible(true);
|
||||
Object r = method.invoke(instance, args);
|
||||
return r;
|
||||
return method.invoke(instance, args);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public interface ReflectionSpecificType {
|
||||
Class specificClass();
|
||||
}
|
||||
|
||||
+2
-2
@@ -235,8 +235,8 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
}
|
||||
|
||||
private inner class MergeStackTracesAction : ToggleAction(
|
||||
"Merge Identical Stacktraces",
|
||||
"Group coroutines with identical stacktraces",
|
||||
"Merge Identical Stacktrace",
|
||||
"Group coroutines with identical stacktrace",
|
||||
AllIcons.Actions.Collapseall
|
||||
), DumbAware {
|
||||
|
||||
|
||||
+23
-10
@@ -8,18 +8,25 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.view
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.settings.ThreadsViewSettings
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.highlighter.JavaHighlightingColors
|
||||
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
|
||||
import com.intellij.openapi.editor.HighlighterColors
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||
import com.intellij.openapi.editor.colors.EditorColors
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
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.intellij.xdebugger.ui.DebuggerColors
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import java.awt.Color
|
||||
import javax.swing.Icon
|
||||
|
||||
class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
|
||||
@@ -45,15 +52,19 @@ class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
|
||||
for (i in 0 until size) {
|
||||
val text: String = texts.get(i)
|
||||
val attribute: TextAttributesKey = textKeyAttributes.get(i)
|
||||
val simpleTextAttrinbute = toSimpleTextAttribute(attribute)
|
||||
|
||||
component.append(text, when(attribute) {
|
||||
CoroutineDebuggerColors.REGULAR_ATTRIBUTES -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
CoroutineDebuggerColors.VALUE_ATTRIBUTES -> XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES
|
||||
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
})
|
||||
component.append(text, simpleTextAttrinbute)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toSimpleTextAttribute(attribute: TextAttributesKey) =
|
||||
when (attribute) {
|
||||
CoroutineDebuggerColors.REGULAR_ATTRIBUTES -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
CoroutineDebuggerColors.VALUE_ATTRIBUTES -> XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES
|
||||
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
}
|
||||
|
||||
fun forEachTextBlock(f: (Pair<String, TextAttributesKey>) -> Unit) {
|
||||
for (pair in texts zip textKeyAttributes)
|
||||
f(pair)
|
||||
@@ -83,14 +94,13 @@ class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
|
||||
|
||||
interface CoroutineDebuggerColors {
|
||||
companion object {
|
||||
val REGULAR_ATTRIBUTES =
|
||||
TextAttributesKey.createTextAttributesKey("REGULAR_ATTRIBUTES", HighlighterColors.NO_HIGHLIGHTING)
|
||||
val VALUE_ATTRIBUTES =
|
||||
TextAttributesKey.createTextAttributesKey("VALUE_ATTRIBUTES", CodeInsightColors.WARNINGS_ATTRIBUTES)
|
||||
val REGULAR_ATTRIBUTES = HighlighterColors.TEXT
|
||||
val VALUE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("KOTLIN_COROUTINE_DEBUGGER_VALUE", HighlighterColors.TEXT)
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleColoredTextIconPresentationRenderer {
|
||||
val log by logger
|
||||
private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance()
|
||||
|
||||
fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon {
|
||||
@@ -157,7 +167,10 @@ class SimpleColoredTextIconPresentationRenderer {
|
||||
}
|
||||
if (settings.SHOW_SOURCE_NAME) {
|
||||
label.append(", ")
|
||||
val sourceName = DebuggerUtilsEx.getSourceName(location) { e: Throwable? -> "Unknown Source" }
|
||||
val sourceName = DebuggerUtilsEx.getSourceName(location) { e: Throwable? ->
|
||||
log.error("Error while trying to resolve sourceName for location", e, location.toString())
|
||||
"Unknown Source"
|
||||
}
|
||||
label.append(sourceName)
|
||||
}
|
||||
return label
|
||||
|
||||
+97
-59
@@ -5,23 +5,35 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.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.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.ide.CommonActionsManager
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
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.psi.JavaPsiFacade
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.ui.CaptionPanel
|
||||
import com.intellij.ui.ComboboxSpeedSearch
|
||||
import com.intellij.ui.DoubleClickListener
|
||||
import com.intellij.ui.OnePixelSplitter
|
||||
import com.intellij.ui.border.CustomLineBorder
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
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.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
|
||||
@@ -29,31 +41,34 @@ 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.ClassType
|
||||
import javaslang.control.Either
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.command.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.SuspendStackFrameDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.SyntheticStackFrame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.AsyncStackTraceContext
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ManagerThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParams
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParamsProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.XDebugSessionListenerProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.JPanel
|
||||
|
||||
|
||||
class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
Disposable, XDebugSessionListenerProvider, CreateContentParamsProvider {
|
||||
val log by logger
|
||||
val splitter = OnePixelSplitter("SomeKey", 0.25f)
|
||||
val versionedImplementationProvider = VersionedImplementationProvider()
|
||||
|
||||
val mainPanel = JPanel(BorderLayout())
|
||||
val someCombobox = ComboBox<String>()
|
||||
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
|
||||
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
|
||||
val javaDebugProcess = session.debugProcess as JavaDebugProcess
|
||||
@@ -70,11 +85,36 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
}
|
||||
|
||||
init {
|
||||
splitter.firstComponent = panel.mainPanel
|
||||
someCombobox.setRenderer(versionedImplementationProvider.comboboxListCellRenderer())
|
||||
object : ComboboxSpeedSearch(someCombobox) {
|
||||
override fun getElementText(element: Any?): String? {
|
||||
return element.toString()
|
||||
}
|
||||
}
|
||||
someCombobox.addItem(null)
|
||||
val myToolbar = createToolbar()
|
||||
val myThreadsPanel = Wrapper()
|
||||
myThreadsPanel.setBorder(CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0))
|
||||
myThreadsPanel.add(myToolbar?.getComponent(), BorderLayout.EAST)
|
||||
myThreadsPanel.add(someCombobox, BorderLayout.CENTER)
|
||||
mainPanel.add(myThreadsPanel, BorderLayout.NORTH)
|
||||
mainPanel.add(panel.mainPanel, BorderLayout.CENTER)
|
||||
selectedNodeListener.installOn()
|
||||
}
|
||||
|
||||
|
||||
private fun createToolbar(): ActionToolbarImpl? {
|
||||
val framesGroup = DefaultActionGroup()
|
||||
val actionsManager = CommonActionsManager.getInstance()
|
||||
framesGroup
|
||||
.addAll(ActionManager.getInstance().getAction(XDebuggerActions.FRAMES_TOP_TOOLBAR_GROUP))
|
||||
val toolbar = ActionManager.getInstance().createActionToolbar(
|
||||
ActionPlaces.DEBUGGER_TOOLBAR, framesGroup, true
|
||||
) as ActionToolbarImpl
|
||||
toolbar.setReservePlaceAutoPopupIcon(false)
|
||||
return toolbar
|
||||
}
|
||||
|
||||
fun saveState() {
|
||||
DebuggerUIUtil.invokeLater {
|
||||
if (! (panel.tree.root is EmptyNode)) {
|
||||
@@ -113,7 +153,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
override fun createContentParams(): CreateContentParams =
|
||||
CreateContentParams(
|
||||
CoroutineDebuggerContentInfo.XCOROUTINE_THREADS_CONTENT,
|
||||
splitter,
|
||||
mainPanel,
|
||||
KotlinBundle.message("debugger.session.tab.xcoroutine.title"),
|
||||
null,
|
||||
panel.tree
|
||||
@@ -148,7 +188,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
}
|
||||
node.addChildren(children, true)
|
||||
} else {
|
||||
node.addChildren(XValueChildrenList.singleton(ErrorNode("Error occured while fetching information")), true)
|
||||
node.addChildren(XValueChildrenList.singleton(ErrorNode("Error occurs while fetching information")), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,24 +248,33 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
applyRenderer(node, presentation)
|
||||
}
|
||||
|
||||
data class KeyMouseEvent(val keyEvent: KeyEvent?, val mouseEvent: MouseEvent?) {
|
||||
constructor(keyEvent: KeyEvent) : this(keyEvent, null)
|
||||
constructor(mouseEvent: MouseEvent) : this(null, mouseEvent)
|
||||
|
||||
fun isKeyEvent() = keyEvent != null
|
||||
|
||||
fun isMouseEvent() = mouseEvent != null
|
||||
}
|
||||
|
||||
inner class XDebuggerTreeSelectedNodeListener(val tree: XDebuggerTree) {
|
||||
|
||||
fun installOn() {
|
||||
object : DoubleClickListener() {
|
||||
override fun onDoubleClick(e: MouseEvent) =
|
||||
nodeSelected(Either.left(e))
|
||||
nodeSelected(KeyMouseEvent(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))
|
||||
nodeSelected(KeyMouseEvent(e))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun nodeSelected(event: Either<MouseEvent, KeyEvent>) : Boolean {
|
||||
fun nodeSelected(event: KeyMouseEvent) : Boolean {
|
||||
val selectedNodes = tree.getSelectedNodes(XValueNodeImpl::class.java, null)
|
||||
if (selectedNodes.size == 1) {
|
||||
val node = selectedNodes[0]
|
||||
@@ -242,21 +291,17 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
is CreationCoroutineStackFrameItem -> {
|
||||
val position = getPosition(frame.stackTraceElement) ?: return false
|
||||
val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
|
||||
val stackFrame =
|
||||
createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) })
|
||||
createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) })
|
||||
}
|
||||
is SuspendCoroutineStackFrameItem -> {
|
||||
val position = getPosition(frame.stackTraceElement) ?: return false
|
||||
val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
|
||||
val executionContext = executionContext(threadSuspendContext, frame.frame)
|
||||
createStackAndSetFrame(threadProxy, { createSyntheticStackFrame(executionContext, frame) })
|
||||
}
|
||||
is AsyncCoroutineStackFrameItem -> {
|
||||
|
||||
}
|
||||
// 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))
|
||||
// }
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,21 +309,22 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
}
|
||||
}
|
||||
|
||||
fun createSyntheticStackFrame() {}
|
||||
|
||||
fun createStackAndSetFrame(threadReferenceProxy: ThreadReferenceProxyImpl, stackFrameProvider: () -> XStackFrame, isCurrentContext: Boolean = false) {
|
||||
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)
|
||||
if(stackFrame is XStackFrame) {
|
||||
val executionStack = createExecutionStack(threadReferenceProxy, isCurrentContext)
|
||||
applicationThreadExecutor.schedule(
|
||||
{
|
||||
session.setCurrentStackFrame(executionStack, stackFrame)
|
||||
}, panel.tree
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false) : XExecutionStack {
|
||||
private fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false) : XExecutionStack {
|
||||
val executionStack = CoroutineDebuggerExecutionStack(proxy, isCurrentContext)
|
||||
executionStack.initTopFrame()
|
||||
return executionStack
|
||||
@@ -300,30 +346,22 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
||||
}
|
||||
|
||||
private fun executionContext(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl) : ExecutionContext {
|
||||
val evaluationContextImpl = EvaluationContextImpl(suspendContext, frameProxy)
|
||||
return ExecutionContext(evaluationContextImpl, frameProxy)
|
||||
}
|
||||
|
||||
private fun createSyntheticStackFrame(
|
||||
descriptor: SuspendStackFrameDescriptor,
|
||||
pos: XSourcePosition
|
||||
): Pair<XExecutionStack, SyntheticStackFrame>? {
|
||||
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)
|
||||
executionContext: ExecutionContext,
|
||||
frame: SuspendCoroutineStackFrameItem
|
||||
): SyntheticStackFrame? {
|
||||
val position = getPosition(frame.stackTraceElement) ?: return null
|
||||
val lookupContinuation = LookupContinuation(executionContext, frame.stackTraceElement)
|
||||
val continuation = lookupContinuation.findContinuation(frame.lastObservedFrameFieldRef) ?: return null
|
||||
|
||||
val asyncStackTraceContext = lookupContinuation.createAsyncStackTraceContext(continuation)
|
||||
val vars = asyncStackTraceContext?.getSpilledVariables(continuation) ?: return null
|
||||
return SyntheticStackFrame(frame.emptyDescriptor(), vars, position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-1
@@ -10,12 +10,14 @@ import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import io.ktor.util.findAllSupertypes
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.getSafe
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithStepping() {
|
||||
@@ -81,9 +83,10 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
|
||||
appendln("Async stack trace:")
|
||||
for (item in trace) {
|
||||
append(MARGIN).appendln(item.toString())
|
||||
val declaredFields = listDeclaredFields(item.javaClass)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val variablesField = item.javaClass.declaredFields
|
||||
val variablesField = declaredFields
|
||||
.first { !Modifier.isStatic(it.modifiers) && it.type == List::class.java }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -100,4 +103,14 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun listDeclaredFields(cls: Class<in Any>): MutableList<Field> {
|
||||
var clazz = cls
|
||||
val declaredFields = mutableListOf<Field>()
|
||||
while (clazz != Class.forName("java.lang.Object")) {
|
||||
declaredFields.addAll(clazz.declaredFields)
|
||||
clazz = clazz.superclass
|
||||
}
|
||||
return declaredFields
|
||||
}
|
||||
}
|
||||
@@ -86,4 +86,9 @@
|
||||
<option name="FOREGROUND" value="20999d" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="KOTLIN_COROUTINE_DEBUGGER_VALUE">
|
||||
<value>
|
||||
<option name="FOREGROUND" value="FF8C8B" />
|
||||
</value>
|
||||
</option>
|
||||
</list>
|
||||
|
||||
@@ -76,4 +76,9 @@
|
||||
<option name="ERROR_STRIPE_COLOR" value="ff0000"/>
|
||||
</value>
|
||||
</option>
|
||||
<option name="KOTLIN_COROUTINE_DEBUGGER_VALUE">
|
||||
<value>
|
||||
<option name="FOREGROUND" value="840000" />
|
||||
</value>
|
||||
</option>
|
||||
</list>
|
||||
|
||||
Reference in New Issue
Block a user