From 775bec79b827ed7942f66d32879aae4e142e1354 Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Wed, 25 Apr 2018 13:50:51 +0300 Subject: [PATCH] Add an ability to load values in Debugger/Console on demand (PY-29468) --- .../pydev/_pydevd_bundle/pydevd_constants.py | 16 ++- .../pydev/_pydevd_bundle/pydevd_xml.py | 8 +- .../python/debugger/PyDebugValue.java | 60 +++++++--- .../python/debugger/PyFrameAccessor.java | 8 ++ .../python/debugger/PyFullValueEvaluator.java | 10 +- .../debugger/PyOnDemandValueEvaluator.kt | 22 ++++ .../console/PydevConsoleCommunication.java | 18 ++- .../console/PydevConsoleRunnerImpl.java | 7 +- .../python/debugger/PyDebugProcess.java | 17 ++- .../python/debugger/PyDebugRunner.java | 4 +- .../python/debugger/PyStackFrame.java | 9 +- .../debugger/PyVariableViewSettings.java | 110 +++++++++++++++--- .../debugger/settings/PyDebuggerSettings.java | 11 +- 13 files changed, 237 insertions(+), 63 deletions(-) create mode 100644 python/pydevSrc/com/jetbrains/python/debugger/PyOnDemandValueEvaluator.kt diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py index ef53106bc4c0..349c6e00e840 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py @@ -104,11 +104,21 @@ USE_LIB_COPY = SUPPORT_GEVENT and \ (IS_PY3K and sys.version_info[1] >= 3)) +class ValuesPolicy: + SYNC = 0 + ASYNC = 1 + ON_DEMAND = 2 + + +LOAD_VALUES_POLICY = ValuesPolicy.SYNC +if os.getenv('PYDEVD_LOAD_VALUES_ASYNC', 'False') == 'True': + LOAD_VALUES_POLICY = ValuesPolicy.ASYNC +if os.getenv('PYDEVD_LOAD_VALUES_ON_DEMAND', 'False') == 'True': + LOAD_VALUES_POLICY = ValuesPolicy.ON_DEMAND +DEFAULT_VALUES_DICT = {ValuesPolicy.ASYNC: "__pydevd_value_async", ValuesPolicy.ON_DEMAND: "__pydevd_value_on_demand"} + INTERACTIVE_MODE_AVAILABLE = sys.platform in ('darwin', 'win32') or os.getenv('DISPLAY') is not None IS_PYCHARM = True - -LOAD_VALUES_ASYNC = os.getenv('PYDEVD_LOAD_VALUES_ASYNC', 'False') == 'True' -DEFAULT_VALUE = "__pydevd_value_async" ASYNC_EVAL_TIMEOUT_SEC = 60 NEXT_VALUE_SEPARATOR = "__pydev_val__" BUILTINS_MODULE_NAME = '__builtin__' if IS_PY2 else 'builtins' diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py index 37826520443a..4e6d2c5a7131 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py @@ -4,9 +4,7 @@ from _pydevd_bundle import pydevd_extension_utils from _pydevd_bundle import pydevd_resolver import sys from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, IS_PY3K, \ - BUILTINS_MODULE_NAME, MAXIMUM_VARIABLE_REPRESENTATION_SIZE, RETURN_VALUES_DICT, LOAD_VALUES_ASYNC, \ - DEFAULT_VALUE - + BUILTINS_MODULE_NAME, MAXIMUM_VARIABLE_REPRESENTATION_SIZE, RETURN_VALUES_DICT, LOAD_VALUES_POLICY, ValuesPolicy, DEFAULT_VALUES_DICT from _pydev_bundle.pydev_imports import quote from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider @@ -232,7 +230,7 @@ def is_builtin(x): def should_evaluate_full_value(val): - return not LOAD_VALUES_ASYNC or (is_builtin(type(val)) and not isinstance(val, (list, tuple, dict))) + return LOAD_VALUES_POLICY == ValuesPolicy.SYNC or (is_builtin(type(val)) and not isinstance(val, (list, tuple, dict))) def frame_vars_to_xml(frame_f_locals, hidden_ns=None): @@ -289,7 +287,7 @@ def var_to_xml(val, name, doTrim=True, additional_in_xml='', evaluate_full_value _type, typeName, resolver = get_type(v) type_qualifier = getattr(_type, "__module__", "") if not evaluate_full_value: - value = DEFAULT_VALUE + value = DEFAULT_VALUES_DICT[LOAD_VALUES_POLICY] else: try: str_from_provider = _str_from_providers(v, _type, typeName) diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java index 532254a237f1..fc3239979dd5 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java @@ -6,6 +6,7 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.xdebugger.frame.*; +import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation; import com.jetbrains.python.debugger.pydev.PyDebugCallback; import com.jetbrains.python.debugger.pydev.PyVariableLocator; import org.jetbrains.annotations.NotNull; @@ -29,7 +30,6 @@ public class PyDebugValue extends XNamedValue { public static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors(); public static final String RETURN_VALUES_PREFIX = "__pydevd_ret_val_dict"; - public static final String DEFAULT_VALUE_ASYNC = "__pydevd_value_async"; private @Nullable String myTempName = null; private final @Nullable String myType; @@ -40,12 +40,23 @@ public class PyDebugValue extends XNamedValue { private final boolean myIsIPythonHidden; private @Nullable PyDebugValue myParent; private @Nullable String myId = null; - private boolean myLoadValueAsync; + private ValuesPolicy myLoadValuePolicy; private @NotNull PyFrameAccessor myFrameAccessor; private @Nullable PyVariableLocator myVariableLocator; private volatile @Nullable XValueNode myLastNode = null; private final boolean myErrorOnEval; + public enum ValuesPolicy { + SYNC, ASYNC, ON_DEMAND + } + + private static final Map POLICY_DEFAULT_VALUES = ImmutableMap.of("__pydevd_value_async", ValuesPolicy.ASYNC, + "__pydevd_value_on_demand", + ValuesPolicy.ON_DEMAND); + + public static final Map POLICY_ENV_VARS = ImmutableMap.of(ValuesPolicy.ASYNC, "PYDEVD_LOAD_VALUES_ASYNC", + ValuesPolicy.ON_DEMAND, "PYDEVD_LOAD_VALUES_ON_DEMAND"); + public PyDebugValue(@NotNull final String name, @Nullable final String type, @Nullable String typeQualifier, @@ -78,9 +89,9 @@ public class PyDebugValue extends XNamedValue { myErrorOnEval = errorOnEval; myParent = parent; myFrameAccessor = frameAccessor; - myLoadValueAsync = false; - if (DEFAULT_VALUE_ASYNC.equals(myValue)) { - myLoadValueAsync = true; + myLoadValuePolicy = ValuesPolicy.SYNC; + if (POLICY_DEFAULT_VALUES.keySet().contains(myValue)) { + myLoadValuePolicy = POLICY_DEFAULT_VALUES.get(myValue); setValue(" "); } } @@ -88,7 +99,7 @@ public class PyDebugValue extends XNamedValue { public PyDebugValue(@NotNull PyDebugValue value, @NotNull String newName) { this(newName, value.getType(), value.getTypeQualifier(), value.getValue(), value.isContainer(), value.isReturnedVal(), value.isIPythonHidden(), value.isErrorOnEval(), value.getParent(), value.getFrameAccessor()); - setLoadValueAsync(value.isLoadValueAsync()); + setLoadValuePolicy(value.getLoadValuePolicy()); setTempName(value.getTempName()); } @@ -149,12 +160,12 @@ public class PyDebugValue extends XNamedValue { return myParent == null ? this : myParent.getTopParent(); } - public boolean isLoadValueAsync() { - return myLoadValueAsync; + public ValuesPolicy getLoadValuePolicy() { + return myLoadValuePolicy; } - public void setLoadValueAsync(boolean loadValueAsync) { - myLoadValueAsync = loadValueAsync; + public void setLoadValuePolicy(ValuesPolicy loadValueAsync) { + myLoadValuePolicy = loadValueAsync; } @Nullable @@ -243,8 +254,22 @@ public class PyDebugValue extends XNamedValue { node.setPresentation(getValueIcon(), myType, value, myContainer); } - public void updateNodeValueAfterLoading(@NotNull XValueNode node, @NotNull String value, @NotNull String linkText) { - node.setPresentation(getValueIcon(), myType, value, myContainer); + public void updateNodeValueAfterLoading(@NotNull XValueNode node, + @NotNull String value, + @NotNull String linkText, + @Nullable String errorMessage) { + if (errorMessage != null) { + node.setPresentation(getValueIcon(), new XRegularValuePresentation(value, myType) { + @Override + public void renderValue(@NotNull XValueTextRenderer renderer) { + renderer.renderError(errorMessage); + } + }, myContainer); + } + else { + node.setPresentation(getValueIcon(), myType, value, myContainer); + } + if (isNumericContainer()) return; // do not update FullValueEvaluator not to break Array Viewer if (value.length() >= MAX_VALUE) { node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, getEvaluationExpression())); @@ -274,11 +299,11 @@ public class PyDebugValue extends XNamedValue { return new PyDebugCallback() { @Override public void ok(String value) { - myLoadValueAsync = false; + myLoadValuePolicy = ValuesPolicy.SYNC; myValue = value; XValueNode node = myLastNode; if (node != null && !node.isObsolete()) { - updateNodeValueAfterLoading(node, value, ""); + updateNodeValueAfterLoading(node, value, "", null); } } @@ -300,7 +325,7 @@ public class PyDebugValue extends XNamedValue { XValue value = childrenList.getValue(i); if (value instanceof PyDebugValue) { PyDebugValue debugValue = (PyDebugValue)value; - if (debugValue.isLoadValueAsync()) { + if (debugValue.getLoadValuePolicy() == ValuesPolicy.ASYNC || debugValue.isNumericContainer()) { variables.add(new PyFrameAccessor.PyAsyncValue<>(debugValue, debugValue.createDebugValueCallback())); } } @@ -328,9 +353,12 @@ public class PyDebugValue extends XNamedValue { if (value.length() >= MAX_VALUE) { node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, treeName)); } - if (myLoadValueAsync) { + if (myLoadValuePolicy == ValuesPolicy.ASYNC) { node.setFullValueEvaluator(new PyLoadingValueEvaluator("... Loading Value", myFrameAccessor, treeName)); } + else if (myLoadValuePolicy == ValuesPolicy.ON_DEMAND) { + node.setFullValueEvaluator(new PyOnDemandValueEvaluator("Show Value", myFrameAccessor, this, node)); + } return; } String linkText = "...View as " + postfix; diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java index afae380d7b92..190373bfaf33 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java @@ -1,6 +1,7 @@ package com.jetbrains.python.debugger; import com.intellij.xdebugger.XSourcePosition; +import com.intellij.xdebugger.frame.XCompositeNode; import com.intellij.xdebugger.frame.XValueChildrenList; import com.jetbrains.python.debugger.pydev.PyDebugCallback; import org.jetbrains.annotations.NotNull; @@ -44,6 +45,13 @@ public interface PyFrameAccessor { return false; } + default void setCurrentRootNode(@NotNull XCompositeNode node) {} + + @Nullable + default XCompositeNode getCurrentRootNode() { + return null; + } + class PyAsyncValue { private final @NotNull PyDebugValue myDebugValue; private final @NotNull PyDebugCallback myCallback; diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java index a1ab5b616427..099c3ba0433c 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFullValueEvaluator.java @@ -8,22 +8,22 @@ import org.jetbrains.annotations.NotNull; * @author traff */ public class PyFullValueEvaluator extends XFullValueEvaluator { - private final PyFrameAccessor myDebugProcess; - private final String myExpression; + @NotNull protected final PyFrameAccessor myDebugProcess; + @NotNull protected final String myExpression; /** * @param linkText text of the link what will be appended to a variables tree node text * @param debugProcess * @param expression */ - protected PyFullValueEvaluator(String linkText, PyFrameAccessor debugProcess, String expression) { + protected PyFullValueEvaluator(String linkText, @NotNull PyFrameAccessor debugProcess, @NotNull String expression) { super(linkText); myDebugProcess = debugProcess; myExpression = expression; } - protected PyFullValueEvaluator(PyFrameAccessor debugProcess, String expression) { + protected PyFullValueEvaluator(@NotNull PyFrameAccessor debugProcess, @NotNull String expression) { myDebugProcess = debugProcess; myExpression = expression; } @@ -32,7 +32,7 @@ public class PyFullValueEvaluator extends XFullValueEvaluator { @Override public void startEvaluation(@NotNull XFullValueEvaluationCallback callback) { String expression = myExpression.trim(); - if ("".equals(expression)) { + if (expression.isEmpty()) { callback.evaluated(""); return; } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyOnDemandValueEvaluator.kt b/python/pydevSrc/com/jetbrains/python/debugger/PyOnDemandValueEvaluator.kt new file mode 100644 index 000000000000..ea4ba592cc56 --- /dev/null +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyOnDemandValueEvaluator.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.debugger + +import com.intellij.xdebugger.frame.XValueNode + +class PyOnDemandValueEvaluator(linkText: String, + debugProcess: PyFrameAccessor, + var debugValue: PyDebugValue, + var node: XValueNode) : PyFullValueEvaluator(linkText, debugProcess, debugValue.evaluationExpression) { + + override fun startEvaluation(callback: XFullValueEvaluationCallback) { + node.setFullValueEvaluator(PyLoadingValueEvaluator("... Loading Value", myDebugProcess, myExpression)) + callback.evaluated("... Loading Value") + val pyAsyncValue = PyFrameAccessor.PyAsyncValue(debugValue, debugValue.createDebugValueCallback()) + myDebugProcess.loadAsyncVariablesValues(listOf(pyAsyncValue)) + } + + override fun isShowValuePopup(): Boolean { + return false + } + +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index 36fbbc1c64ea..be59a3169705 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -31,6 +31,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.xdebugger.XSourcePosition; +import com.intellij.xdebugger.frame.XCompositeNode; import com.intellij.xdebugger.frame.XValueChildrenList; import com.intellij.xdebugger.frame.XValueNode; import com.jetbrains.python.console.parsing.PythonConsoleData; @@ -112,9 +113,11 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl private int myFullValueSeq = 0; private final Map>> myCallbackHashMap = new ConcurrentHashMap<>(); - private @Nullable PythonConsoleView myConsoleView; + @Nullable private PythonConsoleView myConsoleView; private final List myFrameListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + @Nullable private XCompositeNode myCurrentRootNode; + /** * Initializes the xml-rpc communication. * @@ -629,7 +632,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl XValueNode node = value.getLastNode(); if (node != null && !node.isObsolete()) { if (e.getMessage().startsWith("Timeout") || e.getMessage().startsWith("Console already exited")) { - value.updateNodeValueAfterLoading(node, " ", "Timeout Exceeded"); + value.updateNodeValueAfterLoading(node, " ", "", PyVariableViewSettings.LOADING_TIMED_OUT); } else { LOG.error(e); @@ -677,6 +680,17 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl return new XValueChildrenList(); } + @Override + public void setCurrentRootNode(@NotNull XCompositeNode node) { + myCurrentRootNode = node; + } + + @Override + @Nullable + public XCompositeNode getCurrentRootNode() { + return myCurrentRootNode; + } + @Override public void changeVariable(PyDebugValue variable, String value) throws PyDebuggerException { if (myClient != null) { diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java index 617441e81cf5..cb486e0a8ba5 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java @@ -77,6 +77,7 @@ import com.jetbrains.python.PythonHelper; import com.jetbrains.python.console.actions.ShowVarsAction; import com.jetbrains.python.console.pydev.ConsoleCommunicationListener; import com.jetbrains.python.debugger.PyDebugRunner; +import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.PyVariableViewSettings; import com.jetbrains.python.debugger.settings.PyDebuggerSettings; import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; @@ -207,7 +208,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { DefaultActionGroup settings = new DefaultActionGroup("Settings", true); settings.getTemplatePresentation().setIcon(AllIcons.General.GearPlain); settings.add(new PyVariableViewSettings.SimplifiedView(null)); - settings.add(new PyVariableViewSettings.AsyncView()); + settings.add(new PyVariableViewSettings.VariablesPolicyGroup()); toolbarActions.add(settings); @@ -1018,8 +1019,8 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { myEnvironmentVariables = envs; myEnvironmentVariables.putAll(consoleSettings.getEnvs()); PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance(); - if (debuggerSettings.isLoadValuesAsync()) { - myEnvironmentVariables.put(PyVariableViewSettings.PYDEVD_LOAD_VALUES_ASYNC, "True"); + if (debuggerSettings.getValuesPolicy() != PyDebugValue.ValuesPolicy.SYNC) { + myEnvironmentVariables.put(PyDebugValue.POLICY_ENV_VARS.get(debuggerSettings.getValuesPolicy()), "True"); } } diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index 2763f5769ecf..0a56063679db 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -101,6 +101,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr private PyReferrersLoader myReferrersProvider; private final List myFrameListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private boolean isCythonWarningShown = false; + @Nullable private XCompositeNode myCurrentRootNode; public PyDebugProcess(@NotNull XDebugSession session, @NotNull ServerSocket serverSocket, @@ -451,7 +452,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr super.registerAdditionalActions(leftToolbar, topToolbar, settings); settings.add(new WatchReturnValuesAction(this)); settings.add(new PyVariableViewSettings.SimplifiedView(this)); - settings.add(new PyVariableViewSettings.AsyncView()); + settings.add(new PyVariableViewSettings.VariablesPolicyGroup()); } private static class WatchReturnValuesAction extends ToggleAction { @@ -747,7 +748,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr XValueNode node = value.getLastNode(); if (node != null && !node.isObsolete()) { if (e.getMessage().startsWith("Timeout")) { - value.updateNodeValueAfterLoading(node, " ", "Timeout Exceeded"); + value.updateNodeValueAfterLoading(node, " ", "", PyVariableViewSettings.LOADING_TIMED_OUT); + PyVariableViewSettings.showWarningMessage(getCurrentRootNode()); } else { LOG.error(e); @@ -830,6 +832,17 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr return PyDebugSupportUtils.canSaveToTemp(project, name); } + @Override + public void setCurrentRootNode(@Nullable XCompositeNode currentRootNode) { + myCurrentRootNode = currentRootNode; + } + + @Nullable + @Override + public XCompositeNode getCurrentRootNode() { + return myCurrentRootNode; + } + @NotNull private PyStackFrame currentFrame() throws PyDebuggerException { if (!isConnected()) { diff --git a/python/src/com/jetbrains/python/debugger/PyDebugRunner.java b/python/src/com/jetbrains/python/debugger/PyDebugRunner.java index 0d9d2181799e..8f943a0e0156 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugRunner.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugRunner.java @@ -333,8 +333,8 @@ public class PyDebugRunner extends GenericProgramRunner { if (debuggerSettings.isLibrariesFilterEnabled()) { environment.put(PYDEVD_FILTER_LIBRARIES, "True"); } - if (debuggerSettings.isLoadValuesAsync()) { - environment.put(PyVariableViewSettings.PYDEVD_LOAD_VALUES_ASYNC, "True"); + if (debuggerSettings.getValuesPolicy() != PyDebugValue.ValuesPolicy.SYNC) { + environment.put(PyDebugValue.POLICY_ENV_VARS.get(debuggerSettings.getValuesPolicy()), "True"); } PydevConsoleRunnerFactory.putIPythonEnvFlag(project, environment); diff --git a/python/src/com/jetbrains/python/debugger/PyStackFrame.java b/python/src/com/jetbrains/python/debugger/PyStackFrame.java index a341c749ced6..3ad9dad04d14 100644 --- a/python/src/com/jetbrains/python/debugger/PyStackFrame.java +++ b/python/src/com/jetbrains/python/debugger/PyStackFrame.java @@ -129,6 +129,7 @@ public class PyStackFrame extends XStackFrame { @Override public void computeChildren(@NotNull final XCompositeNode node) { if (node.isObsolete()) return; + myDebugProcess.setCurrentRootNode(node); ApplicationManager.getApplication().executeOnPooledThread(() -> { try { boolean cached = myDebugProcess.isCurrentFrameCached(); @@ -214,7 +215,7 @@ public class PyStackFrame extends XStackFrame { node.addChildren(list, true); } - @Nullable + @NotNull @Override public Icon getIcon() { return AllIcons.Debugger.WatchLastReturnValue; @@ -237,7 +238,7 @@ public class PyStackFrame extends XStackFrame { node.addChildren(list, true); } - @Nullable + @NotNull @Override public Icon getIcon() { return PythonIcons.Python.Debug.SpecialVar; @@ -258,10 +259,6 @@ public class PyStackFrame extends XStackFrame { return myFrameInfo.getThreadId() + ":" + myFrameInfo.getId(); } - public String getFrameName() { - return myFrameInfo.getName(); - } - protected XSourcePosition getPosition() { return myPosition; } diff --git a/python/src/com/jetbrains/python/debugger/PyVariableViewSettings.java b/python/src/com/jetbrains/python/debugger/PyVariableViewSettings.java index 96c3d250cca2..b6790ce84325 100644 --- a/python/src/com/jetbrains/python/debugger/PyVariableViewSettings.java +++ b/python/src/com/jetbrains/python/debugger/PyVariableViewSettings.java @@ -15,15 +15,23 @@ */ package com.jetbrains.python.debugger; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.actionSystem.ToggleAction; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.*; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.xdebugger.frame.XCompositeNode; +import com.intellij.xdebugger.frame.XDebuggerTreeNodeHyperlink; import com.jetbrains.python.debugger.settings.PyDebuggerSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + public class PyVariableViewSettings { - public static final String PYDEVD_LOAD_VALUES_ASYNC = "PYDEVD_LOAD_VALUES_ASYNC"; + public static final String LOADING_TIMED_OUT = "Loading timed out"; + public static final String ON_DEMAND_LINK_TEXT = "Switch to loading on demand"; + public static final String WARNING_MESSAGE = "The values of several variables couldn't be loaded "; public static class SimplifiedView extends ToggleAction { private final PyDebugProcess myProcess; @@ -60,33 +68,107 @@ public class PyVariableViewSettings { } } - public static class AsyncView extends ToggleAction { - private final String myText; - private volatile boolean myLazyVariablesEvaluation; + public static void showWarningMessage(@Nullable final XCompositeNode node) { + if (node == null) return; + final PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance(); + if (debuggerSettings.getValuesPolicy() == PyDebugValue.ValuesPolicy.ON_DEMAND) return; - public AsyncView() { - super("", "Load variable values asynchronously", null); - myLazyVariablesEvaluation = PyDebuggerSettings.getInstance().isLoadValuesAsync(); - myText = "Load Values Asynchronously"; + node.setMessage( + WARNING_MESSAGE, AllIcons.General.BalloonWarning, SimpleTextAttributes.REGULAR_ATTRIBUTES, + new XDebuggerTreeNodeHyperlink(ON_DEMAND_LINK_TEXT) { + private boolean linkClicked = false; + + @Override + public void onClick(MouseEvent event) { + debuggerSettings.setValuesPolicy(PyDebugValue.ValuesPolicy.ON_DEMAND); + linkClicked = true; + } + + @NotNull + @Override + public String getLinkText() { + if (linkClicked) { + return ""; + } + else { + return ON_DEMAND_LINK_TEXT; + } + } + }); + } + + public static class VariablesPolicyGroup extends DefaultActionGroup { + @NotNull private final List myValuesPolicyActions = new ArrayList<>(); + + public VariablesPolicyGroup() { + super("Variables Loading Policy", true); + myValuesPolicyActions + .add(new PolicyAction("Synchronously", "Load variable values synchronously", PyDebugValue.ValuesPolicy.SYNC, this)); + myValuesPolicyActions + .add(new PolicyAction("Asynchronously", "Load variable values asynchronously", PyDebugValue.ValuesPolicy.ASYNC, this)); + myValuesPolicyActions + .add(new PolicyAction("On demand", "Load variable values on demand", PyDebugValue.ValuesPolicy.ON_DEMAND, this)); + + for (AnAction action : myValuesPolicyActions) { + add(action); + } + } + + public void updatePolicyActions() { + final PyDebugValue.ValuesPolicy currentValuesPolicy = PyDebuggerSettings.getInstance().getValuesPolicy(); + for (PolicyAction action : myValuesPolicyActions) { + action.setEnabled(currentValuesPolicy == action.getPolicy()); + } + } + } + + public static class PolicyAction extends ToggleAction { + @NotNull private final String myText; + @NotNull private final PyDebugValue.ValuesPolicy myPolicy; + @NotNull private final VariablesPolicyGroup myActionGroup; + private volatile boolean isEnabled; + + public PolicyAction(@NotNull String text, + @NotNull String description, + @NotNull PyDebugValue.ValuesPolicy policy, + @NotNull VariablesPolicyGroup actionGroup) { + super("", description, null); + myText = text; + myPolicy = policy; + myActionGroup = actionGroup; + isEnabled = PyDebuggerSettings.getInstance().getValuesPolicy() == policy; } @Override public void update(@NotNull final AnActionEvent e) { super.update(e); + myActionGroup.updatePolicyActions(); final Presentation presentation = e.getPresentation(); presentation.setEnabled(true); presentation.setText(myText); } + @NotNull + public PyDebugValue.ValuesPolicy getPolicy() { + return myPolicy; + } + + public void setEnabled(boolean enabled) { + isEnabled = enabled; + } + @Override public boolean isSelected(AnActionEvent e) { - return myLazyVariablesEvaluation; + return isEnabled; } @Override public void setSelected(AnActionEvent e, boolean hide) { - myLazyVariablesEvaluation = hide; - PyDebuggerSettings.getInstance().setLoadValuesAsync(hide); + isEnabled = hide; + if (hide) { + PyDebuggerSettings.getInstance().setValuesPolicy(myPolicy); + } + myActionGroup.updatePolicyActions(); } } } diff --git a/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java b/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java index 74e2e2c8de9d..db3c4efce379 100644 --- a/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java +++ b/python/src/com/jetbrains/python/debugger/settings/PyDebuggerSettings.java @@ -9,6 +9,7 @@ import com.intellij.util.SmartList; import com.intellij.util.xmlb.XmlSerializerUtil; import com.intellij.xdebugger.settings.DebuggerSettingsCategory; import com.intellij.xdebugger.settings.XDebuggerSettings; +import com.jetbrains.python.debugger.PyDebugValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,7 +27,7 @@ public class PyDebuggerSettings extends XDebuggerSettings im public static final String FILTERS_DIVIDER = ";"; private boolean myWatchReturnValues = false; private boolean mySimplifiedView = true; - private boolean myLoadValuesAsync = true; + private volatile PyDebugValue.ValuesPolicy myValuesPolicy = PyDebugValue.ValuesPolicy.ASYNC; public PyDebuggerSettings() { super("python"); @@ -49,12 +50,12 @@ public class PyDebuggerSettings extends XDebuggerSettings im mySimplifiedView = simplifiedView; } - public boolean isLoadValuesAsync() { - return myLoadValuesAsync; + public PyDebugValue.ValuesPolicy getValuesPolicy() { + return myValuesPolicy; } - public void setLoadValuesAsync(boolean loadValuesAsync) { - myLoadValuesAsync = loadValuesAsync; + public void setValuesPolicy(PyDebugValue.ValuesPolicy valuesPolicy) { + myValuesPolicy = valuesPolicy; } public static PyDebuggerSettings getInstance() {