From cac4a991f53c194f5972a45d3d07d88d03164faf Mon Sep 17 00:00:00 2001 From: Vladimir Ilmov Date: Wed, 8 Jan 2020 15:48:08 +0100 Subject: [PATCH] coroutine debugger toolbar added --- .../CoroutineAsyncStackTraceProvider.kt | 4 +- .../VersionedImplementationProvider.kt | 21 +++ .../VersionedImplementationProvider.kt.as35 | 21 +++ .../coroutine/command/CoroutineBuilder.kt | 16 +- .../coroutine/data/CoroutineInfoData.kt | 2 +- .../debugger/coroutine/data/descriptors.kt | 2 +- .../intellij/ui/SimpleListCellRenderer.java | 116 +++++++++++++ .../coroutine/proxy/AsyncStackTraceContext.kt | 12 +- .../coroutine/proxy/LookupContinuation.kt | 15 +- .../idea/debugger/coroutine/util/Utils.java | 6 +- .../coroutine/view/CoroutineDumpPanel.kt | 4 +- .../coroutine/view/SimpleColoredTextIcon.kt | 33 ++-- .../debugger/coroutine/view/XCoroutineView.kt | 156 +++++++++++------- .../test/AbstractAsyncStackTraceTest.kt | 15 +- idea/resources/colorScheme/Darcula_Kotlin.xml | 5 + idea/resources/colorScheme/Default_Kotlin.xml | 5 + 16 files changed, 342 insertions(+), 91 deletions(-) create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.as35 create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/override/com/intellij/ui/SimpleListCellRenderer.java diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt index c89a492f85c..107f2e40f47 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt @@ -19,12 +19,12 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider { override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List? { val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList() - return null + return stackFrameList } fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List? { val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList() - return null + return stackFrameList } fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List { diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt new file mode 100644 index 00000000000..2def0e034ed --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt @@ -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? = + SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int -> + if (value != null) { + label.text = value + } else if (index >= 0) { + label.text = "Loading..." + } + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.as35 b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.as35 new file mode 100644 index 00000000000..1cf2405cfbf --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.as35 @@ -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? = + SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int -> + if (value != null) { + label.text = value + } else if (index >= 0) { + label.text = "Loading..." + } + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt index 56224f9651f..215afef3e60 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt @@ -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( diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt index aefbd057d76..3689e201a86 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt @@ -19,7 +19,7 @@ data class CoroutineInfoData( val stackTrace: List, // links to jdi.* references val activeThread: ThreadReference? = null, // for suspended coroutines should be null - val frame: ObjectReference? + val lastObservedFrameFieldRef: ObjectReference? ) { var stackFrameList = mutableListOf() diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/descriptors.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/descriptors.kt index 0fe94ed7268..db70570ea83 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/descriptors.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/descriptors.kt @@ -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) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/override/com/intellij/ui/SimpleListCellRenderer.java b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/override/com/intellij/ui/SimpleListCellRenderer.java new file mode 100644 index 00000000000..eb77b2a0118 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/override/com/intellij/ui/SimpleListCellRenderer.java @@ -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 extends JBLabel implements ListCellRenderer { + + @NotNull + public static SimpleListCellRenderer create(@NotNull String nullValue, @NotNull Function getText) { + return new SimpleListCellRenderer() { + @Override + public void customize(JList list, T value, int index, boolean selected, boolean hasFocus) { + setText(value == null ? nullValue : getText.fun(value)); + } + }; + } + + @NotNull + public static SimpleListCellRenderer create(@NotNull Customizer customizer) { + return new SimpleListCellRenderer() { + @Override + public void customize(JList list, T value, int index, boolean selected, boolean hasFocus) { + customizer.customize(this, value, index); + } + }; + } + + @Override + public Component getListCellRendererComponent(JList 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 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 { + 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 +} diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt index 394fcca417e..7e4466176ac 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt @@ -36,7 +36,11 @@ class AsyncStackTraceContext( fun getAsyncStackTraceIfAny() : List { val continuation = locateContinuation() ?: return emptyList() val frames = mutableListOf() - 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) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LookupContinuation.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LookupContinuation.kt index afe023bafdd..683f71880bc 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LookupContinuation.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LookupContinuation.kt @@ -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) + } } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Utils.java b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Utils.java index 5c484c9a2c5..5d5272226a3 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Utils.java +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Utils.java @@ -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(); } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt index 389d795501c..eac6ecd2d10 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt @@ -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 { diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt index 6d4e8848de4..00a01d6c3a9 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt @@ -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) -> 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 diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt index 96aa444ccc6..7f44ba091d4 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt @@ -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() 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) : 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? { - 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) } } diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt index a84554a7244..0dcb668ed39 100644 --- a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt @@ -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): MutableList { + var clazz = cls + val declaredFields = mutableListOf() + while (clazz != Class.forName("java.lang.Object")) { + declaredFields.addAll(clazz.declaredFields) + clazz = clazz.superclass + } + return declaredFields + } } \ No newline at end of file diff --git a/idea/resources/colorScheme/Darcula_Kotlin.xml b/idea/resources/colorScheme/Darcula_Kotlin.xml index e909f5d570c..555519e21ee 100644 --- a/idea/resources/colorScheme/Darcula_Kotlin.xml +++ b/idea/resources/colorScheme/Darcula_Kotlin.xml @@ -86,4 +86,9 @@ + diff --git a/idea/resources/colorScheme/Default_Kotlin.xml b/idea/resources/colorScheme/Default_Kotlin.xml index 514a3b4b731..82791bcdc51 100644 --- a/idea/resources/colorScheme/Default_Kotlin.xml +++ b/idea/resources/colorScheme/Default_Kotlin.xml @@ -76,4 +76,9 @@ +