[debugger] IDEA-363334 Add watch pause/resume actions in context menu

GitOrigin-RevId: 7098d56696ca46262057bc0ed50b2e74252e25f7
This commit is contained in:
Maksim Zuev
2024-12-20 19:00:22 +00:00
committed by intellij-monorepo-bot
parent 0c86aab5ba
commit ea4944f46a
7 changed files with 116 additions and 2 deletions
@@ -1350,6 +1350,8 @@ action.XDebugger.EditWatch.text=Edit\u2026
action.XDebugger.CopyWatch.text=Duplicate Watch
action.XDebugger.MoveWatchUp.text=Move Watch Up
action.XDebugger.MoveWatchDown.text=Move Watch Down
action.XDebugger.PauseWatch.text=Pause Watch
action.XDebugger.ResumeWatch.text=Resume Watch
action.XDebugger.SeparateWatches.text=Separate Watches
action.XDebugger.ToggleEvaluateExpressionField.text=Show Evaluate Expression Field
action.XDebugger.PreviewTab.text=Open Files in Preview Tab
@@ -26,6 +26,7 @@
<action id="XDebugger.MoveWatchDown" class="com.intellij.xdebugger.impl.frame.actions.XMoveWatchDown" use-shortcut-of="MoveLineDown"/>
<action id="XDebugger.SeparateWatches" class="com.intellij.xdebugger.impl.frame.actions.XSeparateWatchesAndVariables"
icon="AllIcons.Debugger.Watch"/>
<action id="XDebugger.PauseWatch" class="com.intellij.xdebugger.impl.frame.actions.XPauseWatchAction"/>
<action id="XDebugger.ToggleEvaluateExpressionField" class="com.intellij.xdebugger.impl.frame.actions.XToggleEvaluateExpressionFieldAction"/>
<action id="XDebugger.RemoveAllWatches" class="com.intellij.xdebugger.impl.frame.actions.XRemoveAllWatchesAction"/>
<action id="XDebugger.MuteBreakpoints" class="com.intellij.xdebugger.impl.actions.MuteBreakpointAction"
@@ -152,6 +153,7 @@
<reference ref="XDebugger.RemoveWatch"/>
<reference ref="XDebugger.RemoveAllWatches"/>
<reference ref="XDebugger.EditWatch"/>
<reference ref="XDebugger.PauseWatch"/>
<separator/>
<reference ref="XDebugger.ValueGroup"/>
</group>
@@ -296,6 +296,8 @@ memory.view.instances.warning.not.all.loaded=Not all instances will be loaded (o
debugger.inline.watches.group.name=Inline Watches
debugger.inline.watches.edit.watch.expression.text=Edit Watch Expression
debugger.inline.watches.popup.action.add.as.inline.watch=Add as Inline Watch
xdebugger.evaluate.paused.watch=\u2026 Evaluate
xdebugger.watch.is.paused.message=The watch is paused
debug.toolwindow.empty.text.0=To debug your code, do one of the following:
debug.toolwindow.empty.text.1=\u2013 Click the Run icon in the editor gutter
@@ -1583,6 +1583,7 @@ c:com.intellij.xdebugger.impl.ui.tree.nodes.WatchNodeImpl
- getEvaluationOrigin():com.intellij.xdebugger.impl.ui.tree.nodes.XEvaluationOrigin
- getExpression():com.intellij.xdebugger.XExpression
- getValueContainer():com.intellij.xdebugger.frame.XValue
- getXWatch():com.intellij.xdebugger.impl.XWatch
- ps:renderName(com.intellij.xdebugger.XExpression):java.lang.String
- p:shouldUpdateInlineDebuggerData():Z
c:com.intellij.xdebugger.impl.ui.tree.nodes.WatchesRootNode
@@ -0,0 +1,52 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.xdebugger.impl.frame.actions
import com.intellij.icons.AllIcons
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.xdebugger.frame.XValuePlace
import com.intellij.xdebugger.impl.frame.XWatchesView
import com.intellij.xdebugger.impl.frame.actions.XWatchesTreeActionBase.getSelectedNodes
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
import com.intellij.xdebugger.impl.ui.tree.nodes.WatchNodeImpl
private class XPauseWatchAction : XWatchesTreeActionBase() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
override fun isEnabled(e: AnActionEvent, tree: XDebuggerTree): Boolean {
val selectedNode = getSelectedWatchNode(tree) ?: return false
return selectedNode.xWatch.canBePaused
}
override fun update(e: AnActionEvent) {
super.update(e)
e.presentation.isVisible = e.presentation.isEnabled
if (e.presentation.isEnabled) {
val tree = XDebuggerTree.getTree(e) ?: return
val node = getSelectedWatchNode(tree) ?: return
val paused = node.xWatch.isPaused
e.presentation.text =
if (paused) ActionsBundle.message("action.XDebugger.ResumeWatch.text")
else ActionsBundle.message("action.XDebugger.PauseWatch.text")
e.presentation.icon = if (paused) AllIcons.Toolwindows.ToolWindowRun else AllIcons.Actions.Pause
}
}
override fun perform(e: AnActionEvent, tree: XDebuggerTree, watchesView: XWatchesView) {
val node = getSelectedWatchNode(tree) ?: return
val paused = node.xWatch.isPaused
node.xWatch.isPaused = !paused
if (!paused) {
// only update icon, keep calculated value
node.setPresentation(AllIcons.Actions.Pause, node.valuePresentation!!, !node.isLeaf)
}
else {
// resume watch, trigger value evaluation
node.valueContainer.computePresentation(node, XValuePlace.TREE)
}
}
}
private fun getSelectedWatchNode(tree: XDebuggerTree): WatchNodeImpl? =
getSelectedNodes(tree, WatchNodeImpl::class.java).singleOrNull()
@@ -5,6 +5,7 @@ import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.util.ThreeState;
import com.intellij.xdebugger.Obsolescent;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.evaluation.XInstanceEvaluator;
@@ -15,11 +16,14 @@ import com.intellij.xdebugger.impl.XAlwaysEvaluatedWatch;
import com.intellij.xdebugger.impl.XWatch;
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.Promises;
import java.util.concurrent.atomic.AtomicBoolean;
public class WatchNodeImpl extends XValueNodeImpl implements WatchNode {
private final XWatch myWatch;
@@ -46,7 +50,7 @@ public class WatchNodeImpl extends XValueNodeImpl implements WatchNode {
this(tree, parent, new XAlwaysEvaluatedWatch(expression), null, null, value);
}
XWatch getWatch() {
public XWatch getXWatch() {
return myWatch;
}
@@ -99,6 +103,7 @@ public class WatchNodeImpl extends XValueNodeImpl implements WatchNode {
private final XDebuggerTree myTree;
private final XStackFrame myStackFrame;
private volatile XValue myValue;
private final AtomicBoolean myEvaluateRequested = new AtomicBoolean(false);
XWatchValue(XWatch watch, XDebuggerTree tree, XStackFrame stackFrame) {
super(watch.getExpression().getExpression());
@@ -116,6 +121,11 @@ public class WatchNodeImpl extends XValueNodeImpl implements WatchNode {
@Override
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
clearPausedLink(node);
if (myWatch.isPaused() && !myEvaluateRequested.getAndSet(false)) {
showPausedPresentation(node, place);
return;
}
if (myStackFrame != null) {
if (myTree.isShowing() || ApplicationManager.getApplication().isUnitTestMode()) {
XDebuggerEvaluator evaluator = myStackFrame.getEvaluator();
@@ -132,6 +142,18 @@ public class WatchNodeImpl extends XValueNodeImpl implements WatchNode {
node.setPresentation(AllIcons.Debugger.Db_watch, EMPTY_PRESENTATION, false);
}
private void clearPausedLink(@NotNull XValueNode node) {
if (node instanceof XValueNodeImpl xValueNode
&& xValueNode.getFullValueEvaluator() instanceof EvaluateLink) {
xValueNode.clearFullValueEvaluator();
}
}
private void showPausedPresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
node.setPresentation(AllIcons.Actions.Pause, PAUSED_PRESENTATION, false);
node.setFullValueEvaluator(new EvaluateLink(node, place));
}
private class MyEvaluationCallback extends XEvaluationCallbackBase implements XEvaluationCallbackWithOrigin, Obsolescent {
@NotNull private final XValueNode myNode;
@NotNull private final XValuePlace myPlace;
@@ -240,5 +262,38 @@ public class WatchNodeImpl extends XValueNodeImpl implements WatchNode {
public XReferrersProvider getReferrersProvider() {
return myValue != null ? myValue.getReferrersProvider() : null;
}
private class EvaluateLink extends XFullValueEvaluator {
private final @NotNull XValueNode myNode;
private final @NotNull XValuePlace myPlace;
private EvaluateLink(@NotNull XValueNode node, @NotNull XValuePlace place) {
super(XDebuggerBundle.message("xdebugger.evaluate.paused.watch"));
myNode = node;
myPlace = place;
setShowValuePopup(false);
}
@Override
public void startEvaluation(@NotNull XFullValueEvaluationCallback callback) {
callback.evaluated("");
myEvaluateRequested.set(true);
computePresentation(myNode, myPlace);
}
}
}
@ApiStatus.Internal
public static final XValuePresentation PAUSED_PRESENTATION = new XValuePresentation() {
@NotNull
@Override
public String getSeparator() {
return " ";
}
@Override
public void renderValue(@NotNull XValueTextRenderer renderer) {
renderer.renderComment(XDebuggerBundle.message("xdebugger.watch.is.paused.message"));
}
};
}
@@ -165,7 +165,7 @@ public class WatchesRootNode extends XValueContainerNode<XValueContainer> {
return StreamEx.of(getWatchChildren())
.select(WatchNodeImpl.class)
.filter(node -> !(node instanceof ResultNode))
.map(WatchNodeImpl::getWatch)
.map(WatchNodeImpl::getXWatch)
.toList();
}