coroutine debugger toolbar added

This commit is contained in:
Vladimir Ilmov
2020-01-08 15:48:08 +01:00
committed by Vladimir Ilmov
parent e570450c59
commit cac4a991f5
16 changed files with 342 additions and 91 deletions
@@ -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> {
@@ -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..."
}
}
}
@@ -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..."
}
}
}
@@ -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(
@@ -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>()
@@ -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)
@@ -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
}
@@ -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)
@@ -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)
}
}
@@ -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();
}
@@ -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 {
@@ -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
@@ -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)
}
}
@@ -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>