diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java index f6d1dac07622..90e80859551c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java @@ -265,17 +265,39 @@ public class JavaExecutionStack extends XExecutionStack { return myAdded <= StackFrameProxyImpl.FRAMES_BATCH_MAX ? Priority.NORMAL : Priority.LOW; } + private void flushHiddenFrames() { + if (!XFramesView.shouldFoldHiddenFrames()) return; + + if (myHiddenCount > 0) { + assert myFirstHiddenFrame != null; + var placeholder = new XFramesView.HiddenStackFramesItem(myHiddenCount, myFirstHiddenFrame); + myContainer.addStackFrames(Collections.singletonList(placeholder), false); + myHiddenCount = 0; + myFirstHiddenFrame = null; + } + } + + private void rememberHiddenFrame(XStackFrame frame) { + if (!XFramesView.shouldFoldHiddenFrames()) return; + + if (myHiddenCount == 0) { + myFirstHiddenFrame = frame; + } + myHiddenCount++; + } + + private void addStackFrames(List frames, boolean last) { + flushHiddenFrames(); + myContainer.addStackFrames(frames, last); + } + private boolean addFrameIfNeeded(XStackFrame frame, boolean last) { if (++myAdded > mySkip) { - if (myHiddenCount > 0) { - assert myFirstHiddenFrame != null; - myContainer.addStackFrames(XFramesView.createHiddenFramePlaceholder(myHiddenCount, myFirstHiddenFrame), false); - } - myContainer.addStackFrames(Collections.singletonList(frame), last); + addStackFrames(Collections.singletonList(frame), last); return true; } if (last) { - myContainer.addStackFrames(Collections.emptyList(), true); + addStackFrames(Collections.emptyList(), true); } return false; } @@ -307,18 +329,13 @@ public class JavaExecutionStack extends XExecutionStack { if (frame instanceof JavaStackFrame) { ((JavaStackFrame)frame).getDescriptor().updateRepresentationNoNotify(null, () -> { // repaint on icon change - myContainer.addStackFrames(Collections.emptyList(), !myStackFramesIterator.hasNext()); + addStackFrames(Collections.emptyList(), !myStackFramesIterator.hasNext()); }); } addFrameIfNeeded(frame, false); - myHiddenCount = 0; - myFirstHiddenFrame = null; } else { - if (myHiddenCount == 0) { - myFirstHiddenFrame = frame; - } - myHiddenCount++; + rememberHiddenFrame(frame); } } @@ -350,7 +367,7 @@ public class JavaExecutionStack extends XExecutionStack { appendRelatedStack(suspendContext, myAsyncStack.subList(myAddedAsync, myAsyncStack.size())); } else { - myContainer.addStackFrames(Collections.emptyList(), true); + addStackFrames(Collections.emptyList(), true); } } @@ -383,18 +400,29 @@ public class JavaExecutionStack extends XExecutionStack { XStackFrame newFrame = stackFrame.createFrame(myDebugProcess); if (newFrame != null) { if (showFrame(newFrame)) { - StackFrameItem.setWithSeparator(newFrame, mySeparator); + if (mySeparator) { + flushHiddenFrames(); + StackFrameItem.setWithSeparator(newFrame); + } if (addFrameIfNeeded(newFrame, false)) { + // No need to propagate the separator further, because it was added. mySeparator = false; } - myHiddenCount = 0; - myFirstHiddenFrame = null; + } + else if (XFramesView.shouldFoldHiddenFrames()) { + if (mySeparator) { + flushHiddenFrames(); + // The separator for this hidden frame will be used as the placeholder separator. + StackFrameItem.setWithSeparator(newFrame); + mySeparator = false; + } + rememberHiddenFrame(newFrame); } else { - if (myHiddenCount == 0) { - myFirstHiddenFrame = newFrame; + if (!mySeparator && StackFrameItem.hasSeparatorAbove(newFrame)) { + // Frame has a separator, but it wasn't added; we need to propagate the separator further. + mySeparator = true; } - myHiddenCount++; } } schedule(suspendContext, null, myAsyncStack, mySeparator); diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFrameList.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFrameList.java index 5fb50b1a2276..b23ea7ea264c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFrameList.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFrameList.java @@ -57,7 +57,9 @@ class StackFrameList extends XDebuggerFramesList { } else { XStackFrame frame = frameInfo.createFrame(myDebugProcess); - StackFrameItem.setWithSeparator(frame, separator); + if (separator) { + StackFrameItem.setWithSeparator(frame); + } DebuggerUIUtil.invokeLater(() -> getModel().add(frame)); separator = false; } diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java index 0a9966420a40..5eb42ce8c1ea 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java @@ -228,9 +228,14 @@ public class StackFrameItem { return new CapturedStackFrame(debugProcess, this); } - public static void setWithSeparator(XStackFrame frame, boolean withSeparator) { - if (frame instanceof CapturedStackFrame) { - ((CapturedStackFrame)frame).setWithSeparator(withSeparator); + public static boolean hasSeparatorAbove(XStackFrame frame) { + return frame instanceof XDebuggerFramesList.ItemWithSeparatorAbove frameWithSeparator && + frameWithSeparator.hasSeparatorAbove(); + } + + public static void setWithSeparator(XStackFrame frame) { + if (frame instanceof XDebuggerFramesList.ItemWithSeparatorAbove frameWithSeparator) { + frameWithSeparator.setWithSeparator(true); } } @@ -328,6 +333,7 @@ public class StackFrameItem { return myWithSeparator; } + @Override public void setWithSeparator(boolean withSeparator) { myWithSeparator = withSeparator; } diff --git a/platform/xdebugger-api/resources/messages/XDebuggerBundle.properties b/platform/xdebugger-api/resources/messages/XDebuggerBundle.properties index edb5a0c59d66..369d3ec2fd3e 100644 --- a/platform/xdebugger-api/resources/messages/XDebuggerBundle.properties +++ b/platform/xdebugger-api/resources/messages/XDebuggerBundle.properties @@ -230,8 +230,9 @@ memory.instances.close.text=Close threads.list.threads.not.available=Threads are not available data.views.configurable.panel.title=Editor hide.library.frames.tooltip=Hide Frames from Libraries +fold.library.frames.tooltip=Fold Frames from Libraries show.all.frames.tooltip=Show All Frames -label.hidden.frames={0,number} hidden {0,choice, 1#frame|2#frames} +label.folded.frames={0,number} hidden {0,choice, 1#frame|2#frames} options.kotlin.attribute.descriptor.inline.stack.frames=Inline stack frames options.java.attribute.descriptor.breakpoint.line=Breakpoint line options.java.attribute.descriptor.execution.point=Execution point diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/ShowLibraryFramesAction.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/ShowLibraryFramesAction.java index 06144701b18d..3a0a495a0141 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/ShowLibraryFramesAction.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/ShowLibraryFramesAction.java @@ -6,6 +6,7 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerBundle; import com.intellij.xdebugger.impl.XDebuggerUtilImpl; +import com.intellij.xdebugger.impl.frame.XFramesView; import com.intellij.xdebugger.impl.settings.XDebuggerSettingManagerImpl; import org.jetbrains.annotations.NotNull; @@ -43,7 +44,11 @@ final class ShowLibraryFramesAction extends ToggleAction { if (Boolean.TRUE.equals(isSupported)) { presentation.setVisible(true); final boolean shouldShow = !Toggleable.isSelected(presentation); - presentation.setText(XDebuggerBundle.message(shouldShow ? "hide.library.frames.tooltip" : "show.all.frames.tooltip")); + presentation.setText(XDebuggerBundle.message(shouldShow + ? (XFramesView.shouldFoldHiddenFrames() + ? "fold.library.frames.tooltip" + : "hide.library.frames.tooltip") + : "show.all.frames.tooltip")); } else { presentation.setVisible(false); diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XDebuggerFramesList.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XDebuggerFramesList.java index 85138d719416..b6899ee05348 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XDebuggerFramesList.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XDebuggerFramesList.java @@ -426,6 +426,9 @@ public class XDebuggerFramesList extends DebuggerFramesList implements DataProvi public interface ItemWithSeparatorAbove { boolean hasSeparatorAbove(); @NlsContexts.Separator String getCaptionAboveOf(); + + default void setWithSeparator(boolean withSeparator) { + } } public interface ItemWithCustomBackgroundColor { diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java index 2f28cb04f1ae..4604f30ab1e8 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java @@ -725,20 +725,25 @@ public final class XFramesView extends XDebugView { } } - private static class HiddenStackFramesItem extends XStackFrame implements XDebuggerFramesList.ItemWithCustomBackgroundColor, - XDebuggerFramesList.ItemWithSeparatorAbove { + /** + * Synthetic frame which encapsulates hidden library frames as a single fold. + * + * @see #shouldFoldHiddenFrames() + */ + public static class HiddenStackFramesItem extends XStackFrame implements XDebuggerFramesList.ItemWithCustomBackgroundColor, + XDebuggerFramesList.ItemWithSeparatorAbove { private final int hiddenFrameCount; private final @NotNull XStackFrame firstHiddenFrame; - private HiddenStackFramesItem(int hiddenFrameCount, @NotNull XStackFrame firstHiddenFrame) { + public HiddenStackFramesItem(int hiddenFrameCount, @NotNull XStackFrame firstHiddenFrame) { this.hiddenFrameCount = hiddenFrameCount; this.firstHiddenFrame = firstHiddenFrame; } @Override public void customizePresentation(@NotNull ColoredTextContainer component) { - component.append(XDebuggerBundle.message("label.hidden.frames", hiddenFrameCount), SimpleTextAttributes.GRAYED_ATTRIBUTES); + component.append(XDebuggerBundle.message("label.folded.frames", hiddenFrameCount), SimpleTextAttributes.GRAYED_ATTRIBUTES); component.setIcon(EmptyIcon.ICON_16); } @@ -764,10 +769,12 @@ public final class XFramesView extends XDebugView { } } - public static List createHiddenFramePlaceholder(int hiddenFrameCount, @NotNull XStackFrame firstHiddenFrame) { - return Registry.is("debugger.library.frames.fold.instead.of.hide") - ? Collections.singletonList(new HiddenStackFramesItem(hiddenFrameCount, firstHiddenFrame)) - : Collections.emptyList(); + /** + * Whether hidden frames are folded into a single-item placeholder. + * Otherwise, they completely disappear. + */ + public static boolean shouldFoldHiddenFrames() { + return Registry.is("debugger.library.frames.fold.instead.of.hide"); } diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrameItems.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrameItems.kt index c051f3f021ba..36ef65a4c43c 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrameItems.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrameItems.kt @@ -29,7 +29,7 @@ class CreationCoroutineStackFrameItem( return debugProcess.invokeInManagerThread { val frame = debugProcess.findFirstFrame() ?: return@invokeInManagerThread null val position = location.findPosition(debugProcess) - CreationCoroutineStackFrame(frame, position, first, location) + CreationCoroutineStackFrame(frame, position, withSepartor = first, location) } } } diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrames.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrames.kt index 674664864f98..311d7b1633c5 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrames.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrames.kt @@ -44,7 +44,7 @@ class CoroutinePreflightFrame( class CreationCoroutineStackFrame( frame: StackFrameProxyImpl, sourcePosition: XSourcePosition?, - val first: Boolean, + private var withSepartor: Boolean, location: Location? = frame.safeLocation() ) : CoroutineStackFrame(frame, sourcePosition, emptyList(), false, location), XDebuggerFramesList.ItemWithSeparatorAbove { @@ -52,7 +52,11 @@ class CreationCoroutineStackFrame( KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.trace") override fun hasSeparatorAbove() = - first + withSepartor + + override fun setWithSeparator(withSeparator: Boolean) { + this.withSepartor = withSeparator + } } open class CoroutineStackFrame( diff --git a/plugins/kotlin/jvm-debugger/test/testData/continuation/suspendFunStackTraceHidden.out b/plugins/kotlin/jvm-debugger/test/testData/continuation/suspendFunStackTraceHidden.out index 25bcf2bd63ca..2b10c6ca873b 100644 --- a/plugins/kotlin/jvm-debugger/test/testData/continuation/suspendFunStackTraceHidden.out +++ b/plugins/kotlin/jvm-debugger/test/testData/continuation/suspendFunStackTraceHidden.out @@ -8,6 +8,7 @@ Thread stack trace: CoroutineInfo: 1 coroutine RUNNING CoroutineStackFrame main:!LINE_NUMBER!, SuspendFunStackTraceHiddenKt (continuation) () +Coroutine Creation Stack Trace CreationCoroutineStackFrame main:!LINE_NUMBER!, SuspendFunStackTraceHiddenKt (continuation) ($result, this) CreationCoroutineStackFrame FRAME:main:-1, SuspendFunStackTraceHiddenKt (continuation)